[
  {
    "path": ".copywrite.hcl",
    "content": "schema_version = 1\n\nproject {\n  license        = \"MPL-2.0\"\n  copyright_year = 2018\n\n  # (OPTIONAL) A list of globs that should not have copyright/license headers.\n  # Supports doublestar glob patterns for more flexibility in defining which\n  # files or folders should be ignored\n  header_ignore = [\n    # \"vendors/**\",\n    # \"**autogen**\",\n  ]\n}\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @hashicorp/tf-core-cloud\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Let us know about an unexpected error, a crash, or an incorrect behavior.\nlabels: bug\n---\n\n<!--\nHi there,\n\nThank you for opening an issue! Please note that we try to keep the this issue\ntracker reserved for bug reports and feature requests related to the go-tfe API\nwrapper. If you know your issue relates to the HCP Terraform and Terraform Enterprise\nplatform itself, please contact tf-cloud@hashicorp.support. For general usage\nquestions, please post to our community forum: https://discuss.hashicorp.com.\n-->\n\n#### go-tfe version\n<!---\nWhat version of go-tfe are you using?\n-->\n```plaintext\n...\n```\n\n## Description\n<!-- Describe what's happening. -->\n\n## Testing plan\n<!--\n1.  _Describe how to replicate_\n1.  _the conditions under which your code performs its purpose,_\n1.  _including example code to run where necessary._\n-->\n```plaintext\n...\n```\n\n#### Expected Behavior\n<!--\nWhat should have happened?\n-->\n\n#### Actual Behavior\n<!--\nWhat actually happened?\n-->\n\n#### Additional Context\n<!--\nIs there anything atypical about your situation that we should know? For\nexample: is Terraform running in a wrapper script or in a CI system? Are you\npassing any unusual command line options or environment variables to opt-in to\nnon-default behavior?\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nblank_issues_enabled: false\ncontact_links:\n  - name: HCP Terraform and Terraform Enterprise Troubleshooting and Feature Requests\n    url: https://support.hashicorp.com/hc/en-us/requests/new\n    about: For issues and feature requests concerning the HCP Terraform and Terraform Enterprise platform itself, please submit a HashiCorp support request or email tf-cloud@hashicorp.support\n  - name: Terraform Language or Workflow Questions\n    url: https://discuss.hashicorp.com\n    about: Please ask Terraform language or workflow related questions through the HashiCorp Discuss forum\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest a new feature or other enhancement.\nlabels: feature-request\n---\n\n<!--\nHi there,\n\nThank you for opening an issue! Please note that we try to keep the this issue tracker reserved for\nbug reports and feature requests related to the go-tfe API wrapper. If you know\nyour issue relates to the HCP Terraform and Terraform Enterprise platform itself, please contact\ntf-cloud@hashicorp.support. For general usage questions, please post to our community forum:\nhttps://discuss.hashicorp.com.\n-->\n\n\n#### Use-cases\n<!---\nIn order to properly evaluate a feature request, it is necessary to understand the use-cases for it.\n\nPlease describe below the _end goal_ you are trying to achieve that has led you to request this feature.\n\nPlease keep this section focused on the problem and not on the suggested solution. We'll get to that in a moment, below!\n-->\n\n#### Attempted Solutions\n<!---\nIf you've already tried to solve the problem with existing features and found a limitation that prevented you from succeeding, please describe it below in as much detail as possible.\n\nIdeally, this would include real configuration snippets that you tried, real Terraform command lines you ran, and what results you got in each case.\n\nPlease remove any sensitive information such as passwords before sharing configuration snippets and command lines.\n-->\n\n#### Proposal\n<!---\nIf you have an idea for a way to address the problem via a change to this library, please describe it below.\n\nIn this section, it's helpful to include specific examples of how what you are suggesting might look in your code since that allows us to understand the full picture of what you are proposing.\n\nIf you don't know what you'd propose or are unsure of some details, don't worry! When we evaluate the feature request we'll be happy to help.\n-->\n"
  },
  {
    "path": ".github/actions/lint-go-tfe/action.yml",
    "content": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nname: Lint\ndescription: Lints go-tfe\nruns:\n  using: composite\n  steps:\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0\n      with:\n        go-version-file: go.mod\n        cache: true\n\n    - run: make fmtcheck\n      shell: bash\n\n    - name: Install golangci-lint\n      run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/6008b81b81c690c046ffc3fd5bce896da715d5fd/install.sh | sh -s -- -b $(go env GOPATH)/bin $GOLANGCILINT_VERSION\n      shell: bash\n      env:\n        GOLANGCILINT_VERSION: v2.11.3\n\n    - run: make lint\n      shell: bash\n\n    - name: Ensure generate_mocks.sh ends in a newline\n      run: test \"\" = \"$(tail -c1 \"generate_mocks.sh\")\"\n      shell: bash\n\n    - name: Install mockgen\n      shell: bash\n      run: go install go.uber.org/mock/mockgen@v0.4.0\n\n    - name: Get dependencies\n      shell: bash\n      run: go mod download\n\n    - name: Generate mocks\n      shell: bash\n      run:  ./generate_mocks.sh\n\n    - name: verify go.mod and go.sum are consistent\n      shell: bash\n      run : go mod tidy\n\n    - name: Ensure mocks are generated\n      shell: bash\n      run: git diff --exit-code\n"
  },
  {
    "path": ".github/actions/test-go-tfe/action.yml",
    "content": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nname: Test\ndescription: Tests go-tfe within a matrix\ninputs:\n  matrix_index:\n    description: Index of the matrix strategy runner\n    required: true\n  matrix_total:\n    description: Total number of matrix strategy runners\n    required: true\n  address:\n    description: Address of the HCP Terraform instance to test against\n    required: true\n  admin_configuration_token:\n    description: HCP Terraform Admin API Configuration role token\n    required: true\n  admin_provision_licenses_token:\n    description: HCP Terraform Admin API Provision Licenses role token\n    required: true\n  admin_security_maintenance_token:\n    description: HCP Terraform Admin API Security Maintenance role token\n    required: true\n  admin_site_admin_token:\n    description: HCP Terraform Admin API Site Admin role token\n    required: true\n  admin_subscription_token:\n    description: HCP Terraform Admin API Subscription role token\n    required: true\n  admin_support_token:\n    description: HCP Terraform Admin API Support role token\n    required: true\n  admin_version_maintenance_token:\n    description: HCP Terraform Admin API Version Maintenance role token\n    required: true\n  token:\n    description: HCP Terraform token\n    required: true\n  oauth-client-github-token:\n    description: The GitHub token used for testing OAuth scenarios for VCS workspaces\n    required: false\n  enterprise:\n    description: Test enterprise features (`address` must be running in ON_PREM mode)\n    required: false\n  datadog-workflow-token:\n    description: Datadog API key for test optimization\n    required: false\n  skip-statement:\n    description: Skip tests with this statement substring in their name\n    required: false\n\nruns:\n  using: composite\n  steps:\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0\n      with:\n        go-version-file: go.mod\n        cache: true\n        cache-dependency-path: go.sum\n\n    - name: Sync dependencies\n      shell: bash\n      run: |\n        go mod download\n        go mod tidy\n\n    - name: Install gotestsum\n      shell: bash\n      run: go install gotest.tools/gotestsum@c4a0df2e75a225d979a444342dd3db752b53619f # v1.13.0\n\n    - name: Download artifact\n      id: download-artifact\n      uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4\n      with:\n        workflow_conclusion: success\n        name: junit-test-summary\n        if_no_artifact_found: warn\n        branch: main\n\n    - name: Split integration tests\n      id: test_split\n      uses: hashicorp-forge/go-test-split-action@796beedbdb3d1bea14cad2d3057bab5c5cf15fe5 # v1.0.2\n      with:\n        index: ${{ inputs.matrix_index }}\n        total: ${{ inputs.matrix_total }}\n        junit-summary: ./ci-summary.xml\n\n    - name: Configure Datadog Test Optimization\n      uses: datadog/test-visibility-github-action@v2\n      with:\n        languages: go\n        api_key: ${{ inputs.datadog-workflow-token }}\n        site: datadoghq.com\n\n    - name: Run integration tests\n      shell: bash\n      env:\n        TFE_ADDRESS: ${{ inputs.address }}\n        TFE_TOKEN: ${{ inputs.token }}\n        TFE_ADMIN_CONFIGURATION_TOKEN: ${{ inputs.admin_configuration_token }}\n        TFE_ADMIN_PROVISION_LICENSES_TOKEN: ${{ inputs.admin_provision_licenses_token }}\n        TFE_ADMIN_SECURITY_MAINTENANCE_TOKEN: ${{ inputs.admin_security_maintenance_token }}\n        TFE_ADMIN_SITE_ADMIN_TOKEN: ${{ inputs.admin_site_admin_token }}\n        TFE_ADMIN_SUBSCRIPTION_TOKEN: ${{ inputs.admin_subscription_token }}\n        TFE_ADMIN_SUPPORT_TOKEN: ${{ inputs.admin_support_token }}\n        TFE_ADMIN_VERSION_MAINTENANCE_TOKEN: ${{ inputs.admin_version_maintenance_token }}\n        TFC_RUN_TASK_URL: \"http://testing-mocks.tfe:22180/runtasks/pass\"\n        GITHUB_POLICY_SET_IDENTIFIER: \"svc-team-tf-core-cloud/test-policy-set\"\n        GITHUB_REGISTRY_MODULE_IDENTIFIER: \"svc-team-tf-core-cloud/terraform-random-module\"\n        GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER: \"hashicorp/terraform-random-no-code-module\"\n        OAUTH_CLIENT_GITHUB_TOKEN: \"${{ inputs.oauth-client-github-token }}\"\n        SKIP_HYOK_INTEGRATION_TESTS: \"${{ inputs.skip-hyok-integration-tests }}\"\n        HYOK_ORGANIZATION_NAME: \"${{ inputs.hyok-organization-name }}\"\n        HYOK_WORKSPACE_NAME: \"${{ inputs.hyok-workspace-name }}\"\n        HYOK_POOL_ID: \"${{ inputs.hyok-pool-id }}\"\n        HYOK_PLAN_ID: \"${{ inputs.hyok-plan-id }}\"\n        HYOK_STATE_VERSION_ID: \"${{ inputs.hyok-state-version-id }}\"\n        HYOK_CUSTOMER_KEY_VERSION_ID: \"${{ inputs.hyok-customer-key-version-id }}\"\n        HYOK_ENCRYPTED_DATA_KEY_ID: \"${{ inputs.hyok-encrypted-data-key-id }}\"\n        GO111MODULE: \"on\"\n        ENABLE_TFE: ${{ inputs.enterprise }}\n      run: |\n        gotestsum --junitfile summary.xml --format short-verbose --rerun-fails=3 --packages=./ -- -parallel=2 -timeout=59m -coverprofile cover.out -run \"${{ steps.test_split.outputs.run }}\" ${{ inputs.skip-statement }}\n\n    - name: Upload test artifacts\n      uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0\n      with:\n        name: junit-test-summary-${{ matrix.index }}\n        path: |\n          summary.xml\n          cover.out\n        retention-days: 1\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nversion: 2\nupdates:\n- package-ecosystem: gomod\n  directory: \"/\"\n  schedule:\n    interval: daily\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--\nThank you for contributing to hashicorp/go-tfe! Please read docs/CONTRIBUTING.md for detailed information when preparing your change.\n\nHere's what to expect after the pull request is opened:\n\nThe test suite contains many acceptance tests that are run against a test version of HCP Terraform, and additional testing is done against Terraform Enterprise. You can read more about running the tests against your own Terraform Enterprise environment in TESTS.md. Our CI system (Github Actions) will not test your fork until a one-time approval takes place.\n\nYour change, depending on its impact, may be released in the following ways:\n\n  1. For impactful bug fixes, it can be released fairly quickly as a patch release.\n  2. For noncritical bug fixes and new features, it will be incorporated into the next minor version release.\n  3. For breaking changes (those changes that alter the public method signatures), more consideration must be made and alternatives may be considered, depending on upgrade difficulty and release schedule.\n\nPlease note that API features that are not generally available should not be merged/released without prior discussion with the maintainers. See docs/CONTRIBUTING Section \"Adding API changes that are not generally available\" for more information.\n\nPlease fill out the remaining template to assist code reviewers and testers with incorporating your change. If a section does not apply, feel free to delete it.\n-->\n\n## Description\n\n<!-- Describe why you're making this change. -->\n\n## Testing plan\n\n<!--\n1.  _Describe how to replicate_\n1.  _the conditions under which your code performs its purpose,_\n1.  _including example code to run where necessary._\n-->\n\n## External links\n\n<!--\n_Include any links here that might be helpful for people reviewing your PR. If there are none, feel free to delete this section._\n\n- [API documentation](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/xxxx)\n- [Related PR](https://github.com/terraform-providers/terraform-provider-tfe/pull/xxxx)\n\n-->\n\n## Output from tests\nIncluding output from tests may require access to a TFE instance. Ignore this section if you have no environment to test against.\n\n<!--\n_Please run the tests locally for any files you changes and include the output here._\n-->\n```\n$ TFE_ADDRESS=\"https://example\" TFE_TOKEN=\"example\" go test ./... -v -run TestFunctionsAffectedByChange\n\n...\n```\n\n<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->\n## Rollback Plan\n\n<!--\nPlease outline a plan in the event changes need to be rolled back\n\nExample: If a change needs to be reverted, we will roll out an update to the code within 7 days.\n-->\n\n## Changes to Security Controls\n\n<!--\nAre there any changes to security controls (access controls, encryption, logging) in this pull request? If so, explain.\n-->\n"
  },
  {
    "path": ".github/workflows/changelog.yml",
    "content": "# This workflow makes sure contributors don't forget to add a changelog entry or explicitly opt-out of it.\n\nname: Changelog\n\non:\n  pull_request_target:\n    types:\n      - opened\n      - ready_for_review\n      - reopened\n      - synchronize\n      - labeled\n      - unlabeled\n\n# This workflow runs for not-yet-reviewed external contributions and so it\n# intentionally has no write access and only limited read access to the\n# repository.\n\npermissions:\n  contents: read\n  pull-requests: write\n\njobs:\n  check-changelog-entry:\n    name: \"Check Changelog Entry\"\n    runs-on: ubuntu-latest\n    concurrency:\n      group: changelog-${{ github.head_ref }}\n      cancel-in-progress: true\n\n    steps:\n      - name: \"Changed files\"\n        uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2\n        id: changelog\n        with:\n          filters: |\n            changelog:\n              - 'CHANGELOG.md'\n\n      - name: \"Check for changelog entry\"\n        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1\n        with:\n          script: |\n            async function createOrUpdateChangelogComment(commentDetails, deleteComment) {\n                const commentStart = \"## Changelog Warning\"\n                \n                const body = commentStart + \"\\n\\n\" + commentDetails;\n                const { number: issue_number } = context.issue;\n                const { owner, repo } = context.repo;\n                \n                // List all comments\n                const allComments = (await github.rest.issues.listComments({\n                    issue_number,\n                    owner,\n                    repo,\n                })).data;\n\n                const existingComment = allComments.find(c => c.body.startsWith(commentStart));\n                const comment_id = existingComment?.id;\n                \n                if (deleteComment) {\n                    if (existingComment) {\n                        await github.rest.issues.deleteComment({\n                            owner,\n                            repo,\n                            comment_id,\n                        });\n                    }\n                    return;\n                }\n\n                core.setFailed(commentDetails);\n\n                if (existingComment) {\n                    await github.rest.issues.updateComment({\n                        owner,\n                        repo,\n                        comment_id,\n                        body,\n                    });\n                } else {\n                    await github.rest.issues.createComment({\n                        owner,\n                        repo,\n                        issue_number,\n                        body,\n                    });\n                }\n            }\n            \n            const changelogChangesPresent = ${{steps.changelog.outputs.changelog}};\n\n            const prLabels = await github.rest.issues.listLabelsOnIssue({\n                issue_number: context.issue.number,\n                owner: context.repo.owner,\n                repo: context.repo.repo\n            });\n            const noChangelogNeededLabel = prLabels.data.find(label => label.name === 'no-changelog-needed');\n\n            if (noChangelogNeededLabel) {\n                if (changelogChangesPresent) {\n                    await createOrUpdateChangelogComment(\"Please remove either the 'no-changelog-needed' label or the changelog entry from this PR.\");\n                    return;\n                }\n                await createOrUpdateChangelogComment(\"\", true);\n                return;\n            }\n\n            if (!changelogChangesPresent) {\n                await createOrUpdateChangelogComment(\"Please add a changelog entry to `CHANGELOG.md` for this change. If you believe this change does not need a changelog entry, please add the 'no-changelog-needed' label.\");\n                return;\n            }\n\n            // Nothing to complain about, so delete any existing comment\n            await createOrUpdateChangelogComment(\"\", true);"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  push:\n    branches: [main]\n  pull_request:\n\nconcurrency:\n  group: ${{ github.head_ref || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  lint:\n    name: Lint\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6\n\n      - uses: ./.github/actions/lint-go-tfe\n\n  tests:\n    name: Test\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        # If you adjust these parameters, also adjust the jrm input files on the \"Merge reports\" step below\n        total: [8]\n        index: [0, 1, 2, 3, 4, 5, 6, 7]\n\n    steps:\n      - name: terraform-cloud-outputs\n        id: tflocal\n        uses: hashicorp-forge/terraform-cloud-action/outputs@5583d5f554d268ac91b3c37fd0a5e9da2c78c017 # v1.1.0\n        with:\n          token: ${{ secrets.TF_WORKFLOW_TFLOCAL_CLOUD_TFC_TOKEN }}\n          organization: hashicorp-v2\n          workspace: tflocal-go-tfe\n\n      - name: Checkout code\n        uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6\n\n      - uses: ./.github/actions/test-go-tfe\n        with:\n          matrix_index: ${{ matrix.index }}\n          matrix_total: ${{ matrix.total }}\n          address: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_address }}\n          token: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_token }}\n          admin_configuration_token: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_admin_token_by_role.configuration }}\n          admin_provision_licenses_token: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_admin_token_by_role.provision-licenses }}\n          admin_security_maintenance_token: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_admin_token_by_role.security-maintenance }}\n          admin_site_admin_token: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_admin_token_by_role.site-admin }}\n          admin_subscription_token: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_admin_token_by_role.subscription }}\n          admin_support_token: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_admin_token_by_role.support }}\n          admin_version_maintenance_token: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_admin_token_by_role.version-maintenance }}\n          oauth-client-github-token: ${{ secrets.OAUTH_CLIENT_GITHUB_TOKEN }}\n          datadog-workflow-token: ${{ secrets.TF_WORKFLOW_DATADOG_API_KEY }}\n\n  tests-combine-summaries:\n    name: Combine Test Reports\n    needs: [tests]\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6\n\n      - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2\n        with:\n          node-version: 20\n\n      - name: Set up Go\n        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0\n        with:\n          go-version: \"^1.22\"\n          cache: true\n\n      - name: Download artifacts\n        uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.18.0\n\n      - name: Install junit-report-merger\n        run: npm install -g junit-report-merger\n\n      - name: Install gocovmerge\n        run: go install github.com/wadey/gocovmerge@latest\n\n      # Note -- we're intentionally including this in the same job as the running of the tests themselves. This is to\n      # future proof for when Datadog supports tracing of Go tests rather than just uploading coverage results.\n      # Ref: https://docs.datadoghq.com/continuous_integration/setup_tests/\n      - name: prepare datadog-ci\n        run: |\n          curl -L --fail \"https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_linux-x64\" --output \"/usr/local/bin/datadog-ci\"\n          chmod +x /usr/local/bin/datadog-ci\n\n      - name: Merge coverage reports\n        run: |\n          gocovmerge junit-test-summary-{0..7}/cover.out > merged-coverage.out\n\n      - name: Merge junit reports\n        run: jrm ./ci-summary.xml \"junit-test-summary-{0..7}/*.xml\"\n\n      - name: Upload test artifacts\n        uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0\n        with:\n          name: junit-test-summary\n          path: ./ci-summary.xml\n\n      - name: upload coverage\n        env:\n          DATADOG_API_KEY: \"${{ secrets.TF_WORKFLOW_DATADOG_API_KEY }}\"\n          DD_ENV: ci\n        run: |\n          coverage=$(go tool cover -func merged-coverage.out | tail -n 1 | awk '{print $3}' | tr -d -c 0-9.)\n          datadog-ci junit upload --service \"$GITHUB_REPOSITORY\" --report-measures=test.code_coverage.lines_pct:$coverage ./ci-summary.xml\n\n  tests-summarize:\n    name: Summarize Tests\n    needs: [tests]\n    runs-on: ubuntu-latest\n    if: ${{ always() }}\n    steps:\n      - name: Check tests Status\n        run: |\n          if [ \"${{ needs.tests.result }}\" = \"success\" ]; then\n            exit 0\n          fi\n          exit 1\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n  schedule:\n    - cron: '15 2 * * 2'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'go' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]\n        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@9a866ed4524fc3422c3af1e446dab8efa3503411 # codeql-bundle-20230418\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n        # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n        # queries: security-extended,security-and-quality\n\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@9a866ed4524fc3422c3af1e446dab8efa3503411 # codeql-bundle-20230418\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n\n    #   If the Autobuild fails above, remove it and uncomment the following three lines.\n    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.\n\n    # - run: |\n    #   echo \"Run, Build Application using script\"\n    #   ./location_of_script_within_repo/buildscript.sh\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@9a866ed4524fc3422c3af1e446dab8efa3503411 # codeql-bundle-20230418\n"
  },
  {
    "path": ".github/workflows/create-jira-issue.yml",
    "content": "name: Jira Issue Sync\n\non:\n  issues:\n    types: [opened, closed, deleted, reopened]\n  issue_comment:\n    types: [created]\n\njobs:\n  call-workflow:\n    uses: hashicorp/terraform-provider-tfe/.github/workflows/jira-issue-sync.yml@main\n    with:\n      project: TF\n      issue-extra-fields: |\n        { \"customfield_10091\": [\"TF-Core-Cloud\"],\n          \"components\": [{ \"name\": \"Go-TFE\" }],\n          \"customfield_10008\": \"${{ contains(github.event.issue.labels.*.name, 'bug') && 'TF-9179' || 'TF-7490' }}\"\n        }\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/jira-pr-transition.yml",
    "content": "name: Jira PR Transition\n\non:\n  pull_request:\n    types: [opened, closed, reopened, converted_to_draft, ready_for_review]\n\njobs:\n  call-workflow:\n    uses: hashicorp/terraform-provider-tfe/.github/workflows/jira-pr-transition.yml@main\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/merged-pr.yml",
    "content": "name: Merged Pull Request\npermissions:\n  pull-requests: write\n\n# only trigger on pull request closed events\non:\n  pull_request_target:\n    types: [ closed ]\n\njobs:\n  merge_job:\n    # this job will only run if the PR has been merged\n    if: github.event.pull_request.merged == true\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1\n        with:\n          script: |\n            github.rest.issues.createComment({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              body: \"Reminder to the contributor that merged this PR: if your changes have added important functionality or fixed a relevant bug, open a follow-up PR to update CHANGELOG.md with a note on your changes.\"\n            })\n"
  },
  {
    "path": ".github/workflows/nightly-tfe-ci.yml",
    "content": "name: Nightly TFE Tests\non:\n  workflow_dispatch:\n  schedule:\n    # Monday-Friday at 7:30AM UTC (90 minutes after infrastructure rebuild)\n    - cron: '30 7 * * 1-5'\n\njobs:\n  instance:\n    runs-on: ubuntu-latest\n    timeout-minutes: 20\n    steps:\n      - name: terraform-cloud/apply\n        uses: hashicorp-forge/terraform-cloud-action/apply@5583d5f554d268ac91b3c37fd0a5e9da2c78c017 # v1.1.0\n        with:\n          organization: hashicorp-v2\n          workspace: tflocal-go-tfe-nightly\n          token: ${{ secrets.TF_WORKFLOW_TFLOCAL_CLOUD_TFC_TOKEN }}\n          wait: true\n\n  tests:\n    needs: instance\n    runs-on: ubuntu-latest\n    timeout-minutes: 60\n    strategy:\n      fail-fast: false\n      matrix:\n        total: [ 1 ]\n        index: [ 0 ]\n\n    steps:\n      - name: terraform-cloud/outputs\n        id: tflocal\n        uses: hashicorp-forge/terraform-cloud-action/outputs@5583d5f554d268ac91b3c37fd0a5e9da2c78c017 # v1.1.0\n        with:\n          token: ${{ secrets.TF_WORKFLOW_TFLOCAL_CLOUD_TFC_TOKEN }}\n          organization: hashicorp-v2\n          workspace: tflocal-go-tfe-nightly\n\n      - name: Checkout code\n        uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6\n\n      - uses: ./.github/actions/test-go-tfe\n        with:\n          matrix_index: ${{ matrix.index }}\n          matrix_total: ${{ matrix.total }}\n          address: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_address }}\n          token: ${{ fromJSON(steps.tflocal.outputs.workspace-outputs-json).tfe_token }}\n          oauth-client-github-token: ${{ secrets.OAUTH_CLIENT_GITHUB_TOKEN }}\n          enterprise: \"1\"\n\n  tests-summarize:\n    needs: [ tests ]\n    runs-on: ubuntu-latest\n    if: ${{ always() }}\n    steps:\n      - name: Check tests Status\n        run: |\n          if [ \"${{ needs.tests.result }}\" = \"success\" ]; then\n            exit 0\n          fi\n          exit 1\n\n  slack-notify:\n    needs: [ tests-summarize ]\n    if: always() && (needs.tests-summarize.result == 'failure')\n    runs-on: ubuntu-latest\n    steps:\n      - name: Send slack notification on failure\n        uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844 # v1.23.0\n        with:\n          payload: |\n            {\n              \"text\": \":x::moon::sob: Nightly TFE tests *FAILED* on ${{ github.repository }}\",\n              \"attachments\": [\n                {\n                  \"color\": \"#C41E3A\",\n                  \"blocks\": [\n                    {\n                      \"type\": \"section\",\n                      \"fields\": [\n                        {\n                          \"type\": \"mrkdwn\",\n                          \"text\": \"*Workflow:*\\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"\n                        }\n                      ]\n                    }\n                  ]\n                }\n              ]\n            }\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK\n\n  cleanup:\n    runs-on: ubuntu-latest\n    needs: [\"tests-summarize\"]\n    if: \"${{ always() }}\"\n    steps:\n      - name: terraform-cloud/destroy\n        uses: hashicorp-forge/terraform-cloud-action/destroy@5583d5f554d268ac91b3c37fd0a5e9da2c78c017 # v1.1.0\n        with:\n          token: ${{ secrets.TF_WORKFLOW_TFLOCAL_CLOUD_TFC_TOKEN }}\n          organization: hashicorp-v2\n          workspace: tflocal-go-tfe-nightly\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n\n# Commonplace IDE output that should never be committed\n.out\n.vscode/*.log\n.vscode/settings.json\n.idea/\n.envrc\n.rgignore\n"
  },
  {
    "path": ".golangci.yml",
    "content": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nversion: \"2\"\n\nrun:\n  timeout: 5m\n\nlinters:\n# This set of linters are enabled by default: deadcode, errcheck, gosimple, govet, ineffasign, staticcheck, struccheck, typecheck, unused, varcheck\n  enable:\n  # List of all linters: https://golangci-lint.run/usage/linters/\n    - whitespace #https://github.com/ultraware/whitespace\n    # - noctx #https://github.com/sonatard/noctx\n    - nilerr #https://github.com/gostaticanalysis/nilerr\n    - nestif #https://github.com/nakabonne/nestif\n    - copyloopvar #https://github.com/karamaru-alpha/copyloopvar\n    - bodyclose #https://github.com/timakin/bodyclose\n    - goconst #https://github.com/jgautheron/goconst\n    - errcheck #https://github.com/kisielk/errcheck\n    - staticcheck # stylecheck/gosimple checks are part of staticcheck in v2\n    - revive #golint is deprecated and golangci-lint recommends to use revive instead https://github.com/mgechev/revive\n    #other deprecated lint libraries: maligned, scopelint, interfacer\n    - gocritic #https://github.com/go-critic/go-critic\n    - unparam #https://github.com/mvdan/unparam\n    - misspell #https://github.com/client9/misspell\n  exclusions:\n    rules:\n    - path: tfe_integration_test.go\n      linters:\n      - errcheck # Many calls in this test are known to return an error and not checked\n    - linters:\n      - staticcheck\n      text: \"Ascii\" # Permanently part of the public interface unless we break the API\n    - path: _test\\.go\n      linters:\n      - errcheck\n      - goconst\n      - unused\n      - unparam\n  settings:\n    errcheck:\n      # https://github.com/kisielk/errcheck#excluding-functions\n      check-type-assertions: true\n      check-blank: true\n    goconst:\n      min-len: 20\n      min-occurrences: 5\n      ignore-calls: false\n    gocritic:\n      enabled-tags:\n      - diagnostic\n      - opinionated\n      - performance\n      disabled-checks:\n      - unnamedResult\n      - hugeParam\n      - singleCaseSwitch\n      - ifElseChain\n    revive:\n      severity: warning\n      rules:\n      - name: indent-error-flow #Prevents redundant else statements\n        severity: warning\n      - name: useless-break\n        severity: warning\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Unreleased\n\n## Bug Fixes\n* Improve API error handling to decode both JSON:API error objects and regular JSON errors arrays by @uk1288 [#1304](https://github.com/hashicorp/go-tfe/pull/1304)\n\n## Enhancements\n* Adds the `ProviderType` field to `AdminSAMLSetting` and `AdminSAMLSettingsUpdateOptions` to support the `provider-type` SAML setting.\n* Adds `SCIMSettings` / `AdminSCIMSetting` to support managing site-level SCIM settings by @skj-skj [#1307](https://github.com/hashicorp/go-tfe/pull/1307)\n* Adds BETA support for delegating policy overrides on teams by @jbonhag [#1301](https://github.com/hashicorp/go-tfe/pull/1301)\n* Adds `AdminSCIMTokens` to support managing site-level SCIM tokens by @skj-skj [#1310](https://github.com/hashicorp/go-tfe/pull/1310)\n* Add support for trigger patterns and working directories to stacks by @aaabdelgany [#1305](https://github.com/hashicorp/go-tfe/pull/1305)\n* Adds `PolicyUpdatePatterns` to `PolicySet`, `PolicySetCreateOptions`, and `PolicySetUpdateOptions` to support `policy-update-patterns` by @nithishravindra [#1306](https://github.com/hashicorp/go-tfe/pull/1306/)\n* Adds `AdminSCIMGroups` to support fetching SCIM groups provisioned in Terraform Enterprise via an IdP by @skj-skj [#1314](https://github.com/hashicorp/go-tfe/pull/1314)\n\n# v1.103.0\n\n## Enhancements\n* Adds the `ProjectExclusions` field to the `PolicySet` struct to support project level exclusions of policy sets by @subhro-acharjee-1 [#1290](https://github.com/hashicorp/go-tfe/pull/1290)\n\n# v1.102.0\n\n## Enhancements\n* Adds the `Size` field to `StateVersion` by @shaunakone [#1280](https://github.com/hashicorp/go-tfe/pull/1280)\n* Upgrade go version from `1.24` to `1.25` by @uk1288 [#1297](https://github.com/hashicorp/go-tfe/pull/1297)\n\n# v1.101.0\n\n## Enhancements\n* Adds the `SpeculativeEnabled` field to the `StackCreateOptions` and `StackUpdateOptions` structs by @arunatibm [1279](https://github.com/hashicorp/go-tfe/pull/1279)\n* Adds `Name` and `Provider` fields to `RegistryModuleCreateWithVCSConnectionOptions` to support explicit module naming for monorepos with non-standard repository names, by @jillirami [#1277](https://github.com/hashicorp/go-tfe/pull/1277)\n\n# v1.100.0\n\n## Enhancements\n* Adds `ReadWithOptions` method to `RunTriggers` to support including related resources when reading a run trigger by @Maed223 [#1275](https://github.com/hashicorp/go-tfe/pull/1275)\n\n# v1.99.0\n\n## Enhancements\n* Adds `ProjectRemoteState` field to `Workspace` to support state sharing at the project level, by @hs26gill [#1248](https://github.com/hashicorp/go-tfe/pull/1248)\n* Adds 'Migration' field to `StackCreateOptions` and `CreationSource` to stack struct to provide insights re: stack creation [#1266](https://github.com/hashicorp/go-tfe/pull/1266)\n\n# v1.98.0\n\n## Enhancements\n* Adds `UserTokensEnabled` field to `Organization` to support enabling/disabling user tokens for an organization by @JarrettSpiker [#1225](https://github.com/hashicorp/go-tfe/pull/1225)\n* Adds `DeploymentRunStatus` and `DeploymentStepStatus` types by @Maed223 [#1261](https://github.com/hashicorp/go-tfe/pull/1261)\n\n## Bug Fixes\n\n* Resolve differences between given and actual status composition in `StackConfigurationStatus` and `DeploymentGroupStatus`  by @Maed223 [#1261](https://github.com/hashicorp/go-tfe/pull/1261)\n\n# v1.97.0\n\n## Enhancements\n\n* Add variable set support for stacks with `ApplyToStacks`, `RemoveFromStacks`, and `UpdateStacks` API methods by @nithishravindra [#1251](https://github.com/hashicorp/go-tfe/pull/1251)\n\n# v1.96.0\n\n## Enhancements\n\n* QueryRun API is now generally available in HCP Terraform (not available in Terraform Enterprise), by @sowju-hashicorp [#1245](https://github.com/hashicorp/go-tfe/pull/1245)\n\n* Remove org settings validation in RegistryModulesCreateMonorepo tests, by @jillirami ([#1236](https://github.com/hashicorp/go-tfe/pull/1236))\n* Add `RemoteTFENumericVersion()` to the `Client` interface, which exposes the `X-TFE-Current-Version` header set by a remote TFE instance by @skj-skj [#1246](https://github.com/hashicorp/go-tfe/pull/1246)\n\n# v1.95.0\n\n## Enhancements\n\n* Add `Sort` option to `Agents` and `AgentPools`, by @twitnithegirl [#1229](https://github.com/hashicorp/go-tfe/pull/1229)\n* Add serialization for `StacksEnabled` field, `CanEnableStacks` & `CanCreateProject` permissions on Organization Read by @a-anurag27 [#1230](https://github.com/hashicorp/go-tfe/pull/1230)\n* Adds new stacks resources `StackDeployments`, `StackDiagnostics`, and `StackStates`, by @ctrombley [#1226](https://github.com/hashicorp/go-tfe/pull/1226)\n* Adds new `Diagnostics` methods to `StackConfigurations`, and `StackDeploymentSteps`, by @ctrombley [#1226](https://github.com/hashicorp/go-tfe/pull/1226)\n* Adds new `Artifacts` method to `StackDeploymentSteps`, by @ctrombley [#1226](https://github.com/hashicorp/go-tfe/pull/1226)\n* Add serialization for `StacksEnabled` field, `CanEnableStacks` & `CanCreateProject` permissions on Organization Read by @a-anurag27 [#1230](https://github.com/hashicorp/go-tfe/pull/1230)\n\n# v1.94.0\n\n## Enhancements\n\n* Add BETA support for Action invocations via `CreateRunOptions` by @mutahhir [#1206](https://github.com/hashicorp/go-tfe/pull/1206)\n\n# v1.93.0\n\n## Enhancements\n* Exports the StackConfiguration UploadTarGzip receiver function [#1219](https://github.com/hashicorp/go-tfe/pull/1219)\n* Updates BETA stacks resource schemas to match latest API spec by @ctrombley [#1220](https://github.com/hashicorp/go-tfe/pull/1220)\n* Adds support for Hold Your Own Key by @helenjw , @iuri-slywitch-hashicorp and @dominic-retli-hashi [#1201](https://github.com/hashicorp/go-tfe/pull/1201)\n\n## Deprecations\n\n* The beta `StackDeployments`, `StackPlan`, `StackPlanOperation`, and `StackSource` resources have been removed, by @sahar-azizighannad  [#1205](https://github.com/hashicorp/go-tfe/pull/1205)\n\n# v1.92.0\n\n## Enhancements\n\n* Adds BETA support for performing Registry Module test runs on custom Agents by @hashimooon [#1209](httpsLhttps://github.com/hashicorp/go-tfe/pull/1209)\n* Adds support for managing agent pools on `Stacks` by @maed223 [#1214](https://github.com/hashicorp/go-tfe/pull/1214)\n\n## Bug Fixes\n\n* Fixes arch validation on Terraform, OPA, and Sentinel Tool Versions when providing top level `url` and `sha` with multiple `archs` by @kelsi-hoyle [#1212](https://github.com/hashicorp/go-tfe/pull/1212)\n\n# v1.91.1\n\n## Bug Fixes\n\n* Fixes timestamp attribute mapping for deployment runs to use correct API field names (`created-at`/`updated-at` instead of `started-at`/`completed-at`) by @shwetamurali [#1199](https://github.com/hashicorp/go-tfe/pull/1199)\n\n## Enhancements\n\n* Adds support for listing `StackConfigurationSummaries` by @hwatkins05-hashicorp [#1204](https://github.com/hashicorp/go-tfe/pull/1204)\n\n# v1.91.0\n\n## Enhancements\n\n* Adds `Logs` method to `QueryRuns`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @brandonc [#1186](https://github.com/hashicorp/go-tfe/pull/1186)\n* Adds `Source` field to `Workspace` by @jpadrianoGo [#1124](https://github.com/hashicorp/go-tfe/pull/1124)\n* Adds `CreatedBy` relation to `AgentToken` by @jpadrianoGo [#1149](https://github.com/hashicorp/go-tfe/pull/1149)\n* Adds `CreatedAt` field to `AgentPool` by @jpadrianoGo [#1150](https://github.com/hashicorp/go-tfe/pull/1150)\n* Adds `CanceledAt`, `RunEvents`, `TriggerReason` field to `Run` by @jpadrianoGo [#1161](https://github.com/hashicorp/go-tfe/pull/1161)\n* Adds `AllowedProjects` and `ExcludedWorkspaces` to `AgentPool` by @tylerwolf [#1185](https://github.com/hashicorp/go-tfe/pull/1185)\n* Adds `DefaultExecutionMode`, `DefaultAgentPool`, and `SettingOverwrites` to `Project` by @tylerwolf [#1185](https://github.com/hashicorp/go-tfe/pull/1185)\n* Adds `IconUrl`, `InstallationType`, and `InstallationURL` to githubAppInstallation, by @jpadrianoGo [#1191](https://github.com/hashicorp/go-tfe/pull/1191)\n* Adds support for `Workspace` VCSRepoOptions source_directory and tag_prefix, by @jillrami [#1194] (https://github.com/hashicorp/go-tfe/pull/1194)\n\n# v1.90.0\n\n## Bug Fixes\n\n* Fixes `IngressAttributes` field decoding in `PolicySetVersion` by @rageshganeshkumar [#1164](https://github.com/hashicorp/go-tfe/pull/1164)\n* Fixes issue [1061](https://github.com/hashicorp/go-tfe/issues/1061), validation accepts all RunStatus including `\"cost_estimated\"` by @KenCox-Hashicorp [#1171](https://github.com/hashicorp/go-tfe/pull/1171)\n\n## Enhancements\n* Add support for querying and filtering private registry modules based on a search query, `provider`, `registry_name` and `organization_name`, by @gautambaghel [#1179](https://github.com/hashicorp/go-tfe/pull/1179)\n* Adds support for `RegistryModule` VCS source_directory and tag_prefix options, by @jillrami [#1154] (https://github.com/hashicorp/go-tfe/pull/1154)\n* Adds endpoint for reruning a stack deployment by @hwatkins05-hashicorp/@Maed223 [#1176](https://github.com/hashicorp/go-tfe/pull/1176)\n* Adds `ReadByName` for `StackDeploymentGroup` by @Maed223 [#1181](https://github.com/hashicorp/go-tfe/pull/1181)\n* Adds BETA support for listing `QueryRuns`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @brandonc [#1177](https://github.com/hashicorp/go-tfe/pull/1177)\n* Adds BETA support for `CreateAndUpload` for `StackConfiguration`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @Maed223 [#1184](https://github.com/hashicorp/go-tfe/pull/1184)\n\n\n# v1.89.0\n\n## Enhancements\n* Add the `Links` attribute to the `StackDeploymentStep` struct to support the deployment step GET endpoint by @shwetamurali [#1167](https://github.com/h)\n* Adds endpoint for cancelling `StackDeploymentRun` by @Maed223 [#1172](https://github.com/hashicorp/go-tfe/pull/1172)\n* Adds `CreatedAt` and `UpdatedAt` fields to `StackConfiguration` by @Maed223 [#1168](https://github.com/hashicorp/go-tfe/pull/1168)\n* Adds endpoint for advancing `StackDeploymentStep` by @hwatkins05-hashicorp [#1166](https://github.com/hashicorp/go-tfe/pull/1166)\n\n# v1.88.0\n\n## Enhancements\n\n* Adds BETA support for reading, testing and updating Organization Audit Configuration by @glennsarti-hashi [#1151](https://github.com/hashicorp/go-tfe/pull/1151)\n* Adds `Completed` status to `StackConfiguration` by @hwatkins05-hashicorp [#1163](https://github.com/hashicorp/go-tfe/pull/1163)\n\n# v1.87.0\n\n## Bug Fixes\n\n* Fixes incorrect primary key usage on `StackDeploymentRun`, `StackDeploymentGroup`, and `StackDeploymentStep`, by @Maed223 [#1156](https://github.com/hashicorp/go-tfe/pull/1156)\n\n## Enhancements\n\n* Updates endpoint for updating stack configuration from `actions/update-configuration` to `fetch-latest-from-vcs` by @hwatkins05-hashicorp [#1157](https://github.com/hashicorp/go-tfe/pull/1157)\n\n# v1.86.0\n\n## Enhancements\n\n* Adds option for `Includes` for `StackDeploymentRuns` Read/List, by @Maed223 [#1152](https://github.com/hashicorp/go-tfe/pull/1152)\n\n# v1.85.0\n\n## Bug Fixes\n\n* Fix the registry URL path used for `ReadTerraformRegistryModule` by @paladin-devops [#1141](https://github.com/hashicorp/go-tfe/pull/1141)\n\n## Enhancements\n\n* Adds support for managing reserved tag keys within a TFE organization, by @ctrombley [#1145](https://github.com/hashicorp/go-tfe/pull/1145)\n\n# v1.84.0\n\n## Enhancements\n\n* Adds BETA support for listing `StackConfigurationList`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @shwetamurali [#1138](https://github.com/hashicorp/go-tfe/pull/1138)\n* Adds BETA support for approving all plans for a stack deployment run, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @ctrombley [#1136](https://github.com/hashicorp/go-tfe/pull/1136)\n* Adds BETA support for listing and reading `StackDeploymentSteps`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @ctrombley [#1133](https://github.com/hashicorp/go-tfe/pull/1133)\n* Adds BETA support for approving all plans in `StackDeploymentGroups`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @hwatkins05-hashicorp [#1137](https://github.com/hashicorp/go-tfe/pull/1137)\n\n## Bug Fixes\n\n* Fix the registry URL path used for `ReadTerraformRegistryModule` by @paladin-devops [#1141](https://github.com/hashicorp/go-tfe/pull/1141)\n\n# v1.83.0\n\n## Enhancements\n\n* Adds BETA support for listing `StackDeploymentRuns`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @shwetamurali [#1134](https://github.com/hashicorp/go-tfe/pull/1134)\n* Add support for HCP Terraform `/api/v2/workspaces/{external_id}/all-vars` API endpoint to fetch the list of all variables available to a workspace (include inherited variables from varsets) by @debrin-hc [#1105](https://github.com/hashicorp/go-tfe/pull/1105)\n* Adds BETA support for listing `StackDeploymentGroups`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @hwatkins05-hashicorp [#1128](https://github.com/hashicorp/go-tfe/pull/1128)\n* Adds BETA support for removing/adding VCS backing for a Stack, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @Maed223 [#1131](https://github.com/hashicorp/go-tfe/pull/1131)\n* Adds BETA support for reading a `StackDeploymentGroup`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @hwatkins05-hashicorp [#1132](https://github.com/hashicorp-go-tfe/pull/1132)\n\n## Enhancements\n\n* Adds `PrivateRunTasks` field to Entitlements by @glennsarti [#944](https://github.com/hashicorp/go-tfe/pull/944)\n* Adds `AgentPool` relationship to options when creating and updating Run Tasks by @glennsarti [#944](https://github.com/hashicorp/go-tfe/pull/944)\n\n# v1.82.0\n\n## Enhancements\n\n* Adds BETA support for speculative runs with `Stacks` resources and removes VCS repo validity check on `Stack` creation, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @hwatkins05-hashicorp [#1119](https://github.com/hashicorp/go-tfe/pull/1119)\n* Adds support for listing team tokens, by @mkam [#1109](https://github.com/hashicorp/go-tfe/pull/1109)\n\n## Bug Fixes\n\n* Fixes hard coded public terraform registry URL for ReadTerraformRegistryModule, by @paladin-devops [#1126](https://github.com/hashicorp/go-tfe/pull/1126)\n\n# v1.81.0\n\n## Enhancements\n* Adds `IngressAttributes` field to `PolicySetVersion` by @jpadrianoGo [#1092](https://github.com/hashicorp/go-tfe/pull/1092)\n* Adds `ConfirmedBy` field to `Run` by @jpadrianoGo [#1110](https://github.com/hashicorp/go-tfe/pull/1110)\n\n# v1.80.0\n\n## Enhancements\n* Adds BETA support for `PolicyPaths` to the `Runs` interface, by sebasslash [#1104](https://github.com/hashicorp/go-tfe/pull/1104)\n\n# v1.79.0\n\n## BREAKING CHANGES\n\n* Updates team token `Description` to be a pointer, allowing for both nil descriptions and empty string descriptions. Team token descriptions and the ability to create multiple team tokens is in BETA, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users, by @mkam [#1088](https://github.com/hashicorp/go-tfe/pull/1088)\n\n## Enhancements\n\n* Adds `AgentPool` field to the OAuthClientUpdateOptions struct, which is used to associate a VCS Provider with an AgentPool for PrivateVCS support  by @jpogran [#1075](https://github.com/hashicorp/go-tfe/pull/1075)\n\n* Add BETA support for use of OPA and Sentinel with Linux arm64 agents, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users @natalie-todd [#1090](https://github.com/hashicorp/go-tfe/pull/1090)\n\n# v1.78.0\n\n## Enhancements\n* Adds `Links` field to `EffectiveTagBindings` to check whether an effective tag binding is inherited, by @sebasslash [#1087](https://github.com/hashicorp/go-tfe/pull/1087)\n\n# v1.77.0\n\n## Enhancements\n\n* Remove `DefaultProject` from `OrganizationUpdateOptions` to prevent updating an organization's default project, by @netramali [#1078](https://github.com/hashicorp/go-tfe/pull/1078)\n* Adds support for creating multiple team tokens by adding `Description` to `TeamTokenCreateOptions`. This provides BETA support, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users, by @mkam [#1083](https://github.com/hashicorp/go-tfe/pull/1083)\n* Adds support for reading and deleting team tokens by ID, by @mkam [#1083](https://github.com/hashicorp/go-tfe/pull/1083)\n\n## BREAKING CHANGES\n\nIn the last release, Runs interface method `ListForOrganization` included pagination fields `TotalCount` and `TotalPages`, but these are being removed as this feature approaches general availablity by @brandonc [#1074](https://github.com/hashicorp/go-tfe/pull/1074)\n\n\n# v1.76.0\n\n## Enhancements\n\n* Adds `DefaultProject` to `OrganizationUpdateOptions` to support updating an organization's default project. This provides BETA support, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users, by @mkam [#1056](https://github.com/hashicorp/go-tfe/pull/1056)\n* Adds `ReadTerraformRegistryModule` to support reading a registry module from Terraform Registry's proxied endpoints by @paladin-devops [#1057](https://github.com/hashicorp/go-tfe/pull/1057)\n* Adds a new method `ListForOrganization` to list Runs in an organization by @arybolovlev [#1059](https://github.com/hashicorp/go-tfe/pull/1059)\n\n## Bug fixes\n\n* Adds `ToolVersionArchitecture` to `AdminTerraformVersionUpdateOptions` and `AdminTerraformVersion`. This provides BETA support, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @kelsi-hoyle [#1047](https://github.com/hashicorp/go-tfe/pull/1047)\n\n# v1.75.0\n\n## Enhancements\n\n* Adds `EffectiveTagBindings` relation to projects and workspaces, allowing the relation to be included when listing projects or workspaces by @sebasslash [#1043](https://github.com/hashicorp/go-tfe/pull/1043)\n\n# v1.74.1\n\n## Enhancements\n\n* Add parallelism to create options for TF Test Runs by @dsa0x [1037](https://github.com/hashicorp/go-tfe/pull/1025)\n\n# v1.74.0\n\n## Enhancements\n\n* Add BETA support for adding custom project permission for variable sets `ProjectVariableSetsPermission` by @netramali [21879](https://github.com/hashicorp/atlas/pull/21879)\n\n# v1.73.1\n\n## Bug fixes\n\n* Includes a critical security update in an upstream depdendency `hashicorp/go-slug` @NodyHub [#1025](https://github.com/hashicorp/go-tfe/pull/1025)\n* Fix bug in BETA support for Linux arm64 agents, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users @natalie-todd [#1022](https://github.com/hashicorp/go-tfe/pull/1022)\n\n# v1.73.0\n\n## Enhancements\n\n* Add support for team notification configurations @notchairmk [#1016](https://github.com/hashicorp/go-tfe/pull/1016)\n\n# v1.72.0\n\n## Enhancements\n\n* Add support for project level auto destroy settings @simonxmh [#1011](https://github.com/hashicorp/go-tfe/pull/1011)\n* Add BETA support for Linux arm64 agents, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users @natalie-todd [#1022](https://github.com/hashicorp/go-tfe/pull/1022)\n* Adds support to delete all tag bindings on either a project or workspace by @sebasslash [#1023](https://github.com/hashicorp/go-tfe/pull/1023)\n\n# v1.71.0\n\n## Enhancements\n\n* Add support for listing effective tag bindings for a workspace or project by @brandonc [#996](https://github.com/hashicorp/go-tfe/pull/996)\n* Add support for listing no-code modules by @paladin-devops [#1003](https://github.com/hashicorp/go-tfe/pull/1003)\n\n# v1.70.0\n\n## Enhancements\n\n* Actually adds support for adding/updating key/value tags, which was not unintentionally removed from the last release by @brandonc [#987](https://github.com/hashicorp/go-tfe/pull/987)\n\n# v1.69.0\n\n## Enhancements\n\n* Adds BETA support for a variable set `Parent` relation, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @jbonhag [#992](https://github.com/hashicorp/go-tfe/pull/992)\n* Add support for adding/updating key/value tags by @brandonc [#991](https://github.com/hashicorp/go-tfe/pull/991)\n* Add support for reading a registry module by its unique identifier by @dsa0x [#988](https://github.com/hashicorp/go-tfe/pull/988)\n* Add support for enabling Stacks on an organization by @brandonc [#987](https://github.com/hashicorp/go-tfe/pull/987)\n* Add support for filtering by key/value tags by @brandonc [#987](https://github.com/hashicorp/go-tfe/pull/987)\n* Adds `SpeculativePlanManagementEnabled` field to `Organization` by @lilincmu [#983](https://github.com/hashicorp/go-tfe/pull/983)\n\n# v1.68.0\n\n## Enhancements\n\n* Add support for reading a no-code module's variables by @paladin-devops [#979](https://github.com/hashicorp/go-tfe/pull/979)\n* Add Waypoint entitlements (the `waypoint-actions` and `waypoint-templates-and-addons` attributes) to `Entitlements` by @ignatius-j [#984](https://github.com/hashicorp/go-tfe/pull/984)\n\n# v1.67.1\n\n## Bug Fixes\n\n* Fixes a bug in `NewRequest` that did not allow query parameters to be specified in the first parameter, which broke several methods: `RegistryModules ReadVersion`, `VariableSets UpdateWorkspaces`, and `Workspaces Readme` by @brandonc [#982](https://github.com/hashicorp/go-tfe/pull/982)\n\n# v1.67.0\n\n## Enhancements\n\n* `Workspaces`: The `Unlock` method now returns a `ErrWorkspaceLockedStateVersionStillPending` error if the latest state version upload is still pending within the platform. This is a retryable error. by @brandonc [#978](https://github.com/hashicorp/go-tfe/pull/978)\n\n# v1.66.0\n\n## Enhancements\n\n* Adds `billable-rum-count` attribute to `StateVersion` by @shoekstra [#974](https://github.com/hashicorp/go-tfe/pull/974)\n\n## Bug Fixes\n\n* Fixed the incorrect error \"workspace already unlocked\" being returned when attempting to unlock a workspace that was locked by a Team or different User @ctrombley / @lucasmelin [#975](https://github.com/hashicorp/go-tfe/pull/975)\n\n# v1.65.0\n\n## Enhancements\n\n* Adds support for deleting `Stacks` that still have deployments through `ForceDelete` by @hashimoon [#969](https://github.com/hashicorp/go-tfe/pull/969)\n\n## Bug Fixes\n\n* Fixed `RegistryNoCodeModules` method `UpgradeWorkspace` to return a `WorkspaceUpgrade` type. This resulted in a BREAKING CHANGE, yet the previous type was not properly decoded nor reflective of the actual API result by @paladin-devops [#955](https://github.com/hashicorp/go-tfe/pull/955)\n\n# v1.64.2\n\n## Enhancements\n\n* Adds support for including no-code permissions to the `OrganizationPermissions` struct [#967](https://github.com/hashicorp/go-tfe/pull/967)\n\n# v1.64.1\n\n## Bug Fixes\n\n* Fixes BETA feature regression in `Stacks` associated with decoding `StackVCSRepo` data by @brandonc [#964](https://github.com/hashicorp/go-tfe/pull/964)\n\n# v1.64.0\n\n* Adds support for creating different organization token types by @glennsarti [#943](https://github.com/hashicorp/go-tfe/pull/943)\n* Adds more BETA support for `Stacks` resources, which is is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @DanielMSchmidt [#963](https://github.com/hashicorp/go-tfe/pull/963)\n\n# v1.63.0\n\n## Enhancements\n\n* Adds more BETA support for `Stacks` resources, which is is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @brandonc [#957](https://github.com/hashicorp/go-tfe/pull/957) and @DanielMSchmidt [#960](https://github.com/hashicorp/go-tfe/pull/960)\n\n# v1.62.0\n\n## Bug Fixes\n\n* Fixed `RegistryNoCodeModules` methods `CreateWorkspace` and `UpdateWorkspace` to return a `Workspace` type. This resulted in a BREAKING CHANGE, yet the previous type was not properly decoded nor reflective of the actual API result by @paladin-devops [#954](https://github.com/hashicorp/go-tfe/pull/954)\n\n## Enhancements\n\n* Adds `AllowMemberTokenManagement` permission to `Team` by @juliannatetreault [#922](https://github.com/hashicorp/go-tfe/pull/922)\n* Adds `PrivateRunTasks` field to Entitlements by @glennsarti [#944](https://github.com/hashicorp/go-tfe/pull/944)\n* Adds `AgentPool` relationship to options when creating and updating Run Tasks by @glennsarti [#944](https://github.com/hashicorp/go-tfe/pull/944)\n\n# v1.61.0\n\n## Enhancements\n\n* Adds support for creating no-code workspaces by @paladin-devops [#927](https://github.com/hashicorp/go-tfe/pull/927)\n* Adds support for upgrading no-code workspaces by @paladin-devops [#935](https://github.com/hashicorp/go-tfe/pull/935)\n\n# v1.60.0\n\n## Enhancements\n\n* Adds more BETA support for `Stacks` resources, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @brandonc. [#934](https://github.com/hashicorp/go-tfe/pull/934)\n\n# v1.59.0\n\n## Features\n\n* Adds support for the Run Tasks Integration API by @karvounis-form3 [#929](https://github.com/hashicorp/go-tfe/pull/929)\n\n# v1.58.0\n\n## Enhancements\n\n* Adds BETA support for `Stacks` resources, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @brandonc. [#920](https://github.com/hashicorp/go-tfe/pull/920)\n* Adds support for managing resources that have HCP IDs by @roncodingenthusiast. [#924](https://github.com/hashicorp/go-tfe/pull/924)\n\n# v1.57.0\n\n## Enhancements\n\n* Adds the `IsUnified` field to `Project`, `Organization` and `Team` by @roncodingenthusiast [#915](https://github.com/hashicorp/go-tfe/pull/915)\n* Adds Workspace auto-destroy notification types to `NotificationTriggerType` by @notchairmk [#918](https://github.com/hashicorp/go-tfe/pull/918)\n* Adds `CreatedAfter` and `CreatedBefore` Date Time filters to `AdminRunsListOptions` by @maed223 [#916](https://github.com/hashicorp/go-tfe/pull/916)\n\n# v1.56.0\n\n## Enhancements\n* Adds `ManageAgentPools` permission to team `OrganizationAccess` by @emlanctot [#901](https://github.com/hashicorp/go-tfe/pull/901)\n\n# v1.55.0\n\n## Enhancements\n* Adds the `CurrentRunStatus` filter to allow filtering workspaces by their current run status by @arybolovlev [#899](https://github.com/hashicorp/go-tfe/pull/899)\n\n# v1.54.0\n\n## Enhancements\n* Adds the `AutoDestroyActivityDuration` field to `Workspace` by @notchairmk [#902](https://github.com/hashicorp/go-tfe/pull/902)\n\n## Deprecations\n* The `IsSiteAdmin` field on User has been deprecated. Use the `IsAdmin` field instead [#900](https://github.com/hashicorp/go-tfe/pull/900)\n\n# v1.53.0\n\n## Enhancements\n* Adds `ManageTeams`, `ManageOrganizationAccess`, and `AccessSecretTeams` permissions to team `OrganizationAccess` by @juliannatetreault [#874](https://github.com/hashicorp/go-tfe/pull/874)\n* Mocks are now generated using the go.uber.org/mock package [#897](https://github.com/hashicorp/go-tfe/pull/897)\n\n# v1.52.0\n\n## Enhancements\n* Add `EnforcementLevel` to `Policy` create and update options. This will replace the deprecated `[]Enforce` method for specifying enforcement level. @JarrettSpiker [#895](https://github.com/hashicorp/go-tfe/pull/895)\n\n## Deprecations\n* The `Enforce` fields on `Policy`, `PolicyCreateOptions`, and `PolicyUpdateOptions` have been deprecated. Use the `EnforcementLevel` instead. @JarrettSpiker [#895](https://github.com/hashicorp/go-tfe/pull/895)\n\n# v1.51.0\n\n## Enhancements\n* Adds `Teams` field to `OrganizationMembershipCreateOptions` to allow users to be added to teams at the same time they are invited to an organization. by @JarrettSpiker [#886](https://github.com/hashicorp/go-tfe/pull/886)\n* `IsCloud()` returns true when TFP-AppName is \"HCP Terraform\" by @sebasslash [#891](https://github.com/hashicorp/go-tfe/pull/891)\n* `OrganizationScoped` attribute for `OAuthClient` is now generally available by @netramali [#873](https://github.com/hashicorp/go-tfe/pull/873)\n\n# v1.50.0\n\n## Enhancements\n* Adds Bitbucket Data Center as a new `ServiceProviderType` and ensures similar validation as Bitbucket Server by @zainq11 [#879](https://github.com/hashicorp/go-tfe/pull/879)\n* Add `GlobalRunTasks` field to `Entitlements`. by @glennsarti [#865](https://github.com/hashicorp/go-tfe/pull/865)\n* Add `Global` field to `RunTask`. by @glennsarti [#865](https://github.com/hashicorp/go-tfe/pull/865)\n* Add `Stages` field to `WorkspaceRunTask`. by @glennsarti [#865](https://github.com/hashicorp/go-tfe/pull/865)\n* Changing BETA `OrganizationScoped` attribute of `OAuthClient` to be a pointer for bug fix by @netramali [884](https://github.com/hashicorp/go-tfe/pull/884)\n* Adds `Query` parameter to `VariableSetListOptions` to allow searching variable sets by name, by @JarrettSpiker[#877](https://github.com/hashicorp/go-tfe/pull/877)\n\n## Deprecations\n* The `Stage` field has been deprecated on `WorkspaceRunTask`. Instead, use `Stages`. by @glennsarti [#865](https://github.com/hashicorp/go-tfe/pull/865)\n\n# v1.49.0\n\n## Enhancements\n* Adds `post_apply` to list of possible `stages` for Run Tasks by @glennsarti [#878](https://github.com/hashicorp/go-tfe/pull/878)\n\n# v1.48.0\n\n## Features\n* For Terraform Enterprise users who have data retention policies defined on Organizations or Workspaces: A new DataRetentionPolicyChoice relation has been added to reflect that [data retention policies are polymorphic](https://developer.hashicorp.com/terraform/enterprise/api-docs/data-retention-policies#data-retention-policy-types). Organizations and workspaces may be related to a `DataRetentionPolicyDeleteOlder` or `DataRetentionPolicyDontDelete` record through the `DataRetentionPolicyChoice` struct. Data retention policies can be read using `ReadDataRetentionPolicyChoice`, and set or updated (including changing their type) using `SetDataRetentionPolicyDeleteOlder` or `SetDataRetentionPolicyDontDelete` by  @JarrettSpiker [#652](https://github.com/hashicorp/go-tfe/pull/844)\n\n## Deprecations\n* The `DataRetentionPolicy` type, and the `DataRetentionPolicy` relationship on `Organization` and `Workspace`s have been deprecated. The `DataRetentionPolicy` type is equivalent to the new `DataRetentionPolicyDeleteOlder`. The Data retention policy relationships on `Organization` and `Workspace`s are now [polymorphic](https://developer.hashicorp.com/terraform/enterprise/api-docs/data-retention-policies#data-retention-policy-types), and are represented by the `DataRetentionPolicyChoice` relationship. The existing `DataRetentionPolicy` relationship will continue to be populated when reading an `Organization` or `Workspace`, but it may be removed in a future release. @JarrettSpiker [#652](https://github.com/hashicorp/go-tfe/pull/844)\n* The `SetDataRetentionPolicy` function on `Organizations` and `Workspaces` is now deprecated in favour of `SetDataRetentionPolicyDeleteOlder` or `SetDataRetentionPolicyDontDelete`. `SetDataRetentionPolicy` will only update the data retention policy when communicating with TFE versions v202311 and v202312. @JarrettSpiker [#652](https://github.com/hashicorp/go-tfe/pull/844)\n* The `ReadDataRetentionPolicy` function on `Organizations` and `Workspaces` is now deprecated in favour of `ReadDataRetentionPolicyChoice`. `ReadDataRetentionPolicyChoice` may return the different multiple data retention policy types added in TFE 202401-1. `SetDataRetentionPolicy` will only update the data retention policy when communicating with TFE versions v202311 and v202312. @JarrettSpiker [#652](https://github.com/hashicorp/go-tfe/pull/844)\n\n## Enhancements\n* Adds `Variables` relationship field to `Workspace` by @arybolovlev [#872](https://github.com/hashicorp/go-tfe/pull/872)\n\n# v1.47.1\n\n## Bug fixes\n* Change the error message for `ErrWorkspaceStillProcessing` to be the same error message returned by the API by @uturunku1 [#864](https://github.com/hashicorp/go-tfe/pull/864)\n\n# v1.47.0\n\n## Enhancements\n* Adds BETA `description` attribute to `Project` by @netramali [#861](https://github.com/hashicorp/go-tfe/pull/861)\n* Adds `Read` method to `TestVariables` by @aaabdelgany [#851](https://github.com/hashicorp/go-tfe/pull/851)\n\n# v1.46.0\n\n## Enhancements\n* Adds `Query` field to `Project` and `Team` list options, to allow projects and teams to be searched by name by @JarrettSpiker [#849](https://github.com/hashicorp/go-tfe/pull/849)\n* Adds `AgenPool` relation to `OAuthClient` create options to support for Private VCS by enabling creation of OAuth Client when AgentPoolID is set (as an optional param) @roleesinhaHC [#841](https://github.com/hashicorp/go-tfe/pull/841)\n* Add `Sort` field to workspace list options @Maed223 [#859](https://github.com/hashicorp/go-tfe/pull/859)\n\n# v1.45.0\n\n## Enhancements\n* Updates go-tfe client to export the instance name using `AppName()` @sebasslash [#848](https://github.com/hashicorp/go-tfe/pull/848)\n* Add `DeleteByName` API endpoint to `RegistryModule` @laurenolivia [#847](https://github.com/hashicorp/go-tfe/pull/847)\n* Update deprecated `RegistryModule` endpoints `DeleteProvider` and `DeleteVersion` with new API calls @laurenolivia [#847](https://github.com/hashicorp/go-tfe/pull/847)\n\n# v1.44.0\n\n## Enhancements\n* Updates `Workspaces` to include an `AutoDestroyAt` attribute on create and update by @notchairmk and @ctrombley [#786](https://github.com/hashicorp/go-tfe/pull/786)\n* Adds `AgentsEnabled` and `PolicyToolVersion` attributes to `PolicySet` by @mrinalirao [#752](https://github.com/hashicorp/go-tfe/pull/752)\n\n# v1.43.0\n\n## Features\n* Adds `AggregatedCommitStatusEnabled` field to `Organization` by @mjyocca [#829](https://github.com/hashicorp/go-tfe/pull/829)\n\n## Enhancements\n* Adds `GlobalProviderSharing` field to `AdminOrganization` by @alex-ikse [#837](https://github.com/hashicorp/go-tfe/pull/837)\n\n# v1.42.0\n\n## Deprecations\n* The `Sourceable` field has been deprecated on `RunTrigger`. Instead, use `SourceableChoice` to locate the non-empty field representing the actual sourceable value by @brandonc [#816](https://github.com/hashicorp/go-tfe/pull/816)\n\n## Features\n* Added `AdminOPAVersion` and `AdminSentinelVersion` Terraform Enterprise admin endpoints by @mrinalirao [#758](https://github.com/hashicorp/go-tfe/pull/758)\n\n## Enhancements\n* Adds `LockedBy` relationship field to `Workspace` by @brandonc [#816](https://github.com/hashicorp/go-tfe/pull/816)\n* Adds `CreatedBy` relationship field to `TeamToken`, `UserToken`, and `OrganizationToken` by @brandonc [#816](https://github.com/hashicorp/go-tfe/pull/816)\n* Added `Sentinel` field to `PolicyResult` by @stefan-kiss. [Issue#790](https://github.com/hashicorp/go-tfe/issues/790)\n\n# v1.41.0\n\n## Enhancements\n\n* Allow managing workspace and organization data retention policies by @mwudka [#801](https://github.com/hashicorp/go-tfe/pull/817)\n\n# v1.40.0\n\n## Bug Fixes\n* Removed unused field `AgentPoolID` from the Workspace model. (Callers should be using the `AgentPool` relation instead) by @brandonc [#815](https://github.com/hashicorp/go-tfe/pull/815)\n\n## Enhancements\n* Add organization scope field for oauth clients by @Netra2104 [#812](https://github.com/hashicorp/go-tfe/pull/812)\n* Added BETA support for including `projects` relationship to oauth_client on create by @Netra2104 [#806](https://github.com/hashicorp/go-tfe/pull/806)\n* Added BETA method `AddProjects` and `RemoveProjects` for attaching/detaching oauth_client to projects by Netra2104 [#806](https://github.com/hashicorp/go-tfe/pull/806)\n* Adds a missing interface `WorkspaceResources` and the `List` method by @stefan-kiss [Issue#754](https://github.com/hashicorp/go-tfe/issues/754)\n\n# v1.39.2\n\n## Bug Fixes\n* Fixes a dependency build failure for 32 bit linux architectures by @brandonc [#814](https://github.com/hashicorp/go-tfe/pull/814)\n\n# v1.39.1\n\n## Bug Fixes\n* Fixes an issue where the request body is not preserved during certain retry scenarios by @sebasslash [#813](https://github.com/hashicorp/go-tfe/pull/813)\n\n# v1.39.0\n\n## Features\n* New WorkspaceSettingOverwritesOptions field for allowing workspaces to defer some settings to a default from their organization or project by @SwiftEngineer [#762](https://github.com/hashicorp/go-tfe/pull/762)\n* Added support for setting a default execution mode and agent pool at the organization level by @SwiftEngineer [#762](https://github.com/hashicorp/go-tfe/pull/762)\n* Added validation when configuring registry module publishing by @hashimoon [#804](https://github.com/hashicorp/go-tfe/pull/804)\n* Removed BETA labels for StateVersion Upload method, ConfigurationVersion `provisional` field, and `save-plan` runs by @brandonc [#800](https://github.com/hashicorp/go-tfe/pull/800)\n* Allow soft deleting, restoring, and permanently deleting StateVersion and ConfigurationVersion backing data by @mwudka [#801](https://github.com/hashicorp/go-tfe/pull/801)\n* Added the `AutoApplyRunTrigger` attribute to Workspaces by @nfagerlund [#798](https://github.com/hashicorp/go-tfe/pull/798)\n* Removed BETA labels for `priority` attribute in variable sets by @Netra2104 [#796](https://github.com/hashicorp/go-tfe/pull/796)\n\n# v1.38.0\n\n## Features\n* Added BETA support for including `priority` attribute to variable_set on create and update by @Netra2104 [#778](https://github.com/hashicorp/go-tfe/pull/778)\n\n# v1.37.0\n\n## Features\n* Add the tags attribute to VCSRepo to be used with registry modules by @hashimoon [#793](https://github.com/hashicorp/go-tfe/pull/793)\n\n# v1.36.0\n\n## Features\n* Added BETA support for private module registry test variables by @aaabdelgany [#787](https://github.com/hashicorp/go-tfe/pull/787)\n\n## Bug Fixes\n* Fix incorrect attribute type for `RegistryModule.VCSRepo.Tags` by @hashimoon [#789](https://github.com/hashicorp/go-tfe/pull/789)\n* Fix nil dereference panic within `StateVersions` `upload` after not handling certain state version create errors by @brandonc [#792](https://github.com/hashicorp/go-tfe/pull/792)\n\n# v1.35.0\n\n## Features\n* Added BETA support for private module registry tests by @hashimoon [#781](https://github.com/hashicorp/go-tfe/pull/781)\n\n## Enhancements\n* Removed beta flags for `PolicySetProjects` and `PolicySetWorkspaceExclusions` by @Netra2104 [#770](https://github.com/hashicorp/go-tfe/pull/770)\n\n# v1.34.0\n\n## Features\n* Added support for the new Terraform Test Runs API by @liamcervante [#755](https://github.com/hashicorp/go-tfe/pull/755)\n\n## Bug Fixes\n* \"project\" was being rejected as an invalid `Include` option when listing workspaces by @brandonc [#765](https://github.com/hashicorp/go-tfe/pull/765)\n\n\n# v1.33.0\n\n## Enhancements\n* Removed beta tags for TeamProjectAccess by @rberecka [#756](https://github.com/hashicorp/go-tfe/pull/756)\n* Added BETA support for including `workspaceExclusions` relationship to policy_set on create by @Netra2104 [#757](https://github.com/hashicorp/go-tfe/pull/757)\n* Added BETA method `AddWorkspaceExclusions` and `RemoveWorkspaceExclusions` for attaching/detaching workspace-exclusions to a policy-set by @hs26gill [#761](https://github.com/hashicorp/go-tfe/pull/761)\n\n# v1.32.1\n\n## Dependency Update\n* Updated go-slug dependency to v0.12.1\n\n# v1.32.0\n\n## Enhancements\n* Added BETA support for adding and updating custom permissions to `TeamProjectAccesses`. A `TeamProjectAccessType` of `\"custom\"` can set various permissions applied at\nthe project level to the project itself (`TeamProjectAccessProjectPermissionsOptions`) and all of the workspaces in a project (`TeamProjectAccessWorkspacePermissionsOptions`) by @rberecka [#745](https://github.com/hashicorp/go-tfe/pull/745)\n* Added BETA field `Provisional` to `ConfigurationVersions` by @brandonc [#746](https://github.com/hashicorp/go-tfe/pull/746)\n\n\n# v1.31.0\n\n## Enhancements\n* Added BETA support for including `projects` relationship and `projects-count` attribute to policy_set on create by @hs26gill [#737](https://github.com/hashicorp/go-tfe/pull/737)\n* Added BETA method `AddProjects` and `RemoveProjects` for attaching/detaching policy set to projects by @Netra2104 [#735](https://github.com/hashicorp/go-tfe/pull/735)\n\n# v1.30.0\n\n## Enhancements\n* Adds `SignatureSigningMethod` and `SignatureDigestMethod` fields in `AdminSAMLSetting` struct by @karvounis-form3 [#731](https://github.com/hashicorp/go-tfe/pull/731)\n* Adds `Certificate`, `PrivateKey`, `TeamManagementEnabled`, `AuthnRequestsSigned`, `WantAssertionsSigned`, `SignatureSigningMethod`, `SignatureDigestMethod` fields in `AdminSAMLSettingsUpdateOptions` struct by @karvounis-form3 [#731](https://github.com/hashicorp/go-tfe/pull/731)\n\n# v1.29.0\n\n## Enhancements\n* Adds `RunPreApplyCompleted` run status by @uk1288 [#727](https://github.com/hashicorp/go-tfe/pull/727)\n* Added BETA support for saved plan runs, by @nfagerlund [#724](https://github.com/hashicorp/go-tfe/pull/724)\n    * New `SavePlan` fields in `Run` and `RunCreateOptions`\n    * New `RunPlannedAndSaved` `RunStatus` value\n    * New `PlannedAndSavedAt` field in `RunStatusTimestamps`\n    * New `RunOperationSavePlan` constant for run list filters\n\n# v1.28.0\n\n## Enhancements\n* Update `Workspaces` to include associated `project` resource by @glennsarti [#714](https://github.com/hashicorp/go-tfe/pull/714)\n* Adds BETA method `Upload` method to `StateVersions` and support for pending state versions by @brandonc [#717](https://github.com/hashicorp/go-tfe/pull/717)\n* Adds support for the query parameter `q` to search `Organization Tags` by name by @sharathrnair87 [#720](https://github.com/hashicorp/go-tfe/pull/720)\n* Added ContextWithResponseHeaderHook support to `IPRanges` by @brandonc [#717](https://github.com/hashicorp/go-tfe/pull/717)\n\n## Bug Fixes\n* `ConfigurationVersions`, `PolicySetVersions`, and `RegistryModules` `Upload` methods were sending API credentials to the specified upload URL, which was unnecessary by @brandonc [#717](https://github.com/hashicorp/go-tfe/pull/717)\n\n# v1.27.0\n\n## Enhancements\n* Adds `RunPreApplyRunning` and `RunQueuingApply` run statuses by @uk1288 [#712](https://github.com/hashicorp/go-tfe/pull/712)\n\n## Bug Fixes\n* AgentPool `Update` is not able to remove all allowed workspaces from an agent pool. That operation is now handled by a separate `UpdateAllowedWorkspaces` method using `AgentPoolAllowedWorkspacesUpdateOptions` by @hs26gill [#701](https://github.com/hashicorp/go-tfe/pull/701)\n\n# v1.26.0\n\n## Enhancements\n\n* Adds BETA fields `ResourceImports` count to both `Plan` and `Apply` types as well as `AllowConfigGeneration` to the `Run` struct type. These fields are not generally available and are subject to change in a future release.\n\n# v1.25.1\n\n## Bug Fixes\n* Workspace safe delete conflict error when workspace is locked has been restored\nto the original message using the error `ErrWorkspaceLockedCannotDelete` instead of\n`ErrWorkspaceLocked`\n\n# v1.25.0\n\n## Enhancements\n* Workspace safe delete 409 conflict errors associated with resources still being managed or being processed (indicating that you should try again later) are now the named errors  `ErrWorkspaceStillProcessing` and `ErrWorkspaceNotSafeToDelete` by @brandonc [#703](https://github.com/hashicorp/go-tfe/pull/703)\n\n# v1.24.0\n\n## Enhancements\n* Adds support for a new variable field `version-id` by @arybolovlev [#697](https://github.com/hashicorp/go-tfe/pull/697)\n* Adds `ExpiredAt` field to `OrganizationToken`, `TeamToken`, and `UserToken`. This enhancement will be available in TFE release, v202305-1. @JuliannaTetreault [#672](https://github.com/hashicorp/go-tfe/pull/672)\n* Adds `ContextWithResponseHeaderHook` context for use with the ClientRequest Do method that allows callers to define a callback which receives raw http Response headers.  @apparentlymart [#689](https://github.com/hashicorp/go-tfe/pull/689)\n\n\n# v1.23.0\n\n## Features\n* `ApplyToProjects` and `RemoveFromProjects` to `VariableSets` endpoints now generally available.\n* `ListForProject` to `VariableSets` endpoints now generally available.\n\n## Enhancements\n* Adds `OrganizationScoped` and `AllowedWorkspaces` fields for creating workspace scoped agent pools and adds `AllowedWorkspacesName` for filtering agents pools associated with a given workspace by @hs26gill [#682](https://github.com/hashicorp/go-tfe/pull/682/files)\n\n## Bug Fixes\n\n\n# v1.22.0\n\n## Beta API Changes\n* The beta `no_code` field in `RegistryModuleCreateOptions` has been changed from `bool` to `*bool` and will be removed in a future version because a new, preferred method for managing no-code registry modules has been added in this release.\n\n## Features\n* Add beta endpoints `Create`, `Read`, `Update`, and `Delete` to manage no-code provisioning for a `RegistryModule`. This allows users to enable no-code provisioning for a registry module, and to configure the provisioning settings for that module version. This also allows users to disable no-code provisioning for a module version. @dsa0x [#669](https://github.com/hashicorp/go-tfe/pull/669)\n\n# v1.21.0\n\n## Features\n* Add beta endpoints `ApplyToProjects`  and `RemoveFromProjects` to `VariableSets`.  Applying a variable set to a project will apply that variable set to all current and future workspaces in that project.\n* Add beta endpoint `ListForProject` to `VariableSets` to list all variable sets applied to a project.\n* Add endpoint `RunEvents` which lists events for a specific run by @glennsarti [#680](https://github.com/hashicorp/go-tfe/pull/680)\n\n## Bug Fixes\n* `VariableSets.Read` did not honor the Include values due to a syntax error in the struct tag of `VariableSetReadOptions` by @sgap [#678](https://github.com/hashicorp/go-tfe/pull/678)\n\n## Enhancements\n* Adds `ProjectID` filter to allow filtering of workspaces of a given project in an organization by @hs26gill [#671](https://github.com/hashicorp/go-tfe/pull/671)\n* Adds `Name` filter to allow filtering of projects by @hs26gill [#668](https://github.com/hashicorp/go-tfe/pull/668/files)\n* Adds `ManageMembership` permission to team `OrganizationAccess` by @JarrettSpiker [#652](https://github.com/hashicorp/go-tfe/pull/652)\n* Adds `RotateKey` and `TrimKey` Admin endpoints by @mpminardi [#666](https://github.com/hashicorp/go-tfe/pull/666)\n* Adds `Permissions` to `User` by @jeevanragula [#674](https://github.com/hashicorp/go-tfe/pull/674)\n* Adds `IsEnterprise` and `IsCloud` boolean methods to the client by @sebasslash [#675](https://github.com/hashicorp/go-tfe/pull/675)\n\n# v1.20.0\n\n## Enhancements\n* Update team project access to include additional project roles by @joekarl [#642](https://github.com/hashicorp/go-tfe/pull/642)\n\n# v1.19.0\n\n## Enhancements\n* Removed Beta tags from `Project` features by @hs26gill [#637](https://github.com/hashicorp/go-tfe/pull/637)\n* Add `Filter` and `Sort` fields to `AdminWorkspaceListOptions` to allow filtering and sorting of workspaces by @laurenolivia [#641](https://github.com/hashicorp/go-tfe/pull/641)\n* Add support for `List` and `Read` Github app installation APIs by @roleesinhaHC [#655](https://github.com/hashicorp/go-tfe/pull/655)\n* Add `GHAInstallationID` field to `VCSRepoOptions` and `VCSRepo` structs by @roleesinhaHC [#655](https://github.com/hashicorp/go-tfe/pull/655)\n\n# v1.18.0\n\n## Enhancements\n* Adds `BaseURL` and `BaseRegistryURL` methods to `Client` to expose its configuration by @brandonc [#638](https://github.com/hashicorp/go-tfe/pull/638)\n* Adds `ReadWorkspaces` and `ReadProjects` permissions to `Organizations` by @JuliannaTetreault [#614](https://github.com/hashicorp/go-tfe/pull/614)\n\n# v1.17.0\n\n## Enhancements\n* Add Beta endpoint `TeamProjectAccesses` to manage Project Access for Teams by @hs26gill [#599](https://github.com/hashicorp/go-tfe/pull/599)\n* Updates api doc links from terraform.io to developer.hashicorp domain by @uk1288 [#629](https://github.com/hashicorp/go-tfe/pull/629)\n* Adds `UploadTarGzip()` method to `RegistryModules` and `ConfigurationVersions` interface by @sebasslash [#623](https://github.com/hashicorp/go-tfe/pull/623)\n* Adds `ManageProjects` field to `OrganizationAccess` struct by @hs26gill [#633](https://github.com/hashicorp/go-tfe/pull/633)\n* Adds agent-count to `AgentPools` endpoint. @evilensky [#611](https://github.com/hashicorp/go-tfe/pull/611)\n* Adds `Links` to `Workspace`, (currently contains \"self\" and \"self-html\" paths) @brandonc [#622](https://github.com/hashicorp/go-tfe/pull/622)\n\n# v1.16.0\n\n## Bug Fixes\n\n* Project names were being incorrectly validated as ID's @brandonc [#608](https://github.com/hashicorp/go-tfe/pull/608)\n\n## Enhancements\n* Adds `List()` method to `GPGKeys` interface by @sebasslash [#602](https://github.com/hashicorp/go-tfe/pull/602)\n* Adds `ProviderBinaryUploaded` field to `RegistryPlatforms` struct by @sebasslash [#602](https://github.com/hashicorp/go-tfe/pull/602)\n\n# v1.15.0\n\n## Enhancements\n\n* Add Beta `Projects` endpoint. The API is in not yet available to all users @hs26gill [#564](https://github.com/hashicorp/go-tfe/pull/564)\n\n# v1.14.0\n\n## Enhancements\n\n* Adds Beta parameter `Overridable` for OPA `policy set` update API (`PolicySetUpdateOptions`) @mrinalirao [#594](https://github.com/hashicorp/go-tfe/pull/594)\n* Adds new task stage status values representing `canceled`, `errored`, `unreachable` @mrinalirao [#594](https://github.com/hashicorp/go-tfe/pull/594)\n\n# v1.13.0\n\n## Bug Fixes\n\n* Fixes `AuditTrail` pagination parameters (`CurrentPage`, `PreviousPage`, `NextPage`, `TotalPages`, `TotalCount`), which were not deserialized after reading from the List endpoint by @brandonc [#586](https://github.com/hashicorp/go-tfe/pull/586)\n\n## Enhancements\n\n* Add OPA support to the Policy Set APIs by @mrinalirao [#575](https://github.com/hashicorp/go-tfe/pull/575)\n* Add OPA support to the Policy APIs by @mrinalirao [#579](https://github.com/hashicorp/go-tfe/pull/579)\n* Add support for enabling no-code provisioning in an existing or new `RegistryModule` by @miguelhrocha [#562](https://github.com/hashicorp/go-tfe/pull/562)\n* Add Policy Evaluation and Policy Set Outcome APIs by @mrinalirao [#583](https://github.com/hashicorp/go-tfe/pull/583)\n* Add OPA support to Task Stage APIs by @mrinalirao [#584](https://github.com/hashicorp/go-tfe/pull/584)\n\n# v1.12.0\n\n## Enhancements\n\n* Add `search[wildcard-name]` to `WorkspaceListOptions` by @laurenolivia [#569](https://github.com/hashicorp/go-tfe/pull/569)\n* Add `NotificationTriggerAssessmentCheckFailed` notification trigger type by @rexredinger [#549](https://github.com/hashicorp/go-tfe/pull/549)\n* Add `RemoteTFEVersion()` to the `Client` interface, which exposes the `X-TFE-Version` header set by a remote TFE instance by @sebasslash [#563](https://github.com/hashicorp/go-tfe/pull/563)\n* Validate the module version as a version instead of an ID [#409](https://github.com/hashicorp/go-tfe/pull/409)\n* Add `AllowForceDeleteWorkspaces` setting to `Organizations` by @JarrettSpiker [#539](https://github.com/hashicorp/go-tfe/pull/539)\n* Add `SafeDelete` and `SafeDeleteID` APIs to `Workspaces` by @JarrettSpiker [#539](https://github.com/hashicorp/go-tfe/pull/539)\n* Add `ForceExecute()` to `Runs` to allow force executing a run by @annawinkler [#570](https://github.com/hashicorp/go-tfe/pull/570)\n* Pre-plan and Pre-Apply Run Tasks are now generally available (beta comments removed) by @glennsarti [#555](https://github.com/hashicorp/go-tfe/pull/555)\n\n# v1.11.0\n\n## Enhancements\n\n* Add `Query` and `Status` fields to `OrganizationMembershipListOptions` to allow filtering memberships by status or username by @sebasslash [#550](https://github.com/hashicorp/go-tfe/pull/550)\n* Add `ListForWorkspace` method to `VariableSets` interface to enable fetching variable sets associated with a workspace by @tstapler [#552](https://github.com/hashicorp/go-tfe/pull/552)\n* Add `NotificationTriggerAssessmentDrifted` and `NotificationTriggerAssessmentFailed` notification trigger types by @lawliet89 [#542](https://github.com/hashicorp/go-tfe/pull/542)\n\n## Bug Fixes\n* Fix marshalling of run variables in `RunCreateOptions`. The `Variables` field type in `Run` struct has changed from `[]*RunVariable` to `[]*RunVariableAttr` by @Uk1288 [#531](https://github.com/hashicorp/go-tfe/pull/531)\n\n# v1.10.0\n\n## Enhancements\n\n* Add `Query` param field to `OrganizationListOptions` to allow searching based on name or email by @laurenolivia [#529](https://github.com/hashicorp/go-tfe/pull/529)\n* Add optional `AssessmentsEnforced` to organizations and `AssessmentsEnabled` to workspaces for managing the workspace and organization health assessment (drift detection) setting by @rexredinger [#462](https://github.com/hashicorp/go-tfe/pull/462)\n\n## Bug Fixes\n* Fixes null value returned in variable set relationship in `VariableSetVariable` by @sebasslash [#521](https://github.com/hashicorp/go-tfe/pull/521)\n\n# v1.9.0\n\n## Enhancements\n* `RunListOptions` is generally available, and rename field (Name -> User) by @mjyocca [#472](https://github.com/hashicorp/go-tfe/pull/472)\n* [Beta] Adds optional `JsonState` field to `StateVersionCreateOptions` by @megan07 [#514](https://github.com/hashicorp/go-tfe/pull/514)\n\n## Bug Fixes\n* Fixed invalid memory address error when using `TaskResults` field by @glennsarti [#517](https://github.com/hashicorp/go-tfe/pull/517)\n\n# v1.8.0\n\n## Enhancements\n\n* Adds support for reading and listing Agents by @laurenolivia [#456](https://github.com/hashicorp/go-tfe/pull/456)\n* It was previously logged that we added an `Include` param field to `PolicySetListOptions` to allow policy list to include related resource data such as workspaces, policies, newest_version, or current_version by @Uk1288 [#497](https://github.com/hashicorp/go-tfe/pull/497) in 1.7.0, but this was a mistake and the field is added in v1.8.0\n\n# v1.7.0\n\n## Enhancements\n\n* Adds new run creation attributes: `allow-empty-apply`, `terraform-version`, `plan-only` by @sebasslash [#482](https://github.com/hashicorp/go-tfe/pull/447)\n* Adds additional Task Stage and Run Statuses for Pre-plan run tasks by @glennsarti [#469](https://github.com/hashicorp/go-tfe/pull/469)\n* Adds `stage` field to the create and update methods for Workspace Run Tasks by @glennsarti [#469](https://github.com/hashicorp/go-tfe/pull/469)\n* Adds `ResourcesProcessed`, `StateVersion`, `TerraformVersion`, `Modules`, `Providers`, and `Resources` fields to the State Version struct by @laurenolivia [#484](https://github.com/hashicorp/go-tfe/pull/484)\n* Add `Include` param field to `PolicySetListOptions` to allow policy list to include related resource data such as workspaces, policies, newest_version, or current_version by @Uk1288 [#497](https://github.com/hashicorp/go-tfe/pull/497)\n* Allow FileTriggersEnabled to be set to false when Git tags are present by @mjyocca @hashimoon  [#468] (https://github.com/hashicorp/go-tfe/pull/468)\n\n# v1.6.0\n\n## Enhancements\n* Remove beta messaging for Run Tasks by @glennsarti [#447](https://github.com/hashicorp/go-tfe/pull/447)\n* Adds `Description` field to the `RunTask` object by @glennsarti [#447](https://github.com/hashicorp/go-tfe/pull/447)\n* Add `Name` field to `OAuthClient` by @barrettclark [#466](https://github.com/hashicorp/go-tfe/pull/466)\n* Add support for creating both public and private `RegistryModule` with no VCS connection by @Uk1288 [#460](https://github.com/hashicorp/go-tfe/pull/460)\n* Add `ConfigurationSourceAdo` configuration source option by @mjyocca [#467](https://github.com/hashicorp/go-tfe/pull/467)\n* [beta] state version outputs may now include a detailed-type attribute in a future API release by @brandonc [#479](https://github.com/hashicorp/go-tfe/pull/429)\n\n# v1.5.0\n\n## Enhancements\n* [beta] Add support for triggering Workspace runs through matching Git tags [#434](https://github.com/hashicorp/go-tfe/pull/434)\n* Add `Query` param field to `AgentPoolListOptions` to allow searching based on agent pool name, by @JarrettSpiker [#417](https://github.com/hashicorp/go-tfe/pull/417)\n* Add organization scope and allowed workspaces field for scope agents by @Netra2104 [#453](https://github.com/hashicorp/go-tfe/pull/453)\n* Adds `Namespace` and `RegistryName` fields to `RegistryModuleID` to allow reading of Public Registry Modules by @Uk1288 [#464](https://github.com/hashicorp/go-tfe/pull/464)\n\n## Bug fixes\n* Fixed JSON mapping for Configuration Versions failing to properly set the `speculative` property [#459](https://github.com/hashicorp/go-tfe/pull/459)\n\n# v1.4.0\n\n## Enhancements\n* Adds `RetryServerErrors` field to the `Config` object by @sebasslash [#439](https://github.com/hashicorp/go-tfe/pull/439)\n* Adds support for the GPG Keys API by @sebasslash [#429](https://github.com/hashicorp/go-tfe/pull/429)\n* Adds support for new `WorkspaceLimit` Admin setting for organizations [#425](https://github.com/hashicorp/go-tfe/pull/425)\n* Adds support for new `ExcludeTags` workspace list filter field by @Uk1288 [#438](https://github.com/hashicorp/go-tfe/pull/438)\n* [beta] Adds additional filter fields to `RunListOptions` by @mjyocca [#424](https://github.com/hashicorp/go-tfe/pull/424)\n* [beta] Renames the optional StateVersion field `ExtState` to `JSONStateOutputs` and changes the purpose and type by @annawinkler [#444](https://github.com/hashicorp/go-tfe/pull/444) and @brandoncroft [#452](https://github.com/hashicorp/go-tfe/pull/452)\n\n# v1.3.0\n\n## Enhancements\n* Adds support for Microsoft Teams notification configuration by @JarrettSpiker [#398](https://github.com/hashicorp/go-tfe/pull/389)\n* Add support for Audit Trail API by @sebasslash [#407](https://github.com/hashicorp/go-tfe/pull/407)\n* Adds Private Registry Provider, Provider Version, and Provider Platform APIs support by @joekarl and @annawinkler [#313](https://github.com/hashicorp/go-tfe/pull/313)\n* Adds List Registry Modules endpoint by @chroju [#385](https://github.com/hashicorp/go-tfe/pull/385)\n* Adds `WebhookURL` field to `VCSRepo` struct by @kgns [#413](https://github.com/hashicorp/go-tfe/pull/413)\n* Adds `Category` field to `VariableUpdateOptions` struct by @jtyr [#397](https://github.com/hashicorp/go-tfe/pull/397)\n* Adds `TriggerPatterns` to `Workspace` by @matejrisek [#400](https://github.com/hashicorp/go-tfe/pull/400)\n* [beta] Adds `ExtState` field to `StateVersionCreateOptions` by @brandonc [#416](https://github.com/hashicorp/go-tfe/pull/416)\n\n# v1.2.0\n\n## Enhancements\n* Adds support for reading current state version outputs to StateVersionOutputs, which can be useful for reading outputs when users don't have the necessary permissions to read the entire state by @brandonc [#370](https://github.com/hashicorp/go-tfe/pull/370)\n* Adds Variable Set methods for `ApplyToWorkspaces` and `RemoveFromWorkspaces` by @byronwolfman [#375](https://github.com/hashicorp/go-tfe/pull/375)\n* Adds `Names` query param field to `TeamListOptions` by @sebasslash [#393](https://github.com/hashicorp/go-tfe/pull/393)\n* Adds `Emails` query param field to `OrganizationMembershipListOptions` by @sebasslash [#393](https://github.com/hashicorp/go-tfe/pull/393)\n* Adds Run Tasks API support by @glennsarti [#381](https://github.com/hashicorp/go-tfe/pull/381), [#382](https://github.com/hashicorp/go-tfe/pull/382) and [#383](https://github.com/hashicorp/go-tfe/pull/383)\n\n\n## Bug fixes\n* Fixes ignored comment when performing apply, discard, cancel, and force-cancel run actions [#388](https://github.com/hashicorp/go-tfe/pull/388)\n\n# v1.1.0\n\n## Enhancements\n\n* Add Variable Set API support by @rexredinger [#305](https://github.com/hashicorp/go-tfe/pull/305)\n* Add Comments API support by @alex-ikse [#355](https://github.com/hashicorp/go-tfe/pull/355)\n* Add beta support for SSOTeamID to `Team`, `TeamCreateOptions`, `TeamUpdateOptions` by @xlgmokha [#364](https://github.com/hashicorp/go-tfe/pull/364)\n\n# v1.0.0\n\n## Breaking Changes\n* Renamed methods named Generate to Create for `AgentTokens`, `OrganizationTokens`, `TeamTokens`, `UserTokens` by @sebasslash [#327](https://github.com/hashicorp/go-tfe/pull/327)\n* Methods that express an action on a relationship have been prefixed with a verb, e.g `Current()` is now `ReadCurrent()` by @sebasslash [#327](https://github.com/hashicorp/go-tfe/pull/327)\n* All list option structs are now pointers @uturunku1 [#309](https://github.com/hashicorp/go-tfe/pull/309)\n* All errors have been refactored into constants in `errors.go` @uturunku1 [#310](https://github.com/hashicorp/go-tfe/pull/310)\n* The `ID` field in Create/Update option structs has been renamed to `Type` in accordance with the JSON:API spec by @omarismail, @uturunku1 [#190](https://github.com/hashicorp/go-tfe/pull/190), [#323](https://github.com/hashicorp/go-tfe/pull/323), [#332](https://github.com/hashicorp/go-tfe/pull/332)\n* Nested URL params (consisting of an organization, module and provider name) used to identify a `RegistryModule` have been refactored into a struct `RegistryModuleID` by @sebasslash [#337](https://github.com/hashicorp/go-tfe/pull/337)\n\n\n## Enhancements\n* Added missing include fields for `AdminRuns`, `AgentPools`, `ConfigurationVersions`, `OAuthClients`, `Organizations`, `PolicyChecks`, `PolicySets`, `Policies` and `RunTriggers` by @uturunku1 [#339](https://github.com/hashicorp/go-tfe/pull/339)\n* Cleanup documentation and improve consistency by @uturunku1 [#331](https://github.com/hashicorp/go-tfe/pull/331)\n* Add more linters to our CI pipeline by @sebasslash [#326](https://github.com/hashicorp/go-tfe/pull/326)\n* Resolve `TFE_HOSTNAME` as fallback for `TFE_ADDRESS` by @sebasslash [#340](https://github.com/hashicorp/go-tfe/pull/326)\n* Adds a `fetching` status to `RunStatus` and adds the `Archive` method to the ConfigurationVersions interface by @mpminardi [#338](https://github.com/hashicorp/go-tfe/pull/338)\n* Added a `Download` method to the `ConfigurationVersions` interface by @tylerwolf [#358](https://github.com/hashicorp/go-tfe/pull/358)\n* API Coverage documentation by @laurenolivia [#334](https://github.com/hashicorp/go-tfe/pull/334)\n\n## Bug Fixes\n* Fixed invalid memory address error when `AdminSMTPSettingsUpdateOptions.Auth` field is empty and accessed by @uturunku1 [#335](https://github.com/hashicorp/go-tfe/pull/335)\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2018 HashiCorp, Inc.\n\nMozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. “Contributor”\n\n     means each individual or legal entity that creates, contributes to the\n     creation of, or owns Covered Software.\n\n1.2. “Contributor Version”\n\n     means the combination of the Contributions of others (if any) used by a\n     Contributor and that particular Contributor’s Contribution.\n\n1.3. “Contribution”\n\n     means Covered Software of a particular Contributor.\n\n1.4. “Covered Software”\n\n     means Source Code Form to which the initial Contributor has attached the\n     notice in Exhibit A, the Executable Form of such Source Code Form, and\n     Modifications of such Source Code Form, in each case including portions\n     thereof.\n\n1.5. “Incompatible With Secondary Licenses”\n     means\n\n     a. that the initial Contributor has attached the notice described in\n        Exhibit B to the Covered Software; or\n\n     b. that the Covered Software was made available under the terms of version\n        1.1 or earlier of the License, but not also under the terms of a\n        Secondary License.\n\n1.6. “Executable Form”\n\n     means any form of the work other than Source Code Form.\n\n1.7. “Larger Work”\n\n     means a work that combines Covered Software with other material, in a separate\n     file or files, that is not Covered Software.\n\n1.8. “License”\n\n     means this document.\n\n1.9. “Licensable”\n\n     means having the right to grant, to the maximum extent possible, whether at the\n     time of the initial grant or subsequently, any and all of the rights conveyed by\n     this License.\n\n1.10. “Modifications”\n\n     means any of the following:\n\n     a. any file in Source Code Form that results from an addition to, deletion\n        from, or modification of the contents of Covered Software; or\n\n     b. any new file in Source Code Form that contains any Covered Software.\n\n1.11. “Patent Claims” of a Contributor\n\n      means any patent claim(s), including without limitation, method, process,\n      and apparatus claims, in any patent Licensable by such Contributor that\n      would be infringed, but for the grant of the License, by the making,\n      using, selling, offering for sale, having made, import, or transfer of\n      either its Contributions or its Contributor Version.\n\n1.12. “Secondary License”\n\n      means either the GNU General Public License, Version 2.0, the GNU Lesser\n      General Public License, Version 2.1, the GNU Affero General Public\n      License, Version 3.0, or any later versions of those licenses.\n\n1.13. “Source Code Form”\n\n      means the form of the work preferred for making modifications.\n\n1.14. “You” (or “Your”)\n\n      means an individual or a legal entity exercising rights under this\n      License. For legal entities, “You” includes any entity that controls, is\n      controlled by, or is under common control with You. For purposes of this\n      definition, “control” means (a) the power, direct or indirect, to cause\n      the direction or management of such entity, whether by contract or\n      otherwise, or (b) ownership of more than fifty percent (50%) of the\n      outstanding shares or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n2.1. Grants\n\n     Each Contributor hereby grants You a world-wide, royalty-free,\n     non-exclusive license:\n\n     a. under intellectual property rights (other than patent or trademark)\n        Licensable by such Contributor to use, reproduce, make available,\n        modify, display, perform, distribute, and otherwise exploit its\n        Contributions, either on an unmodified basis, with Modifications, or as\n        part of a Larger Work; and\n\n     b. under Patent Claims of such Contributor to make, use, sell, offer for\n        sale, have made, import, and otherwise transfer either its Contributions\n        or its Contributor Version.\n\n2.2. Effective Date\n\n     The licenses granted in Section 2.1 with respect to any Contribution become\n     effective for each Contribution on the date the Contributor first distributes\n     such Contribution.\n\n2.3. Limitations on Grant Scope\n\n     The licenses granted in this Section 2 are the only rights granted under this\n     License. No additional rights or licenses will be implied from the distribution\n     or licensing of Covered Software under this License. Notwithstanding Section\n     2.1(b) above, no patent license is granted by a Contributor:\n\n     a. for any code that a Contributor has removed from Covered Software; or\n\n     b. for infringements caused by: (i) Your and any other third party’s\n        modifications of Covered Software, or (ii) the combination of its\n        Contributions with other software (except as part of its Contributor\n        Version); or\n\n     c. under Patent Claims infringed by Covered Software in the absence of its\n        Contributions.\n\n     This License does not grant any rights in the trademarks, service marks, or\n     logos of any Contributor (except as may be necessary to comply with the\n     notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\n     No Contributor makes additional grants as a result of Your choice to\n     distribute the Covered Software under a subsequent version of this License\n     (see Section 10.2) or under the terms of a Secondary License (if permitted\n     under the terms of Section 3.3).\n\n2.5. Representation\n\n     Each Contributor represents that the Contributor believes its Contributions\n     are its original creation(s) or it has sufficient rights to grant the\n     rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\n     This License is not intended to limit any rights You have under applicable\n     copyright doctrines of fair use, fair dealing, or other equivalents.\n\n2.7. Conditions\n\n     Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in\n     Section 2.1.\n\n\n3. Responsibilities\n\n3.1. Distribution of Source Form\n\n     All distribution of Covered Software in Source Code Form, including any\n     Modifications that You create or to which You contribute, must be under the\n     terms of this License. You must inform recipients that the Source Code Form\n     of the Covered Software is governed by the terms of this License, and how\n     they can obtain a copy of this License. You may not attempt to alter or\n     restrict the recipients’ rights in the Source Code Form.\n\n3.2. Distribution of Executable Form\n\n     If You distribute Covered Software in Executable Form then:\n\n     a. such Covered Software must also be made available in Source Code Form,\n        as described in Section 3.1, and You must inform recipients of the\n        Executable Form how they can obtain a copy of such Source Code Form by\n        reasonable means in a timely manner, at a charge no more than the cost\n        of distribution to the recipient; and\n\n     b. You may distribute such Executable Form under the terms of this License,\n        or sublicense it under different terms, provided that the license for\n        the Executable Form does not attempt to limit or alter the recipients’\n        rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\n     You may create and distribute a Larger Work under terms of Your choice,\n     provided that You also comply with the requirements of this License for the\n     Covered Software. If the Larger Work is a combination of Covered Software\n     with a work governed by one or more Secondary Licenses, and the Covered\n     Software is not Incompatible With Secondary Licenses, this License permits\n     You to additionally distribute such Covered Software under the terms of\n     such Secondary License(s), so that the recipient of the Larger Work may, at\n     their option, further distribute the Covered Software under the terms of\n     either this License or such Secondary License(s).\n\n3.4. Notices\n\n     You may not remove or alter the substance of any license notices (including\n     copyright notices, patent notices, disclaimers of warranty, or limitations\n     of liability) contained within the Source Code Form of the Covered\n     Software, except that You may alter any license notices to the extent\n     required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\n     You may choose to offer, and to charge a fee for, warranty, support,\n     indemnity or liability obligations to one or more recipients of Covered\n     Software. However, You may do so only on Your own behalf, and not on behalf\n     of any Contributor. You must make it absolutely clear that any such\n     warranty, support, indemnity, or liability obligation is offered by You\n     alone, and You hereby agree to indemnify every Contributor for any\n     liability incurred by such Contributor as a result of warranty, support,\n     indemnity or liability terms You offer. You may include additional\n     disclaimers of warranty and limitations of liability specific to any\n     jurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n\n   If it is impossible for You to comply with any of the terms of this License\n   with respect to some or all of the Covered Software due to statute, judicial\n   order, or regulation then You must: (a) comply with the terms of this License\n   to the maximum extent possible; and (b) describe the limitations and the code\n   they affect. Such description must be placed in a text file included with all\n   distributions of the Covered Software under this License. Except to the\n   extent prohibited by statute or regulation, such description must be\n   sufficiently detailed for a recipient of ordinary skill to be able to\n   understand it.\n\n5. Termination\n\n5.1. The rights granted under this License will terminate automatically if You\n     fail to comply with any of its terms. However, if You become compliant,\n     then the rights granted under this License from a particular Contributor\n     are reinstated (a) provisionally, unless and until such Contributor\n     explicitly and finally terminates Your grants, and (b) on an ongoing basis,\n     if such Contributor fails to notify You of the non-compliance by some\n     reasonable means prior to 60 days after You have come back into compliance.\n     Moreover, Your grants from a particular Contributor are reinstated on an\n     ongoing basis if such Contributor notifies You of the non-compliance by\n     some reasonable means, this is the first time You have received notice of\n     non-compliance with this License from such Contributor, and You become\n     compliant prior to 30 days after Your receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\n     infringement claim (excluding declaratory judgment actions, counter-claims,\n     and cross-claims) alleging that a Contributor Version directly or\n     indirectly infringes any patent, then the rights granted to You by any and\n     all Contributors for the Covered Software under Section 2.1 of this License\n     shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user\n     license agreements (excluding distributors and resellers) which have been\n     validly granted by You or Your distributors under this License prior to\n     termination shall survive termination.\n\n6. Disclaimer of Warranty\n\n   Covered Software is provided under this License on an “as is” basis, without\n   warranty of any kind, either expressed, implied, or statutory, including,\n   without limitation, warranties that the Covered Software is free of defects,\n   merchantable, fit for a particular purpose or non-infringing. The entire\n   risk as to the quality and performance of the Covered Software is with You.\n   Should any Covered Software prove defective in any respect, You (not any\n   Contributor) assume the cost of any necessary servicing, repair, or\n   correction. This disclaimer of warranty constitutes an essential part of this\n   License. No use of  any Covered Software is authorized under this License\n   except under this disclaimer.\n\n7. Limitation of Liability\n\n   Under no circumstances and under no legal theory, whether tort (including\n   negligence), contract, or otherwise, shall any Contributor, or anyone who\n   distributes Covered Software as permitted above, be liable to You for any\n   direct, indirect, special, incidental, or consequential damages of any\n   character including, without limitation, damages for lost profits, loss of\n   goodwill, work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses, even if such party shall have been\n   informed of the possibility of such damages. This limitation of liability\n   shall not apply to liability for death or personal injury resulting from such\n   party’s negligence to the extent applicable law prohibits such limitation.\n   Some jurisdictions do not allow the exclusion or limitation of incidental or\n   consequential damages, so this exclusion and limitation may not apply to You.\n\n8. Litigation\n\n   Any litigation relating to this License may be brought only in the courts of\n   a jurisdiction where the defendant maintains its principal place of business\n   and such litigation shall be governed by laws of that jurisdiction, without\n   reference to its conflict-of-law provisions. Nothing in this Section shall\n   prevent a party’s ability to bring cross-claims or counter-claims.\n\n9. Miscellaneous\n\n   This License represents the complete agreement concerning the subject matter\n   hereof. If any provision of this License is held to be unenforceable, such\n   provision shall be reformed only to the extent necessary to make it\n   enforceable. Any law or regulation which provides that the language of a\n   contract shall be construed against the drafter shall not be used to construe\n   this License against a Contributor.\n\n\n10. Versions of the License\n\n10.1. New Versions\n\n      Mozilla Foundation is the license steward. Except as provided in Section\n      10.3, no one other than the license steward has the right to modify or\n      publish new versions of this License. Each version will be given a\n      distinguishing version number.\n\n10.2. Effect of New Versions\n\n      You may distribute the Covered Software under the terms of the version of\n      the License under which You originally received the Covered Software, or\n      under the terms of any subsequent version published by the license\n      steward.\n\n10.3. Modified Versions\n\n      If you create software not governed by this License, and you want to\n      create a new license for such software, you may create and use a modified\n      version of this License if you rename the license and remove any\n      references to the name of the license steward (except to note that such\n      modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses\n      If You choose to distribute Source Code Form that is Incompatible With\n      Secondary Licenses under the terms of this version of the License, the\n      notice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n\n      This Source Code Form is subject to the\n      terms of the Mozilla Public License, v.\n      2.0. If a copy of the MPL was not\n      distributed with this file, You can\n      obtain one at\n      http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular file, then\nYou may include the notice in a location (such as a LICENSE file in a relevant\ndirectory) where a recipient would be likely to look for such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - “Incompatible With Secondary Licenses” Notice\n\n      This Source Code Form is “Incompatible\n      With Secondary Licenses”, as defined by\n      the Mozilla Public License, v. 2.0.\n\n"
  },
  {
    "path": "META.d/_summary.yaml",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\n---\n\nschema: 1.1\npartition: tfc\ncategory: library\n\nsummary:\n  owner: team-tf-core-cloud\n  description: HCP Terraform and Terraform Enterprise API Client/SDK in Golang\n  visibility: external\n"
  },
  {
    "path": "META.d/data.yaml",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\ndata_summary:\n  gdpr:\n    exempt: true\n    last_reviewed: 2024-11-04\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: vet fmt lint test mocks envvars generate\n\n# Make target to generate resource scaffolding for specified RESOURCE\ngenerate: check-resource\n\t@cd ./scripts/generate_resource; \\\n\tgo mod tidy; \\\n\tgo run . $(RESOURCE) ;\n\nvet:\n\tgo vet\n\nfmt:\n\tgofmt -s -l -w .\n\nfmtcheck:\n\t./scripts/gofmtcheck.sh\n\nlint:\n\tgolangci-lint run .\n\ntest:\n\tgo test ./... $(TESTARGS) -timeout=30m\n\n# Make target to generate mocks for specified FILENAME\nmocks: check-filename\n\t@echo \"mockgen -source=$(FILENAME) -destination=mocks/$(subst .go,_mocks.go,$(FILENAME)) -package=mocks\" >> generate_mocks.sh\n\t./generate_mocks.sh\n\nenvvars:\n\t./scripts/setup-test-envvars.sh\n\ncheck-filename:\nifndef FILENAME\n\t$(error Missing FILENAME param. Example usage: FILENAME=example_resource.go make mocks)\nendif\n\ncheck-resource:\nifndef RESOURCE\n\t$(error Missing RESOURCE param. Example usage: RESOURCE=foo_bar make generate)\nendif\n\n"
  },
  {
    "path": "README.md",
    "content": "HCP Terraform and Terraform Enterprise Go Client\n==============================\n\n[![Tests](https://github.com/hashicorp/go-tfe/actions/workflows/ci.yml/badge.svg)](https://github.com/hashicorp/go-tfe/actions/workflows/ci.yml)\n[![GitHub license](https://img.shields.io/github/license/hashicorp/go-tfe.svg)](https://github.com/hashicorp/go-tfe/blob/main/LICENSE)\n[![GoDoc](https://godoc.org/github.com/hashicorp/go-tfe?status.svg)](https://godoc.org/github.com/hashicorp/go-tfe)\n[![Go Report Card](https://goreportcard.com/badge/github.com/hashicorp/go-tfe)](https://goreportcard.com/report/github.com/hashicorp/go-tfe)\n[![GitHub issues](https://img.shields.io/github/issues/hashicorp/go-tfe.svg)](https://github.com/hashicorp/go-tfe/issues)\n\nThe official Go API client for [HCP Terraform and Terraform Enterprise](https://www.hashicorp.com/products/terraform).\n\nThis client supports the [HCP Terraform V2 API](https://developer.hashicorp.com/terraform/cloud-docs/api-docs).\nAs Terraform Enterprise is a self-hosted distribution of HCP Terraform, this\nclient supports both HCP Terraform and Terraform Enterprise use cases. In all package\ndocumentation and API, the platform will always be stated as 'Terraform\nEnterprise' - but a feature will be explicitly noted as only supported in one or\nthe other, if applicable (rare).\n\n## Version Information\n\nAlmost always, minor version changes will indicate backwards-compatible features and enhancements. Occasionally, function signature changes that reflect a bug fix may appear as a minor version change. Patch version changes will be used for bug fixes, performance improvements, and otherwise unimpactful changes.\n\n## Example Usage\n\nConstruct a new TFE client, then use the various endpoints on the client to\naccess different parts of the Terraform Enterprise API. The following example lists\nall organizations.\n\n### (Recommended Approach) Using custom config to provide configuration details to the API client\n\n```go\nimport (\n  \"context\"\n  \"log\"\n\n  \"github.com/hashicorp/go-tfe\"\n)\n\nconfig := &tfe.Config{\n\tAddress: \"https://tfe.local\",\n\tToken: \"insert-your-token-here\",\n  RetryServerErrors: true,\n}\n\nclient, err := tfe.NewClient(config)\nif err != nil {\n\tlog.Fatal(err)\n}\n\norgs, err := client.Organizations.List(context.Background(), nil)\nif err != nil {\n\tlog.Fatal(err)\n}\n```\n\n### Using the default config with env vars\nThe default configuration makes use of the `TFE_ADDRESS` and `TFE_TOKEN` environment variables.\n\n1. `TFE_ADDRESS` - URL of a HCP Terraform or Terraform Enterprise instance. Example: `https://tfe.local`\n1. `TFE_TOKEN` - An [API token](https://developer.hashicorp.com/terraform/cloud-docs/users-teams-organizations/api-tokens) for the HCP Terraform or Terraform Enterprise instance.\n\n**Note:** Alternatively, you can set `TFE_HOSTNAME` which serves as a fallback for `TFE_ADDRESS`. It will only be used if `TFE_ADDRESS` is not set and will resolve the host to an `https` scheme. Example: `tfe.local` => resolves to `https://tfe.local`\n\nThe environment variables are used as a fallback to configure TFE client if the Address or Token values are not provided as in the cases below:\n\n#### Using the default configuration\n```go\nimport (\n  \"context\"\n  \"log\"\n\n  \"github.com/hashicorp/go-tfe\"\n)\n\n// Passing nil to tfe.NewClient method will also use the default configuration\nclient, err := tfe.NewClient(tfe.DefaultConfig())\nif err != nil {\n\tlog.Fatal(err)\n}\n\norgs, err := client.Organizations.List(context.Background(), nil)\nif err != nil {\n\tlog.Fatal(err)\n}\n```\n\n#### When Address or Token has no value\n```go\nimport (\n  \"context\"\n  \"log\"\n\n  \"github.com/hashicorp/go-tfe\"\n)\n\nconfig := &tfe.Config{\n\tAddress: \"\",\n\tToken: \"\",\n}\n\nclient, err := tfe.NewClient(config)\nif err != nil {\n\tlog.Fatal(err)\n}\n\norgs, err := client.Organizations.List(context.Background(), nil)\nif err != nil {\n\tlog.Fatal(err)\n}\n```\n\n## Documentation\n\nFor complete usage of the API client, see the [full package docs](https://pkg.go.dev/github.com/hashicorp/go-tfe).\n\n## Examples\n\nSee the [examples directory](https://github.com/hashicorp/go-tfe/tree/main/examples).\n\n## Running tests\n\nSee [TESTS.md](docs/TESTS.md).\n\n## Issues and Contributing\n\nSee [CONTRIBUTING.md](docs/CONTRIBUTING.md)\n\n## Releases\n\nSee [RELEASES.md](docs/RELEASES.md)\n"
  },
  {
    "path": "admin_opa_version.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ AdminOPAVersions = (*adminOPAVersions)(nil)\n\n// AdminOPAVersions describes all the admin OPA versions related methods that\n// the Terraform Enterprise API supports.\n// Note that admin OPA versions are only available in Terraform Enterprise.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/opa-versions\ntype AdminOPAVersions interface {\n\t// List all the OPA versions.\n\tList(ctx context.Context, options *AdminOPAVersionsListOptions) (*AdminOPAVersionsList, error)\n\n\t// Read a OPA version by its ID.\n\tRead(ctx context.Context, id string) (*AdminOPAVersion, error)\n\n\t// Create a OPA version.\n\tCreate(ctx context.Context, options AdminOPAVersionCreateOptions) (*AdminOPAVersion, error)\n\n\t// Update a OPA version.\n\tUpdate(ctx context.Context, id string, options AdminOPAVersionUpdateOptions) (*AdminOPAVersion, error)\n\n\t// Delete a OPA version\n\tDelete(ctx context.Context, id string) error\n}\n\n// adminOPAVersions implements AdminOPAVersions.\ntype adminOPAVersions struct {\n\tclient *Client\n}\n\n// AdminOPAVersion represents a OPA Version\ntype AdminOPAVersion struct {\n\tID               string                     `jsonapi:\"primary,opa-versions\"`\n\tVersion          string                     `jsonapi:\"attr,version\"`\n\tURL              string                     `jsonapi:\"attr,url,omitempty\"`\n\tSHA              string                     `jsonapi:\"attr,sha,omitempty\"`\n\tDeprecated       bool                       `jsonapi:\"attr,deprecated\"`\n\tDeprecatedReason *string                    `jsonapi:\"attr,deprecated-reason,omitempty\"`\n\tOfficial         bool                       `jsonapi:\"attr,official\"`\n\tEnabled          bool                       `jsonapi:\"attr,enabled\"`\n\tBeta             bool                       `jsonapi:\"attr,beta\"`\n\tUsage            int                        `jsonapi:\"attr,usage\"`\n\tCreatedAt        time.Time                  `jsonapi:\"attr,created-at,iso8601\"`\n\tArchs            []*ToolVersionArchitecture `jsonapi:\"attr,archs,omitempty\"`\n}\n\n// AdminOPAVersionsListOptions represents the options for listing\n// OPA versions.\ntype AdminOPAVersionsListOptions struct {\n\tListOptions\n\n\t// Optional: A query string to find an exact version\n\tFilter string `url:\"filter[version],omitempty\"`\n\n\t// Optional: A search query string to find all versions that match version substring\n\tSearch string `url:\"search[version],omitempty\"`\n}\n\n// AdminOPAVersionCreateOptions for creating an OPA version.\ntype AdminOPAVersionCreateOptions struct {\n\tType             string                     `jsonapi:\"primary,opa-versions\"`\n\tVersion          string                     `jsonapi:\"attr,version\"`       // Required\n\tURL              string                     `jsonapi:\"attr,url,omitempty\"` // Required w/ SHA unless Archs are provided\n\tSHA              string                     `jsonapi:\"attr,sha,omitempty\"` // Required w/ URL unless Archs are provided\n\tOfficial         *bool                      `jsonapi:\"attr,official,omitempty\"`\n\tDeprecated       *bool                      `jsonapi:\"attr,deprecated,omitempty\"`\n\tDeprecatedReason *string                    `jsonapi:\"attr,deprecated-reason,omitempty\"`\n\tEnabled          *bool                      `jsonapi:\"attr,enabled,omitempty\"`\n\tBeta             *bool                      `jsonapi:\"attr,beta,omitempty\"`\n\tArchs            []*ToolVersionArchitecture `jsonapi:\"attr,archs,omitempty\"` // Required unless URL and SHA are provided\n}\n\n// AdminOPAVersionUpdateOptions for updating OPA version.\ntype AdminOPAVersionUpdateOptions struct {\n\tType             string                     `jsonapi:\"primary,opa-versions\"`\n\tVersion          *string                    `jsonapi:\"attr,version,omitempty\"`\n\tURL              *string                    `jsonapi:\"attr,url,omitempty\"`\n\tSHA              *string                    `jsonapi:\"attr,sha,omitempty\"`\n\tOfficial         *bool                      `jsonapi:\"attr,official,omitempty\"`\n\tDeprecated       *bool                      `jsonapi:\"attr,deprecated,omitempty\"`\n\tDeprecatedReason *string                    `jsonapi:\"attr,deprecated-reason,omitempty\"`\n\tEnabled          *bool                      `jsonapi:\"attr,enabled,omitempty\"`\n\tBeta             *bool                      `jsonapi:\"attr,beta,omitempty\"`\n\tArchs            []*ToolVersionArchitecture `jsonapi:\"attr,archs,omitempty\"`\n}\n\n// AdminOPAVersionsList represents a list of OPA versions.\ntype AdminOPAVersionsList struct {\n\t*Pagination\n\tItems []*AdminOPAVersion\n}\n\n// List all the OPA versions.\nfunc (a *adminOPAVersions) List(ctx context.Context, options *AdminOPAVersionsListOptions) (*AdminOPAVersionsList, error) {\n\treq, err := a.client.NewRequest(\"GET\", \"admin/opa-versions\", options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tol := &AdminOPAVersionsList{}\n\terr = req.Do(ctx, ol)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ol, nil\n}\n\n// Read a OPA version by its ID.\nfunc (a *adminOPAVersions) Read(ctx context.Context, id string) (*AdminOPAVersion, error) {\n\tif !validStringID(&id) {\n\t\treturn nil, ErrInvalidOPAVersionID\n\t}\n\n\tu := fmt.Sprintf(\"admin/opa-versions/%s\", url.PathEscape(id))\n\treq, err := a.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tov := &AdminOPAVersion{}\n\terr = req.Do(ctx, ov)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ov, nil\n}\n\n// Create a new OPA version.\nfunc (a *adminOPAVersions) Create(ctx context.Context, options AdminOPAVersionCreateOptions) (*AdminOPAVersion, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\treq, err := a.client.NewRequest(\"POST\", \"admin/opa-versions\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tov := &AdminOPAVersion{}\n\terr = req.Do(ctx, ov)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ov, nil\n}\n\n// Update an existing OPA version.\nfunc (a *adminOPAVersions) Update(ctx context.Context, id string, options AdminOPAVersionUpdateOptions) (*AdminOPAVersion, error) {\n\tif !validStringID(&id) {\n\t\treturn nil, ErrInvalidOPAVersionID\n\t}\n\n\tu := fmt.Sprintf(\"admin/opa-versions/%s\", url.PathEscape(id))\n\treq, err := a.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tov := &AdminOPAVersion{}\n\terr = req.Do(ctx, ov)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ov, nil\n}\n\n// Delete a OPA version.\nfunc (a *adminOPAVersions) Delete(ctx context.Context, id string) error {\n\tif !validStringID(&id) {\n\t\treturn ErrInvalidOPAVersionID\n\t}\n\n\tu := fmt.Sprintf(\"admin/opa-versions/%s\", url.PathEscape(id))\n\treq, err := a.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o AdminOPAVersionCreateOptions) valid() error {\n\tif (reflect.DeepEqual(o, AdminOPAVersionCreateOptions{})) {\n\t\treturn ErrRequiredOPAVerCreateOps\n\t}\n\tif o.Version == \"\" {\n\t\treturn ErrRequiredVersion\n\t}\n\tif !o.validArch() {\n\t\treturn ErrRequiredArchsOrURLAndSha\n\t}\n\treturn nil\n}\n\nfunc (o AdminOPAVersionCreateOptions) validArch() bool {\n\tif o.Archs == nil && o.URL != \"\" && o.SHA != \"\" {\n\t\treturn true\n\t}\n\n\tfor _, a := range o.Archs {\n\t\tif !validArch(a) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "admin_opa_version_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminOPAVersions_List(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\toList, err := client.Admin.OPAVersions.List(ctx, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, oList.Items)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\toList, err := client.Admin.OPAVersions.List(ctx, &AdminOPAVersionsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\t// Out of range page number, so the items should be empty\n\t\tassert.Empty(t, oList.Items)\n\t\tassert.Equal(t, 999, oList.CurrentPage)\n\n\t\toList, err = client.Admin.OPAVersions.List(ctx, &AdminOPAVersionsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, oList.CurrentPage)\n\t\tfor _, item := range oList.Items {\n\t\t\tassert.NotNil(t, item.ID)\n\t\t\tassert.NotEmpty(t, item.Version)\n\t\t\tassert.NotEmpty(t, item.URL)\n\t\t\tassert.NotEmpty(t, item.SHA)\n\t\t\tassert.NotNil(t, item.Official)\n\t\t\tassert.NotNil(t, item.Deprecated)\n\t\t\tif item.Deprecated {\n\t\t\t\tassert.NotNil(t, item.DeprecatedReason)\n\t\t\t} else {\n\t\t\t\tassert.Nil(t, item.DeprecatedReason)\n\t\t\t}\n\t\t\tassert.NotNil(t, item.Enabled)\n\t\t\tassert.NotNil(t, item.Beta)\n\t\t\tassert.NotNil(t, item.Usage)\n\t\t\tassert.NotNil(t, item.CreatedAt)\n\t\t\tassert.NotEmpty(t, item.Archs)\n\t\t}\n\t})\n\n\tt.Run(\"with filter query string\", func(t *testing.T) {\n\t\toList, err := client.Admin.OPAVersions.List(ctx, &AdminOPAVersionsListOptions{\n\t\t\tFilter: \"0.59.0\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, len(oList.Items))\n\n\t\t// Query for a OPA version that does not exist\n\t\toList, err = client.Admin.OPAVersions.List(ctx, &AdminOPAVersionsListOptions{\n\t\t\tFilter: \"1000.1000.42\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, oList.Items)\n\t})\n\n\tt.Run(\"with search version query string\", func(t *testing.T) {\n\t\tsearchVersion := \"0.59.0\"\n\t\toList, err := client.Admin.OPAVersions.List(ctx, &AdminOPAVersionsListOptions{\n\t\t\tSearch: searchVersion,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, oList.Items)\n\n\t\tt.Run(\"ensure each version matches substring\", func(t *testing.T) {\n\t\t\tfor _, item := range oList.Items {\n\t\t\t\tassert.Equal(t, true, strings.Contains(item.Version, searchVersion))\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc TestAdminOPAVersions_CreateDelete(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\tversion := createAdminOPAVersion()\n\turl := \"https://www.hashicorp.com\"\n\tamd64Sha := *String(genSha(t))\n\n\tt.Run(\"with valid options including top level url & sha and archs\", func(t *testing.T) {\n\t\topts := AdminOPAVersionCreateOptions{\n\t\t\tVersion:          version,\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tOfficial:         Bool(false),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tURL:              url,\n\t\t\tSHA:              amd64Sha,\n\n\t\t\tArchs: []*ToolVersionArchitecture{\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  amd64Sha,\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: amd64,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  *String(genSha(t)),\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: arm64,\n\t\t\t\t}},\n\t\t}\n\t\tov, err := client.Admin.OPAVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.OPAVersions.Delete(ctx, ov.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, opts.Version, ov.Version)\n\t\tassert.Equal(t, *opts.Official, ov.Official)\n\t\tassert.Equal(t, *opts.Deprecated, ov.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *ov.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, ov.Enabled)\n\t\tassert.Equal(t, *opts.Beta, ov.Beta)\n\t\tassert.Equal(t, len(opts.Archs), len(ov.Archs))\n\t\tassert.Equal(t, opts.URL, ov.URL)\n\t\tassert.Equal(t, opts.SHA, ov.SHA)\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, ov.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, ov.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, ov.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, ov.Archs[i].Arch)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options including archs\", func(t *testing.T) {\n\t\tversion = createAdminOPAVersion()\n\t\topts := AdminOPAVersionCreateOptions{\n\t\t\tVersion:          version,\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tOfficial:         Bool(false),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tArchs: []*ToolVersionArchitecture{\n\t\t\t\t{\n\t\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\t\tSha:  *String(genSha(t)),\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: amd64,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\t\tSha:  *String(genSha(t)),\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: arm64,\n\t\t\t\t}},\n\t\t}\n\n\t\tov, err := client.Admin.OPAVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.OPAVersions.Delete(ctx, ov.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, opts.Version, ov.Version)\n\t\tassert.Equal(t, *opts.Official, ov.Official)\n\t\tassert.Equal(t, *opts.Deprecated, ov.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *ov.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, ov.Enabled)\n\t\tassert.Equal(t, *opts.Beta, ov.Beta)\n\t\tassert.Equal(t, len(opts.Archs), len(ov.Archs))\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, ov.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, ov.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, ov.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, ov.Archs[i].Arch)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options including, url, and sha\", func(t *testing.T) {\n\t\topts := AdminOPAVersionCreateOptions{\n\t\t\tVersion:          version,\n\t\t\tURL:              url,\n\t\t\tSHA:              genSha(t),\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tOfficial:         Bool(false),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t}\n\t\tov, err := client.Admin.OPAVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.OPAVersions.Delete(ctx, ov.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, opts.Version, ov.Version)\n\t\tassert.Equal(t, opts.URL, ov.URL)\n\t\tassert.Equal(t, opts.SHA, ov.SHA)\n\t\tassert.Equal(t, *opts.Official, ov.Official)\n\t\tassert.Equal(t, *opts.Deprecated, ov.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *ov.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, ov.Enabled)\n\t\tassert.Equal(t, *opts.Beta, ov.Beta)\n\t\tassert.Equal(t, 1, len(ov.Archs))\n\t\tassert.Equal(t, opts.URL, ov.Archs[0].URL)\n\t\tassert.Equal(t, opts.SHA, ov.Archs[0].Sha)\n\t\tassert.Equal(t, linux, ov.Archs[0].OS)\n\t\tassert.Equal(t, amd64, ov.Archs[0].Arch)\n\t})\n\n\tt.Run(\"with only required options including tool version url and sha\", func(t *testing.T) {\n\t\tversion = createAdminOPAVersion()\n\t\topts := AdminOPAVersionCreateOptions{\n\t\t\tVersion: version,\n\t\t\tURL:     \"https://www.hashicorp.com\",\n\t\t\tSHA:     genSha(t),\n\t\t}\n\t\tov, err := client.Admin.OPAVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.OPAVersions.Delete(ctx, ov.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, opts.Version, ov.Version)\n\t\tassert.Equal(t, opts.URL, ov.URL)\n\t\tassert.Equal(t, opts.SHA, ov.SHA)\n\t\tassert.Equal(t, false, ov.Official)\n\t\tassert.Equal(t, false, ov.Deprecated)\n\t\tassert.Nil(t, ov.DeprecatedReason)\n\t\tassert.Equal(t, true, ov.Enabled)\n\t\tassert.Equal(t, false, ov.Beta)\n\t\tassert.Equal(t, 1, len(ov.Archs))\n\t\tassert.Equal(t, opts.URL, ov.Archs[0].URL)\n\t\tassert.Equal(t, opts.SHA, ov.Archs[0].Sha)\n\t\tassert.Equal(t, linux, ov.Archs[0].OS)\n\t\tassert.Equal(t, amd64, ov.Archs[0].Arch)\n\t})\n\n\tt.Run(\"with only required options including archs\", func(t *testing.T) {\n\t\tversion = createAdminOPAVersion()\n\t\topts := AdminOPAVersionCreateOptions{\n\t\t\tVersion: version,\n\t\t\tArchs: []*ToolVersionArchitecture{\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  amd64Sha,\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: amd64,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  *String(genSha(t)),\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: arm64,\n\t\t\t\t}},\n\t\t}\n\t\tov, err := client.Admin.OPAVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.OPAVersions.Delete(ctx, ov.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, opts.Version, ov.Version)\n\t\tassert.Equal(t, false, ov.Official)\n\t\tassert.Equal(t, false, ov.Deprecated)\n\t\tassert.Nil(t, ov.DeprecatedReason)\n\t\tassert.Equal(t, true, ov.Enabled)\n\t\tassert.Equal(t, false, ov.Beta)\n\t\tassert.Equal(t, len(opts.Archs), len(ov.Archs))\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, ov.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, ov.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, ov.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, ov.Archs[i].Arch)\n\t\t}\n\t})\n\n\tt.Run(\"with empty options\", func(t *testing.T) {\n\t\t_, err := client.Admin.OPAVersions.Create(ctx, AdminOPAVersionCreateOptions{})\n\t\trequire.Equal(t, err, ErrRequiredOPAVerCreateOps)\n\t})\n}\n\nfunc TestAdminOPAVersions_ReadUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"reads and updates\", func(t *testing.T) {\n\t\tversion := createAdminOPAVersion()\n\t\tsha := String(genSha(t))\n\t\topts := AdminOPAVersionCreateOptions{\n\t\t\tVersion:          version,\n\t\t\tURL:              \"https://www.hashicorp.com\",\n\t\t\tSHA:              genSha(t),\n\t\t\tOfficial:         Bool(false),\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tArchs: []*ToolVersionArchitecture{{\n\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\tSha:  *sha,\n\t\t\t\tOS:   linux,\n\t\t\t\tArch: amd64,\n\t\t\t}},\n\t\t}\n\t\tov, err := client.Admin.OPAVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\t\tid := ov.ID\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.OPAVersions.Delete(ctx, id)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tov, err = client.Admin.OPAVersions.Read(ctx, id)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, opts.Version, ov.Version)\n\t\tassert.Equal(t, opts.Archs[0].URL, ov.URL)\n\t\tassert.Equal(t, opts.Archs[0].Sha, ov.SHA)\n\t\tassert.Equal(t, *opts.Official, ov.Official)\n\t\tassert.Equal(t, *opts.Deprecated, ov.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *ov.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, ov.Enabled)\n\t\tassert.Equal(t, *opts.Beta, ov.Beta)\n\t\tassert.Equal(t, len(opts.Archs), len(ov.Archs))\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, ov.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, ov.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, ov.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, ov.Archs[i].Arch)\n\t\t}\n\n\t\tupdateVersion := createAdminOPAVersion()\n\t\tupdateURL := \"https://app.terraform.io/\"\n\t\tupdateOpts := AdminOPAVersionUpdateOptions{\n\t\t\tVersion:    String(updateVersion),\n\t\t\tURL:        String(updateURL),\n\t\t\tDeprecated: Bool(false),\n\t\t}\n\n\t\tov, err = client.Admin.OPAVersions.Update(ctx, id, updateOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, updateVersion, ov.Version)\n\t\tassert.Equal(t, updateURL, ov.URL)\n\t\tassert.Equal(t, opts.SHA, ov.SHA)\n\t\tassert.Equal(t, *opts.Official, ov.Official)\n\t\tassert.Equal(t, *updateOpts.Deprecated, ov.Deprecated)\n\t\tassert.Equal(t, *opts.Enabled, ov.Enabled)\n\t\tassert.Equal(t, *opts.Beta, ov.Beta)\n\t\tassert.Equal(t, len(opts.Archs), len(ov.Archs))\n\t\tassert.Equal(t, *updateOpts.URL, ov.Archs[0].URL)\n\t\tassert.Equal(t, opts.Archs[0].Sha, ov.Archs[0].Sha)\n\t\tassert.Equal(t, opts.Archs[0].OS, ov.Archs[0].OS)\n\t\tassert.Equal(t, opts.Archs[0].Arch, ov.Archs[0].Arch)\n\t})\n\n\tt.Run(\"update with Archs\", func(t *testing.T) {\n\t\tversion := genSafeRandomTerraformVersion()\n\t\tsha := String(genSha(t))\n\t\topts := AdminOPAVersionCreateOptions{\n\t\t\tVersion:          *String(version),\n\t\t\tOfficial:         Bool(false),\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tArchs: []*ToolVersionArchitecture{{\n\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\tSha:  *sha,\n\t\t\t\tOS:   linux,\n\t\t\t\tArch: amd64,\n\t\t\t}},\n\t\t}\n\t\tov, err := client.Admin.OPAVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\t\tid := ov.ID\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.OPAVersions.Delete(ctx, id)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tupdateArchOpts := AdminOPAVersionUpdateOptions{\n\t\t\tArchs: []*ToolVersionArchitecture{{\n\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\tSha:  *sha,\n\t\t\t\tOS:   linux,\n\t\t\t\tArch: arm64,\n\t\t\t}},\n\t\t}\n\n\t\tov, err = client.Admin.OPAVersions.Update(ctx, id, updateArchOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, opts.Version, ov.Version)\n\t\tassert.Equal(t, \"\", ov.URL)\n\t\tassert.Equal(t, \"\", ov.SHA)\n\t\tassert.Equal(t, *opts.Official, ov.Official)\n\t\tassert.Equal(t, *opts.Deprecated, ov.Deprecated)\n\t\tassert.Equal(t, *opts.Enabled, ov.Enabled)\n\t\tassert.Equal(t, *opts.Beta, ov.Beta)\n\t\tassert.Equal(t, len(ov.Archs), 1)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].URL, ov.Archs[0].URL)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].Sha, ov.Archs[0].Sha)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].OS, ov.Archs[0].OS)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].Arch, ov.Archs[0].Arch)\n\t})\n\n\tt.Run(\"with non-existent OPA version\", func(t *testing.T) {\n\t\trandomID := \"random-id\"\n\t\t_, err := client.Admin.OPAVersions.Read(ctx, randomID)\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "admin_organization.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ AdminOrganizations = (*adminOrganizations)(nil)\n\n// AdminOrganizations describes all of the admin organization related methods that the Terraform\n// Enterprise API supports. Note that admin settings are only available in Terraform Enterprise.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/organizations\ntype AdminOrganizations interface {\n\t// List all the organizations visible to the current user.\n\tList(ctx context.Context, options *AdminOrganizationListOptions) (*AdminOrganizationList, error)\n\n\t// Read attributes of an existing organization via admin API.\n\tRead(ctx context.Context, organization string) (*AdminOrganization, error)\n\n\t// Update attributes of an existing organization via admin API.\n\tUpdate(ctx context.Context, organization string, options AdminOrganizationUpdateOptions) (*AdminOrganization, error)\n\n\t// Delete an organization by its name via admin API\n\tDelete(ctx context.Context, organization string) error\n\n\t// ListModuleConsumers lists specific organizations in the Terraform Enterprise installation that have permission to use an organization's modules.\n\tListModuleConsumers(ctx context.Context, organization string, options *AdminOrganizationListModuleConsumersOptions) (*AdminOrganizationList, error)\n\n\t// UpdateModuleConsumers specifies a list of organizations that can use modules from the sharing organization's private registry. Setting a list of module consumers will turn off global module sharing for an organization.\n\tUpdateModuleConsumers(ctx context.Context, organization string, consumerOrganizations []string) error\n}\n\n// adminOrganizations implements AdminOrganizations.\ntype adminOrganizations struct {\n\tclient *Client\n}\n\n// AdminOrganization represents a Terraform Enterprise organization returned from the Admin API.\ntype AdminOrganization struct {\n\tName                             string `jsonapi:\"primary,organizations\"`\n\tAccessBetaTools                  bool   `jsonapi:\"attr,access-beta-tools\"`\n\tExternalID                       string `jsonapi:\"attr,external-id\"`\n\tGlobalModuleSharing              *bool  `jsonapi:\"attr,global-module-sharing\"`\n\tGlobalProviderSharing            *bool  `jsonapi:\"attr,global-provider-sharing\"`\n\tIsDisabled                       bool   `jsonapi:\"attr,is-disabled\"`\n\tNotificationEmail                string `jsonapi:\"attr,notification-email\"`\n\tSsoEnabled                       bool   `jsonapi:\"attr,sso-enabled\"`\n\tTerraformBuildWorkerApplyTimeout string `jsonapi:\"attr,terraform-build-worker-apply-timeout\"`\n\tTerraformBuildWorkerPlanTimeout  string `jsonapi:\"attr,terraform-build-worker-plan-timeout\"`\n\tApplyTimeout                     string `jsonapi:\"attr,apply-timeout\"`\n\tPlanTimeout                      string `jsonapi:\"attr,plan-timeout\"`\n\tTerraformWorkerSudoEnabled       bool   `jsonapi:\"attr,terraform-worker-sudo-enabled\"`\n\tWorkspaceLimit                   *int   `jsonapi:\"attr,workspace-limit\"`\n\n\t// Relations\n\tOwners []*User `jsonapi:\"relation,owners\"`\n}\n\n// AdminOrganizationUpdateOptions represents the admin options for updating an organization.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/organizations#request-body\ntype AdminOrganizationUpdateOptions struct {\n\tAccessBetaTools                  *bool   `jsonapi:\"attr,access-beta-tools,omitempty\"`\n\tGlobalModuleSharing              *bool   `jsonapi:\"attr,global-module-sharing,omitempty\"`\n\tGlobalProviderSharing            *bool   `jsonapi:\"attr,global-provider-sharing,omitempty\"`\n\tIsDisabled                       *bool   `jsonapi:\"attr,is-disabled,omitempty\"`\n\tTerraformBuildWorkerApplyTimeout *string `jsonapi:\"attr,terraform-build-worker-apply-timeout,omitempty\"`\n\tTerraformBuildWorkerPlanTimeout  *string `jsonapi:\"attr,terraform-build-worker-plan-timeout,omitempty\"`\n\tApplyTimeout                     *string `jsonapi:\"attr,apply-timeout,omitempty\"`\n\tPlanTimeout                      *string `jsonapi:\"attr,plan-timeout,omitempty\"`\n\tTerraformWorkerSudoEnabled       bool    `jsonapi:\"attr,terraform-worker-sudo-enabled,omitempty\"`\n\tWorkspaceLimit                   *int    `jsonapi:\"attr,workspace-limit,omitempty\"`\n}\n\n// AdminOrganizationList represents a list of organizations via Admin API.\ntype AdminOrganizationList struct {\n\t*Pagination\n\tItems []*AdminOrganization\n}\n\n// AdminOrgIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/organizations#available-related-resources\ntype AdminOrgIncludeOpt string\n\nconst AdminOrgOwners AdminOrgIncludeOpt = \"owners\"\n\n// AdminOrganizationListOptions represents the options for listing organizations via Admin API.\ntype AdminOrganizationListOptions struct {\n\tListOptions\n\n\t// Optional: A query string used to filter organizations.\n\t// Any organizations with a name or notification email partially matching this value will be returned.\n\tQuery string `url:\"q,omitempty\"`\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/organizations#available-related-resources\n\tInclude []AdminOrgIncludeOpt `url:\"include,omitempty\"`\n}\n\n// AdminOrganizationListModuleConsumersOptions represents the options for listing organization module consumers through the Admin API\ntype AdminOrganizationListModuleConsumersOptions struct {\n\tListOptions\n}\n\ntype AdminOrganizationID struct {\n\tID string `jsonapi:\"primary,organizations\"`\n}\n\n// List all the organizations visible to the current user.\nfunc (s *adminOrganizations) List(ctx context.Context, options *AdminOrganizationListOptions) (*AdminOrganizationList, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\tu := \"admin/organizations\"\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\torgl := &AdminOrganizationList{}\n\terr = req.Do(ctx, orgl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn orgl, nil\n}\n\n// ListModuleConsumers lists specific organizations in the Terraform Enterprise installation that have permission to use an organization's modules.\nfunc (s *adminOrganizations) ListModuleConsumers(ctx context.Context, organization string, options *AdminOrganizationListModuleConsumersOptions) (*AdminOrganizationList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"admin/organizations/%s/relationships/module-consumers\", url.PathEscape(organization))\n\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\torgl := &AdminOrganizationList{}\n\terr = req.Do(ctx, orgl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn orgl, nil\n}\n\n// Read an organization by its name.\nfunc (s *adminOrganizations) Read(ctx context.Context, organization string) (*AdminOrganization, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"admin/organizations/%s\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\torg := &AdminOrganization{}\n\terr = req.Do(ctx, org)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn org, nil\n}\n\n// Update an organization by its name.\nfunc (s *adminOrganizations) Update(ctx context.Context, organization string, options AdminOrganizationUpdateOptions) (*AdminOrganization, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"admin/organizations/%s\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\torg := &AdminOrganization{}\n\terr = req.Do(ctx, org)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn org, nil\n}\n\n// UpdateModuleConsumers updates an organization to specify a list of organizations that can use modules from the sharing organization's private registry.\nfunc (s *adminOrganizations) UpdateModuleConsumers(ctx context.Context, organization string, consumerOrganizationIDs []string) error {\n\tif !validStringID(&organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"admin/organizations/%s/relationships/module-consumers\", url.PathEscape(organization))\n\n\tvar organizations []*AdminOrganizationID\n\tfor _, id := range consumerOrganizationIDs {\n\t\tif !validStringID(&id) {\n\t\t\treturn ErrInvalidOrg\n\t\t}\n\t\torganizations = append(organizations, &AdminOrganizationID{ID: id})\n\t}\n\n\treq, err := s.client.NewRequest(\"PATCH\", u, organizations)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = req.Do(ctx, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Delete an organization by its name.\nfunc (s *adminOrganizations) Delete(ctx context.Context, organization string) error {\n\tif !validStringID(&organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"admin/organizations/%s\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o *AdminOrganizationListOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "admin_organization_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminOrganizations_List(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with no list options\", func(t *testing.T) {\n\t\tadminOrgList, err := client.Admin.Organizations.List(ctx, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// Given that org creation occurs on every test, the ordering is not\n\t\t// guaranteed. It may be that the `org` created in this test does not appear\n\t\t// in this list, so we want to test that the items are filled.\n\t\tassert.NotEmpty(t, adminOrgList.Items)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// creating second org so that the query can only find the main org\n\t\t_, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\tadminOrgList, err := client.Admin.Organizations.List(ctx, &AdminOrganizationListOptions{\n\t\t\tQuery: org.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, true, adminOrgItemsContainsName(adminOrgList.Items, org.Name))\n\t\tassert.Equal(t, 1, adminOrgList.CurrentPage)\n\t\tassert.Equal(t, 1, adminOrgList.TotalCount)\n\t})\n\n\tt.Run(\"with list options and org name that doesn't exist\", func(t *testing.T) {\n\t\trandomName := \"random-org-name\"\n\n\t\tadminOrgList, err := client.Admin.Organizations.List(ctx, &AdminOrganizationListOptions{\n\t\t\tQuery: randomName,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, false, adminOrgItemsContainsName(adminOrgList.Items, org.Name))\n\t\tassert.Equal(t, 1, adminOrgList.CurrentPage)\n\t\tassert.Equal(t, 0, adminOrgList.TotalCount)\n\t})\n\n\tt.Run(\"with owners included\", func(t *testing.T) {\n\t\tadminOrgList, err := client.Admin.Organizations.List(ctx, &AdminOrganizationListOptions{\n\t\t\tInclude: []AdminOrgIncludeOpt{AdminOrgOwners},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, adminOrgList.Items)\n\t\tassert.NotNil(t, adminOrgList.Items[0].Owners)\n\t\tassert.NotEmpty(t, adminOrgList.Items[0].Owners[0].Email)\n\t})\n}\n\nfunc TestAdminOrganizations_Read(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"it fails to read an organization with an invalid id\", func(t *testing.T) {\n\t\tadminOrg, err := client.Admin.Organizations.Read(ctx, \"\")\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t\tassert.Nil(t, adminOrg)\n\t})\n\n\tt.Run(\"it returns ErrResourceNotFound for an organization that doesn't exist\", func(t *testing.T) {\n\t\torgName := fmt.Sprintf(\"non-existing-%s\", randomString(t))\n\t\tadminOrg, err := client.Admin.Organizations.Read(ctx, orgName)\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t\tassert.Nil(t, adminOrg)\n\t})\n\n\tt.Run(\"it reads an organization successfully\", func(t *testing.T) {\n\t\torg, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\tadminOrg, err := client.Admin.Organizations.Read(ctx, org.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNilf(t, adminOrg, \"Organization is not nil\")\n\t\tassert.Equal(t, adminOrg.Name, org.Name)\n\n\t\t// attributes part of an AdminOrganization response that are not null\n\t\tassert.NotNilf(t, adminOrg.AccessBetaTools, \"AccessBetaTools is not nil\")\n\t\tassert.NotNilf(t, adminOrg.ExternalID, \"ExternalID is not nil\")\n\t\tassert.NotNilf(t, adminOrg.IsDisabled, \"IsDisabled is not nil\")\n\t\tassert.NotNilf(t, adminOrg.NotificationEmail, \"NotificationEmail is not nil\")\n\t\tassert.NotNilf(t, adminOrg.SsoEnabled, \"SsoEnabled is not nil\")\n\t\tassert.NotNilf(t, adminOrg.TerraformWorkerSudoEnabled, \"TerraformWorkerSudoEnabledis not nil\")\n\t\tassert.Nilf(t, adminOrg.WorkspaceLimit, \"WorkspaceLimit is nil\")\n\t})\n}\n\nfunc TestAdminOrganizations_Delete(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"it fails to delete an organization with an invalid id\", func(t *testing.T) {\n\t\terr := client.Admin.Organizations.Delete(ctx, \"\")\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"it returns ErrResourceNotFound during an attempt to delete an organization that doesn't exist\", func(t *testing.T) {\n\t\torgName := fmt.Sprintf(\"non-existing-%s\", randomString(t))\n\t\terr := client.Admin.Organizations.Delete(ctx, orgName)\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n\n\tt.Run(\"it deletes an organization successfully\", func(t *testing.T) {\n\t\toriginalOrg, _ := createOrganization(t, client)\n\n\t\tadminOrg, err := client.Admin.Organizations.Read(ctx, originalOrg.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, adminOrg)\n\t\tassert.Equal(t, adminOrg.Name, originalOrg.Name)\n\n\t\terr = client.Admin.Organizations.Delete(ctx, adminOrg.Name)\n\t\trequire.NoError(t, err)\n\n\t\t// Cannot find deleted org\n\t\t_, err = client.Admin.Organizations.Read(ctx, originalOrg.Name)\n\t\tassert.Error(t, err)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n\nfunc TestAdminOrganizations_ModuleConsumers(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"returns error if invalid org string is used\", func(t *testing.T) {\n\t\torg1, org1TestCleanup := createOrganization(t, client)\n\t\tdefer org1TestCleanup()\n\n\t\terr := client.Admin.Organizations.UpdateModuleConsumers(ctx, org1.Name, []string{\"1Hello!\"})\n\t\tassert.Error(t, err, \"Organization 1Hello! not found\")\n\t})\n\n\tt.Run(\"can list and update module consumers\", func(t *testing.T) {\n\t\torg1, org1TestCleanup := createOrganization(t, client)\n\t\tdefer org1TestCleanup()\n\n\t\torg2, org2TestCleanup := createOrganization(t, client)\n\t\tdefer org2TestCleanup()\n\n\t\terr := client.Admin.Organizations.UpdateModuleConsumers(ctx, org1.Name, []string{org2.Name})\n\t\trequire.NoError(t, err)\n\n\t\tadminModuleConsumerList, err := client.Admin.Organizations.ListModuleConsumers(ctx, org1.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, len(adminModuleConsumerList.Items), 1)\n\t\tassert.Equal(t, adminModuleConsumerList.Items[0].Name, org2.Name)\n\n\t\torg3, org3TestCleanup := createOrganization(t, client)\n\t\tdefer org3TestCleanup()\n\n\t\terr = client.Admin.Organizations.UpdateModuleConsumers(ctx, org1.Name, []string{org3.Name})\n\t\trequire.NoError(t, err)\n\n\t\tadminModuleConsumerList, err = client.Admin.Organizations.ListModuleConsumers(ctx, org1.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, len(adminModuleConsumerList.Items), 1)\n\t\tassert.Equal(t, adminModuleConsumerList.Items[0].Name, org3.Name)\n\t})\n}\n\nfunc TestAdminOrganizations_Update(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"it fails to update an organization with an invalid id\", func(t *testing.T) {\n\t\t_, err := client.Admin.Organizations.Update(ctx, \"\", AdminOrganizationUpdateOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"it returns ErrResourceNotFound for during an update on an organization that doesn't exist\", func(t *testing.T) {\n\t\torgName := fmt.Sprintf(\"non-existing-%s\", randomString(t))\n\t\t_, err := client.Admin.Organizations.Update(ctx, orgName, AdminOrganizationUpdateOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n\n\tt.Run(\"fetches and updates organization\", func(t *testing.T) {\n\t\torg, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\tadminOrg, err := client.Admin.Organizations.Read(ctx, org.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNilf(t, adminOrg, \"Org returned as nil\")\n\n\t\taccessBetaTools := true\n\t\tglobalModuleSharing := false\n\t\tglobalProviderSharing := false\n\t\tisDisabled := false\n\t\tapplyTimeout := \"24h\"\n\t\tplanTimeout := \"24h\"\n\t\tterraformWorkerSudoEnabled := true\n\n\t\topts := AdminOrganizationUpdateOptions{\n\t\t\tAccessBetaTools:                  &accessBetaTools,\n\t\t\tGlobalModuleSharing:              &globalModuleSharing,\n\t\t\tGlobalProviderSharing:            &globalProviderSharing,\n\t\t\tIsDisabled:                       &isDisabled,\n\t\t\tTerraformBuildWorkerApplyTimeout: &applyTimeout,\n\t\t\tTerraformBuildWorkerPlanTimeout:  &planTimeout,\n\t\t\tApplyTimeout:                     &applyTimeout,\n\t\t\tPlanTimeout:                      &planTimeout,\n\t\t\tTerraformWorkerSudoEnabled:       terraformWorkerSudoEnabled,\n\t\t}\n\n\t\tadminOrg, err = client.Admin.Organizations.Update(ctx, org.Name, opts)\n\t\trequire.NotNilf(t, adminOrg, \"Org returned as nil when it shouldn't be.\")\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, accessBetaTools, adminOrg.AccessBetaTools)\n\t\tassert.Equal(t, adminOrg.GlobalModuleSharing, &globalModuleSharing)\n\t\tassert.Equal(t, adminOrg.GlobalProviderSharing, &globalProviderSharing)\n\t\tassert.Equal(t, isDisabled, adminOrg.IsDisabled)\n\t\tassert.Equal(t, applyTimeout, adminOrg.TerraformBuildWorkerApplyTimeout)\n\t\tassert.Equal(t, planTimeout, adminOrg.TerraformBuildWorkerPlanTimeout)\n\t\tassert.Equal(t, applyTimeout, adminOrg.ApplyTimeout)\n\t\tassert.Equal(t, planTimeout, adminOrg.PlanTimeout)\n\t\tassert.Equal(t, terraformWorkerSudoEnabled, adminOrg.TerraformWorkerSudoEnabled)\n\t\tassert.Nil(t, adminOrg.WorkspaceLimit, \"default workspace limit should be nil\")\n\n\t\tisDisabled = true\n\t\tglobalModuleSharing = true\n\t\tglobalProviderSharing = true\n\t\tworkspaceLimit := 42\n\t\topts = AdminOrganizationUpdateOptions{\n\t\t\tGlobalModuleSharing:   &globalModuleSharing,\n\t\t\tGlobalProviderSharing: &globalProviderSharing,\n\t\t\tIsDisabled:            &isDisabled,\n\t\t\tWorkspaceLimit:        &workspaceLimit,\n\t\t}\n\n\t\tadminOrg, err = client.Admin.Organizations.Update(ctx, org.Name, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNilf(t, adminOrg, \"Org returned as nil when it shouldn't be.\")\n\n\t\tassert.Equal(t, adminOrg.GlobalModuleSharing, &globalModuleSharing)\n\t\tassert.Equal(t, adminOrg.GlobalProviderSharing, &globalProviderSharing)\n\t\tassert.Equal(t, adminOrg.IsDisabled, isDisabled)\n\t\tassert.Equal(t, &workspaceLimit, adminOrg.WorkspaceLimit)\n\n\t\tglobalModuleSharing = false\n\t\tglobalProviderSharing = false\n\t\tisDisabled = false\n\t\tworkspaceLimit = 0\n\t\topts = AdminOrganizationUpdateOptions{\n\t\t\tGlobalModuleSharing:   &globalModuleSharing,\n\t\t\tGlobalProviderSharing: &globalProviderSharing,\n\t\t\tIsDisabled:            &isDisabled,\n\t\t\tWorkspaceLimit:        &workspaceLimit,\n\t\t}\n\n\t\tadminOrg, err = client.Admin.Organizations.Update(ctx, org.Name, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNilf(t, adminOrg, \"Org returned as nil when it shouldn't be.\")\n\n\t\tassert.Equal(t, &globalModuleSharing, adminOrg.GlobalModuleSharing)\n\t\tassert.Equal(t, &globalProviderSharing, adminOrg.GlobalProviderSharing)\n\t\tassert.Equal(t, adminOrg.IsDisabled, isDisabled)\n\n\t\tassert.Equal(t, &workspaceLimit, adminOrg.WorkspaceLimit)\n\t})\n}\n\nfunc adminOrgItemsContainsName(items []*AdminOrganization, name string) bool {\n\thasName := false\n\tfor _, item := range items {\n\t\tif item.Name == name {\n\t\t\thasName = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn hasName\n}\n"
  },
  {
    "path": "admin_run.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ AdminRuns = (*adminRuns)(nil)\n\n// AdminRuns describes all the admin run related methods that the Terraform\n// Enterprise  API supports.\n// It contains endpoints to help site administrators manage their runs.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/runs\ntype AdminRuns interface {\n\t// List all the runs of the given installation.\n\tList(ctx context.Context, options *AdminRunsListOptions) (*AdminRunsList, error)\n\n\t// Force-cancel a run by its ID.\n\tForceCancel(ctx context.Context, runID string, options AdminRunForceCancelOptions) error\n}\n\n// AdminRun represents AdminRuns interface.\ntype AdminRun struct {\n\tID               string               `jsonapi:\"primary,runs\"`\n\tCreatedAt        time.Time            `jsonapi:\"attr,created-at,iso8601\"`\n\tHasChanges       bool                 `jsonapi:\"attr,has-changes\"`\n\tStatus           RunStatus            `jsonapi:\"attr,status\"`\n\tStatusTimestamps *RunStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\n\t// Relations\n\tWorkspace    *AdminWorkspace    `jsonapi:\"relation,workspace\"`\n\tOrganization *AdminOrganization `jsonapi:\"relation,workspace.organization\"`\n}\n\n// AdminRunsList represents a list of runs.\ntype AdminRunsList struct {\n\t*Pagination\n\tItems []*AdminRun\n}\n\n// AdminRunIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/runs#available-related-resources\ntype AdminRunIncludeOpt string\n\nconst (\n\tAdminRunWorkspace          AdminRunIncludeOpt = \"workspace\"\n\tAdminRunWorkspaceOrg       AdminRunIncludeOpt = \"workspace.organization\"\n\tAdminRunWorkspaceOrgOwners AdminRunIncludeOpt = \"workspace.organization.owners\"\n)\n\n// AdminRunsListOptions represents the options for listing runs.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/runs#query-parameters\ntype AdminRunsListOptions struct {\n\tListOptions\n\n\tRunStatus     string `url:\"filter[status],omitempty\"`\n\tCreatedBefore string `url:\"filter[to],omitempty\"`\n\tCreatedAfter  string `url:\"filter[from],omitempty\"`\n\tQuery         string `url:\"q,omitempty\"`\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/runs#available-related-resources\n\tInclude []AdminRunIncludeOpt `url:\"include,omitempty\"`\n}\n\n// adminRuns implements the AdminRuns interface.\ntype adminRuns struct {\n\tclient *Client\n}\n\n// List all the runs of the terraform enterprise installation.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/runs#list-all-runs\nfunc (s *adminRuns) List(ctx context.Context, options *AdminRunsListOptions) (*AdminRunsList, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := \"admin/runs\"\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trl := &AdminRunsList{}\n\terr = req.Do(ctx, rl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rl, nil\n}\n\n// AdminRunForceCancelOptions represents the options for force-canceling a run.\ntype AdminRunForceCancelOptions struct {\n\t// An optional comment explaining the reason for the force-cancel.\n\t// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/runs#request-body\n\tComment *string `json:\"comment,omitempty\"`\n}\n\n// ForceCancel is used to forcefully cancel a run by its ID.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/runs#force-a-run-into-the-quot-cancelled-quot-state\nfunc (s *adminRuns) ForceCancel(ctx context.Context, runID string, options AdminRunForceCancelOptions) error {\n\tif !validStringID(&runID) {\n\t\treturn ErrInvalidRunID\n\t}\n\n\tu := fmt.Sprintf(\"admin/runs/%s/actions/force-cancel\", url.PathEscape(runID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o *AdminRunsListOptions) valid() error {\n\tif o == nil { // nothing to validate\n\t\treturn nil\n\t}\n\n\tif err := validateAdminRunDateRanges(o.CreatedBefore, o.CreatedAfter); err != nil {\n\t\treturn err\n\t}\n\n\tif err := validateAdminRunFilterParams(o.RunStatus); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc validateAdminRunDateRanges(before, after string) error {\n\tif validString(&before) {\n\t\t_, err := time.Parse(time.RFC3339, before)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid date format for CreatedBefore: '%s', must be in RFC3339 format\", before)\n\t\t}\n\t}\n\n\tif validString(&after) {\n\t\t_, err := time.Parse(time.RFC3339, after)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid date format for CreatedAfter: '%s', must be in RFC3339 format\", after)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc validateAdminRunFilterParams(runStatus string) error {\n\t// For the platform, an invalid filter value is a semantically understood query that returns an empty set, no error, no warning. But for go-tfe, an invalid value is good enough reason to error prior to a network call to the platform:\n\tif validString(&runStatus) {\n\t\tsanitizedRunstatus := strings.TrimSpace(runStatus)\n\t\trunStatuses := strings.Split(sanitizedRunstatus, \",\")\n\t\t// iterate over our statuses, and ensure it is valid.\n\t\tfor _, status := range runStatuses {\n\t\t\tswitch status {\n\t\t\tcase string(RunApplied),\n\t\t\t\tstring(RunApplyQueued),\n\t\t\t\tstring(RunApplying),\n\t\t\t\tstring(RunCanceled),\n\t\t\t\tstring(RunConfirmed),\n\t\t\t\tstring(RunCostEstimated),\n\t\t\t\tstring(RunCostEstimating),\n\t\t\t\tstring(RunDiscarded),\n\t\t\t\tstring(RunErrored),\n\t\t\t\tstring(RunFetching),\n\t\t\t\tstring(RunFetchingCompleted),\n\t\t\t\tstring(RunPending),\n\t\t\t\tstring(RunPlanned),\n\t\t\t\tstring(RunPlannedAndFinished),\n\t\t\t\tstring(RunPlannedAndSaved),\n\t\t\t\tstring(RunPlanning),\n\t\t\t\tstring(RunPlanQueued),\n\t\t\t\tstring(RunPolicyChecked),\n\t\t\t\tstring(RunPolicyChecking),\n\t\t\t\tstring(RunPolicyOverride),\n\t\t\t\tstring(RunPolicySoftFailed),\n\t\t\t\tstring(RunPostPlanAwaitingDecision),\n\t\t\t\tstring(RunPostPlanCompleted),\n\t\t\t\tstring(RunPostPlanRunning),\n\t\t\t\tstring(RunPreApplyRunning),\n\t\t\t\tstring(RunPreApplyCompleted),\n\t\t\t\tstring(RunPrePlanCompleted),\n\t\t\t\tstring(RunPrePlanRunning),\n\t\t\t\tstring(RunQueuing),\n\t\t\t\tstring(RunQueuingApply),\n\t\t\t\t\"\":\n\t\t\t\t// do nothing\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(`invalid value \"%s\" for run status`, status)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "admin_run_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminRuns_List_RunDependent(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tdefer orgCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, org)\n\tdefer wTestCleanup()\n\n\trTest1, rTestCleanup1 := createRun(t, client, wTest)\n\tdefer rTestCleanup1()\n\trTest2, rTestCleanup2 := createRun(t, client, wTest)\n\tdefer rTestCleanup2()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\trl, err := client.Admin.Runs.List(ctx, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, rl.Items)\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, rTest1.ID), true)\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, rTest2.ID), true)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\trl, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\t// Out of range page number, so the items should be empty\n\t\tassert.Empty(t, rl.Items)\n\t\tassert.Equal(t, 999, rl.CurrentPage)\n\n\t\trl, err = client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, rl.Items)\n\t\tassert.Equal(t, 1, rl.CurrentPage)\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, rTest1.ID), true)\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, rTest2.ID), true)\n\t})\n\n\tt.Run(\"with workspace included\", func(t *testing.T) {\n\t\trl, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tInclude: []AdminRunIncludeOpt{AdminRunWorkspace},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, rl.Items)\n\t\trequire.NotNil(t, rl.Items[0].Workspace)\n\t\tassert.NotEmpty(t, rl.Items[0].Workspace.Name)\n\t})\n\n\tt.Run(\"with workspace.organization included\", func(t *testing.T) {\n\t\trl, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tInclude: []AdminRunIncludeOpt{AdminRunWorkspaceOrg},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, rl.Items)\n\n\t\trequire.NotNil(t, rl.Items[0].Workspace)\n\t\trequire.NotNil(t, rl.Items[0].Workspace.Organization)\n\t\tassert.NotEmpty(t, rl.Items[0].Workspace.Organization.Name)\n\t})\n\n\tt.Run(\"with invalid Include option\", func(t *testing.T) {\n\t\t_, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tInclude: []AdminRunIncludeOpt{\"workpsace\"},\n\t\t})\n\n\t\tassert.Equal(t, ErrInvalidIncludeValue, err)\n\t})\n\n\tt.Run(\"with RunStatus.pending filter\", func(t *testing.T) {\n\t\tr1, err := client.Runs.Read(ctx, rTest1.ID)\n\t\trequire.NoError(t, err)\n\t\tr2, err := client.Runs.Read(ctx, rTest2.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// There should be pending Runs\n\t\trl, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tRunStatus: string(RunPending),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, rl.Items)\n\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, r1.ID), false)\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, r2.ID), true)\n\t})\n\n\tt.Run(\"with RunStatus.applied filter\", func(t *testing.T) {\n\t\t// There should be no applied Runs\n\t\trl, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tRunStatus: string(RunApplied),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, rl.Items)\n\t})\n\n\tt.Run(\"with query\", func(t *testing.T) {\n\t\trl, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tQuery: rTest1.ID,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, rl.Items)\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, rTest1.ID), true)\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, rTest2.ID), false)\n\n\t\trl, err = client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tQuery: rTest2.ID,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, rl.Items)\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, rTest1.ID), false)\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, rTest2.ID), true)\n\t})\n}\n\nfunc TestAdminRuns_ForceCancel_RunDependent(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tdefer orgCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, org)\n\tdefer wTestCleanup()\n\n\t// We need to create 2 runs here.\n\t// The first run will automatically be planned\n\t// so that one cannot be cancelled.\n\trTest1, rCleanup1 := createRun(t, client, wTest)\n\tdefer rCleanup1()\n\t// The second one will be pending until the first one is\n\t// confirmed or discarded, so we can cancel that one.\n\trTest2, rCleanup2 := createRun(t, client, wTest)\n\tdefer rCleanup2()\n\n\tassert.Equal(t, true, rTest1.Actions.IsCancelable)\n\tassert.Equal(t, true, rTest1.Permissions.CanForceCancel)\n\n\tassert.Equal(t, true, rTest2.Actions.IsCancelable)\n\tassert.Equal(t, true, rTest2.Permissions.CanForceCancel)\n\n\tt.Run(\"when the run does not exist\", func(t *testing.T) {\n\t\terr := client.Admin.Runs.ForceCancel(ctx, \"nonexisting\", AdminRunForceCancelOptions{})\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"with invalid run ID\", func(t *testing.T) {\n\t\terr := client.Admin.Runs.ForceCancel(ctx, badIdentifier, AdminRunForceCancelOptions{})\n\t\tassert.EqualError(t, err, ErrInvalidRunID.Error())\n\t})\n\n\tt.Run(\"with can force cancel\", func(t *testing.T) {\n\t\trTestPlanning, err := client.Runs.Read(ctx, rTest1.ID)\n\t\trequire.NoError(t, err)\n\n\t\tctxPollRunStatus, cancelPollPlanned := context.WithTimeout(ctx, 2*time.Minute)\n\t\tdefer cancelPollPlanned()\n\t\tpollRunStatus(t, client, ctxPollRunStatus, rTestPlanning, []RunStatus{RunPlanning, RunPlanned, RunCostEstimated})\n\n\t\trequire.NotNil(t, rTestPlanning.Actions)\n\t\trequire.NotNil(t, rTestPlanning.Permissions)\n\t\tassert.Equal(t, true, rTestPlanning.Actions.IsCancelable)\n\t\tassert.Equal(t, true, rTestPlanning.Permissions.CanForceCancel)\n\n\t\trTestPending, err := client.Runs.Read(ctx, rTest2.ID)\n\t\trequire.NoError(t, err)\n\n\t\tpollRunStatus(t, client, ctxPollRunStatus, rTest2, []RunStatus{RunPending})\n\n\t\trequire.NotNil(t, rTestPlanning.Actions)\n\t\trequire.NotNil(t, rTestPlanning.Permissions)\n\t\tassert.Equal(t, true, rTestPending.Actions.IsCancelable)\n\t\tassert.Equal(t, true, rTestPending.Permissions.CanForceCancel)\n\n\t\tcomment1 := \"Misclick\"\n\t\terr = client.Admin.Runs.ForceCancel(ctx, rTestPending.ID, AdminRunForceCancelOptions{\n\t\t\tComment: String(comment1),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trTestPendingResult, err := client.Runs.Read(ctx, rTestPending.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, RunCanceled, rTestPendingResult.Status)\n\n\t\tcomment2 := \"Another misclick\"\n\t\terr = client.Admin.Runs.ForceCancel(ctx, rTestPlanning.ID, AdminRunForceCancelOptions{\n\t\t\tComment: String(comment2),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trTestPlanningResult, err := client.Runs.Read(ctx, rTestPlanning.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, RunCanceled, rTestPlanningResult.Status)\n\t})\n}\n\nfunc TestAdminRuns_ListFilterByDates_RunDependent(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tdefer orgCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, org)\n\tdefer wTestCleanup()\n\n\ttimestamp1 := time.Now().Format(time.RFC3339)\n\t// Sleeping helps ensure that the timestamps on client and server don't\n\t// need to be exactly in sync\n\ttime.Sleep(2 * time.Second)\n\n\trTest1, rCleanup1 := createRun(t, client, wTest)\n\tdefer rCleanup1()\n\n\trTest2, rCleanup2 := createRun(t, client, wTest)\n\tdefer rCleanup2()\n\n\ttime.Sleep(2 * time.Second)\n\ttimestamp2 := time.Now().Format(time.RFC3339)\n\n\t_, rCleanup3 := createRun(t, client, wTest)\n\tdefer rCleanup3()\n\n\ttime.Sleep(2 * time.Second)\n\ttimestamp3 := time.Now().Format(time.RFC3339)\n\n\tt.Run(\"has valid date ranges\", func(t *testing.T) {\n\t\trl, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tCreatedAfter:  timestamp1,\n\t\t\tCreatedBefore: timestamp2,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 2, len(rl.Items))\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, rTest1.ID), true)\n\t\tassert.Equal(t, adminRunItemsContainsID(rl.Items, rTest2.ID), true)\n\t})\n\n\tt.Run(\"has no items when CreatedAfter and CreatedBefore datetimes has no overlap\", func(t *testing.T) {\n\t\trl, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tCreatedAfter:  timestamp3,\n\t\t\tCreatedBefore: timestamp2,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 0, len(rl.Items))\n\t})\n\n\tt.Run(\"errors with invalid input for CreatedAfter\", func(t *testing.T) {\n\t\t_, err := client.Admin.Runs.List(ctx, &AdminRunsListOptions{\n\t\t\tCreatedAfter: \"invalid\",\n\t\t})\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestAdminRuns_AdminRunsListOptions_valid(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tt.Run(\"has valid status\", func(t *testing.T) {\n\t\topts := AdminRunsListOptions{\n\t\t\tRunStatus: string(RunPending),\n\t\t}\n\n\t\terr := opts.valid()\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"has invalid status\", func(t *testing.T) {\n\t\topts := AdminRunsListOptions{\n\t\t\tRunStatus: \"random_status\",\n\t\t}\n\n\t\terr := opts.valid()\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"has invalid status, even with a valid one\", func(t *testing.T) {\n\t\tstatuses := fmt.Sprintf(\"%s,%s\", string(RunPending), \"random_status\")\n\t\topts := AdminRunsListOptions{\n\t\t\tRunStatus: statuses,\n\t\t}\n\n\t\terr := opts.valid()\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"has trailing comma and trailing space\", func(t *testing.T) {\n\t\topts := AdminRunsListOptions{\n\t\t\tRunStatus: \"pending, \",\n\t\t}\n\n\t\terr := opts.valid()\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestAdminRun_ForceCancel_Marshal(t *testing.T) {\n\tt.Parallel()\n\topts := AdminRunForceCancelOptions{\n\t\tComment: String(\"cancel comment\"),\n\t}\n\n\treqBody, err := serializeRequestBody(&opts)\n\trequire.NoError(t, err)\n\treq, err := retryablehttp.NewRequest(\"POST\", \"url\", reqBody)\n\trequire.NoError(t, err)\n\tbodyBytes, err := req.BodyBytes()\n\trequire.NoError(t, err)\n\n\texpectedBody := `{\"comment\":\"cancel comment\"}`\n\tassert.Equal(t, expectedBody, string(bodyBytes))\n}\n\nfunc TestAdminRun_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"runs\",\n\t\t\t\"id\":   \"run-VCsNJXa59eUza53R\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"created-at\":  \"2018-03-02T23:42:06.651Z\",\n\t\t\t\t\"has-changes\": true,\n\t\t\t\t\"status\":      RunApplied,\n\t\t\t\t\"status-timestamps\": map[string]string{\n\t\t\t\t\t\"plan-queued-at\": \"2020-03-16T23:15:59+00:00\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tbyteData, err := json.Marshal(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tplanQueuedParsedTime, err := time.Parse(time.RFC3339, \"2020-03-16T23:15:59+00:00\")\n\trequire.NoError(t, err)\n\n\tadminRun := &AdminRun{}\n\tresponseBody := bytes.NewReader(byteData)\n\terr = unmarshalResponse(responseBody, adminRun)\n\trequire.NoError(t, err)\n\tassert.Equal(t, adminRun.ID, \"run-VCsNJXa59eUza53R\")\n\tassert.Equal(t, adminRun.HasChanges, true)\n\tassert.Equal(t, adminRun.Status, RunApplied)\n\tassert.Equal(t, adminRun.StatusTimestamps.PlanQueuedAt, planQueuedParsedTime)\n}\n\nfunc adminRunItemsContainsID(items []*AdminRun, id string) bool {\n\thasID := false\n\tfor _, item := range items {\n\t\tif item.ID == id {\n\t\t\thasID = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn hasID\n}\n"
  },
  {
    "path": "admin_run_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_validateAdminRunFilterParams(t *testing.T) {\n\tt.Parallel()\n\t// All RunStatus values - keep this in sync with run.go\n\tvalidRunStatuses := []string{\n\t\t\"applied\",\n\t\t\"applying\",\n\t\t\"apply_queued\",\n\t\t\"canceled\",\n\t\t\"confirmed\",\n\t\t\"cost_estimated\",\n\t\t\"cost_estimating\",\n\t\t\"discarded\",\n\t\t\"errored\",\n\t\t\"fetching\",\n\t\t\"fetching_completed\",\n\t\t\"pending\",\n\t\t\"planned\",\n\t\t\"planned_and_finished\",\n\t\t\"planned_and_saved\",\n\t\t\"planning\",\n\t\t\"plan_queued\",\n\t\t\"policy_checked\",\n\t\t\"policy_checking\",\n\t\t\"policy_override\",\n\t\t\"policy_soft_failed\",\n\t\t\"post_plan_awaiting_decision\",\n\t\t\"post_plan_completed\",\n\t\t\"post_plan_running\",\n\t\t\"pre_apply_running\",\n\t\t\"pre_apply_completed\",\n\t\t\"pre_plan_completed\",\n\t\t\"pre_plan_running\",\n\t\t\"queuing\",\n\t\t\"queuing_apply\",\n\t}\n\tfor _, v := range validRunStatuses {\n\t\tt.Run(v, func(t *testing.T) {\n\t\t\trequire.NoError(t, validateAdminRunFilterParams(v), fmt.Sprintf(\"'%s' should be valid\", v))\n\t\t})\n\t}\n\n\t// empty string is allowed\n\trequire.NoError(t, validateAdminRunFilterParams(\"\"), \"empty string should be valid\")\n\n\t// comma-separated list, all valid\n\trequire.NoError(t, validateAdminRunFilterParams(\"applied,planned,canceled\"), \"'applied,planned,canceled' should be valid)\")\n\n\t// invalid values\n\trequire.Error(t, validateAdminRunFilterParams(\"cost_estimate\"), \"invalid value: cost_estimate\")\n\n\t// comma-separated list, some invalid\n\trequire.Error(t, validateAdminRunFilterParams(\"applied,not-planned,canceled\"), \"'applied,not-planned,canceled' should be invalid)\")\n}\n"
  },
  {
    "path": "admin_sentinel_version.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ AdminSentinelVersions = (*adminSentinelVersions)(nil)\n\n// AdminSentinelVersions describes all the admin Sentinel versions related methods that\n// the Terraform Enterprise API supports.\n// Note that admin Sentinel versions are only available in Terraform Enterprise.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/sentinel-versions\ntype AdminSentinelVersions interface {\n\t// List all the Sentinel versions.\n\tList(ctx context.Context, options *AdminSentinelVersionsListOptions) (*AdminSentinelVersionsList, error)\n\n\t// Read a Sentinel version by its ID.\n\tRead(ctx context.Context, id string) (*AdminSentinelVersion, error)\n\n\t// Create a Sentinel version.\n\tCreate(ctx context.Context, options AdminSentinelVersionCreateOptions) (*AdminSentinelVersion, error)\n\n\t// Update a Sentinel version.\n\tUpdate(ctx context.Context, id string, options AdminSentinelVersionUpdateOptions) (*AdminSentinelVersion, error)\n\n\t// Delete a Sentinel version\n\tDelete(ctx context.Context, id string) error\n}\n\n// adminSentinelVersions implements AdminSentinelVersions.\ntype adminSentinelVersions struct {\n\tclient *Client\n}\n\n// AdminSentinelVersion represents a Sentinel Version\ntype AdminSentinelVersion struct {\n\tID               string                     `jsonapi:\"primary,sentinel-versions\"`\n\tVersion          string                     `jsonapi:\"attr,version\"`\n\tURL              string                     `jsonapi:\"attr,url,omitempty\"`\n\tSHA              string                     `jsonapi:\"attr,sha,omitempty\"`\n\tDeprecated       bool                       `jsonapi:\"attr,deprecated\"`\n\tDeprecatedReason *string                    `jsonapi:\"attr,deprecated-reason,omitempty\"`\n\tOfficial         bool                       `jsonapi:\"attr,official\"`\n\tEnabled          bool                       `jsonapi:\"attr,enabled\"`\n\tBeta             bool                       `jsonapi:\"attr,beta\"`\n\tUsage            int                        `jsonapi:\"attr,usage\"`\n\tCreatedAt        time.Time                  `jsonapi:\"attr,created-at,iso8601\"`\n\tArchs            []*ToolVersionArchitecture `jsonapi:\"attr,archs,omitempty\"`\n}\n\n// AdminSentinelVersionsListOptions represents the options for listing\n// Sentinel versions.\ntype AdminSentinelVersionsListOptions struct {\n\tListOptions\n\n\t// Optional: A query string to find an exact version\n\tFilter string `url:\"filter[version],omitempty\"`\n\n\t// Optional: A search query string to find all versions that match version substring\n\tSearch string `url:\"search[version],omitempty\"`\n}\n\n// AdminSentinelVersionCreateOptions for creating an Sentinel version.\ntype AdminSentinelVersionCreateOptions struct {\n\tType             string                     `jsonapi:\"primary,sentinel-versions\"`\n\tVersion          string                     `jsonapi:\"attr,version\"`       // Required\n\tURL              string                     `jsonapi:\"attr,url,omitempty\"` // Required w/ SHA unless Archs are provided\n\tSHA              string                     `jsonapi:\"attr,sha,omitempty\"` // Required w/ URL unless Archs are provided\n\tOfficial         *bool                      `jsonapi:\"attr,official,omitempty\"`\n\tDeprecated       *bool                      `jsonapi:\"attr,deprecated,omitempty\"`\n\tDeprecatedReason *string                    `jsonapi:\"attr,deprecated-reason,omitempty\"`\n\tEnabled          *bool                      `jsonapi:\"attr,enabled,omitempty\"`\n\tBeta             *bool                      `jsonapi:\"attr,beta,omitempty\"`\n\tArchs            []*ToolVersionArchitecture `jsonapi:\"attr,archs,omitempty\"` // Required unless URL and SHA are provided\n}\n\n// AdminSentinelVersionUpdateOptions for updating Sentinel version.\ntype AdminSentinelVersionUpdateOptions struct {\n\tType             string                     `jsonapi:\"primary,sentinel-versions\"`\n\tVersion          *string                    `jsonapi:\"attr,version,omitempty\"`\n\tURL              *string                    `jsonapi:\"attr,url,omitempty\"`\n\tSHA              *string                    `jsonapi:\"attr,sha,omitempty\"`\n\tOfficial         *bool                      `jsonapi:\"attr,official,omitempty\"`\n\tDeprecated       *bool                      `jsonapi:\"attr,deprecated,omitempty\"`\n\tDeprecatedReason *string                    `jsonapi:\"attr,deprecated-reason,omitempty\"`\n\tEnabled          *bool                      `jsonapi:\"attr,enabled,omitempty\"`\n\tBeta             *bool                      `jsonapi:\"attr,beta,omitempty\"`\n\tArchs            []*ToolVersionArchitecture `jsonapi:\"attr,archs,omitempty\"`\n}\n\n// AdminSentinelVersionsList represents a list of Sentinel versions.\ntype AdminSentinelVersionsList struct {\n\t*Pagination\n\tItems []*AdminSentinelVersion\n}\n\n// List all the Sentinel versions.\nfunc (a *adminSentinelVersions) List(ctx context.Context, options *AdminSentinelVersionsListOptions) (*AdminSentinelVersionsList, error) {\n\treq, err := a.client.NewRequest(\"GET\", \"admin/sentinel-versions\", options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsl := &AdminSentinelVersionsList{}\n\terr = req.Do(ctx, sl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sl, nil\n}\n\n// Read a Sentinel version by its ID.\nfunc (a *adminSentinelVersions) Read(ctx context.Context, id string) (*AdminSentinelVersion, error) {\n\tif !validStringID(&id) {\n\t\treturn nil, ErrInvalidSentinelVersionID\n\t}\n\n\tu := fmt.Sprintf(\"admin/sentinel-versions/%s\", url.PathEscape(id))\n\treq, err := a.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsv := &AdminSentinelVersion{}\n\terr = req.Do(ctx, sv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sv, nil\n}\n\n// Create a new Sentinel version.\nfunc (a *adminSentinelVersions) Create(ctx context.Context, options AdminSentinelVersionCreateOptions) (*AdminSentinelVersion, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\treq, err := a.client.NewRequest(\"POST\", \"admin/sentinel-versions\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsv := &AdminSentinelVersion{}\n\terr = req.Do(ctx, sv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sv, nil\n}\n\n// Update an existing Sentinel version.\nfunc (a *adminSentinelVersions) Update(ctx context.Context, id string, options AdminSentinelVersionUpdateOptions) (*AdminSentinelVersion, error) {\n\tif !validStringID(&id) {\n\t\treturn nil, ErrInvalidSentinelVersionID\n\t}\n\n\tu := fmt.Sprintf(\"admin/sentinel-versions/%s\", url.PathEscape(id))\n\treq, err := a.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsv := &AdminSentinelVersion{}\n\terr = req.Do(ctx, sv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sv, nil\n}\n\n// Delete a Sentinel version.\nfunc (a *adminSentinelVersions) Delete(ctx context.Context, id string) error {\n\tif !validStringID(&id) {\n\t\treturn ErrInvalidSentinelVersionID\n\t}\n\n\tu := fmt.Sprintf(\"admin/sentinel-versions/%s\", url.PathEscape(id))\n\treq, err := a.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o AdminSentinelVersionCreateOptions) valid() error {\n\tif (reflect.DeepEqual(o, AdminSentinelVersionCreateOptions{})) {\n\t\treturn ErrRequiredSentinelVerCreateOps\n\t}\n\tif o.Version == \"\" {\n\t\treturn ErrRequiredVersion\n\t}\n\tif !o.validArch() {\n\t\treturn ErrRequiredArchsOrURLAndSha\n\t}\n\treturn nil\n}\n\nfunc (o AdminSentinelVersionCreateOptions) validArch() bool {\n\tif o.Archs == nil && o.URL != \"\" && o.SHA != \"\" {\n\t\treturn true\n\t}\n\n\tfor _, a := range o.Archs {\n\t\tif !validArch(a) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "admin_sentinel_version_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminSentinelVersions_List(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tsList, err := client.Admin.SentinelVersions.List(ctx, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, sList.Items)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tsList, err := client.Admin.SentinelVersions.List(ctx, &AdminSentinelVersionsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\t// Out of range page number, so the items should be empty\n\t\tassert.Empty(t, sList.Items)\n\t\tassert.Equal(t, 999, sList.CurrentPage)\n\n\t\tsList, err = client.Admin.SentinelVersions.List(ctx, &AdminSentinelVersionsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, sList.CurrentPage)\n\t\tfor _, item := range sList.Items {\n\t\t\tassert.NotNil(t, item.ID)\n\t\t\tassert.NotEmpty(t, item.Version)\n\t\t\tassert.NotEmpty(t, item.URL)\n\t\t\tassert.NotEmpty(t, item.SHA)\n\t\t\tassert.NotNil(t, item.Official)\n\t\t\tassert.NotNil(t, item.Deprecated)\n\t\t\tif item.Deprecated {\n\t\t\t\tassert.NotNil(t, item.DeprecatedReason)\n\t\t\t} else {\n\t\t\t\tassert.Nil(t, item.DeprecatedReason)\n\t\t\t}\n\t\t\tassert.NotNil(t, item.Enabled)\n\t\t\tassert.NotNil(t, item.Beta)\n\t\t\tassert.NotNil(t, item.Usage)\n\t\t\tassert.NotNil(t, item.CreatedAt)\n\t\t\tassert.NotNil(t, item.Archs)\n\t\t}\n\t})\n\n\tt.Run(\"with filter query string\", func(t *testing.T) {\n\t\tsList, err := client.Admin.SentinelVersions.List(ctx, &AdminSentinelVersionsListOptions{\n\t\t\tFilter: \"0.22.1\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, len(sList.Items))\n\n\t\t// Query for a Sentinel version that does not exist\n\t\tsList, err = client.Admin.SentinelVersions.List(ctx, &AdminSentinelVersionsListOptions{\n\t\t\tFilter: \"1000.1000.42\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, sList.Items)\n\t})\n\n\tt.Run(\"with search version query string\", func(t *testing.T) {\n\t\tsearchVersion := \"0.22.1\"\n\t\tsList, err := client.Admin.SentinelVersions.List(ctx, &AdminSentinelVersionsListOptions{\n\t\t\tSearch: searchVersion,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, sList.Items)\n\n\t\tt.Run(\"ensure each version matches substring\", func(t *testing.T) {\n\t\t\tfor _, item := range sList.Items {\n\t\t\t\tassert.Equal(t, true, strings.Contains(item.Version, searchVersion))\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc TestAdminSentinelVersions_CreateDelete(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\tversion := createAdminSentinelVersion()\n\turl := \"https://www.hashicorp.com\"\n\tamd64Sha := String(genSha(t))\n\n\tt.Run(\"with valid options including top level url & sha and archs\", func(t *testing.T) {\n\t\topts := AdminSentinelVersionCreateOptions{\n\t\t\tVersion:          version,\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tOfficial:         Bool(false),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tURL:              url,\n\t\t\tSHA:              *amd64Sha,\n\t\t\tArchs: []*ToolVersionArchitecture{\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  *amd64Sha,\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: amd64,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  *String(genSha(t)),\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: arm64,\n\t\t\t\t}},\n\t\t}\n\t\tsv, err := client.Admin.SentinelVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.SentinelVersions.Delete(ctx, sv.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, opts.Version, sv.Version)\n\t\tassert.Equal(t, *opts.Official, sv.Official)\n\t\tassert.Equal(t, *opts.Deprecated, sv.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *sv.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, sv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, sv.Beta)\n\t\tassert.Equal(t, opts.URL, sv.URL)\n\t\tassert.Equal(t, opts.SHA, sv.SHA)\n\t\tassert.Equal(t, len(opts.Archs), len(sv.Archs))\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, sv.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, sv.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, sv.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, sv.Archs[i].Arch)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options including archs\", func(t *testing.T) {\n\t\topts := AdminSentinelVersionCreateOptions{\n\t\t\tVersion:          version,\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tOfficial:         Bool(false),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tArchs: []*ToolVersionArchitecture{\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  *amd64Sha,\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: amd64,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  *String(genSha(t)),\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: arm64,\n\t\t\t\t}},\n\t\t}\n\t\tsv, err := client.Admin.SentinelVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.SentinelVersions.Delete(ctx, sv.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, opts.Version, sv.Version)\n\t\tassert.Equal(t, *opts.Official, sv.Official)\n\t\tassert.Equal(t, *opts.Deprecated, sv.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *sv.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, sv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, sv.Beta)\n\t\tassert.Equal(t, len(opts.Archs), len(sv.Archs))\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, sv.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, sv.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, sv.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, sv.Archs[i].Arch)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options including url, and sha\", func(t *testing.T) {\n\t\topts := AdminSentinelVersionCreateOptions{\n\t\t\tVersion:          version,\n\t\t\tURL:              url,\n\t\t\tSHA:              *amd64Sha,\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tOfficial:         Bool(false),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t}\n\t\tsv, err := client.Admin.SentinelVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.SentinelVersions.Delete(ctx, sv.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, opts.Version, sv.Version)\n\t\tassert.Equal(t, opts.URL, sv.URL)\n\t\tassert.Equal(t, opts.SHA, sv.SHA)\n\t\tassert.Equal(t, *opts.Official, sv.Official)\n\t\tassert.Equal(t, *opts.Deprecated, sv.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *sv.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, sv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, sv.Beta)\n\t\tassert.Equal(t, 1, len(sv.Archs))\n\t\tassert.Equal(t, opts.URL, sv.Archs[0].URL)\n\t\tassert.Equal(t, opts.SHA, sv.Archs[0].Sha)\n\t\tassert.Equal(t, linux, sv.Archs[0].OS)\n\t\tassert.Equal(t, amd64, sv.Archs[0].Arch)\n\t})\n\n\tt.Run(\"with only required options including tool version url and sha\", func(t *testing.T) {\n\t\tversion = createAdminSentinelVersion()\n\t\topts := AdminSentinelVersionCreateOptions{\n\t\t\tVersion: version,\n\t\t\tURL:     url,\n\t\t\tSHA:     *amd64Sha,\n\t\t}\n\t\tsv, err := client.Admin.SentinelVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.SentinelVersions.Delete(ctx, sv.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, opts.Version, sv.Version)\n\t\tassert.Equal(t, opts.URL, sv.URL)\n\t\tassert.Equal(t, opts.SHA, sv.SHA)\n\t\tassert.Equal(t, false, sv.Official)\n\t\tassert.Equal(t, false, sv.Deprecated)\n\t\tassert.Nil(t, sv.DeprecatedReason)\n\t\tassert.Equal(t, true, sv.Enabled)\n\t\tassert.Equal(t, false, sv.Beta)\n\t\tassert.Equal(t, 1, len(sv.Archs))\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, sv.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, sv.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, sv.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, sv.Archs[i].Arch)\n\t\t}\n\t})\n\n\tt.Run(\"with only required options including archs\", func(t *testing.T) {\n\t\tversion = createAdminSentinelVersion()\n\t\topts := AdminSentinelVersionCreateOptions{\n\t\t\tVersion: version,\n\t\t\tArchs: []*ToolVersionArchitecture{\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  *amd64Sha,\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: amd64,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\t\tSha:  *String(genSha(t)),\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: arm64,\n\t\t\t\t}},\n\t\t}\n\t\tsv, err := client.Admin.SentinelVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.SentinelVersions.Delete(ctx, sv.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, opts.Version, sv.Version)\n\t\tassert.Equal(t, false, sv.Official)\n\t\tassert.Equal(t, false, sv.Deprecated)\n\t\tassert.Nil(t, sv.DeprecatedReason)\n\t\tassert.Equal(t, true, sv.Enabled)\n\t\tassert.Equal(t, false, sv.Beta)\n\t\tassert.Equal(t, len(opts.Archs), len(sv.Archs))\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, sv.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, sv.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, sv.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, sv.Archs[i].Arch)\n\t\t}\n\t})\n\n\tt.Run(\"with empty options\", func(t *testing.T) {\n\t\t_, err := client.Admin.SentinelVersions.Create(ctx, AdminSentinelVersionCreateOptions{})\n\t\trequire.Equal(t, err, ErrRequiredSentinelVerCreateOps)\n\t})\n}\n\nfunc TestAdminSentinelVersions_ReadUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"reads and updates\", func(t *testing.T) {\n\t\tversion := createAdminSentinelVersion()\n\t\tsha := String(genSha(t))\n\t\topts := AdminSentinelVersionCreateOptions{\n\t\t\tVersion:          version,\n\t\t\tURL:              \"https://www.hashicorp.com\",\n\t\t\tSHA:              genSha(t),\n\t\t\tOfficial:         Bool(false),\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tArchs: []*ToolVersionArchitecture{{\n\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\tSha:  *sha,\n\t\t\t\tOS:   linux,\n\t\t\t\tArch: amd64,\n\t\t\t}},\n\t\t}\n\t\tsv, err := client.Admin.SentinelVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\t\tid := sv.ID\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.SentinelVersions.Delete(ctx, id)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tsv, err = client.Admin.SentinelVersions.Read(ctx, id)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, opts.Version, sv.Version)\n\t\tassert.Equal(t, opts.Archs[0].URL, sv.URL)\n\t\tassert.Equal(t, opts.Archs[0].Sha, sv.SHA)\n\t\tassert.Equal(t, *opts.Official, sv.Official)\n\t\tassert.Equal(t, *opts.Deprecated, sv.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *sv.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, sv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, sv.Beta)\n\t\tassert.Equal(t, len(opts.Archs), len(sv.Archs))\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, sv.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, sv.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, sv.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, sv.Archs[i].Arch)\n\t\t}\n\n\t\tupdateVersion := createAdminSentinelVersion()\n\t\tupdateURL := \"https://app.terraform.io/\"\n\t\tupdateOpts := AdminSentinelVersionUpdateOptions{\n\t\t\tVersion:    String(updateVersion),\n\t\t\tURL:        String(updateURL),\n\t\t\tDeprecated: Bool(false),\n\t\t}\n\n\t\tsv, err = client.Admin.SentinelVersions.Update(ctx, id, updateOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, updateVersion, sv.Version)\n\t\tassert.Equal(t, updateURL, sv.URL)\n\t\tassert.Equal(t, opts.SHA, sv.SHA)\n\t\tassert.Equal(t, *opts.Official, sv.Official)\n\t\tassert.Equal(t, *updateOpts.Deprecated, sv.Deprecated)\n\t\tassert.Equal(t, *opts.Enabled, sv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, sv.Beta)\n\t\tassert.Equal(t, len(opts.Archs), len(sv.Archs))\n\t\tassert.Equal(t, updateURL, sv.Archs[0].URL)\n\t\tassert.Equal(t, opts.SHA, sv.Archs[0].Sha)\n\t\tassert.Equal(t, opts.Archs[0].OS, sv.Archs[0].OS)\n\t\tassert.Equal(t, opts.Archs[0].Arch, sv.Archs[0].Arch)\n\t})\n\n\tt.Run(\"update with Archs\", func(t *testing.T) {\n\t\tversion := createAdminSentinelVersion()\n\t\tsha := String(genSha(t))\n\t\topts := AdminSentinelVersionCreateOptions{\n\t\t\tVersion:          *String(version),\n\t\t\tURL:              *String(\"https://www.hashicorp.com\"),\n\t\t\tSHA:              *String(genSha(t)),\n\t\t\tOfficial:         Bool(false),\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tArchs: []*ToolVersionArchitecture{{\n\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\tSha:  *sha,\n\t\t\t\tOS:   linux,\n\t\t\t\tArch: amd64,\n\t\t\t}},\n\t\t}\n\t\tsv, err := client.Admin.SentinelVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\t\tid := sv.ID\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.SentinelVersions.Delete(ctx, id)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tupdateArchOpts := AdminSentinelVersionUpdateOptions{\n\t\t\tArchs: []*ToolVersionArchitecture{{\n\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\tSha:  *sha,\n\t\t\t\tOS:   linux,\n\t\t\t\tArch: arm64,\n\t\t\t}},\n\t\t}\n\n\t\tsv, err = client.Admin.SentinelVersions.Update(ctx, id, updateArchOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, opts.Version, sv.Version)\n\t\tassert.Equal(t, \"\", sv.URL)\n\t\tassert.Equal(t, \"\", sv.SHA)\n\t\tassert.Equal(t, *opts.Official, sv.Official)\n\t\tassert.Equal(t, *opts.Deprecated, sv.Deprecated)\n\t\tassert.Equal(t, *opts.Enabled, sv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, sv.Beta)\n\t\tassert.Equal(t, len(sv.Archs), 1)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].URL, sv.Archs[0].URL)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].Sha, sv.Archs[0].Sha)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].OS, sv.Archs[0].OS)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].Arch, sv.Archs[0].Arch)\n\t})\n\n\tt.Run(\"with non-existent Sentinel version\", func(t *testing.T) {\n\t\trandomID := \"random-id\"\n\t\t_, err := client.Admin.SentinelVersions.Read(ctx, randomID)\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "admin_setting.go",
    "content": "// Copyright IBM Corp. 2018, 2026\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\n// SCIMResource groups the SCIM related resources together.\n// This struct should be constructed with keyed fields only or obtained via the client\n// to prevent breakages when new fields are added.\ntype SCIMResource struct {\n\tSCIMSettings\n\tTokens AdminSCIMTokens\n\tGroups AdminSCIMGroups\n}\n\n// AdminSettings describes all the admin settings related methods that the Terraform Enterprise API supports.\n// Note that admin settings are only available in Terraform Enterprise.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings\ntype AdminSettings struct {\n\tGeneral        GeneralSettings\n\tSAML           SAMLSettings\n\tCostEstimation CostEstimationSettings\n\tSMTP           SMTPSettings\n\tTwilio         TwilioSettings\n\tCustomization  CustomizationSettings\n\tOIDC           OIDCSettings\n\tSCIM           *SCIMResource\n}\n\nfunc newAdminSettings(client *Client) *AdminSettings {\n\treturn &AdminSettings{\n\t\tGeneral:        &adminGeneralSettings{client: client},\n\t\tSAML:           &adminSAMLSettings{client: client},\n\t\tCostEstimation: &adminCostEstimationSettings{client: client},\n\t\tSMTP:           &adminSMTPSettings{client: client},\n\t\tTwilio:         &adminTwilioSettings{client: client},\n\t\tCustomization:  &adminCustomizationSettings{client: client},\n\t\tOIDC:           &adminOIDCSettings{client: client},\n\t\tSCIM: &SCIMResource{\n\t\t\tSCIMSettings: &adminSCIMSettings{client: client},\n\t\t\tTokens:       &adminSCIMTokens{client: client},\n\t\t\tGroups:       &adminSCIMGroups{client: client},\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "admin_setting_cost_estimation.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ CostEstimationSettings = (*adminCostEstimationSettings)(nil)\n\n// CostEstimationSettings describes all the cost estimation admin settings for the Admin Setting API.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings\ntype CostEstimationSettings interface {\n\t// Read returns the cost estimation settings.\n\tRead(ctx context.Context) (*AdminCostEstimationSetting, error)\n\n\t// Update updates the cost estimation settings.\n\tUpdate(ctx context.Context, options AdminCostEstimationSettingOptions) (*AdminCostEstimationSetting, error)\n}\n\ntype adminCostEstimationSettings struct {\n\tclient *Client\n}\n\n// AdminCostEstimationSetting represents the admin cost estimation settings.\ntype AdminCostEstimationSetting struct {\n\tID                        string `jsonapi:\"primary,cost-estimation-settings\"`\n\tEnabled                   bool   `jsonapi:\"attr,enabled\"`\n\tAWSAccessKeyID            string `jsonapi:\"attr,aws-access-key-id\"`\n\tAWSAccessKey              string `jsonapi:\"attr,aws-secret-key\"`\n\tAWSEnabled                bool   `jsonapi:\"attr,aws-enabled\"`\n\tAWSInstanceProfileEnabled bool   `jsonapi:\"attr,aws-instance-profile-enabled\"`\n\tGCPCredentials            string `jsonapi:\"attr,gcp-credentials\"`\n\tGCPEnabled                bool   `jsonapi:\"attr,gcp-enabled\"`\n\tAzureEnabled              bool   `jsonapi:\"attr,azure-enabled\"`\n\tAzureClientID             string `jsonapi:\"attr,azure-client-id\"`\n\tAzureClientSecret         string `jsonapi:\"attr,azure-client-secret\"`\n\tAzureSubscriptionID       string `jsonapi:\"attr,azure-subscription-id\"`\n\tAzureTenantID             string `jsonapi:\"attr,azure-tenant-id\"`\n}\n\n// AdminCostEstimationSettingOptions represents the admin options for updating\n// the cost estimation settings.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings#request-body-1\ntype AdminCostEstimationSettingOptions struct {\n\tEnabled             *bool   `jsonapi:\"attr,enabled,omitempty\"`\n\tAWSAccessKeyID      *string `jsonapi:\"attr,aws-access-key-id,omitempty\"`\n\tAWSAccessKey        *string `jsonapi:\"attr,aws-secret-key,omitempty\"`\n\tGCPCredentials      *string `jsonapi:\"attr,gcp-credentials,omitempty\"`\n\tAzureClientID       *string `jsonapi:\"attr,azure-client-id,omitempty\"`\n\tAzureClientSecret   *string `jsonapi:\"attr,azure-client-secret,omitempty\"`\n\tAzureSubscriptionID *string `jsonapi:\"attr,azure-subscription-id,omitempty\"`\n\tAzureTenantID       *string `jsonapi:\"attr,azure-tenant-id,omitempty\"`\n}\n\n// Read returns the cost estimation settings.\nfunc (a *adminCostEstimationSettings) Read(ctx context.Context) (*AdminCostEstimationSetting, error) {\n\treq, err := a.client.NewRequest(\"GET\", \"admin/cost-estimation-settings\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tace := &AdminCostEstimationSetting{}\n\terr = req.Do(ctx, ace)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ace, nil\n}\n\n// Update updates the cost-estimation settings.\nfunc (a *adminCostEstimationSettings) Update(ctx context.Context, options AdminCostEstimationSettingOptions) (*AdminCostEstimationSetting, error) {\n\treq, err := a.client.NewRequest(\"PATCH\", \"admin/cost-estimation-settings\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tace := &AdminCostEstimationSetting{}\n\terr = req.Do(ctx, ace)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ace, nil\n}\n"
  },
  {
    "path": "admin_setting_cost_estimation_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminSettings_CostEstimation_Read(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tcostEstimationSettings, err := client.Admin.Settings.CostEstimation.Read(ctx)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"cost-estimation\", costEstimationSettings.ID)\n\tassert.NotNil(t, costEstimationSettings.Enabled)\n}\n\nfunc TestAdminSettings_CostEstimation_Update(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\t_, err := client.Admin.Settings.CostEstimation.Read(ctx)\n\trequire.NoError(t, err)\n\n\tcostEnabled := false\n\tcostEstimationSettings, err := client.Admin.Settings.CostEstimation.Update(ctx, AdminCostEstimationSettingOptions{\n\t\tEnabled: Bool(costEnabled),\n\t})\n\trequire.NoError(t, err)\n\tassert.Equal(t, costEnabled, costEstimationSettings.Enabled)\n}\n"
  },
  {
    "path": "admin_setting_customization.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ CustomizationSettings = (*adminCustomizationSettings)(nil)\n\n// CustomizationSettings describes all the Customization admin settings.\ntype CustomizationSettings interface {\n\t// Read returns the customization settings.\n\tRead(ctx context.Context) (*AdminCustomizationSetting, error)\n\n\t// Update updates the customization settings.\n\tUpdate(ctx context.Context, options AdminCustomizationSettingsUpdateOptions) (*AdminCustomizationSetting, error)\n}\n\ntype adminCustomizationSettings struct {\n\tclient *Client\n}\n\n// AdminCustomizationSetting represents the Customization settings in Terraform Enterprise for the Admin Settings API.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings\ntype AdminCustomizationSetting struct {\n\tID           string `jsonapi:\"primary,customization-settings\"`\n\tSupportEmail string `jsonapi:\"attr,support-email-address\"`\n\tLoginHelp    string `jsonapi:\"attr,login-help\"`\n\tFooter       string `jsonapi:\"attr,footer\"`\n\tError        string `jsonapi:\"attr,error\"`\n\tNewUser      string `jsonapi:\"attr,new-user\"`\n}\n\n// Read returns the Customization settings.\nfunc (a *adminCustomizationSettings) Read(ctx context.Context) (*AdminCustomizationSetting, error) {\n\treq, err := a.client.NewRequest(\"GET\", \"admin/customization-settings\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcs := &AdminCustomizationSetting{}\n\terr = req.Do(ctx, cs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cs, nil\n}\n\n// AdminCustomizationSettingsUpdateOptions represents the admin options for updating\n// Customization settings.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings#request-body-6\ntype AdminCustomizationSettingsUpdateOptions struct {\n\tSupportEmail *string `jsonapi:\"attr,support-email-address,omitempty\"`\n\tLoginHelp    *string `jsonapi:\"attr,login-help,omitempty\"`\n\tFooter       *string `jsonapi:\"attr,footer,omitempty\"`\n\tError        *string `jsonapi:\"attr,error,omitempty\"`\n\tNewUser      *string `jsonapi:\"attr,new-user,omitempty\"`\n}\n\n// Update updates the customization settings.\nfunc (a *adminCustomizationSettings) Update(ctx context.Context, options AdminCustomizationSettingsUpdateOptions) (*AdminCustomizationSetting, error) {\n\treq, err := a.client.NewRequest(\"PATCH\", \"admin/customization-settings\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcs := &AdminCustomizationSetting{}\n\terr = req.Do(ctx, cs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cs, nil\n}\n"
  },
  {
    "path": "admin_setting_customization_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminSettings_Customization_Read(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tcustomizationSettings, err := client.Admin.Settings.Customization.Read(ctx)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"customization\", customizationSettings.ID)\n\tassert.NotNil(t, customizationSettings.SupportEmail)\n\tassert.NotNil(t, customizationSettings.LoginHelp)\n\tassert.NotNil(t, customizationSettings.Footer)\n\tassert.NotNil(t, customizationSettings.Error)\n\tassert.NotNil(t, customizationSettings.NewUser)\n}\n\nfunc TestAdminSettings_Customization_Update(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\temail := \"test@example.com\"\n\tloginHelp := \"<div>Login Help</div>\"\n\tfooter := \"<p>Custom Footer Content</p>\"\n\tcustomError := \"<em>Custom Error Instructions</em>\"\n\tnewUser := \"New user? <a href=\\\"#\\\">Click Here</a>\"\n\n\tcustomizationSettings, err := client.Admin.Settings.Customization.Update(ctx, AdminCustomizationSettingsUpdateOptions{\n\t\tSupportEmail: String(email),\n\t\tLoginHelp:    String(loginHelp),\n\t\tFooter:       String(footer),\n\t\tError:        String(customError),\n\t\tNewUser:      String(newUser),\n\t})\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, email, customizationSettings.SupportEmail)\n\tassert.Equal(t, loginHelp, customizationSettings.LoginHelp)\n\tassert.Equal(t, footer, customizationSettings.Footer)\n\tassert.Equal(t, customError, customizationSettings.Error)\n\tassert.Equal(t, newUser, customizationSettings.NewUser)\n}\n"
  },
  {
    "path": "admin_setting_general.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ GeneralSettings = (*adminGeneralSettings)(nil)\n\n// GeneralSettings describes the general admin settings for the Admin Setting API.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings\ntype GeneralSettings interface {\n\t// Read returns the general settings\n\tRead(ctx context.Context) (*AdminGeneralSetting, error)\n\n\t// Update updates general settings.\n\tUpdate(ctx context.Context, options AdminGeneralSettingsUpdateOptions) (*AdminGeneralSetting, error)\n}\n\ntype adminGeneralSettings struct {\n\tclient *Client\n}\n\n// AdminGeneralSetting represents a the general settings in Terraform Enterprise.\ntype AdminGeneralSetting struct {\n\tID                               string `jsonapi:\"primary,general-settings\"`\n\tLimitUserOrganizationCreation    bool   `jsonapi:\"attr,limit-user-organization-creation\"`\n\tAPIRateLimitingEnabled           bool   `jsonapi:\"attr,api-rate-limiting-enabled\"`\n\tAPIRateLimit                     int    `jsonapi:\"attr,api-rate-limit\"`\n\tSendPassingStatusesEnabled       bool   `jsonapi:\"attr,send-passing-statuses-for-untriggered-speculative-plans\"`\n\tAllowSpeculativePlansOnPR        bool   `jsonapi:\"attr,allow-speculative-plans-on-pull-requests-from-forks\"`\n\tRequireTwoFactorForAdmin         bool   `jsonapi:\"attr,require-two-factor-for-admins\"`\n\tFairRunQueuingEnabled            bool   `jsonapi:\"attr,fair-run-queuing-enabled\"`\n\tLimitOrgsPerUser                 bool   `jsonapi:\"attr,limit-organizations-per-user\"`\n\tDefaultOrgsPerUserCeiling        int    `jsonapi:\"attr,default-organizations-per-user-ceiling\"`\n\tLimitWorkspacesPerOrg            bool   `jsonapi:\"attr,limit-workspaces-per-organization\"`\n\tDefaultWorkspacesPerOrgCeiling   int    `jsonapi:\"attr,default-workspaces-per-organization-ceiling\"`\n\tTerraformBuildWorkerApplyTimeout string `jsonapi:\"attr,terraform-build-worker-apply-timeout\"`\n\tTerraformBuildWorkerPlanTimeout  string `jsonapi:\"attr,terraform-build-worker-plan-timeout\"`\n\tApplyTimeout                     string `jsonapi:\"attr,apply-timeout\"`\n\tPlanTimeout                      string `jsonapi:\"attr,plan-timeout\"`\n\tDefaultRemoteStateAccess         bool   `jsonapi:\"attr,default-remote-state-access\"`\n}\n\n// AdminGeneralSettingsUpdateOptions represents the admin options for updating\n// general settings.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings#request-body\ntype AdminGeneralSettingsUpdateOptions struct {\n\tLimitUserOrgCreation              *bool   `jsonapi:\"attr,limit-user-organization-creation,omitempty\"`\n\tAPIRateLimitingEnabled            *bool   `jsonapi:\"attr,api-rate-limiting-enabled,omitempty\"`\n\tAPIRateLimit                      *int    `jsonapi:\"attr,api-rate-limit,omitempty\"`\n\tSendPassingStatusUntriggeredPlans *bool   `jsonapi:\"attr,send-passing-statuses-for-untriggered-speculative-plans,omitempty\"`\n\tAllowSpeculativePlansOnPR         *bool   `jsonapi:\"attr,allow-speculative-plans-on-pull-requests-from-forks,omitempty\"`\n\tDefaultRemoteStateAccess          *bool   `jsonapi:\"attr,default-remote-state-access,omitempty\"`\n\tApplyTimeout                      *string `jsonapi:\"attr,apply-timeout\"`\n\tPlanTimeout                       *string `jsonapi:\"attr,plan-timeout\"`\n}\n\n// Read returns the general settings.\nfunc (a *adminGeneralSettings) Read(ctx context.Context) (*AdminGeneralSetting, error) {\n\treq, err := a.client.NewRequest(\"GET\", \"admin/general-settings\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tags := &AdminGeneralSetting{}\n\terr = req.Do(ctx, ags)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ags, nil\n}\n\n// Update updates the general settings.\nfunc (a *adminGeneralSettings) Update(ctx context.Context, options AdminGeneralSettingsUpdateOptions) (*AdminGeneralSetting, error) {\n\treq, err := a.client.NewRequest(\"PATCH\", \"admin/general-settings\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tags := &AdminGeneralSetting{}\n\terr = req.Do(ctx, ags)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ags, nil\n}\n"
  },
  {
    "path": "admin_setting_general_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminSettings_General_Read(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tgeneralSettings, err := client.Admin.Settings.General.Read(ctx)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"general\", generalSettings.ID)\n\tassert.NotNil(t, generalSettings.LimitUserOrganizationCreation)\n\tassert.NotNil(t, generalSettings.APIRateLimitingEnabled)\n\tassert.NotNil(t, generalSettings.APIRateLimit)\n\tassert.NotNil(t, generalSettings.SendPassingStatusesEnabled)\n\tassert.NotNil(t, generalSettings.AllowSpeculativePlansOnPR)\n\tassert.NotNil(t, generalSettings.RequireTwoFactorForAdmin)\n\tassert.NotNil(t, generalSettings.FairRunQueuingEnabled)\n\tassert.NotNil(t, generalSettings.LimitOrgsPerUser)\n\tassert.NotNil(t, generalSettings.DefaultOrgsPerUserCeiling)\n\tassert.NotNil(t, generalSettings.LimitWorkspacesPerOrg)\n\tassert.NotNil(t, generalSettings.DefaultWorkspacesPerOrgCeiling)\n\tassert.NotNil(t, generalSettings.TerraformBuildWorkerApplyTimeout)\n\tassert.NotNil(t, generalSettings.TerraformBuildWorkerPlanTimeout)\n\tassert.NotNil(t, generalSettings.ApplyTimeout)\n\tassert.NotNil(t, generalSettings.PlanTimeout)\n\tassert.NotNil(t, generalSettings.DefaultRemoteStateAccess)\n}\n\nfunc TestAdminSettings_General_Update(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tgeneralSettings, err := client.Admin.Settings.General.Read(ctx)\n\trequire.NoError(t, err)\n\n\torigLimitOrgCreation := generalSettings.LimitUserOrganizationCreation\n\torigAPIRateLimitEnabled := generalSettings.APIRateLimitingEnabled\n\torigAPIRateLimit := generalSettings.APIRateLimit\n\torigDefaultRemoteState := generalSettings.DefaultRemoteStateAccess\n\torigApplyTimeout := generalSettings.ApplyTimeout\n\torigPlanTimeout := generalSettings.PlanTimeout\n\n\tlimitOrgCreation := true\n\tapiRateLimitEnabled := true\n\tapiRateLimit := 50\n\tdefaultRemoteStateAccess := false\n\tapplyTimeout := \"2h\"\n\tplanTimeout := \"30m\"\n\n\tgeneralSettings, err = client.Admin.Settings.General.Update(ctx, AdminGeneralSettingsUpdateOptions{\n\t\tLimitUserOrgCreation:     Bool(limitOrgCreation),\n\t\tAPIRateLimitingEnabled:   Bool(apiRateLimitEnabled),\n\t\tAPIRateLimit:             Int(apiRateLimit),\n\t\tDefaultRemoteStateAccess: Bool(defaultRemoteStateAccess),\n\t\tApplyTimeout:             &applyTimeout,\n\t\tPlanTimeout:              &planTimeout,\n\t})\n\trequire.NoError(t, err)\n\tassert.Equal(t, limitOrgCreation, generalSettings.LimitUserOrganizationCreation)\n\tassert.Equal(t, apiRateLimitEnabled, generalSettings.APIRateLimitingEnabled)\n\tassert.Equal(t, apiRateLimit, generalSettings.APIRateLimit)\n\tassert.Equal(t, defaultRemoteStateAccess, generalSettings.DefaultRemoteStateAccess)\n\tassert.Equal(t, applyTimeout, generalSettings.ApplyTimeout)\n\tassert.Equal(t, planTimeout, generalSettings.PlanTimeout)\n\n\t// Undo Updates, revert back to original\n\tgeneralSettings, err = client.Admin.Settings.General.Update(ctx, AdminGeneralSettingsUpdateOptions{\n\t\tLimitUserOrgCreation:     Bool(origLimitOrgCreation),\n\t\tAPIRateLimitingEnabled:   Bool(origAPIRateLimitEnabled),\n\t\tAPIRateLimit:             Int(origAPIRateLimit),\n\t\tDefaultRemoteStateAccess: Bool(origDefaultRemoteState),\n\t\tApplyTimeout:             &origApplyTimeout,\n\t\tPlanTimeout:              &origPlanTimeout,\n\t})\n\trequire.NoError(t, err)\n\tassert.Equal(t, origLimitOrgCreation, generalSettings.LimitUserOrganizationCreation)\n\tassert.Equal(t, origAPIRateLimitEnabled, generalSettings.APIRateLimitingEnabled)\n\tassert.Equal(t, origAPIRateLimit, generalSettings.APIRateLimit)\n\tassert.Equal(t, origDefaultRemoteState, generalSettings.DefaultRemoteStateAccess)\n\tassert.Equal(t, origApplyTimeout, generalSettings.ApplyTimeout)\n\tassert.Equal(t, origPlanTimeout, generalSettings.PlanTimeout)\n}\n"
  },
  {
    "path": "admin_setting_oidc.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ OIDCSettings = (*adminOIDCSettings)(nil)\n\n// OidcSettings describes all the OIDC admin settings for the Admin Setting API.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings\ntype OIDCSettings interface {\n\t// Rotate the key used for signing OIDC tokens for workload identity\n\tRotateKey(ctx context.Context) error\n\n\t// Trim old version of the key used for signing OIDC tokens for workload identity\n\tTrimKey(ctx context.Context) error\n}\n\ntype adminOIDCSettings struct {\n\tclient *Client\n}\n\n// Rotate the key used for signing OIDC tokens for workload identity\nfunc (a *adminOIDCSettings) RotateKey(ctx context.Context) error {\n\treq, err := a.client.NewRequest(\"POST\", \"admin/oidc-settings/actions/rotate-key\", nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Trim old version of the key used for signing OIDC tokens for workload identity\nfunc (a *adminOIDCSettings) TrimKey(ctx context.Context) error {\n\treq, err := a.client.NewRequest(\"POST\", \"admin/oidc-settings/actions/trim-key\", nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "admin_setting_oidc_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype wellKnownJwks struct {\n\tKeys []struct {\n\t\tKid string `json:\"kid\"`\n\t} `json:\"keys\"`\n}\n\nfunc TestAdminSettings_Oidc_RotateKey(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tjwksClient := http.Client{\n\t\tTimeout: time.Second * 2,\n\t}\n\tbaseURL := client.baseURL\n\ttoken := client.token\n\n\tctx := context.Background()\n\n\tjwks, err := getJwks(jwksClient, baseURL, token)\n\trequire.NoError(t, err)\n\n\t// Don't assume there is only 1 key to start\n\toriginalNumKeys := len(jwks.Keys)\n\n\terr = client.Admin.Settings.OIDC.RotateKey(ctx)\n\trequire.NoError(t, err)\n\n\tjwks, err = getJwks(jwksClient, baseURL, token)\n\trequire.NoError(t, err)\n\n\tnewNumKeys := len(jwks.Keys)\n\n\t// Rotate should add 1 additional key\n\tassert.Equal(t, originalNumKeys+1, newNumKeys)\n}\n\nfunc TestAdminSettings_Oidc_TrimKey(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tjwksClient := http.Client{\n\t\tTimeout: time.Second * 2,\n\t}\n\tbaseURL := client.baseURL\n\ttoken := client.token\n\n\tctx := context.Background()\n\n\tjwks, err := getJwks(jwksClient, baseURL, token)\n\trequire.NoError(t, err)\n\n\t// Don't assume there is only 1 key to start\n\toriginalNumKeys := len(jwks.Keys)\n\n\toriginalKids := make([]string, originalNumKeys)\n\n\tfor i := 0; i < originalNumKeys; i++ {\n\t\toriginalKids[i] = jwks.Keys[i].Kid\n\t}\n\n\terr = client.Admin.Settings.OIDC.RotateKey(ctx)\n\trequire.NoError(t, err)\n\n\tjwks, err = getJwks(jwksClient, baseURL, token)\n\trequire.NoError(t, err)\n\n\tbeforeTrimNumKeys := len(jwks.Keys)\n\n\tassert.Equal(t, originalNumKeys+1, beforeTrimNumKeys)\n\n\terr = client.Admin.Settings.OIDC.TrimKey(ctx)\n\trequire.NoError(t, err)\n\n\tjwks, err = getJwks(jwksClient, baseURL, token)\n\trequire.NoError(t, err)\n\n\tafterTrimNumKeys := len(jwks.Keys)\n\n\tassert.Equal(t, 1, afterTrimNumKeys)\n\n\t// Make sure we actually trimmed the keys\n\tassert.NotContains(t, originalKids, jwks.Keys[0].Kid)\n}\n\nfunc getJwks(client http.Client, baseURL *url.URL, token string) (*wellKnownJwks, error) {\n\tjwksEndpoint, err := baseURL.Parse(\"/.well-known/jwks\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := http.NewRequest(http.MethodGet, jwksEndpoint.String(), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tres, getErr := client.Do(req)\n\tif getErr != nil {\n\t\treturn nil, getErr\n\t}\n\n\tif res.Body != nil {\n\t\tdefer res.Body.Close()\n\t}\n\n\tif res.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d. Expected a 200 response\", res.StatusCode)\n\t}\n\n\tvar result wellKnownJwks\n\tjsonErr := json.NewDecoder(res.Body).Decode(&result)\n\tif jsonErr != nil {\n\t\treturn nil, jsonErr\n\t}\n\n\treturn &result, nil\n}\n"
  },
  {
    "path": "admin_setting_saml.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ SAMLSettings = (*adminSAMLSettings)(nil)\n\n// SAMLSettings describes all the SAML admin settings for the Admin Setting API.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings\ntype SAMLSettings interface {\n\t// Read returns the SAML settings.\n\tRead(ctx context.Context) (*AdminSAMLSetting, error)\n\n\t// Update updates the SAML settings.\n\tUpdate(ctx context.Context, options AdminSAMLSettingsUpdateOptions) (*AdminSAMLSetting, error)\n\n\t// RevokeIdpCert revokes the older IdP certificate when the new IdP\n\t// certificate is known to be functioning correctly.\n\tRevokeIdpCert(ctx context.Context) (*AdminSAMLSetting, error)\n}\n\ntype adminSAMLSettings struct {\n\tclient *Client\n}\n\n// SAMLProviderType represents the SAML identity provider type.\ntype SAMLProviderType string\n\n// SAMLProviderType constants define the supported SAML identity provider types.\nconst (\n\tSAMLProviderTypeOkta    SAMLProviderType = \"okta\"\n\tSAMLProviderTypeEntra   SAMLProviderType = \"entra\"\n\tSAMLProviderTypeGeneric SAMLProviderType = \"saml\"\n\tSAMLProviderTypeUnknown SAMLProviderType = \"unknown\"\n)\n\n// AdminSAMLSetting represents the SAML settings in Terraform Enterprise.\ntype AdminSAMLSetting struct {\n\tID                        string           `jsonapi:\"primary,saml-settings\"`\n\tEnabled                   bool             `jsonapi:\"attr,enabled\"`\n\tDebug                     bool             `jsonapi:\"attr,debug\"`\n\tAuthnRequestsSigned       bool             `jsonapi:\"attr,authn-requests-signed\"`\n\tWantAssertionsSigned      bool             `jsonapi:\"attr,want-assertions-signed\"`\n\tTeamManagementEnabled     bool             `jsonapi:\"attr,team-management-enabled\"`\n\tOldIDPCert                string           `jsonapi:\"attr,old-idp-cert\"`\n\tIDPCert                   string           `jsonapi:\"attr,idp-cert\"`\n\tSLOEndpointURL            string           `jsonapi:\"attr,slo-endpoint-url\"`\n\tSSOEndpointURL            string           `jsonapi:\"attr,sso-endpoint-url\"`\n\tAttrUsername              string           `jsonapi:\"attr,attr-username\"`\n\tAttrGroups                string           `jsonapi:\"attr,attr-groups\"`\n\tAttrSiteAdmin             string           `jsonapi:\"attr,attr-site-admin\"`\n\tSiteAdminRole             string           `jsonapi:\"attr,site-admin-role\"`\n\tSSOAPITokenSessionTimeout int              `jsonapi:\"attr,sso-api-token-session-timeout\"`\n\tACSConsumerURL            string           `jsonapi:\"attr,acs-consumer-url\"`\n\tMetadataURL               string           `jsonapi:\"attr,metadata-url\"`\n\tCertificate               string           `jsonapi:\"attr,certificate\"`\n\tPrivateKey                string           `jsonapi:\"attr,private-key\"`\n\tSignatureSigningMethod    string           `jsonapi:\"attr,signature-signing-method\"`\n\tSignatureDigestMethod     string           `jsonapi:\"attr,signature-digest-method\"`\n\tProviderType              SAMLProviderType `jsonapi:\"attr,provider-type\"`\n}\n\n// Read returns the SAML settings.\nfunc (a *adminSAMLSettings) Read(ctx context.Context) (*AdminSAMLSetting, error) {\n\treq, err := a.client.NewRequest(\"GET\", \"admin/saml-settings\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsaml := &AdminSAMLSetting{}\n\terr = req.Do(ctx, saml)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn saml, nil\n}\n\n// AdminSAMLSettingsUpdateOptions represents the admin options for updating\n// SAML settings.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings#request-body-2\ntype AdminSAMLSettingsUpdateOptions struct {\n\tEnabled                   *bool             `jsonapi:\"attr,enabled,omitempty\"`\n\tDebug                     *bool             `jsonapi:\"attr,debug,omitempty\"`\n\tIDPCert                   *string           `jsonapi:\"attr,idp-cert,omitempty\"`\n\tCertificate               *string           `jsonapi:\"attr,certificate,omitempty\"`\n\tPrivateKey                *string           `jsonapi:\"attr,private-key,omitempty\"`\n\tSLOEndpointURL            *string           `jsonapi:\"attr,slo-endpoint-url,omitempty\"`\n\tSSOEndpointURL            *string           `jsonapi:\"attr,sso-endpoint-url,omitempty\"`\n\tAttrUsername              *string           `jsonapi:\"attr,attr-username,omitempty\"`\n\tAttrGroups                *string           `jsonapi:\"attr,attr-groups,omitempty\"`\n\tAttrSiteAdmin             *string           `jsonapi:\"attr,attr-site-admin,omitempty\"`\n\tSiteAdminRole             *string           `jsonapi:\"attr,site-admin-role,omitempty\"`\n\tSSOAPITokenSessionTimeout *int              `jsonapi:\"attr,sso-api-token-session-timeout,omitempty\"`\n\tTeamManagementEnabled     *bool             `jsonapi:\"attr,team-management-enabled,omitempty\"`\n\tAuthnRequestsSigned       *bool             `jsonapi:\"attr,authn-requests-signed,omitempty\"`\n\tWantAssertionsSigned      *bool             `jsonapi:\"attr,want-assertions-signed,omitempty\"`\n\tSignatureSigningMethod    *string           `jsonapi:\"attr,signature-signing-method,omitempty\"`\n\tSignatureDigestMethod     *string           `jsonapi:\"attr,signature-digest-method,omitempty\"`\n\tProviderType              *SAMLProviderType `jsonapi:\"attr,provider-type,omitempty\"`\n}\n\n// Update updates the SAML settings.\nfunc (a *adminSAMLSettings) Update(ctx context.Context, options AdminSAMLSettingsUpdateOptions) (*AdminSAMLSetting, error) {\n\tif options.ProviderType != nil {\n\t\tswitch *options.ProviderType {\n\t\tcase SAMLProviderTypeOkta, SAMLProviderTypeEntra, SAMLProviderTypeGeneric, SAMLProviderTypeUnknown:\n\t\tdefault:\n\t\t\treturn nil, ErrInvalidSAMLProviderType\n\t\t}\n\t}\n\n\treq, err := a.client.NewRequest(\"PATCH\", \"admin/saml-settings\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsaml := &AdminSAMLSetting{}\n\terr = req.Do(ctx, saml)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn saml, nil\n}\n\n// RevokeIdpCert revokes the older IdP certificate when the new IdP\n// certificate is known to be functioning correctly.\nfunc (a *adminSAMLSettings) RevokeIdpCert(ctx context.Context) (*AdminSAMLSetting, error) {\n\treq, err := a.client.NewRequest(\"POST\", \"admin/saml-settings/actions/revoke-old-certificate\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsaml := &AdminSAMLSetting{}\n\terr = req.Do(ctx, saml)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn saml, nil\n}\n"
  },
  {
    "path": "admin_setting_saml_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminSettings_SAML_Read(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tsamlSettings, err := client.Admin.Settings.SAML.Read(ctx)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"saml\", samlSettings.ID)\n\tassert.NotNil(t, samlSettings.Enabled)\n\tassert.NotNil(t, samlSettings.Debug)\n\tassert.NotNil(t, samlSettings.SLOEndpointURL)\n\tassert.NotNil(t, samlSettings.SSOEndpointURL)\n\tassert.NotNil(t, samlSettings.AttrUsername)\n\tassert.NotNil(t, samlSettings.AttrGroups)\n\tassert.NotNil(t, samlSettings.AttrSiteAdmin)\n\tassert.NotNil(t, samlSettings.SiteAdminRole)\n\tassert.NotNil(t, samlSettings.SSOAPITokenSessionTimeout)\n\tassert.NotNil(t, samlSettings.ACSConsumerURL)\n\tassert.NotNil(t, samlSettings.MetadataURL)\n\tassert.NotNil(t, samlSettings.TeamManagementEnabled)\n\tassert.NotNil(t, samlSettings.Certificate)\n\tassert.NotNil(t, samlSettings.AuthnRequestsSigned)\n\tassert.NotNil(t, samlSettings.WantAssertionsSigned)\n\tassert.NotNil(t, samlSettings.PrivateKey)\n\tassert.NotNil(t, samlSettings.SignatureSigningMethod)\n\tassert.NotNil(t, samlSettings.SignatureDigestMethod)\n\tassert.NotNil(t, samlSettings.ProviderType)\n}\n\nfunc TestAdminSettings_SAML_Update(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\t_, err := client.Admin.Settings.SAML.Read(ctx)\n\trequire.NoError(t, err)\n\n\tenabled := false\n\tdebug := false\n\n\tsamlSettings, err := client.Admin.Settings.SAML.Update(ctx, AdminSAMLSettingsUpdateOptions{\n\t\tEnabled: Bool(enabled),\n\t\tDebug:   Bool(debug),\n\t})\n\trequire.NoError(t, err)\n\tassert.Equal(t, enabled, samlSettings.Enabled)\n\tassert.Equal(t, debug, samlSettings.Debug)\n\tassert.Empty(t, samlSettings.PrivateKey)\n\n\tt.Run(\"with certificate defined\", func(t *testing.T) {\n\t\tcert := \"testCert\"\n\t\tpKey := \"testPrivateKey\"\n\t\tsignatureSigningMethod := \"SHA1\"\n\t\tsignatureDigestMethod := \"SHA1\"\n\t\tsamlSettingsUpd, err := client.Admin.Settings.SAML.Update(ctx, AdminSAMLSettingsUpdateOptions{\n\t\t\tCertificate:            String(cert),\n\t\t\tPrivateKey:             String(pKey),\n\t\t\tIDPCert:                String(cert),\n\t\t\tSLOEndpointURL:         String(\"https://example.com/slo\"),\n\t\t\tSSOEndpointURL:         String(\"https://example.com/sso\"),\n\t\t\tSignatureSigningMethod: String(signatureSigningMethod),\n\t\t\tSignatureDigestMethod:  String(signatureDigestMethod),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, cert, samlSettingsUpd.Certificate)\n\t\tassert.NotNil(t, samlSettingsUpd.PrivateKey)\n\t\tassert.Equal(t, signatureSigningMethod, samlSettingsUpd.SignatureSigningMethod)\n\t\tassert.Equal(t, signatureDigestMethod, samlSettingsUpd.SignatureDigestMethod)\n\t})\n\n\tt.Run(\"with team management enabled\", func(t *testing.T) {\n\t\tcert := \"testCert\"\n\t\tpKey := \"testPrivateKey\"\n\t\tsignatureSigningMethod := \"SHA1\"\n\t\tsignatureDigestMethod := \"SHA1\"\n\n\t\tsamlSettingsUpd, err := client.Admin.Settings.SAML.Update(ctx, AdminSAMLSettingsUpdateOptions{\n\t\t\tEnabled:                Bool(true),\n\t\t\tTeamManagementEnabled:  Bool(true),\n\t\t\tCertificate:            String(cert),\n\t\t\tPrivateKey:             String(pKey),\n\t\t\tSignatureSigningMethod: String(signatureSigningMethod),\n\t\t\tSignatureDigestMethod:  String(signatureDigestMethod),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, samlSettingsUpd.TeamManagementEnabled)\n\t})\n\n\tt.Run(\"with invalid signature digest method\", func(t *testing.T) {\n\t\t_, err := client.Admin.Settings.SAML.Update(ctx, AdminSAMLSettingsUpdateOptions{\n\t\t\tAuthnRequestsSigned:   Bool(true),\n\t\t\tSignatureDigestMethod: String(\"SHA1234\"),\n\t\t})\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"with invalid signature signing method\", func(t *testing.T) {\n\t\t_, err := client.Admin.Settings.SAML.Update(ctx, AdminSAMLSettingsUpdateOptions{\n\t\t\tAuthnRequestsSigned:    Bool(true),\n\t\t\tSignatureSigningMethod: String(\"SHA1234\"),\n\t\t})\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"revoke IDP cert\", func(t *testing.T) {\n\t\t_, err := client.Admin.Settings.SAML.Update(ctx, AdminSAMLSettingsUpdateOptions{\n\t\t\tEnabled: Bool(true),\n\t\t\tIDPCert: String(\"anotherTestCert\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tsamlSettings, err = client.Admin.Settings.SAML.RevokeIdpCert(ctx)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, samlSettings.IDPCert)\n\t})\n\n\tt.Run(\"with provider type defined\", func(t *testing.T) {\n\t\ttestCases := []struct {\n\t\t\tname         string\n\t\t\tproviderType SAMLProviderType\n\t\t\traiseError   bool\n\t\t}{\n\t\t\t{\"valid okta\", SAMLProviderTypeOkta, false},\n\t\t\t{\"valid entra\", SAMLProviderTypeEntra, false},\n\t\t\t{\"valid saml\", SAMLProviderTypeGeneric, false},\n\t\t\t{\"valid unknown - for backward compatibility\", SAMLProviderTypeUnknown, false},\n\t\t\t{\"invalid provider type\", \"error\", true},\n\t\t}\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\t_, err := client.Admin.Settings.SAML.Update(ctx, AdminSAMLSettingsUpdateOptions{\n\t\t\t\t\tEnabled:      Bool(true),\n\t\t\t\t\tProviderType: SAMLProvider(tc.providerType),\n\t\t\t\t})\n\n\t\t\t\tif tc.raiseError {\n\t\t\t\t\trequire.Error(t, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tsamlSettings, err = client.Admin.Settings.SAML.Read(ctx)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, tc.providerType, samlSettings.ProviderType)\n\t\t\t})\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "admin_setting_scim.go",
    "content": "// Copyright IBM Corp. 2018, 2026\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ SCIMSettings = (*adminSCIMSettings)(nil)\n\n// SCIMSettings describes all the scim settings related methods that the Terraform\n// Enterprise API supports\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings\ntype SCIMSettings interface {\n\t// Read scim settings\n\tRead(ctx context.Context) (*AdminSCIMSetting, error)\n\n\t// Update scim settings\n\tUpdate(ctx context.Context, options AdminSCIMSettingUpdateOptions) (*AdminSCIMSetting, error)\n\n\t// Delete scim settings\n\tDelete(ctx context.Context) error\n}\n\n// adminSCIMSettings implements SCIMSettings.\ntype adminSCIMSettings struct {\n\tclient *Client\n}\n\n// AdminSCIMSetting represents the SCIM setting in Terraform Enterprise\ntype AdminSCIMSetting struct {\n\tID                        string `jsonapi:\"primary,scim-settings\"`\n\tEnabled                   bool   `jsonapi:\"attr,enabled\"`\n\tPaused                    bool   `jsonapi:\"attr,paused\"`\n\tSiteAdminGroupSCIMID      string `jsonapi:\"attr,site-admin-group-scim-id\"`\n\tSiteAdminGroupDisplayName string `jsonapi:\"attr,site-admin-group-display-name\"`\n}\n\n// AdminSCIMSettingUpdateOptions represents the options for updating an admin SCIM setting.\ntype AdminSCIMSettingUpdateOptions struct {\n\tEnabled              *bool   `jsonapi:\"attr,enabled,omitempty\"`\n\tPaused               *bool   `jsonapi:\"attr,paused,omitempty\"`\n\tSiteAdminGroupSCIMID *string `jsonapi:\"attr,site-admin-group-scim-id,omitempty\"`\n}\n\n// Read scim setting.\nfunc (a *adminSCIMSettings) Read(ctx context.Context) (*AdminSCIMSetting, error) {\n\treq, err := a.client.NewRequest(\"GET\", \"admin/scim-settings\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tscim := &AdminSCIMSetting{}\n\terr = req.Do(ctx, scim)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn scim, nil\n}\n\n// Update scim setting.\nfunc (a *adminSCIMSettings) Update(ctx context.Context, options AdminSCIMSettingUpdateOptions) (*AdminSCIMSetting, error) {\n\treq, err := a.client.NewRequest(\"PATCH\", \"admin/scim-settings\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tscim := &AdminSCIMSetting{}\n\terr = req.Do(ctx, scim)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn scim, nil\n}\n\n// Delete scim setting.\nfunc (a *adminSCIMSettings) Delete(ctx context.Context) error {\n\treq, err := a.client.NewRequest(\"DELETE\", \"admin/scim-settings\", nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "admin_setting_scim_groups.go",
    "content": "// Copyright IBM Corp. 2018, 2026\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\nvar _ AdminSCIMGroups = (*adminSCIMGroups)(nil)\n\n// AdminSCIMGroups describes all the SCIM group related methods that the Terraform\n// Enterprise API supports\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/scim-groups\ntype AdminSCIMGroups interface {\n\t// List all SCIM groups.\n\tList(ctx context.Context, options *AdminSCIMGroupListOptions) (*AdminSCIMGroupList, error)\n}\n\n// adminSCIMGroups implements AdminSCIMGroups\ntype adminSCIMGroups struct {\n\tclient *Client\n}\n\n// AdminSCIMGroupList represents a list of SCIM groups\ntype AdminSCIMGroupList struct {\n\t*Pagination\n\tItems []*AdminSCIMGroup\n}\n\n// AdminSCIMGroup represents a Terraform Enterprise SCIM group\ntype AdminSCIMGroup struct {\n\tID   string `jsonapi:\"primary,scim-groups\"`\n\tName string `jsonapi:\"attr,name\"`\n}\n\n// AdminSCIMGroupListOptions represents the options for listing SCIM groups\ntype AdminSCIMGroupListOptions struct {\n\tListOptions\n\tQuery string `url:\"q,omitempty\"`\n}\n\nfunc (o *AdminSCIMGroupListOptions) valid() error {\n\tif o == nil {\n\t\treturn nil\n\t}\n\tif o.PageNumber < 0 || o.PageSize < 0 {\n\t\treturn ErrInvalidPagination\n\t}\n\n\treturn nil\n}\n\n// List all SCIM groups.\nfunc (a *adminSCIMGroups) List(ctx context.Context, options *AdminSCIMGroupListOptions) (*AdminSCIMGroupList, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\treq, err := a.client.NewRequest(\"GET\", AdminSCIMGroupsPath, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tscimGroups := &AdminSCIMGroupList{}\n\terr = req.Do(ctx, scimGroups)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn scimGroups, nil\n}\n"
  },
  {
    "path": "admin_setting_scim_groups_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2026\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminSCIMGroups_List(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tscimClient := client.Admin.Settings.SCIM\n\n\tenableSCIM(ctx, t, client, true)\n\tdefer enableSCIM(ctx, t, client, false)\n\n\tscimToken, err := scimClient.Tokens.Create(ctx, \"integration-test-token\")\n\trequire.NoError(t, err)\n\n\tconst (\n\t\tdefaultPageSize   = 20\n\t\tmaxGroupsToCreate = defaultPageSize + defaultPageSize/2\n\t)\n\n\tt.Run(\"basic list operations\", func(t *testing.T) {\n\t\tt.Run(\"empty list immediately after enabling SCIM\", func(t *testing.T) {\n\t\t\tscimGroups, err := scimClient.Groups.List(ctx, nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Len(t, scimGroups.Items, 0)\n\t\t\tassert.Equal(t, 0, scimGroups.TotalCount)\n\t\t})\n\n\t\tt.Run(\"list all created groups\", func(t *testing.T) {\n\t\t\tvar groupIDs []string\n\t\t\tvar expectedGroups []AdminSCIMGroup\n\t\t\tt.Cleanup(func() {\n\t\t\t\tfor _, id := range groupIDs {\n\t\t\t\t\tdeleteSCIMGroup(ctx, t, client, id, scimToken.Token)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tfor range 2 {\n\t\t\t\tgroupName := randomStringWithoutSpecialChar(t)\n\t\t\t\tid := createSCIMGroup(ctx, t, client, groupName, scimToken.Token)\n\t\t\t\tgroupIDs = append(groupIDs, id)\n\t\t\t\texpectedGroups = append(expectedGroups, AdminSCIMGroup{ID: id, Name: groupName})\n\t\t\t}\n\n\t\t\tscimGroups, err := scimClient.Groups.List(ctx, nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Len(t, scimGroups.Items, 2)\n\t\t\tassert.Equal(t, 2, scimGroups.TotalCount)\n\n\t\t\tvar found int\n\t\t\tfor _, eg := range expectedGroups {\n\t\t\t\tfor _, g := range scimGroups.Items {\n\t\t\t\t\tif g.ID == eg.ID {\n\t\t\t\t\t\tassert.Equal(t, eg.Name, g.Name)\n\t\t\t\t\t\tfound++\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\tassert.Equal(t, 2, found, \"all created groups should have matched ID and Name\")\n\t\t})\n\t})\n\n\tt.Run(\"filter groups using search query\", func(t *testing.T) {\n\t\tvar groupIDs []string\n\t\tt.Cleanup(func() {\n\t\t\tfor _, id := range groupIDs {\n\t\t\t\tdeleteSCIMGroup(ctx, t, client, id, scimToken.Token)\n\t\t\t}\n\t\t})\n\n\t\tprefix := randomStringWithoutSpecialChar(t) + \"-\"\n\t\t// Create a cohesive set of 10 groups that satisfy all query scenarios\n\t\tgroupIDs = append(groupIDs,\n\t\t\tcreateSCIMGroup(ctx, t, client, prefix+\"this-group-exists\", scimToken.Token),\n\t\t\tcreateSCIMGroup(ctx, t, client, prefix+\"matching-group-1\", scimToken.Token),\n\t\t\tcreateSCIMGroup(ctx, t, client, prefix+\"matching-group-2\", scimToken.Token),\n\t\t\tcreateSCIMGroup(ctx, t, client, prefix+\"matching-group-3\", scimToken.Token),\n\t\t\tcreateSCIMGroup(ctx, t, client, prefix+\"CaSe-InSeNsItIvE-gRoUp\", scimToken.Token),\n\t\t)\n\t\tfor range 5 {\n\t\t\tid := createSCIMGroup(ctx, t, client, prefix+\"random-\"+randomStringWithoutSpecialChar(t), scimToken.Token)\n\t\t\tgroupIDs = append(groupIDs, id)\n\t\t}\n\n\t\ttestCases := []struct {\n\t\t\tname               string\n\t\t\toptions            AdminSCIMGroupListOptions\n\t\t\texpectedGroupCount int\n\t\t}{\n\t\t\t{\n\t\t\t\tname:               \"query returns no results for non-existent prefix\",\n\t\t\t\toptions:            AdminSCIMGroupListOptions{Query: prefix + \"this-group-doesnot-exist\"},\n\t\t\t\texpectedGroupCount: 0,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:               \"query returns exact match for specific group\",\n\t\t\t\toptions:            AdminSCIMGroupListOptions{Query: prefix + \"this-group-exists\"},\n\t\t\t\texpectedGroupCount: 1,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:               \"query returns multiple groups matching prefix\",\n\t\t\t\toptions:            AdminSCIMGroupListOptions{Query: prefix + \"matching-group-\"},\n\t\t\t\texpectedGroupCount: 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:               \"query performs case-insensitive match\",\n\t\t\t\toptions:            AdminSCIMGroupListOptions{Query: prefix + \"case-insensitive-group\"},\n\t\t\t\texpectedGroupCount: 1,\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tscimGroups, err := scimClient.Groups.List(ctx, &tc.options)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Len(t, scimGroups.Items, tc.expectedGroupCount)\n\t\t\t\tassert.Equal(t, tc.expectedGroupCount, scimGroups.TotalCount)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"paginate through groups\", func(t *testing.T) {\n\t\tvar groupIDs []string\n\t\tt.Cleanup(func() {\n\t\t\tfor _, id := range groupIDs {\n\t\t\t\tdeleteSCIMGroup(ctx, t, client, id, scimToken.Token)\n\t\t\t}\n\t\t})\n\n\t\tprefix := randomStringWithoutSpecialChar(t) + \"-\"\n\n\t\t// Create groups to test default page size\n\t\tfor range maxGroupsToCreate {\n\t\t\tgroupName := prefix + randomStringWithoutSpecialChar(t)\n\t\t\tid := createSCIMGroup(ctx, t, client, groupName, scimToken.Token)\n\t\t\tgroupIDs = append(groupIDs, id)\n\t\t}\n\n\t\ttestCases := []struct {\n\t\t\tname               string\n\t\t\toptions            AdminSCIMGroupListOptions\n\t\t\texcludeOptions     *AdminSCIMGroupListOptions\n\t\t\texpectedGroupCount int\n\t\t\texpectedTotalCount int\n\t\t\texpectedTotalPages int\n\t\t\texpectedPage       int\n\t\t\texpectedNextPage   int\n\t\t\texpectedPrevPage   int\n\t\t}{\n\t\t\t{\n\t\t\t\tname:               fmt.Sprintf(\"default page size (%d) returns first page\", defaultPageSize),\n\t\t\t\toptions:            AdminSCIMGroupListOptions{Query: prefix, ListOptions: ListOptions{PageNumber: 1}},\n\t\t\t\texpectedGroupCount: defaultPageSize,\n\t\t\t\texpectedTotalCount: maxGroupsToCreate,\n\t\t\t\texpectedTotalPages: 2,\n\t\t\t\texpectedPage:       1,\n\t\t\t\texpectedNextPage:   2,\n\t\t\t\texpectedPrevPage:   0,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:               fmt.Sprintf(\"default page size (%d) returns second page\", defaultPageSize),\n\t\t\t\toptions:            AdminSCIMGroupListOptions{Query: prefix, ListOptions: ListOptions{PageNumber: 2}},\n\t\t\t\texcludeOptions:     &AdminSCIMGroupListOptions{Query: prefix, ListOptions: ListOptions{PageNumber: 1}},\n\t\t\t\texpectedGroupCount: maxGroupsToCreate - defaultPageSize,\n\t\t\t\texpectedTotalCount: maxGroupsToCreate,\n\t\t\t\texpectedTotalPages: 2,\n\t\t\t\texpectedPage:       2,\n\t\t\t\texpectedNextPage:   0,\n\t\t\t\texpectedPrevPage:   1,\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tscimGroups, err := scimClient.Groups.List(ctx, &tc.options)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Len(\n\t\t\t\t\tt, scimGroups.Items, tc.expectedGroupCount,\n\t\t\t\t\t\"expected default page size to be 20; if this fails, the API default page size may have changed\",\n\t\t\t\t)\n\t\t\t\tassert.Equal(t, tc.expectedTotalCount, scimGroups.TotalCount)\n\t\t\t\tassert.Equal(t, tc.expectedTotalPages, scimGroups.TotalPages)\n\t\t\t\tassert.Equal(t, tc.expectedPage, scimGroups.CurrentPage)\n\t\t\t\tassert.Equal(t, tc.expectedNextPage, scimGroups.NextPage)\n\t\t\t\tassert.Equal(t, tc.expectedPrevPage, scimGroups.PreviousPage)\n\n\t\t\t\t// Verify mutually exclusive items\n\t\t\t\tif tc.excludeOptions != nil {\n\t\t\t\t\texcludedGroups, err := scimClient.Groups.List(ctx, tc.excludeOptions)\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\tfor _, g := range scimGroups.Items {\n\t\t\t\t\t\tfor _, exGroup := range excludedGroups.Items {\n\t\t\t\t\t\t\tassert.NotEqual(t, g.ID, exGroup.ID)\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\tt.Run(\"combine query filtering and pagination\", func(t *testing.T) {\n\t\tvar groupIDs []string\n\t\tt.Cleanup(func() {\n\t\t\tfor _, id := range groupIDs {\n\t\t\t\tdeleteSCIMGroup(ctx, t, client, id, scimToken.Token)\n\t\t\t}\n\t\t})\n\n\t\tprefix := randomStringWithoutSpecialChar(t)\n\n\t\t// Create 4 random groups\n\t\tfor range 4 {\n\t\t\tgroupName := prefix + \"-\" + randomStringWithoutSpecialChar(t)\n\t\t\tid := createSCIMGroup(ctx, t, client, groupName, scimToken.Token)\n\t\t\tgroupIDs = append(groupIDs, id)\n\t\t}\n\n\t\t// Create 6 matching groups with same suffix \"-idp-group\"\n\t\tfor range 6 {\n\t\t\tgroupName := fmt.Sprintf(\"%s-idp-group-%s\", prefix, randomStringWithoutSpecialChar(t))\n\t\t\tid := createSCIMGroup(ctx, t, client, groupName, scimToken.Token)\n\t\t\tgroupIDs = append(groupIDs, id)\n\t\t}\n\n\t\ttestCases := []struct {\n\t\t\tname               string\n\t\t\toptions            AdminSCIMGroupListOptions\n\t\t\texcludeOptions     *AdminSCIMGroupListOptions\n\t\t\texpectedGroupCount int\n\t\t\texpectedTotalCount int\n\t\t\texpectedTotalPages int\n\t\t\texpectedPage       int\n\t\t}{\n\t\t\t{\n\t\t\t\tname: \"first page of filtered results\",\n\t\t\t\toptions: AdminSCIMGroupListOptions{\n\t\t\t\t\tQuery:       prefix + \"-idp-group\",\n\t\t\t\t\tListOptions: ListOptions{PageSize: 3, PageNumber: 1},\n\t\t\t\t},\n\t\t\t\texpectedGroupCount: 3,\n\t\t\t\texpectedTotalCount: 6,\n\t\t\t\texpectedTotalPages: 2,\n\t\t\t\texpectedPage:       1,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"second page of filtered results\",\n\t\t\t\toptions: AdminSCIMGroupListOptions{\n\t\t\t\t\tQuery:       prefix + \"-idp-group\",\n\t\t\t\t\tListOptions: ListOptions{PageSize: 3, PageNumber: 2},\n\t\t\t\t},\n\t\t\t\texcludeOptions: &AdminSCIMGroupListOptions{\n\t\t\t\t\tQuery:       prefix + \"-idp-group\",\n\t\t\t\t\tListOptions: ListOptions{PageSize: 3, PageNumber: 1},\n\t\t\t\t},\n\t\t\t\texpectedGroupCount: 3,\n\t\t\t\texpectedTotalCount: 6,\n\t\t\t\texpectedTotalPages: 2,\n\t\t\t\texpectedPage:       2,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"out of bounds page of filtered results returns empty list\",\n\t\t\t\toptions: AdminSCIMGroupListOptions{\n\t\t\t\t\tQuery:       prefix + \"-idp-group\",\n\t\t\t\t\tListOptions: ListOptions{PageSize: 3, PageNumber: 3},\n\t\t\t\t},\n\t\t\t\texpectedGroupCount: 0,\n\t\t\t\texpectedTotalCount: 6,\n\t\t\t\texpectedTotalPages: 2,\n\t\t\t\texpectedPage:       3,\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tscimGroups, err := scimClient.Groups.List(ctx, &tc.options)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Len(t, scimGroups.Items, tc.expectedGroupCount)\n\t\t\t\tassert.Equal(t, tc.expectedTotalCount, scimGroups.TotalCount)\n\t\t\t\tassert.Equal(t, tc.expectedTotalPages, scimGroups.TotalPages)\n\t\t\t\tassert.Equal(t, tc.expectedPage, scimGroups.CurrentPage)\n\n\t\t\t\t// Verify mutually exclusive items\n\t\t\t\tif tc.excludeOptions != nil {\n\t\t\t\t\texcludedGroups, err := scimClient.Groups.List(ctx, tc.excludeOptions)\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\tfor _, g := range scimGroups.Items {\n\t\t\t\t\t\tfor _, exGroup := range excludedGroups.Items {\n\t\t\t\t\t\t\tassert.NotEqual(t, g.ID, exGroup.ID)\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"
  },
  {
    "path": "admin_setting_scim_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2026\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminSettings_SCIM_Read(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"read scim settings with default values\", func(t *testing.T) {\n\t\tscimSettings, err := client.Admin.Settings.SCIM.Read(ctx)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, \"scim\", scimSettings.ID)\n\t\tassert.False(t, scimSettings.Enabled)\n\t\tassert.False(t, scimSettings.Paused)\n\t\tassert.Empty(t, scimSettings.SiteAdminGroupSCIMID)\n\t\tassert.Empty(t, scimSettings.SiteAdminGroupDisplayName)\n\t})\n}\n\nfunc TestAdminSettings_SCIM_Update(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tenableSAML(ctx, t, client, true)\n\tdefer enableSAML(ctx, t, client, false)\n\n\tscimClient := client.Admin.Settings.SCIM\n\n\tt.Run(\"enable scim settings\", func(t *testing.T) {\n\t\terr := setSAMLProviderType(ctx, t, client, true)\n\t\trequire.NoErrorf(t, err, \"failed to set SAML provider type\")\n\t\tdefer cleanupSCIMSettings(ctx, t, client)\n\n\t\tscimSettings, err := scimClient.Update(ctx, AdminSCIMSettingUpdateOptions{Enabled: Bool(true)})\n\t\trequire.NoError(t, err)\n\n\t\tassert.True(t, scimSettings.Enabled)\n\t})\n\n\tt.Run(\"pause scim settings\", func(t *testing.T) {\n\t\terr := setSAMLProviderType(ctx, t, client, true)\n\t\trequire.NoErrorf(t, err, \"failed to set SAML provider type\")\n\t\tdefer cleanupSCIMSettings(ctx, t, client)\n\n\t\t_, err = scimClient.Update(ctx, AdminSCIMSettingUpdateOptions{\n\t\t\tEnabled: Bool(true),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\ttestCases := []struct {\n\t\t\tname   string\n\t\t\tpaused bool\n\t\t}{\n\t\t\t{\"pause scim provisioning\", true},\n\t\t\t{\"unpause scim provisioning\", false},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\t_, err := scimClient.Update(ctx, AdminSCIMSettingUpdateOptions{Paused: &tc.paused})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tscimSettings, err := scimClient.Read(ctx)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, tc.paused, scimSettings.Paused)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"update site admin group scim id\", func(t *testing.T) {\n\t\terr := setSAMLProviderType(ctx, t, client, true)\n\t\trequire.NoErrorf(t, err, \"failed to set SAML provider type\")\n\t\tdefer cleanupSCIMSettings(ctx, t, client)\n\n\t\t_, err = scimClient.Update(ctx, AdminSCIMSettingUpdateOptions{Enabled: Bool(true)})\n\t\trequire.NoError(t, err)\n\n\t\tscimToken, err := scimClient.Tokens.Create(ctx, \"scim integration test token\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, scimToken.Token)\n\t\tscimGroupID := createSCIMGroup(ctx, t, client, \"foo\", scimToken.Token)\n\n\t\ttestCases := []struct {\n\t\t\tname        string\n\t\t\tscimGroupID string\n\t\t\traiseError  bool\n\t\t}{\n\t\t\t{\"link scim group to site admin role\", scimGroupID, false},\n\t\t\t{\"trying to link non-existent group - should raise error\", \"this-group-doesn't-exist\", true},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\t_, err := scimClient.Update(ctx, AdminSCIMSettingUpdateOptions{SiteAdminGroupSCIMID: &tc.scimGroupID})\n\t\t\t\tif tc.raiseError {\n\t\t\t\t\trequire.Error(t, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tscimSettings, err := scimClient.Read(ctx)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, tc.scimGroupID, scimSettings.SiteAdminGroupSCIMID)\n\t\t\t\tassert.Equal(t, \"foo\", scimSettings.SiteAdminGroupDisplayName)\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestAdminSettings_SCIM_Delete(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tenableSAML(ctx, t, client, true)\n\tdefer enableSAML(ctx, t, client, false)\n\n\tscimClient := client.Admin.Settings.SCIM\n\n\tt.Run(\"disable scim settings\", func(t *testing.T) {\n\t\terr := setSAMLProviderType(ctx, t, client, true)\n\t\trequire.NoErrorf(t, err, \"failed to set SAML provider type\")\n\t\tdefer cleanupSCIMSettings(ctx, t, client)\n\n\t\ttestCases := []struct {\n\t\t\tname          string\n\t\t\tisScimEnabled bool\n\t\t}{\n\t\t\t{\"disable scim provisioning when it's already enabled\", true},\n\t\t\t{\"disable scim provisioning when it's already disabled - should not raise error\", false},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tif tc.isScimEnabled {\n\t\t\t\t\t_, err := scimClient.Update(ctx, AdminSCIMSettingUpdateOptions{Enabled: Bool(true)})\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t}\n\n\t\t\t\terr := scimClient.Delete(ctx)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tscimSettings, err := scimClient.Read(ctx)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.False(t, scimSettings.Enabled)\n\t\t\t})\n\t\t}\n\t})\n}\n\n// cleanup scim settings by disabling scim provisioning and setting saml provider type to unknown.\nfunc cleanupSCIMSettings(ctx context.Context, t *testing.T, client *Client) {\n\tt.Helper()\n\tscimSettings, err := client.Admin.Settings.SCIM.Read(ctx)\n\tif err == nil && scimSettings.Enabled {\n\t\terr = client.Admin.Settings.SCIM.Delete(ctx)\n\t\trequire.NoErrorf(t, err, \"failed to disable SCIM provisioning\")\n\t}\n\n\terr = setSAMLProviderType(ctx, t, client, false)\n\trequire.NoErrorf(t, err, \"failed to set SAML provider type\")\n}\n"
  },
  {
    "path": "admin_setting_scim_token.go",
    "content": "// Copyright IBM Corp. 2018, 2026\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\nvar _ AdminSCIMTokens = (*adminSCIMTokens)(nil)\n\n// AdminSCIMTokens describes all the Admin SCIM token related methods that the Terraform\n// Enterprise API supports\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/scim-tokens\ntype AdminSCIMTokens interface {\n\t// List all Admin SCIM tokens.\n\tList(ctx context.Context) (*AdminSCIMTokenList, error)\n\n\t// Create an Admin SCIM token.\n\tCreate(ctx context.Context, description string) (*AdminSCIMToken, error)\n\n\t// Create an Admin SCIM token with options.\n\tCreateWithOptions(ctx context.Context, options AdminSCIMTokenCreateOptions) (*AdminSCIMToken, error)\n\n\t// Read an Admin SCIM token by its ID.\n\tRead(ctx context.Context, scimTokenID string) (*AdminSCIMToken, error)\n\n\t// Delete an Admin SCIM token.\n\tDelete(ctx context.Context, scimTokenID string) error\n}\n\n// adminSCIMTokens implements AdminSCIMTokens\ntype adminSCIMTokens struct {\n\tclient *Client\n}\n\n// AdminSCIMTokenList represents a list of Admin SCIM tokens\ntype AdminSCIMTokenList struct {\n\tItems []*AdminSCIMToken\n}\n\n// AdminSCIMToken represents a Terraform Enterprise Admin SCIM token.\ntype AdminSCIMToken struct {\n\tID          string    `jsonapi:\"primary,authentication-tokens\"`\n\tCreatedAt   time.Time `jsonapi:\"attr,created-at,iso8601\"`\n\tLastUsedAt  time.Time `jsonapi:\"attr,last-used-at,iso8601\"`\n\tExpiredAt   time.Time `jsonapi:\"attr,expired-at,iso8601\"`\n\tDescription string    `jsonapi:\"attr,description\"`\n\tToken       string    `jsonapi:\"attr,token,omitempty\"`\n}\n\n// AdminSCIMTokenCreateOptions represents the options for creating an Admin SCIM token\ntype AdminSCIMTokenCreateOptions struct {\n\t// Required: A human-readable description of the token's purpose\n\t// (for example, Okta SCIM Integration).\n\tDescription *string `jsonapi:\"attr,description\"`\n\n\t// Optional: Optional ISO-8601 timestamp for token expiration.\n\t// Defaults to 365 days in the future. Must be between 29 and 365 days in the future.\n\tExpiredAt *time.Time `jsonapi:\"attr,expired-at,iso8601,omitempty\"`\n}\n\n// List all Admin SCIM tokens.\nfunc (a *adminSCIMTokens) List(ctx context.Context) (*AdminSCIMTokenList, error) {\n\treq, err := a.client.NewRequest(\"GET\", AdminSCIMTokensPath, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tscimTokens := &AdminSCIMTokenList{}\n\terr = req.Do(ctx, scimTokens)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn scimTokens, nil\n}\n\n// Create an Admin SCIM token.\nfunc (a *adminSCIMTokens) Create(ctx context.Context, description string) (*AdminSCIMToken, error) {\n\treturn a.CreateWithOptions(ctx, AdminSCIMTokenCreateOptions{\n\t\tDescription: &description,\n\t})\n}\n\n// Create an Admin SCIM token with options.\nfunc (a *adminSCIMTokens) CreateWithOptions(ctx context.Context, options AdminSCIMTokenCreateOptions) (*AdminSCIMToken, error) {\n\tif !validString(options.Description) {\n\t\treturn nil, ErrSCIMTokenDescription\n\t}\n\treq, err := a.client.NewRequest(\"POST\", AdminSCIMTokensPath, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tscimToken := &AdminSCIMToken{}\n\terr = req.Do(ctx, scimToken)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn scimToken, nil\n}\n\n// Read an Admin SCIM token by its ID.\nfunc (a *adminSCIMTokens) Read(ctx context.Context, scimTokenID string) (*AdminSCIMToken, error) {\n\tif !validStringID(&scimTokenID) {\n\t\treturn nil, ErrInvalidTokenID\n\t}\n\tu := fmt.Sprintf(\"%s/%s\", AdminSCIMTokensPath, url.PathEscape(scimTokenID))\n\treq, err := a.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tscimToken := &AdminSCIMToken{}\n\terr = req.Do(ctx, scimToken)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn scimToken, nil\n}\n\n// Delete an Admin SCIM token.\nfunc (a *adminSCIMTokens) Delete(ctx context.Context, scimTokenID string) error {\n\tif !validStringID(&scimTokenID) {\n\t\treturn ErrInvalidTokenID\n\t}\n\tu := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(scimTokenID))\n\treq, err := a.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "admin_setting_scim_token_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2026\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminSCIMTokens_Create(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tenableSCIM(ctx, t, client, true)\n\tdefer enableSCIM(ctx, t, client, false)\n\n\tscimTokenClient := client.Admin.Settings.SCIM.Tokens\n\n\tt.Run(\"create token\", func(t *testing.T) {\n\t\ttestCases := []struct {\n\t\t\tname        string\n\t\t\tdescription string\n\t\t\traiseError  bool\n\t\t}{\n\t\t\t{\"with valid description\", \"Test Description\", false},\n\t\t\t{\"with empty description\", \"\", true},\n\t\t}\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tscimToken, err := scimTokenClient.Create(ctx, tc.description)\n\n\t\t\t\tif tc.raiseError {\n\t\t\t\t\trequire.Error(t, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tt.Cleanup(func() {\n\t\t\t\t\terr := scimTokenClient.Delete(ctx, scimToken.ID)\n\t\t\t\t\tif err != nil && err != ErrResourceNotFound {\n\t\t\t\t\t\tt.Logf(\"failed to cleanup SCIM token %q: %v\", scimToken.ID, err)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, scimToken)\n\t\t\t\tassert.NotEmpty(t, scimToken)\n\t\t\t\tassert.NotEmpty(t, scimToken.ID)\n\t\t\t\tassert.NotEmpty(t, scimToken.Token)\n\t\t\t\tassert.NotEmpty(t, scimToken.Description)\n\n\t\t\t\tassert.Equal(t, tc.description, scimToken.Description)\n\t\t\t\tassert.WithinDuration(t, time.Now(), scimToken.CreatedAt, 10*time.Second)\n\t\t\t\tassert.WithinDuration(t, time.Now().Add(365*24*time.Hour), scimToken.ExpiredAt, 10*time.Second)\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestAdminSCIMTokens_CreateWithOptions(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tenableSCIM(ctx, t, client, true)\n\tdefer enableSCIM(ctx, t, client, false)\n\n\tscimTokenClient := client.Admin.Settings.SCIM.Tokens\n\n\tt.Run(\"create token\", func(t *testing.T) {\n\t\ttestCases := []struct {\n\t\t\tname       string\n\t\t\toptions    AdminSCIMTokenCreateOptions\n\t\t\traiseError bool\n\t\t}{\n\t\t\t{\"with no options - should fail\", AdminSCIMTokenCreateOptions{}, true},\n\t\t\t{\n\t\t\t\t\"with nil description - should fail`\",\n\t\t\t\tAdminSCIMTokenCreateOptions{\n\t\t\t\t\tDescription: nil,\n\t\t\t\t},\n\t\t\t\ttrue,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"with empty description - should fail`\",\n\t\t\t\tAdminSCIMTokenCreateOptions{\n\t\t\t\t\tDescription: String(\"\"),\n\t\t\t\t},\n\t\t\t\ttrue,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"with description\",\n\t\t\t\tAdminSCIMTokenCreateOptions{\n\t\t\t\t\tDescription: String(\"Test Description\"),\n\t\t\t\t},\n\t\t\t\tfalse,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"with only expiration - should fail\",\n\t\t\t\tAdminSCIMTokenCreateOptions{\n\t\t\t\t\tExpiredAt: Ptr(time.Now().Add(30 * 24 * time.Hour)),\n\t\t\t\t},\n\t\t\t\ttrue,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"with description and expiration\",\n\t\t\t\tAdminSCIMTokenCreateOptions{\n\t\t\t\t\tDescription: String(\"Test Description\"),\n\t\t\t\t\tExpiredAt:   Ptr(time.Now().Add(60 * 24 * time.Hour)),\n\t\t\t\t},\n\t\t\t\tfalse,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"with expiration in 20 days - should fail\",\n\t\t\t\tAdminSCIMTokenCreateOptions{\n\t\t\t\t\tExpiredAt: Ptr(time.Now().Add(20 * 24 * time.Hour)),\n\t\t\t\t},\n\t\t\t\ttrue,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"with expiration in 400 days - should fail\",\n\t\t\t\tAdminSCIMTokenCreateOptions{\n\t\t\t\t\tExpiredAt: Ptr(time.Now().Add(400 * 24 * time.Hour)),\n\t\t\t\t},\n\t\t\t\ttrue,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"with expiration in 29 days\",\n\t\t\t\tAdminSCIMTokenCreateOptions{\n\t\t\t\t\tDescription: String(\"Test Description\"),\n\t\t\t\t\tExpiredAt:   Ptr(time.Now().Add(29*24*time.Hour + 10*time.Second)), // adding 10 sec to account for any delays in test execution\n\t\t\t\t},\n\t\t\t\tfalse,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"with expiration in 365 days\",\n\t\t\t\tAdminSCIMTokenCreateOptions{\n\t\t\t\t\tDescription: String(\"Test Description\"),\n\t\t\t\t\tExpiredAt:   Ptr(time.Now().Add(365 * 24 * time.Hour)),\n\t\t\t\t},\n\t\t\t\tfalse,\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tvar scimToken *AdminSCIMToken\n\t\t\t\tvar err error\n\n\t\t\t\tscimToken, err = scimTokenClient.CreateWithOptions(ctx, tc.options)\n\n\t\t\t\tif tc.raiseError {\n\t\t\t\t\trequire.Error(t, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, scimToken)\n\t\t\t\tassert.NotEmpty(t, scimToken)\n\t\t\t\tassert.NotEmpty(t, scimToken.ID)\n\n\t\t\t\tt.Cleanup(func() {\n\t\t\t\t\terr := scimTokenClient.Delete(ctx, scimToken.ID)\n\t\t\t\t\tif err != nil && err != ErrResourceNotFound {\n\t\t\t\t\t\tt.Logf(\"failed to cleanup SCIM token %q: %v\", scimToken.ID, err)\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tif tc.options.ExpiredAt != nil {\n\t\t\t\t\tassert.WithinDuration(t, *tc.options.ExpiredAt, scimToken.ExpiredAt, 10*time.Second)\n\t\t\t\t} else {\n\t\t\t\t\texpectedExpiredAt := scimToken.CreatedAt.Add(365 * 24 * time.Hour)\n\t\t\t\t\tassert.WithinDuration(t, expectedExpiredAt, scimToken.ExpiredAt, 10*time.Second)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestAdminSCIMTokens_List(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tenableSCIM(ctx, t, client, true)\n\tdefer enableSCIM(ctx, t, client, false)\n\n\tscimTokenClient := client.Admin.Settings.SCIM.Tokens\n\n\tt.Run(\"list tokens\", func(t *testing.T) {\n\t\t// create tokens to ensure there is data to list\n\t\tvar scimTokens []*AdminSCIMToken\n\t\tfor i := 0; i < 3; i++ {\n\t\t\tscimToken, err := scimTokenClient.Create(ctx, fmt.Sprintf(\"foo token %d\", i))\n\t\t\trequire.NoError(t, err)\n\t\t\ttokenID := scimToken.ID\n\t\t\tt.Cleanup(func() {\n\t\t\t\terr := scimTokenClient.Delete(ctx, tokenID)\n\t\t\t\tif err != nil && err != ErrResourceNotFound {\n\t\t\t\t\tt.Logf(\"failed to cleanup SCIM token %q: %v\", tokenID, err)\n\t\t\t\t}\n\t\t\t})\n\t\t\tscimTokens = append(scimTokens, scimToken)\n\t\t}\n\n\t\ttokenList, err := scimTokenClient.List(ctx)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tokenList)\n\t\tassert.NotEmpty(t, tokenList.Items)\n\n\t\tvar expectedIDs []string\n\t\tvar actualIDs []string\n\t\tfor _, listedToken := range tokenList.Items {\n\t\t\tactualIDs = append(actualIDs, listedToken.ID)\n\t\t}\n\n\t\tfor _, token := range scimTokens {\n\t\t\texpectedIDs = append(expectedIDs, token.ID)\n\t\t\tassert.Contains(t, actualIDs, token.ID)\n\t\t}\n\n\t\tassert.Subset(t, actualIDs, expectedIDs)\n\t})\n}\n\nfunc TestAdminSCIMTokens_Read(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tenableSCIM(ctx, t, client, true)\n\tdefer enableSCIM(ctx, t, client, false)\n\n\tscimTokenClient := client.Admin.Settings.SCIM.Tokens\n\n\tt.Run(\"read token\", func(t *testing.T) {\n\t\t// create a token to ensure there is data to read\n\t\tscimToken, err := scimTokenClient.CreateWithOptions(ctx, AdminSCIMTokenCreateOptions{\n\t\t\tDescription: String(\"Test Desc\"),\n\t\t\tExpiredAt:   Ptr(time.Now().Add(60 * 24 * time.Hour)),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, scimToken)\n\n\t\tt.Cleanup(func() {\n\t\t\terr := scimTokenClient.Delete(ctx, scimToken.ID)\n\t\t\tif err != nil && err != ErrResourceNotFound {\n\t\t\t\tt.Logf(\"failed to cleanup SCIM token %q: %v\", scimToken.ID, err)\n\t\t\t}\n\t\t})\n\n\t\ttestCases := []struct {\n\t\t\tname       string\n\t\t\ttokenID    string\n\t\t\traiseError bool\n\t\t}{\n\t\t\t{\"with valid token ID\", scimToken.ID, false},\n\t\t\t{\"with invalid token ID\", \"invalid id\", true},\n\t\t\t{\"with empty token ID\", \"\", true},\n\t\t\t{\"with non-existent token ID\", \"this-does-not-exist\", true},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\ttoken, err := scimTokenClient.Read(ctx, tc.tokenID)\n\t\t\t\tif tc.raiseError {\n\t\t\t\t\trequire.Error(t, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, token)\n\t\t\t\tassert.Equal(t, tc.tokenID, token.ID)\n\n\t\t\t\t// Verify specific field properties for the valid token\n\t\t\t\tif !tc.raiseError {\n\t\t\t\t\tassert.Equal(t, scimToken.Description, token.Description)\n\t\t\t\t\tassert.WithinDuration(t, scimToken.ExpiredAt, token.ExpiredAt, time.Second)\n\t\t\t\t\tassert.NotEmpty(t, scimToken.Token)\n\t\t\t\t\tassert.Empty(t, token.Token)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestAdminSCIMTokens_Delete(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tenableSCIM(ctx, t, client, true)\n\tdefer enableSCIM(ctx, t, client, false)\n\n\tscimTokenClient := client.Admin.Settings.SCIM.Tokens\n\n\tt.Run(\"delete token\", func(t *testing.T) {\n\t\t// create a token to ensure there is data to delete\n\t\tscimToken, err := scimTokenClient.Create(ctx, \"foo token\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, scimToken)\n\t\tt.Cleanup(func() {\n\t\t\terr := scimTokenClient.Delete(ctx, scimToken.ID)\n\t\t\tif err != nil && err != ErrResourceNotFound {\n\t\t\t\tt.Logf(\"failed to cleanup SCIM token %q: %v\", scimToken.ID, err)\n\t\t\t}\n\t\t})\n\n\t\ttestCases := []struct {\n\t\t\tname       string\n\t\t\ttokenID    string\n\t\t\traiseError bool\n\t\t}{\n\t\t\t{\"with valid token ID\", scimToken.ID, false},\n\t\t\t{\"with invalid token ID\", \"invalid id\", true},\n\t\t\t{\"with empty token ID\", \"\", true},\n\t\t\t{\"with non-existent token ID\", \"this-does-not-exist\", true},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\terr = scimTokenClient.Delete(ctx, tc.tokenID)\n\t\t\t\tif tc.raiseError {\n\t\t\t\t\trequire.Error(t, err)\n\t\t\t\t\tif tc.tokenID == \"this-does-not-exist\" {\n\t\t\t\t\t\tassert.ErrorIs(t, err, ErrResourceNotFound)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tassert.ErrorIs(t, err, ErrInvalidTokenID)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t// verify deletion\n\t\t\t\t_, err = scimTokenClient.Read(ctx, tc.tokenID)\n\t\t\t\trequire.Error(t, err)\n\t\t\t})\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "admin_setting_smtp.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ SMTPSettings = (*adminSMTPSettings)(nil)\n\n// SMTPSettings describes all the SMTP admin settings for the Admin Setting API https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings\ntype SMTPSettings interface {\n\t// Read returns the SMTP settings.\n\tRead(ctx context.Context) (*AdminSMTPSetting, error)\n\n\t// Update updates SMTP settings.\n\tUpdate(ctx context.Context, options AdminSMTPSettingsUpdateOptions) (*AdminSMTPSetting, error)\n}\n\ntype adminSMTPSettings struct {\n\tclient *Client\n}\n\n// SMTPAuthType represents valid SMTP Auth types.\ntype SMTPAuthType string\n\n// List of all SMTP auth types.\nconst (\n\tSMTPAuthNone  SMTPAuthType = \"none\"\n\tSMTPAuthPlain SMTPAuthType = \"plain\"\n\tSMTPAuthLogin SMTPAuthType = \"login\"\n)\n\n// AdminSMTPSetting represents a the SMTP settings in Terraform Enterprise.\ntype AdminSMTPSetting struct {\n\tID       string       `jsonapi:\"primary,smtp-settings\"`\n\tEnabled  bool         `jsonapi:\"attr,enabled\"`\n\tHost     string       `jsonapi:\"attr,host\"`\n\tPort     int          `jsonapi:\"attr,port\"`\n\tSender   string       `jsonapi:\"attr,sender\"`\n\tAuth     SMTPAuthType `jsonapi:\"attr,auth\"`\n\tUsername string       `jsonapi:\"attr,username\"`\n}\n\n// Read returns the SMTP settings.\nfunc (a *adminSMTPSettings) Read(ctx context.Context) (*AdminSMTPSetting, error) {\n\treq, err := a.client.NewRequest(\"GET\", \"admin/smtp-settings\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsmtp := &AdminSMTPSetting{}\n\terr = req.Do(ctx, smtp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn smtp, nil\n}\n\n// AdminSMTPSettingsUpdateOptions represents the admin options for updating\n// SMTP settings.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings#request-body-3\ntype AdminSMTPSettingsUpdateOptions struct {\n\tEnabled          *bool         `jsonapi:\"attr,enabled,omitempty\"`\n\tHost             *string       `jsonapi:\"attr,host,omitempty\"`\n\tPort             *int          `jsonapi:\"attr,port,omitempty\"`\n\tSender           *string       `jsonapi:\"attr,sender,omitempty\"`\n\tAuth             *SMTPAuthType `jsonapi:\"attr,auth,omitempty\"`\n\tUsername         *string       `jsonapi:\"attr,username,omitempty\"`\n\tPassword         *string       `jsonapi:\"attr,password,omitempty\"`\n\tTestEmailAddress *string       `jsonapi:\"attr,test-email-address,omitempty\"`\n}\n\n// Update updates the SMTP settings.\nfunc (a *adminSMTPSettings) Update(ctx context.Context, options AdminSMTPSettingsUpdateOptions) (*AdminSMTPSetting, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := a.client.NewRequest(\"PATCH\", \"admin/smtp-settings\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsmtp := &AdminSMTPSetting{}\n\terr = req.Do(ctx, smtp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn smtp, nil\n}\n\nfunc (o AdminSMTPSettingsUpdateOptions) valid() error {\n\tif validString((*string)(o.Auth)) {\n\t\tif err := validateAdminSettingSMTPAuth(*o.Auth); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc validateAdminSettingSMTPAuth(authVal SMTPAuthType) error {\n\tswitch authVal {\n\tcase SMTPAuthNone, SMTPAuthPlain, SMTPAuthLogin:\n\t\t// do nothing\n\tdefault:\n\t\treturn ErrInvalidSMTPAuth\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "admin_setting_smtp_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminSettings_SMTP_Read(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tsmtpSettings, err := client.Admin.Settings.SMTP.Read(ctx)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"smtp\", smtpSettings.ID)\n\tassert.NotNil(t, smtpSettings.Enabled)\n\tassert.NotNil(t, smtpSettings.Host)\n\tassert.NotNil(t, smtpSettings.Port)\n\tassert.NotNil(t, smtpSettings.Sender)\n\tassert.NotNil(t, smtpSettings.Auth)\n\tassert.NotNil(t, smtpSettings.Username)\n}\n\nfunc TestAdminSettings_SMTP_Update(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tenabled := true\n\tdisabled := false\n\n\tt.Run(\"with Auth option defined\", func(t *testing.T) {\n\t\tsmtpSettings, err := client.Admin.Settings.SMTP.Update(ctx, AdminSMTPSettingsUpdateOptions{\n\t\t\tEnabled: Bool(disabled),\n\t\t\tAuth:    SMTPAuthValue(SMTPAuthNone),\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, disabled, smtpSettings.Enabled)\n\t})\n\tt.Run(\"with no Auth option\", func(t *testing.T) {\n\t\tsmtpSettings, err := client.Admin.Settings.SMTP.Update(ctx, AdminSMTPSettingsUpdateOptions{\n\t\t\tEnabled:          Bool(disabled),\n\t\t\tTestEmailAddress: String(\"test@example.com\"),\n\t\t\tHost:             String(\"123\"),\n\t\t\tPort:             Int(123),\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, SMTPAuthNone, smtpSettings.Auth)\n\t\tassert.Equal(t, disabled, smtpSettings.Enabled)\n\t})\n\tt.Run(\"with invalid Auth option\", func(t *testing.T) {\n\t\tvar SMTPAuthPlained SMTPAuthType = \"plained\"\n\t\t_, err := client.Admin.Settings.SMTP.Update(ctx, AdminSMTPSettingsUpdateOptions{\n\t\t\tEnabled: Bool(enabled),\n\t\t\tAuth:    &SMTPAuthPlained,\n\t\t})\n\n\t\tassert.Equal(t, err, ErrInvalidSMTPAuth)\n\t})\n}\n"
  },
  {
    "path": "admin_setting_twilio.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ TwilioSettings = (*adminTwilioSettings)(nil)\n\n// TwilioSettings describes all the Twilio admin settings for the Admin Setting API.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings\ntype TwilioSettings interface {\n\t// Read returns the Twilio settings.\n\tRead(ctx context.Context) (*AdminTwilioSetting, error)\n\n\t// Update updates Twilio settings.\n\tUpdate(ctx context.Context, options AdminTwilioSettingsUpdateOptions) (*AdminTwilioSetting, error)\n\n\t// Verify verifies Twilio settings.\n\tVerify(ctx context.Context, options AdminTwilioSettingsVerifyOptions) error\n}\n\ntype adminTwilioSettings struct {\n\tclient *Client\n}\n\n// AdminTwilioSetting represents the Twilio settings in Terraform Enterprise.\ntype AdminTwilioSetting struct {\n\tID         string `jsonapi:\"primary,twilio-settings\"`\n\tEnabled    bool   `jsonapi:\"attr,enabled\"`\n\tAccountSid string `jsonapi:\"attr,account-sid\"`\n\tFromNumber string `jsonapi:\"attr,from-number\"`\n}\n\n// Read returns the Twilio settings.\nfunc (a *adminTwilioSettings) Read(ctx context.Context) (*AdminTwilioSetting, error) {\n\treq, err := a.client.NewRequest(\"GET\", \"admin/twilio-settings\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttwilio := &AdminTwilioSetting{}\n\terr = req.Do(ctx, twilio)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn twilio, nil\n}\n\n// AdminTwilioSettingsUpdateOptions represents the admin options for updating\n// Twilio settings.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings#request-body-4\ntype AdminTwilioSettingsUpdateOptions struct {\n\tEnabled    *bool   `jsonapi:\"attr,enabled,omitempty\"`\n\tAccountSid *string `jsonapi:\"attr,account-sid,omitempty\"`\n\tAuthToken  *string `jsonapi:\"attr,auth-token,omitempty\"`\n\tFromNumber *string `jsonapi:\"attr,from-number,omitempty\"`\n}\n\n// AdminTwilioSettingsVerifyOptions represents the test number to verify Twilio.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings#verify-twilio-settings\ntype AdminTwilioSettingsVerifyOptions struct {\n\tTestNumber *string `jsonapi:\"attr,test-number\"` // Required\n}\n\n// Update updates the Twilio settings.\nfunc (a *adminTwilioSettings) Update(ctx context.Context, options AdminTwilioSettingsUpdateOptions) (*AdminTwilioSetting, error) {\n\treq, err := a.client.NewRequest(\"PATCH\", \"admin/twilio-settings\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttwilio := &AdminTwilioSetting{}\n\terr = req.Do(ctx, twilio)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn twilio, nil\n}\n\n// Verify verifies Twilio settings.\nfunc (a *adminTwilioSettings) Verify(ctx context.Context, options AdminTwilioSettingsVerifyOptions) error {\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\treq, err := a.client.NewRequest(\"PATCH\", \"admin/twilio-settings/verify\", &options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o AdminTwilioSettingsVerifyOptions) valid() error {\n\tif !validString(o.TestNumber) {\n\t\treturn ErrRequiredTestNumber\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "admin_setting_twilio_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminSettings_Twilio_Read(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttwilioSettings, err := client.Admin.Settings.Twilio.Read(ctx)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"twilio\", twilioSettings.ID)\n\tassert.NotNil(t, twilioSettings.Enabled)\n\tassert.NotNil(t, twilioSettings.AccountSid)\n\tassert.NotNil(t, twilioSettings.FromNumber)\n}\n\nfunc TestAdminSettings_Twilio_Update(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttwilioSettings, err := client.Admin.Settings.Twilio.Update(ctx, AdminTwilioSettingsUpdateOptions{\n\t\tEnabled: Bool(false),\n\t})\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, false, twilioSettings.Enabled)\n}\n\nfunc TestAdminSettings_Twilio_Verify(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\terr := client.Admin.Settings.Twilio.Verify(ctx, AdminTwilioSettingsVerifyOptions{})\n\n\tassert.Equal(t, err, ErrRequiredTestNumber)\n}\n"
  },
  {
    "path": "admin_terraform_version.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ AdminTerraformVersions = (*adminTerraformVersions)(nil)\n\nconst (\n\tlinux = \"linux\"\n\tamd64 = \"amd64\"\n\tarm64 = \"arm64\"\n)\n\n// AdminTerraformVersions describes all the admin terraform versions related methods that\n// the Terraform Enterprise API supports.\n// Note that admin terraform versions are only available in Terraform Enterprise.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/terraform-versions\ntype AdminTerraformVersions interface {\n\t// List all the terraform versions.\n\tList(ctx context.Context, options *AdminTerraformVersionsListOptions) (*AdminTerraformVersionsList, error)\n\n\t// Read a terraform version by its ID.\n\tRead(ctx context.Context, id string) (*AdminTerraformVersion, error)\n\n\t// Create a terraform version.\n\tCreate(ctx context.Context, options AdminTerraformVersionCreateOptions) (*AdminTerraformVersion, error)\n\n\t// Update a terraform version.\n\tUpdate(ctx context.Context, id string, options AdminTerraformVersionUpdateOptions) (*AdminTerraformVersion, error)\n\n\t// Delete a terraform version\n\tDelete(ctx context.Context, id string) error\n}\n\n// adminTerraformVersions implements AdminTerraformVersions.\ntype adminTerraformVersions struct {\n\tclient *Client\n}\n\n// AdminTerraformVersion represents a Terraform Version\ntype AdminTerraformVersion struct {\n\tID               string                     `jsonapi:\"primary,terraform-versions\"`\n\tVersion          string                     `jsonapi:\"attr,version\"`\n\tURL              string                     `jsonapi:\"attr,url,omitempty\"`\n\tSha              string                     `jsonapi:\"attr,sha,omitempty\"`\n\tDeprecated       bool                       `jsonapi:\"attr,deprecated\"`\n\tDeprecatedReason *string                    `jsonapi:\"attr,deprecated-reason,omitempty\"`\n\tOfficial         bool                       `jsonapi:\"attr,official\"`\n\tEnabled          bool                       `jsonapi:\"attr,enabled\"`\n\tBeta             bool                       `jsonapi:\"attr,beta\"`\n\tUsage            int                        `jsonapi:\"attr,usage\"`\n\tCreatedAt        time.Time                  `jsonapi:\"attr,created-at,iso8601\"`\n\tArchs            []*ToolVersionArchitecture `jsonapi:\"attr,archs,omitempty\"`\n}\n\ntype ToolVersionArchitecture struct {\n\tURL  string `jsonapi:\"attr,url\"`\n\tSha  string `jsonapi:\"attr,sha\"`\n\tOS   string `jsonapi:\"attr,os\"`\n\tArch string `jsonapi:\"attr,arch\"`\n}\n\n// AdminTerraformVersionsListOptions represents the options for listing\n// terraform versions.\ntype AdminTerraformVersionsListOptions struct {\n\tListOptions\n\n\t// Optional: A query string to find an exact version\n\tFilter string `url:\"filter[version],omitempty\"`\n\n\t// Optional: A search query string to find all versions that match version substring\n\tSearch string `url:\"search[version],omitempty\"`\n}\n\n// AdminTerraformVersionCreateOptions for creating a terraform version.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/terraform-versions#request-body\ntype AdminTerraformVersionCreateOptions struct {\n\tType             string                     `jsonapi:\"primary,terraform-versions\"`\n\tVersion          *string                    `jsonapi:\"attr,version\"` // Required\n\tURL              *string                    `jsonapi:\"attr,url,omitempty\"`\n\tSha              *string                    `jsonapi:\"attr,sha,omitempty\"`\n\tOfficial         *bool                      `jsonapi:\"attr,official,omitempty\"`\n\tDeprecated       *bool                      `jsonapi:\"attr,deprecated,omitempty\"`\n\tDeprecatedReason *string                    `jsonapi:\"attr,deprecated-reason,omitempty\"`\n\tEnabled          *bool                      `jsonapi:\"attr,enabled,omitempty\"`\n\tBeta             *bool                      `jsonapi:\"attr,beta,omitempty\"`\n\tArchs            []*ToolVersionArchitecture `jsonapi:\"attr,archs,omitempty\"`\n}\n\n// AdminTerraformVersionUpdateOptions for updating terraform version.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/terraform-versions#request-body\ntype AdminTerraformVersionUpdateOptions struct {\n\tType             string                     `jsonapi:\"primary,terraform-versions\"`\n\tVersion          *string                    `jsonapi:\"attr,version,omitempty\"`\n\tURL              *string                    `jsonapi:\"attr,url,omitempty\"`\n\tSha              *string                    `jsonapi:\"attr,sha,omitempty\"`\n\tOfficial         *bool                      `jsonapi:\"attr,official,omitempty\"`\n\tDeprecated       *bool                      `jsonapi:\"attr,deprecated,omitempty\"`\n\tDeprecatedReason *string                    `jsonapi:\"attr,deprecated-reason,omitempty\"`\n\tEnabled          *bool                      `jsonapi:\"attr,enabled,omitempty\"`\n\tBeta             *bool                      `jsonapi:\"attr,beta,omitempty\"`\n\tArchs            []*ToolVersionArchitecture `jsonapi:\"attr,archs,omitempty\"`\n}\n\n// AdminTerraformVersionsList represents a list of terraform versions.\ntype AdminTerraformVersionsList struct {\n\t*Pagination\n\tItems []*AdminTerraformVersion\n}\n\n// List all the terraform versions.\nfunc (a *adminTerraformVersions) List(ctx context.Context, options *AdminTerraformVersionsListOptions) (*AdminTerraformVersionsList, error) {\n\treq, err := a.client.NewRequest(\"GET\", \"admin/terraform-versions\", options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttvl := &AdminTerraformVersionsList{}\n\terr = req.Do(ctx, tvl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tvl, nil\n}\n\n// Read a terraform version by its ID.\nfunc (a *adminTerraformVersions) Read(ctx context.Context, id string) (*AdminTerraformVersion, error) {\n\tif !validStringID(&id) {\n\t\treturn nil, ErrInvalidTerraformVersionID\n\t}\n\n\tu := fmt.Sprintf(\"admin/terraform-versions/%s\", url.PathEscape(id))\n\treq, err := a.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttfv := &AdminTerraformVersion{}\n\terr = req.Do(ctx, tfv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tfv, nil\n}\n\n// Create a new terraform version.\nfunc (a *adminTerraformVersions) Create(ctx context.Context, options AdminTerraformVersionCreateOptions) (*AdminTerraformVersion, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\treq, err := a.client.NewRequest(\"POST\", \"admin/terraform-versions\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttfv := &AdminTerraformVersion{}\n\terr = req.Do(ctx, tfv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn tfv, nil\n}\n\n// Update an existing terraform version.\nfunc (a *adminTerraformVersions) Update(ctx context.Context, id string, options AdminTerraformVersionUpdateOptions) (*AdminTerraformVersion, error) {\n\tif !validStringID(&id) {\n\t\treturn nil, ErrInvalidTerraformVersionID\n\t}\n\n\tu := fmt.Sprintf(\"admin/terraform-versions/%s\", url.PathEscape(id))\n\treq, err := a.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttfv := &AdminTerraformVersion{}\n\terr = req.Do(ctx, tfv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tfv, nil\n}\n\n// Delete a terraform version.\nfunc (a *adminTerraformVersions) Delete(ctx context.Context, id string) error {\n\tif !validStringID(&id) {\n\t\treturn ErrInvalidTerraformVersionID\n\t}\n\n\tu := fmt.Sprintf(\"admin/terraform-versions/%s\", url.PathEscape(id))\n\treq, err := a.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o AdminTerraformVersionCreateOptions) valid() error {\n\tif (reflect.DeepEqual(o, AdminTerraformVersionCreateOptions{})) {\n\t\treturn ErrRequiredTFVerCreateOps\n\t}\n\tif !validString(o.Version) {\n\t\treturn ErrRequiredVersion\n\t}\n\tif !o.validArchs() {\n\t\treturn ErrRequiredArchsOrURLAndSha\n\t}\n\treturn nil\n}\n\nfunc (o AdminTerraformVersionCreateOptions) validArchs() bool {\n\tif o.Archs == nil && validString(o.URL) && validString(o.Sha) {\n\t\treturn true\n\t}\n\n\tfor _, a := range o.Archs {\n\t\tif !validArch(a) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc validArch(a *ToolVersionArchitecture) bool {\n\treturn a.URL != \"\" && a.Sha != \"\" && a.OS == linux && (a.Arch == amd64 || a.Arch == arm64)\n}\n"
  },
  {
    "path": "admin_terraform_version_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminTerraformVersions_List(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\ttfList, err := client.Admin.TerraformVersions.List(ctx, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, tfList.Items)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\ttfList, err := client.Admin.TerraformVersions.List(ctx, &AdminTerraformVersionsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\t// Out of range page number, so the items should be empty\n\t\tassert.Empty(t, tfList.Items)\n\t\tassert.Equal(t, 999, tfList.CurrentPage)\n\n\t\ttfList, err = client.Admin.TerraformVersions.List(ctx, &AdminTerraformVersionsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, tfList.CurrentPage)\n\t\tfor _, item := range tfList.Items {\n\t\t\tassert.NotNil(t, item.ID)\n\t\t\tassert.NotNil(t, item.Version)\n\t\t\tassert.NotNil(t, item.URL)\n\t\t\tassert.NotNil(t, item.Sha)\n\t\t\tassert.NotNil(t, item.Official)\n\t\t\tassert.NotNil(t, item.Deprecated)\n\t\t\tif item.Deprecated {\n\t\t\t\tassert.NotNil(t, item.DeprecatedReason)\n\t\t\t} else {\n\t\t\t\tassert.Nil(t, item.DeprecatedReason)\n\t\t\t}\n\t\t\tassert.NotNil(t, item.Enabled)\n\t\t\tassert.NotNil(t, item.Beta)\n\t\t\tassert.NotNil(t, item.Usage)\n\t\t\tassert.NotNil(t, item.CreatedAt)\n\t\t\tassert.NotNil(t, item.Archs)\n\t\t}\n\t})\n\n\tt.Run(\"with filter query string\", func(t *testing.T) {\n\t\ttfList, err := client.Admin.TerraformVersions.List(ctx, &AdminTerraformVersionsListOptions{\n\t\t\tFilter: \"1.0.4\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, len(tfList.Items))\n\n\t\t// Query for a Terraform version that does not exist\n\t\ttfList, err = client.Admin.TerraformVersions.List(ctx, &AdminTerraformVersionsListOptions{\n\t\t\tFilter: \"1000.1000.42\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, tfList.Items)\n\t})\n\n\tt.Run(\"with search version query string\", func(t *testing.T) {\n\t\tsearchVersion := \"1.0\"\n\t\ttfList, err := client.Admin.TerraformVersions.List(ctx, &AdminTerraformVersionsListOptions{\n\t\t\tSearch: searchVersion,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, tfList.Items)\n\n\t\tt.Run(\"ensure each version matches substring\", func(t *testing.T) {\n\t\t\tfor _, item := range tfList.Items {\n\t\t\t\tassert.Equal(t, true, strings.Contains(item.Version, searchVersion))\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc TestAdminTerraformVersions_CreateDelete(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\tamd64Sha := genSha(t)\n\turl := \"https://www.hashicorp.com\"\n\n\tt.Run(\"with valid options including top level url & sha and archs\", func(t *testing.T) {\n\t\topts := AdminTerraformVersionCreateOptions{\n\t\t\tVersion:          String(genSafeRandomTerraformVersion()),\n\t\t\tURL:              String(url),\n\t\t\tSha:              &amd64Sha,\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tOfficial:         Bool(false),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tArchs: []*ToolVersionArchitecture{\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  amd64Sha,\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: amd64,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  genSha(t),\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: arm64,\n\t\t\t\t}},\n\t\t}\n\t\ttfv, err := client.Admin.TerraformVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.TerraformVersions.Delete(ctx, tfv.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, *opts.Version, tfv.Version)\n\t\tassert.Equal(t, *opts.URL, tfv.URL)\n\t\tassert.Equal(t, *opts.Sha, tfv.Sha)\n\t\tassert.Equal(t, *opts.Official, tfv.Official)\n\t\tassert.Equal(t, *opts.Deprecated, tfv.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *tfv.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, tfv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, tfv.Beta)\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, tfv.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, tfv.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, tfv.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, tfv.Archs[i].Arch)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options including archs\", func(t *testing.T) {\n\t\topts := AdminTerraformVersionCreateOptions{\n\t\t\tVersion:          String(genSafeRandomTerraformVersion()),\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tOfficial:         Bool(false),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tArchs: []*ToolVersionArchitecture{\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  amd64Sha,\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: amd64,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  *String(genSha(t)),\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: arm64,\n\t\t\t\t}},\n\t\t}\n\t\ttfv, err := client.Admin.TerraformVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.TerraformVersions.Delete(ctx, tfv.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, *opts.Version, tfv.Version)\n\t\tassert.Equal(t, *opts.Official, tfv.Official)\n\t\tassert.Equal(t, *opts.Deprecated, tfv.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *tfv.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, tfv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, tfv.Beta)\n\t\tassert.Equal(t, len(opts.Archs), len(tfv.Archs))\n\t\tfor i, arch := range opts.Archs {\n\t\t\tassert.Equal(t, arch.URL, tfv.Archs[i].URL)\n\t\t\tassert.Equal(t, arch.Sha, tfv.Archs[i].Sha)\n\t\t\tassert.Equal(t, arch.OS, tfv.Archs[i].OS)\n\t\t\tassert.Equal(t, arch.Arch, tfv.Archs[i].Arch)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options including url and sha\", func(t *testing.T) {\n\t\topts := AdminTerraformVersionCreateOptions{\n\t\t\tVersion:          String(genSafeRandomTerraformVersion()),\n\t\t\tURL:              &url,\n\t\t\tSha:              &amd64Sha,\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tOfficial:         Bool(false),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t}\n\t\ttfv, err := client.Admin.TerraformVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.TerraformVersions.Delete(ctx, tfv.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, *opts.Version, tfv.Version)\n\t\tassert.Equal(t, *opts.URL, tfv.URL)\n\t\tassert.Equal(t, *opts.Sha, tfv.Sha)\n\t\tassert.Equal(t, *opts.Official, tfv.Official)\n\t\tassert.Equal(t, *opts.Deprecated, tfv.Deprecated)\n\t\tassert.Equal(t, opts.DeprecatedReason, tfv.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, tfv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, tfv.Beta)\n\t\tassert.Equal(t, 1, len(tfv.Archs))\n\t\tassert.Equal(t, *opts.URL, tfv.Archs[0].URL)\n\t\tassert.Equal(t, *opts.Sha, tfv.Archs[0].Sha)\n\t\tassert.Equal(t, linux, tfv.Archs[0].OS)\n\t\tassert.Equal(t, amd64, tfv.Archs[0].Arch)\n\t})\n\n\tt.Run(\"with only required options including tool version url and sha\", func(t *testing.T) {\n\t\tversion := genSafeRandomTerraformVersion()\n\t\topts := AdminTerraformVersionCreateOptions{\n\t\t\tVersion: String(version),\n\t\t\tURL:     &url,\n\t\t\tSha:     &amd64Sha,\n\t\t}\n\t\ttfv, err := client.Admin.TerraformVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.TerraformVersions.Delete(ctx, tfv.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, *opts.Version, tfv.Version)\n\t\tassert.Equal(t, *opts.URL, tfv.URL)\n\t\tassert.Equal(t, *opts.Sha, tfv.Sha)\n\t\tassert.Equal(t, false, tfv.Official)\n\t\tassert.Equal(t, false, tfv.Deprecated)\n\t\tassert.Nil(t, tfv.DeprecatedReason)\n\t\tassert.Equal(t, true, tfv.Enabled)\n\t\tassert.Equal(t, false, tfv.Beta)\n\t\tassert.Equal(t, 1, len(tfv.Archs))\n\t\tassert.Equal(t, *opts.URL, tfv.Archs[0].URL)\n\t\tassert.Equal(t, *opts.Sha, tfv.Archs[0].Sha)\n\t\tassert.Equal(t, linux, tfv.Archs[0].OS)\n\t\tassert.Equal(t, amd64, tfv.Archs[0].Arch)\n\t})\n\n\tt.Run(\"with only required options including archs\", func(t *testing.T) {\n\t\tversion := genSafeRandomTerraformVersion()\n\t\topts := AdminTerraformVersionCreateOptions{\n\t\t\tVersion: String(version),\n\t\t\tArchs: []*ToolVersionArchitecture{\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  amd64Sha,\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: amd64,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tURL:  url,\n\t\t\t\t\tSha:  *String(genSha(t)),\n\t\t\t\t\tOS:   linux,\n\t\t\t\t\tArch: arm64,\n\t\t\t\t}},\n\t\t}\n\t\ttfv, err := client.Admin.TerraformVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.TerraformVersions.Delete(ctx, tfv.ID)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tassert.Equal(t, *opts.Version, tfv.Version)\n\t\tassert.Equal(t, false, tfv.Official)\n\t\tassert.Equal(t, false, tfv.Deprecated)\n\t\tassert.Nil(t, tfv.DeprecatedReason)\n\t\tassert.Equal(t, true, tfv.Enabled)\n\t\tassert.Equal(t, false, tfv.Beta)\n\t})\n\n\tt.Run(\"with empty options\", func(t *testing.T) {\n\t\t_, err := client.Admin.TerraformVersions.Create(ctx, AdminTerraformVersionCreateOptions{})\n\t\trequire.Equal(t, err, ErrRequiredTFVerCreateOps)\n\t})\n}\n\nfunc TestAdminTerraformVersions_ReadUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\turl := \"https://www.hashicorp.com\"\n\tamd64Sha := genSha(t)\n\n\tt.Run(\"reads and updates\", func(t *testing.T) {\n\t\tversion := genSafeRandomTerraformVersion()\n\t\topts := AdminTerraformVersionCreateOptions{\n\t\t\tVersion:          String(version),\n\t\t\tURL:              String(url),\n\t\t\tSha:              &amd64Sha,\n\t\t\tOfficial:         Bool(false),\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tArchs: []*ToolVersionArchitecture{{\n\t\t\t\tURL:  url,\n\t\t\t\tSha:  amd64Sha,\n\t\t\t\tOS:   linux,\n\t\t\t\tArch: amd64,\n\t\t\t}, {\n\t\t\t\tURL:  url,\n\t\t\t\tSha:  genSha(t),\n\t\t\t\tOS:   linux,\n\t\t\t\tArch: arm64,\n\t\t\t}},\n\t\t}\n\t\ttfv, err := client.Admin.TerraformVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\t\tid := tfv.ID\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.TerraformVersions.Delete(ctx, id)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\ttfv, err = client.Admin.TerraformVersions.Read(ctx, id)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *opts.Version, tfv.Version)\n\t\tassert.Equal(t, 2, len(tfv.Archs))\n\t\tassert.Equal(t, opts.Archs[0].URL, tfv.URL)\n\t\tassert.Equal(t, opts.Archs[0].Sha, tfv.Sha)\n\t\tassert.Equal(t, *opts.Official, tfv.Official)\n\t\tassert.Equal(t, *opts.Deprecated, tfv.Deprecated)\n\t\tassert.Equal(t, *opts.DeprecatedReason, *tfv.DeprecatedReason)\n\t\tassert.Equal(t, *opts.Enabled, tfv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, tfv.Beta)\n\n\t\tupdateVersion := genSafeRandomTerraformVersion()\n\t\tupdateURL := \"https://app.terraform.io/\"\n\t\tupdateSha := genSha(t)\n\t\tupdateOpts := AdminTerraformVersionUpdateOptions{\n\t\t\tVersion:    String(updateVersion),\n\t\t\tURL:        String(updateURL),\n\t\t\tSha:        &updateSha,\n\t\t\tDeprecated: Bool(false),\n\t\t}\n\n\t\ttfv, err = client.Admin.TerraformVersions.Update(ctx, id, updateOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, updateVersion, tfv.Version)\n\t\tassert.Equal(t, updateURL, tfv.URL)\n\t\tassert.Equal(t, updateSha, tfv.Sha)\n\t\tassert.Equal(t, *opts.Official, tfv.Official)\n\t\tassert.Equal(t, *updateOpts.Deprecated, tfv.Deprecated)\n\t\tassert.Equal(t, *opts.Enabled, tfv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, tfv.Beta)\n\t\tassert.Equal(t, 1, len(tfv.Archs))\n\t\tassert.Equal(t, updateURL, tfv.Archs[0].URL)\n\t\tassert.Equal(t, updateSha, tfv.Archs[0].Sha)\n\t\tassert.Equal(t, linux, tfv.Archs[0].OS)\n\t\tassert.Equal(t, amd64, tfv.Archs[0].Arch)\n\t})\n\n\tt.Run(\"update with Archs\", func(t *testing.T) {\n\t\tversion := genSafeRandomTerraformVersion()\n\t\tsha := String(genSha(t))\n\t\topts := AdminTerraformVersionCreateOptions{\n\t\t\tVersion:          String(version),\n\t\t\tURL:              String(\"https://www.hashicorp.com\"),\n\t\t\tSha:              String(genSha(t)),\n\t\t\tOfficial:         Bool(false),\n\t\t\tDeprecated:       Bool(true),\n\t\t\tDeprecatedReason: String(\"Test Reason\"),\n\t\t\tEnabled:          Bool(false),\n\t\t\tBeta:             Bool(false),\n\t\t\tArchs: []*ToolVersionArchitecture{{\n\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\tSha:  *sha,\n\t\t\t\tOS:   linux,\n\t\t\t\tArch: amd64,\n\t\t\t}},\n\t\t}\n\t\ttfv, err := client.Admin.TerraformVersions.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\t\tid := tfv.ID\n\n\t\tdefer func() {\n\t\t\tdeleteErr := client.Admin.TerraformVersions.Delete(ctx, id)\n\t\t\trequire.NoError(t, deleteErr)\n\t\t}()\n\n\t\tupdateArchOpts := AdminTerraformVersionUpdateOptions{\n\t\t\tArchs: []*ToolVersionArchitecture{{\n\t\t\t\tURL:  \"https://www.hashicorp.com\",\n\t\t\t\tSha:  *sha,\n\t\t\t\tOS:   linux,\n\t\t\t\tArch: arm64,\n\t\t\t}},\n\t\t}\n\n\t\ttfv, err = client.Admin.TerraformVersions.Update(ctx, id, updateArchOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *opts.Version, tfv.Version)\n\t\tassert.Equal(t, \"\", tfv.URL)\n\t\tassert.Equal(t, \"\", tfv.Sha)\n\t\tassert.Equal(t, *opts.Official, tfv.Official)\n\t\tassert.Equal(t, *opts.Deprecated, tfv.Deprecated)\n\t\tassert.Equal(t, *opts.Enabled, tfv.Enabled)\n\t\tassert.Equal(t, *opts.Beta, tfv.Beta)\n\t\tassert.Equal(t, 1, len(tfv.Archs))\n\t\tassert.Equal(t, updateArchOpts.Archs[0].URL, tfv.Archs[0].URL)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].Sha, tfv.Archs[0].Sha)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].OS, tfv.Archs[0].OS)\n\t\tassert.Equal(t, updateArchOpts.Archs[0].Arch, tfv.Archs[0].Arch)\n\t})\n\n\tt.Run(\"with non-existent terraform version\", func(t *testing.T) {\n\t\trandomID := \"random-id\"\n\t\t_, err := client.Admin.TerraformVersions.Read(ctx, randomID)\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "admin_user.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ AdminUsers = (*adminUsers)(nil)\n\n// AdminUsers describes all the admin user related methods that the Terraform\n// Enterprise  API supports.\n// It contains endpoints to help site administrators manage their users.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/users\ntype AdminUsers interface {\n\t// List all the users of the given installation.\n\tList(ctx context.Context, options *AdminUserListOptions) (*AdminUserList, error)\n\n\t// Delete a user by its ID.\n\tDelete(ctx context.Context, userID string) error\n\n\t// Suspend a user by its ID.\n\tSuspend(ctx context.Context, userID string) (*AdminUser, error)\n\n\t// Unsuspend a user by its ID.\n\tUnsuspend(ctx context.Context, userID string) (*AdminUser, error)\n\n\t// GrantAdmin grants admin privileges to a user by its ID.\n\tGrantAdmin(ctx context.Context, userID string) (*AdminUser, error)\n\n\t// RevokeAdmin revokees admin privileges to a user by its ID.\n\tRevokeAdmin(ctx context.Context, userID string) (*AdminUser, error)\n\n\t// Disable2FA disables a user's two-factor authentication in the situation\n\t// where they have lost access to their device and recovery codes.\n\tDisable2FA(ctx context.Context, userID string) (*AdminUser, error)\n}\n\n// adminUsers implements the AdminUsers interface.\ntype adminUsers struct {\n\tclient *Client\n}\n\n// AdminUser represents a user as seen by an Admin.\ntype AdminUser struct {\n\tID               string     `jsonapi:\"primary,users\"`\n\tEmail            string     `jsonapi:\"attr,email\"`\n\tUsername         string     `jsonapi:\"attr,username\"`\n\tAvatarURL        string     `jsonapi:\"attr,avatar-url\"`\n\tTwoFactor        *TwoFactor `jsonapi:\"attr,two-factor\"`\n\tIsAdmin          bool       `jsonapi:\"attr,is-admin\"`\n\tIsSuspended      bool       `jsonapi:\"attr,is-suspended\"`\n\tIsServiceAccount bool       `jsonapi:\"attr,is-service-account\"`\n\n\t// Relations\n\tOrganizations []*Organization `jsonapi:\"relation,organizations\"`\n}\n\n// AdminUserList represents a list of users.\ntype AdminUserList struct {\n\t*Pagination\n\tItems []*AdminUser\n}\n\n// AdminUserIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/users#available-related-resources\ntype AdminUserIncludeOpt string\n\nconst AdminUserOrgs AdminUserIncludeOpt = \"organizations\"\n\n// AdminUserListOptions represents the options for listing users.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/users#query-parameters\ntype AdminUserListOptions struct {\n\tListOptions\n\n\t// Optional: A search query string. Users are searchable by username and email address.\n\tQuery string `url:\"q,omitempty\"`\n\n\t// Optional: Can be \"true\" or \"false\" to show only administrators or non-administrators.\n\tAdministrators string `url:\"filter[admin],omitempty\"`\n\n\t// Optional: Can be \"true\" or \"false\" to show only suspended users or users who are not suspended.\n\tSuspendedUsers string `url:\"filter[suspended],omitempty\"`\n\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/users#available-related-resources\n\tInclude []AdminUserIncludeOpt `url:\"include,omitempty\"`\n}\n\n// List all user accounts in the Terraform Enterprise installation\nfunc (a *adminUsers) List(ctx context.Context, options *AdminUserListOptions) (*AdminUserList, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := \"admin/users\"\n\treq, err := a.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\taul := &AdminUserList{}\n\terr = req.Do(ctx, aul)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn aul, nil\n}\n\n// Delete a user by its ID.\nfunc (a *adminUsers) Delete(ctx context.Context, userID string) error {\n\tif !validStringID(&userID) {\n\t\treturn ErrInvalidUserValue\n\t}\n\n\tu := fmt.Sprintf(\"admin/users/%s\", url.PathEscape(userID))\n\treq, err := a.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Suspend a user by its ID.\nfunc (a *adminUsers) Suspend(ctx context.Context, userID string) (*AdminUser, error) {\n\tif !validStringID(&userID) {\n\t\treturn nil, ErrInvalidUserValue\n\t}\n\n\tu := fmt.Sprintf(\"admin/users/%s/actions/suspend\", url.PathEscape(userID))\n\treq, err := a.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tau := &AdminUser{}\n\terr = req.Do(ctx, au)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn au, nil\n}\n\n// Unsuspend a user by its ID.\nfunc (a *adminUsers) Unsuspend(ctx context.Context, userID string) (*AdminUser, error) {\n\tif !validStringID(&userID) {\n\t\treturn nil, ErrInvalidUserValue\n\t}\n\n\tu := fmt.Sprintf(\"admin/users/%s/actions/unsuspend\", url.PathEscape(userID))\n\treq, err := a.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tau := &AdminUser{}\n\terr = req.Do(ctx, au)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn au, nil\n}\n\n// GrantAdmin grants admin privileges to a user by its ID.\nfunc (a *adminUsers) GrantAdmin(ctx context.Context, userID string) (*AdminUser, error) {\n\tif !validStringID(&userID) {\n\t\treturn nil, ErrInvalidUserValue\n\t}\n\n\tu := fmt.Sprintf(\"admin/users/%s/actions/grant_admin\", url.PathEscape(userID))\n\treq, err := a.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tau := &AdminUser{}\n\terr = req.Do(ctx, au)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn au, nil\n}\n\n// RevokeAdmin revokes admin privileges to a user by its ID.\nfunc (a *adminUsers) RevokeAdmin(ctx context.Context, userID string) (*AdminUser, error) {\n\tif !validStringID(&userID) {\n\t\treturn nil, ErrInvalidUserValue\n\t}\n\n\tu := fmt.Sprintf(\"admin/users/%s/actions/revoke_admin\", url.PathEscape(userID))\n\treq, err := a.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tau := &AdminUser{}\n\terr = req.Do(ctx, au)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn au, nil\n}\n\n// Disable2FA disables a user's two-factor authentication in the situation\n// where they have lost access to their device and recovery codes.\nfunc (a *adminUsers) Disable2FA(ctx context.Context, userID string) (*AdminUser, error) {\n\tif !validStringID(&userID) {\n\t\treturn nil, ErrInvalidUserValue\n\t}\n\n\tu := fmt.Sprintf(\"admin/users/%s/actions/disable_two_factor\", url.PathEscape(userID))\n\treq, err := a.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tau := &AdminUser{}\n\terr = req.Do(ctx, au)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn au, nil\n}\n\nfunc (o *AdminUserListOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "admin_user_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAdminUsers_List(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tcurrentUser, err := client.Users.ReadCurrent(ctx)\n\trequire.NoError(t, err)\n\n\torg, orgCleanup := createOrganization(t, client)\n\tdefer orgCleanup()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tul, err := client.Admin.Users.List(ctx, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, ul.Items)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tul, err := client.Admin.Users.List(ctx, &AdminUserListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\t// Out of range page number, so the items should be empty\n\t\tassert.Empty(t, ul.Items)\n\t\tassert.Equal(t, 999, ul.CurrentPage)\n\n\t\tul, err = client.Admin.Users.List(ctx, &AdminUserListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, ul.Items)\n\t\tassert.Equal(t, 1, ul.CurrentPage)\n\t})\n\n\tt.Run(\"query by username or email\", func(t *testing.T) {\n\t\tul, err := client.Admin.Users.List(ctx, &AdminUserListOptions{\n\t\t\tQuery: \"admin-security-maintenance\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, ul.CurrentPage)\n\t\tassert.Equal(t, true, ul.TotalCount == 1)\n\n\t\tmember, memberCleanup := createOrganizationMembership(t, client, org)\n\t\tdefer memberCleanup()\n\n\t\tul, err = client.Admin.Users.List(ctx, &AdminUserListOptions{\n\t\t\tQuery: member.User.Email,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, member.User.Email, ul.Items[0].Email)\n\t\tassert.Equal(t, 1, ul.CurrentPage)\n\t\tassert.Equal(t, true, ul.TotalCount == 1)\n\t})\n\n\tt.Run(\"with organization included\", func(t *testing.T) {\n\t\tul, err := client.Admin.Users.List(ctx, &AdminUserListOptions{\n\t\t\tInclude: []AdminUserIncludeOpt{AdminUserOrgs},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, ul.Items)\n\t\trequire.NotEmpty(t, ul.Items[0].Organizations)\n\n\t\tassert.NotEmpty(t, ul.Items[0].Organizations[0].Name)\n\t})\n\n\tt.Run(\"filter by admin\", func(t *testing.T) {\n\t\tul, err := client.Admin.Users.List(ctx, &AdminUserListOptions{\n\t\t\tAdministrators: \"true\",\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, ul.Items)\n\t\trequire.NotNil(t, ul.Items[0])\n\t\t// We use this `includesEmail` helper function because throughout\n\t\t// the tests, there could be multiple admins, depending on the\n\t\t// ordering of the test runs.\n\t\tassert.Equal(t, true, includesEmail(currentUser.Email, ul.Items))\n\t})\n}\n\nfunc TestAdminUsers_Delete(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tdefer orgCleanup()\n\n\tt.Run(\"an existing user\", func(t *testing.T) {\n\t\t// Avoid the member cleanup function because the user\n\t\t// gets deleted below.\n\t\tmember, _ := createOrganizationMembership(t, client, org)\n\n\t\tul, err := client.Admin.Users.List(ctx, &AdminUserListOptions{\n\t\t\tQuery: member.User.Email,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, member.User.Email, ul.Items[0].Email)\n\t\tassert.Equal(t, 1, ul.CurrentPage)\n\t\tassert.Equal(t, true, ul.TotalCount == 1)\n\n\t\terr = client.Admin.Users.Delete(ctx, member.User.ID)\n\t\trequire.NoError(t, err)\n\n\t\tul, err = client.Admin.Users.List(ctx, &AdminUserListOptions{\n\t\t\tQuery: member.User.Email,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, ul.Items)\n\t\tassert.Equal(t, 1, ul.CurrentPage)\n\t\tassert.Equal(t, 0, ul.TotalCount)\n\t})\n\n\tt.Run(\"an non-existing user\", func(t *testing.T) {\n\t\terr := client.Admin.Users.Delete(ctx, \"non-existing-user-id\")\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc TestAdminUsers_Disable2FA(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tdefer orgCleanup()\n\n\tmember, memberCleanup := createOrganizationMembership(t, client, org)\n\tdefer memberCleanup()\n\n\tif !member.User.TwoFactor.Enabled {\n\t\tt.Skip(\"User does not have 2FA enabled. Skipping\")\n\t}\n\tuser, err := client.Admin.Users.Disable2FA(ctx, member.User.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, user)\n}\n\nfunc includesEmail(email string, userList []*AdminUser) bool {\n\tfor _, user := range userList {\n\t\tif user.Email == email {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "admin_workspace.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ AdminWorkspaces = (*adminWorkspaces)(nil)\n\n// AdminWorkspaces describes all the admin workspace related methods that the Terraform Enterprise API supports.\n// Note that admin settings are only available in Terraform Enterprise.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/workspaces\ntype AdminWorkspaces interface {\n\t// List all the workspaces within a workspace.\n\tList(ctx context.Context, options *AdminWorkspaceListOptions) (*AdminWorkspaceList, error)\n\n\t// Read a workspace by its ID.\n\tRead(ctx context.Context, workspaceID string) (*AdminWorkspace, error)\n\n\t// Delete a workspace by its ID.\n\tDelete(ctx context.Context, workspaceID string) error\n}\n\n// adminWorkspaces implements AdminWorkspaces interface.\ntype adminWorkspaces struct {\n\tclient *Client\n}\n\n// AdminVCSRepo represents a VCS repository\ntype AdminVCSRepo struct {\n\tIdentifier string `jsonapi:\"attr,identifier\"`\n}\n\n// AdminWorkspaces represents a Terraform Enterprise admin workspace.\ntype AdminWorkspace struct {\n\tID      string        `jsonapi:\"primary,workspaces\"`\n\tName    string        `jsonapi:\"attr,name\"`\n\tLocked  bool          `jsonapi:\"attr,locked\"`\n\tVCSRepo *AdminVCSRepo `jsonapi:\"attr,vcs-repo\"`\n\n\t// Relations\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n\tCurrentRun   *Run          `jsonapi:\"relation,current-run\"`\n}\n\n// AdminWorkspaceIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/workspaces#available-related-resources\ntype AdminWorkspaceIncludeOpt string\n\nconst (\n\tAdminWorkspaceOrg        AdminWorkspaceIncludeOpt = \"organization\"\n\tAdminWorkspaceCurrentRun AdminWorkspaceIncludeOpt = \"current_run\"\n\tAdminWorkspaceOrgOwners  AdminWorkspaceIncludeOpt = \"organization.owners\"\n)\n\n// AdminWorkspaceListOptions represents the options for listing workspaces.\ntype AdminWorkspaceListOptions struct {\n\tListOptions\n\n\t// A query string (partial workspace name) used to filter the results.\n\t// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/workspaces#query-parameters\n\tQuery string `url:\"q,omitempty\"`\n\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/workspaces#available-related-resources\n\tInclude []AdminWorkspaceIncludeOpt `url:\"include,omitempty\"`\n\n\t// Optional: A comma-separated list of Run statuses to restrict results. See available resources\n\t// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/workspaces#query-parameters\n\tFilter string `url:\"filter[current_run][status],omitempty\"`\n\n\t// Optional: May sort on \"name\" (the default) and \"current-run.created-at\" (which sorts by the time of the current run)\n\t// Prepending a hyphen to the sort parameter will reverse the order (e.g. \"-name\" to reverse the default order)\n\tSort string `url:\"sort,omitempty\"`\n}\n\n// AdminWorkspaceList represents a list of workspaces.\ntype AdminWorkspaceList struct {\n\t*Pagination\n\tItems []*AdminWorkspace\n}\n\n// List all the workspaces within a workspace.\nfunc (s *adminWorkspaces) List(ctx context.Context, options *AdminWorkspaceListOptions) (*AdminWorkspaceList, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := \"admin/workspaces\"\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tawl := &AdminWorkspaceList{}\n\terr = req.Do(ctx, awl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn awl, nil\n}\n\n// Read a workspace by its ID.\nfunc (s *adminWorkspaces) Read(ctx context.Context, workspaceID string) (*AdminWorkspace, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceValue\n\t}\n\n\tu := fmt.Sprintf(\"admin/workspaces/%s\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\taw := &AdminWorkspace{}\n\terr = req.Do(ctx, aw)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn aw, nil\n}\n\n// Delete a workspace by its ID.\nfunc (s *adminWorkspaces) Delete(ctx context.Context, workspaceID string) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceValue\n\t}\n\n\tu := fmt.Sprintf(\"admin/workspaces/%s\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o *AdminWorkspaceListOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "admin_workspace_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// BEWARE: The admin workspaces API can view all of the workspaces created by\n// EVERY test organization in EVERY concurrent test run (or other usage) for the\n// current HCP Terraform instance. It's generally not safe to assume that the workspaces\n// you create in a given test will be within the first page of list results, so\n// you might have to get creative and/or settle for less when testing the\n// behavior of these endpoints.\n\nfunc TestAdminWorkspaces_ListWithFilter_RunDependent(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tdefer orgCleanup()\n\n\twTest1, wTest1Cleanup := createWorkspace(t, client, org)\n\tdefer wTest1Cleanup()\n\n\twTest2, wTest2Cleanup := createWorkspace(t, client, org)\n\tdefer wTest2Cleanup()\n\n\tt.Run(\"when filtering workspaces on a current run status\", func(t *testing.T) {\n\t\t_, appliedCleanup := createRunApply(t, client, wTest1)\n\t\tt.Cleanup(appliedCleanup)\n\n\t\t_, unAppliedCleanup := createRunUnapplied(t, client, wTest2)\n\t\tt.Cleanup(unAppliedCleanup)\n\n\t\twl, err := client.Admin.Workspaces.List(ctx, &AdminWorkspaceListOptions{\n\t\t\tFilter: string(RunApplied), Include: []AdminWorkspaceIncludeOpt{AdminWorkspaceCurrentRun},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, wl.Items)\n\t\tassert.Equal(t, wl.Items[0].CurrentRun.Status, RunApplied)\n\t\tassert.NotContains(t, wl.Items, wTest2)\n\t})\n}\n\nfunc TestAdminWorkspaces_ListWithSort_RunDependent(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tdefer orgCleanup()\n\n\twTest1, wTest1Cleanup := createWorkspace(t, client, org)\n\tdefer wTest1Cleanup()\n\n\twTest2, wTest2Cleanup := createWorkspace(t, client, org)\n\tdefer wTest2Cleanup()\n\n\tt.Run(\"when sorting by workspace names\", func(t *testing.T) {\n\t\twl, err := client.Admin.Workspaces.List(ctx, &AdminWorkspaceListOptions{\n\t\t\tSort: \"name\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, wl.Items)\n\t\trequire.GreaterOrEqual(t, len(wl.Items), 2)\n\t\tassert.Equal(t, wl.Items[0].Name < wl.Items[1].Name, true)\n\t})\n\n\tt.Run(\"when sorting workspaces on current-run.created-at\", func(t *testing.T) {\n\t\t_, unappliedCleanup1 := createRunUnapplied(t, client, wTest1)\n\t\tt.Cleanup(unappliedCleanup1)\n\n\t\t_, unappliedCleanup2 := createRunUnapplied(t, client, wTest2)\n\t\tt.Cleanup(unappliedCleanup2)\n\n\t\twl, err := client.Admin.Workspaces.List(ctx, &AdminWorkspaceListOptions{\n\t\t\tInclude: []AdminWorkspaceIncludeOpt{AdminWorkspaceCurrentRun},\n\t\t\tSort:    \"current-run.created-at\",\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, wl.Items)\n\t\trequire.GreaterOrEqual(t, len(wl.Items), 2)\n\t\tassert.True(t, wl.Items[1].CurrentRun.CreatedAt.After(wl.Items[0].CurrentRun.CreatedAt))\n\t})\n}\n\nfunc TestAdminWorkspaces_List_RunDependent(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tdefer orgCleanup()\n\n\twTest1, wTest1Cleanup := createWorkspace(t, client, org)\n\tdefer wTest1Cleanup()\n\n\twTest2, wTest2Cleanup := createWorkspace(t, client, org)\n\tdefer wTest2Cleanup()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\twl, err := client.Admin.Workspaces.List(ctx, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.GreaterOrEqual(t, len(wl.Items), 2)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\twl, err := client.Admin.Workspaces.List(ctx, &AdminWorkspaceListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\t// Out of range page number, so the items should be empty\n\t\tassert.Empty(t, wl.Items)\n\t\tassert.Equal(t, 999, wl.CurrentPage)\n\n\t\twl, err = client.Admin.Workspaces.List(ctx, &AdminWorkspaceListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, wl.CurrentPage)\n\t\tassert.Equal(t, adminWorkspaceItemsContainsID(wl.Items, wTest1.ID), true)\n\t\tassert.Equal(t, adminWorkspaceItemsContainsID(wl.Items, wTest2.ID), true)\n\t})\n\n\tt.Run(\"when searching a known workspace\", func(t *testing.T) {\n\t\t// Use a known workspace prefix as search attribute. The result\n\t\t// should be successful and only contain the matching workspace.\n\t\twl, err := client.Admin.Workspaces.List(ctx, &AdminWorkspaceListOptions{\n\t\t\tQuery: wTest1.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, adminWorkspaceItemsContainsID(wl.Items, wTest1.ID), true)\n\t\tassert.Equal(t, adminWorkspaceItemsContainsID(wl.Items, wTest2.ID), false)\n\t\tassert.Equal(t, 1, wl.CurrentPage)\n\t\tassert.Equal(t, true, wl.TotalCount == 1)\n\t})\n\n\tt.Run(\"when searching an unknown workspace\", func(t *testing.T) {\n\t\t// Use a nonexisting workspace name as search attribute. The result\n\t\t// should be successful, but return no results.\n\t\twl, err := client.Admin.Workspaces.List(ctx, &AdminWorkspaceListOptions{\n\t\t\tQuery: \"nonexisting\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, wl.Items)\n\t\tassert.Equal(t, 1, wl.CurrentPage)\n\t\tassert.Equal(t, 0, wl.TotalCount)\n\t})\n\n\tt.Run(\"with organization included\", func(t *testing.T) {\n\t\twl, err := client.Admin.Workspaces.List(ctx, &AdminWorkspaceListOptions{\n\t\t\tInclude: []AdminWorkspaceIncludeOpt{AdminWorkspaceOrg},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, wl.Items)\n\t\trequire.NotNil(t, wl.Items[0].Organization)\n\t\tassert.NotEmpty(t, wl.Items[0].Organization.Name)\n\t})\n\n\t// This sub-test should remain last because it creates a run that does not apply\n\t// Any subsequent runs will be queued until a timeout is triggered\n\tt.Run(\"with current_run included\", func(t *testing.T) {\n\t\tcvTest, cvCleanup := createUploadedConfigurationVersion(t, client, wTest1)\n\t\tdefer cvCleanup()\n\n\t\trunOpts := RunCreateOptions{\n\t\t\tConfigurationVersion: cvTest,\n\t\t\tWorkspace:            wTest1,\n\t\t}\n\t\trun, err := client.Runs.Create(ctx, runOpts)\n\t\trequire.NoError(t, err)\n\n\t\twl, err := client.Admin.Workspaces.List(ctx, &AdminWorkspaceListOptions{\n\t\t\tInclude: []AdminWorkspaceIncludeOpt{AdminWorkspaceCurrentRun},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, wl.Items)\n\t\trequire.NotNil(t, wl.Items[0].CurrentRun)\n\t\tassert.Equal(t, wl.Items[0].CurrentRun.ID, run.ID)\n\t})\n}\n\nfunc TestAdminWorkspaces_Read(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"it fails to read a workspace with an invalid name\", func(t *testing.T) {\n\t\tworkspace, err := client.Admin.Workspaces.Read(ctx, \"\")\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceValue.Error())\n\t\tassert.Nil(t, workspace)\n\t})\n\n\tt.Run(\"it fails to read a workspace that is non existent\", func(t *testing.T) {\n\t\tworkspaceID := fmt.Sprintf(\"non-existent-%s\", randomString(t))\n\t\tadminWorkspace, err := client.Admin.Workspaces.Read(ctx, workspaceID)\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t\tassert.Nil(t, adminWorkspace)\n\t})\n\n\tt.Run(\"it reads a workspace successfully\", func(t *testing.T) {\n\t\torg, orgCleanup := createOrganization(t, client)\n\t\tdefer orgCleanup()\n\n\t\tworkspace, workspaceCleanup := createWorkspace(t, client, org)\n\t\tdefer workspaceCleanup()\n\n\t\tadminWorkspace, err := client.Admin.Workspaces.Read(ctx, workspace.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNilf(t, adminWorkspace, \"Admin Workspace is not nil\")\n\t\tassert.Equal(t, adminWorkspace.ID, workspace.ID)\n\t\tassert.Equal(t, adminWorkspace.Name, workspace.Name)\n\t\tassert.Equal(t, adminWorkspace.Locked, workspace.Locked)\n\t})\n}\n\nfunc TestAdminWorkspaces_Delete(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"it fails to delete an organization with an invalid id\", func(t *testing.T) {\n\t\terr := client.Admin.Workspaces.Delete(ctx, \"\")\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceValue.Error())\n\t})\n\n\tt.Run(\"it fails to delete an organization with an bad org name\", func(t *testing.T) {\n\t\tworkspaceID := fmt.Sprintf(\"non-existent-%s\", randomString(t))\n\t\terr := client.Admin.Workspaces.Delete(ctx, workspaceID)\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n\n\tt.Run(\"it deletes a workspace successfully\", func(t *testing.T) {\n\t\torg, orgCleanup := createOrganization(t, client)\n\t\tdefer orgCleanup()\n\n\t\tworkspace, _ := createWorkspace(t, client, org)\n\n\t\tadminWorkspace, err := client.Admin.Workspaces.Read(ctx, workspace.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNilf(t, adminWorkspace, \"Admin Workspace is not nil\")\n\t\tassert.Equal(t, adminWorkspace.ID, workspace.ID)\n\n\t\terr = client.Admin.Workspaces.Delete(ctx, adminWorkspace.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Cannot find deleted workspace\n\t\t_, err = client.Admin.Workspaces.Read(ctx, workspace.ID)\n\t\tassert.Error(t, err)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n\nfunc adminWorkspaceItemsContainsID(items []*AdminWorkspace, id string) bool {\n\thasID := false\n\tfor _, item := range items {\n\t\tif item.ID == id {\n\t\t\thasID = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn hasID\n}\n\nfunc TestAdminWorkspace_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"workspaces\",\n\t\t\t\"id\":   \"workspaces-VCsNJXa59eUza53R\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"name\":   \"workspace-name\",\n\t\t\t\t\"locked\": false,\n\t\t\t\t\"vcs-repo\": map[string]string{\n\t\t\t\t\t\"identifier\": \"github\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tbyteData, err := json.Marshal(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tadminWorkspace := &AdminWorkspace{}\n\tresponseBody := bytes.NewReader(byteData)\n\terr = unmarshalResponse(responseBody, adminWorkspace)\n\trequire.NoError(t, err)\n\tassert.Equal(t, adminWorkspace.ID, \"workspaces-VCsNJXa59eUza53R\")\n\tassert.Equal(t, adminWorkspace.Name, \"workspace-name\")\n\tassert.Equal(t, adminWorkspace.Locked, false)\n\tassert.Equal(t, adminWorkspace.VCSRepo.Identifier, \"github\")\n}\n"
  },
  {
    "path": "agent.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Agents = (*agents)(nil)\n\n// Agents describes all the agent-related methods that the\n// HCP Terraform API supports.\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/agents\ntype Agents interface {\n\t// Read an agent by its ID.\n\tRead(ctx context.Context, agentID string) (*Agent, error)\n\n\t// List all the agents of the given pool.\n\tList(ctx context.Context, agentPoolID string, options *AgentListOptions) (*AgentList, error)\n}\n\n// agents implements Agents.\ntype agents struct {\n\tclient *Client\n}\n\n// AgentList represents a list of agents.\ntype AgentList struct {\n\t*Pagination\n\tItems []*Agent\n}\n\n// Agent represents a HCP Terraform agent.\ntype Agent struct {\n\tID         string `jsonapi:\"primary,agents\"`\n\tName       string `jsonapi:\"attr,name\"`\n\tIP         string `jsonapi:\"attr,ip-address\"`\n\tStatus     string `jsonapi:\"attr,status\"`\n\tLastPingAt string `jsonapi:\"attr,last-ping-at\"`\n}\n\ntype AgentListOptions struct {\n\tListOptions\n\n\t//Optional:\n\tLastPingSince time.Time `url:\"filter[last-ping-since],omitempty,iso8601\"`\n\n\t// Optional: Allows sorting the agents by \"created-by\"\n\tSort string `url:\"sort,omitempty\"`\n}\n\n// Read a single agent by its ID\nfunc (s *agents) Read(ctx context.Context, agentID string) (*Agent, error) {\n\tif !validStringID(&agentID) {\n\t\treturn nil, ErrInvalidAgentID\n\t}\n\n\tu := fmt.Sprintf(\"agents/%s\", url.PathEscape(agentID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tagent := &Agent{}\n\terr = req.Do(ctx, agent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn agent, nil\n}\n\n// List all the agents of the given organization.\nfunc (s *agents) List(ctx context.Context, agentPoolID string, options *AgentListOptions) (*AgentList, error) {\n\tif !validStringID(&agentPoolID) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"agent-pools/%s/agents\", url.PathEscape(agentPoolID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tagentList := &AgentList{}\n\terr = req.Do(ctx, agentList)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn agentList, nil\n}\n"
  },
  {
    "path": "agent_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAgentsRead(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessLinuxAMD64(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tt.Cleanup(orgCleanup)\n\n\tupgradeOrganizationSubscription(t, client, org)\n\n\tagent, _, agentCleanup := createAgent(t, client, org)\n\n\tt.Cleanup(agentCleanup)\n\n\tt.Run(\"when the agent exists\", func(t *testing.T) {\n\t\tk, err := client.Agents.Read(ctx, agent.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, agent, k)\n\t})\n\n\tt.Run(\"when the agent does not exist\", func(t *testing.T) {\n\t\tk, err := client.Agents.Read(ctx, \"nonexistent\")\n\t\tassert.Nil(t, k)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without a valid agent ID\", func(t *testing.T) {\n\t\tk, err := client.Agents.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, k)\n\t\tassert.EqualError(t, err, ErrInvalidAgentID.Error())\n\t})\n}\n\nfunc TestAgentsList(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessLinuxAMD64(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tt.Cleanup(orgCleanup)\n\n\tupgradeOrganizationSubscription(t, client, org)\n\n\t_, agentPool, agentCleanup := createAgent(t, client, org)\n\tt.Cleanup(agentCleanup)\n\n\tt.Run(\"expect an agent to exist\", func(t *testing.T) {\n\t\tagent, err := client.Agents.List(ctx, agentPool.ID, nil)\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, agent.Items)\n\t\tassert.NotEmpty(t, agent.Items[0].ID)\n\t})\n\n\tt.Run(\"without a valid agent pool ID\", func(t *testing.T) {\n\t\tagent, err := client.Agents.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, agent)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n"
  },
  {
    "path": "agent_pool.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ AgentPools = (*agentPools)(nil)\n\n// AgentPools describes all the agent pool related methods that the HCP Terraform\n// API supports. Note that agents are not available in Terraform Enterprise.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/agents\ntype AgentPools interface {\n\t// List all the agent pools of the given organization.\n\tList(ctx context.Context, organization string, options *AgentPoolListOptions) (*AgentPoolList, error)\n\n\t// Create a new agent pool with the given options.\n\tCreate(ctx context.Context, organization string, options AgentPoolCreateOptions) (*AgentPool, error)\n\n\t// Read an agent pool by its ID.\n\tRead(ctx context.Context, agentPoolID string) (*AgentPool, error)\n\n\t// Read an agent pool by its ID with the given options.\n\tReadWithOptions(ctx context.Context, agentPoolID string, options *AgentPoolReadOptions) (*AgentPool, error)\n\n\t// Update an agent pool by its ID.\n\tUpdate(ctx context.Context, agentPool string, options AgentPoolUpdateOptions) (*AgentPool, error)\n\n\t// UpdateAllowedWorkspaces updates the list of allowed workspaces associated with an agent pool.\n\tUpdateAllowedWorkspaces(ctx context.Context, agentPool string, options AgentPoolAllowedWorkspacesUpdateOptions) (*AgentPool, error)\n\n\t// UpdateAllowedProjects updates the list of allowed projects associated with an agent pool.\n\tUpdateAllowedProjects(ctx context.Context, agentPool string, options AgentPoolAllowedProjectsUpdateOptions) (*AgentPool, error)\n\n\t// UpdateExcludedWorkspaces updates the list of excluded workspaces associated with an agent pool.\n\tUpdateExcludedWorkspaces(ctx context.Context, agentPool string, options AgentPoolExcludedWorkspacesUpdateOptions) (*AgentPool, error)\n\n\t// Delete an agent pool by its ID.\n\tDelete(ctx context.Context, agentPoolID string) error\n}\n\n// agentPools implements AgentPools.\ntype agentPools struct {\n\tclient *Client\n}\n\n// AgentPoolList represents a list of agent pools.\ntype AgentPoolList struct {\n\t*Pagination\n\tItems []*AgentPool\n}\n\n// AgentPool represents a HCP Terraform agent pool.\ntype AgentPool struct {\n\tID                 string    `jsonapi:\"primary,agent-pools\"`\n\tName               string    `jsonapi:\"attr,name\"`\n\tAgentCount         int       `jsonapi:\"attr,agent-count\"`\n\tOrganizationScoped bool      `jsonapi:\"attr,organization-scoped\"`\n\tCreatedAt          time.Time `jsonapi:\"attr,created-at,iso8601\"`\n\n\t// Relations\n\tOrganization       *Organization        `jsonapi:\"relation,organization\"`\n\tHYOKConfigurations []*HYOKConfiguration `jsonapi:\"relation,hyok-configurations\"`\n\tWorkspaces         []*Workspace         `jsonapi:\"relation,workspaces\"`\n\tAllowedWorkspaces  []*Workspace         `jsonapi:\"relation,allowed-workspaces\"`\n\tAllowedProjects    []*Project           `jsonapi:\"relation,allowed-projects\"`\n\tExcludedWorkspaces []*Workspace         `jsonapi:\"relation,excluded-workspaces\"`\n}\n\n// A list of relations to include\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/agents#available-related-resources\ntype AgentPoolIncludeOpt string\n\nconst (\n\tAgentPoolWorkspaces         AgentPoolIncludeOpt = \"workspaces\"\n\tAgentPoolHYOKConfigurations AgentPoolIncludeOpt = \"hyok-configurations\"\n)\n\ntype AgentPoolReadOptions struct {\n\tInclude []AgentPoolIncludeOpt `url:\"include,omitempty\"`\n}\n\n// AgentPoolListOptions represents the options for listing agent pools.\ntype AgentPoolListOptions struct {\n\tListOptions\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/agents#available-related-resources\n\tInclude []AgentPoolIncludeOpt `url:\"include,omitempty\"`\n\n\t// Optional: A search query string used to filter agent pool. Agent pools are searchable by name\n\tQuery string `url:\"q,omitempty\"`\n\n\t// Optional: String (workspace name) used to filter the results.\n\tAllowedWorkspacesName string `url:\"filter[allowed_workspaces][name],omitempty\"`\n\n\t// Optional: String (project name) used to filter the results.\n\tAllowedProjectsName string `url:\"filter[allowed_projects][name],omitempty\"`\n\n\t// Optional: Allows sorting the agent pools by \"created-by\" or \"name\"\n\tSort string `url:\"sort,omitempty\"`\n}\n\n// AgentPoolCreateOptions represents the options for creating an agent pool.\ntype AgentPoolCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,agent-pools\"`\n\n\t// Required: A name to identify the agent pool.\n\tName *string `jsonapi:\"attr,name\"`\n\n\t// True if the agent pool is organization scoped, false otherwise.\n\tOrganizationScoped *bool `jsonapi:\"attr,organization-scoped,omitempty\"`\n\n\t// List of workspaces that are associated with an agent pool.\n\tAllowedWorkspaces []*Workspace `jsonapi:\"relation,allowed-workspaces,omitempty\"`\n\n\t// List of projects that are associated with an agent pool.\n\tAllowedProjects []*Project `jsonapi:\"relation,allowed-projects,omitempty\"`\n\n\t// List of workspaces that are excluded from the scope of an agent pool.\n\tExcludedWorkspaces []*Workspace `jsonapi:\"relation,excluded-workspaces,omitempty\"`\n}\n\n// List all the agent pools of the given organization.\nfunc (s *agentPools) List(ctx context.Context, organization string, options *AgentPoolListOptions) (*AgentPoolList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/agent-pools\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpoolList := &AgentPoolList{}\n\terr = req.Do(ctx, poolList)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn poolList, nil\n}\n\n// Create a new agent pool with the given options.\nfunc (s *agentPools) Create(ctx context.Context, organization string, options AgentPoolCreateOptions) (*AgentPool, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/agent-pools\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpool := &AgentPool{}\n\terr = req.Do(ctx, pool)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pool, nil\n}\n\n// Read a single agent pool by its ID\nfunc (s *agentPools) Read(ctx context.Context, agentpoolID string) (*AgentPool, error) {\n\treturn s.ReadWithOptions(ctx, agentpoolID, nil)\n}\n\n// Read a single agent pool by its ID with options.\nfunc (s *agentPools) ReadWithOptions(ctx context.Context, agentpoolID string, options *AgentPoolReadOptions) (*AgentPool, error) {\n\tif !validStringID(&agentpoolID) {\n\t\treturn nil, ErrInvalidAgentPoolID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"agent-pools/%s\", url.PathEscape(agentpoolID))\n\treq, err := s.client.NewRequest(\"GET\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpool := &AgentPool{}\n\terr = req.Do(ctx, pool)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pool, nil\n}\n\n// AgentPoolUpdateOptions represents the options for updating an agent pool.\ntype AgentPoolUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,agent-pools\"`\n\n\t// A new name to identify the agent pool.\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// True if the agent pool is organization scoped, false otherwise.\n\tOrganizationScoped *bool `jsonapi:\"attr,organization-scoped,omitempty\"`\n\n\t// A new list of workspaces that are associated with an agent pool.\n\tAllowedWorkspaces []*Workspace `jsonapi:\"relation,allowed-workspaces,omitempty\"`\n\n\t// A new list of projects that are associated with an agent pool.\n\tAllowedProjects []*Project `jsonapi:\"relation,allowed-projects,omitempty\"`\n\n\t// A new list of workspaces that are excluded from the scope of an agent pool.\n\tExcludedWorkspaces []*Workspace `jsonapi:\"relation,excluded-workspaces,omitempty\"`\n}\n\n// AgentPoolAllowedWorkspacesUpdateOptions represents the options for updating the allowed workspace on an agent pool\ntype AgentPoolAllowedWorkspacesUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,agent-pools\"`\n\n\t// A new list of workspaces that are associated with an agent pool.\n\tAllowedWorkspaces []*Workspace `jsonapi:\"relation,allowed-workspaces\"`\n}\n\n// AgentPoolAllowedProjectsUpdateOptions represents the options for updating the allowed projects on an agent pool\ntype AgentPoolAllowedProjectsUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,agent-pools\"`\n\n\t// A new list of projects that are associated with an agent pool.\n\tAllowedProjects []*Project `jsonapi:\"relation,allowed-projects\"`\n}\n\n// AgentPoolExcludedWorkspacesUpdateOptions represents the options for updating the excluded workspace on an agent pool\ntype AgentPoolExcludedWorkspacesUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,agent-pools\"`\n\n\t// A new list of workspaces that are excluded from the scope of an agent pool.\n\tExcludedWorkspaces []*Workspace `jsonapi:\"relation,excluded-workspaces\"`\n}\n\n// Update an agent pool by its ID.\n// **Note:** This method cannot be used to clear the allowed workspaces, allowed projects, or excluded workspaces fields.\n// instead use UpdateAllowedWorkspaces, UpdateAllowedProjects, or UpdateExcludedWorkspaces methods respectively.\nfunc (s *agentPools) Update(ctx context.Context, agentPoolID string, options AgentPoolUpdateOptions) (*AgentPool, error) {\n\tif !validStringID(&agentPoolID) {\n\t\treturn nil, ErrInvalidAgentPoolID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"agent-pools/%s\", url.PathEscape(agentPoolID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tk := &AgentPool{}\n\terr = req.Do(ctx, k)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn k, nil\n}\n\nfunc (s *agentPools) UpdateAllowedWorkspaces(ctx context.Context, agentPoolID string, options AgentPoolAllowedWorkspacesUpdateOptions) (*AgentPool, error) {\n\treturn s.updateArrayAttribute(ctx, agentPoolID, &options)\n}\n\nfunc (s *agentPools) UpdateAllowedProjects(ctx context.Context, agentPoolID string, options AgentPoolAllowedProjectsUpdateOptions) (*AgentPool, error) {\n\treturn s.updateArrayAttribute(ctx, agentPoolID, &options)\n}\n\nfunc (s *agentPools) UpdateExcludedWorkspaces(ctx context.Context, agentPoolID string, options AgentPoolExcludedWorkspacesUpdateOptions) (*AgentPool, error) {\n\treturn s.updateArrayAttribute(ctx, agentPoolID, &options)\n}\n\n// Delete an agent pool by its ID.\nfunc (s *agentPools) Delete(ctx context.Context, agentPoolID string) error {\n\tif !validStringID(&agentPoolID) {\n\t\treturn ErrInvalidAgentPoolID\n\t}\n\n\tu := fmt.Sprintf(\"agent-pools/%s\", url.PathEscape(agentPoolID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// updateArrayAttribute is a helper function to update array attributes of an agent pool, such as allowed workspaces, allowed projects, or excluded workspaces.\n// Note: This function does not validate the options parameter, so it should be used with caution.  It is intended to be used with options structs\n// (e.g. AgentPoolAllowedWorkspacesUpdateOptions, AgentPoolAllowedProjectsUpdateOptions, AgentPoolExcludedWorkspacesUpdateOptions) whose array\n// attributes are NOT marked `omitempty`, so that an empty array is sent to the API to clear the existing values.\nfunc (s *agentPools) updateArrayAttribute(ctx context.Context, agentPoolID string, options any) (*AgentPool, error) {\n\tif !validStringID(&agentPoolID) {\n\t\treturn nil, ErrInvalidAgentPoolID\n\t}\n\n\tu := fmt.Sprintf(\"agent-pools/%s\", url.PathEscape(agentPoolID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tk := &AgentPool{}\n\terr = req.Do(ctx, k)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn k, nil\n}\n\nfunc (o AgentPoolCreateOptions) valid() error {\n\tif !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\tif !validStringID(o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\treturn nil\n}\n\nfunc (o AgentPoolUpdateOptions) valid() error {\n\tif o.Name != nil && !validStringID(o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\treturn nil\n}\n\nfunc (o *AgentPoolReadOptions) valid() error {\n\treturn nil\n}\n\nfunc (o *AgentPoolListOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "agent_pool_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAgentPoolsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tagentPool, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\tt.Cleanup(agentPoolCleanup)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tpools, err := client.AgentPools.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, pools.Items, agentPool)\n\n\t\tassert.Equal(t, 1, pools.CurrentPage)\n\t\tassert.Equal(t, 1, pools.TotalCount)\n\t})\n\n\tt.Run(\"with Include option\", func(t *testing.T) {\n\t\t_, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName:          String(\"bar\"),\n\t\t\tExecutionMode: String(\"agent\"),\n\t\t\tAgentPoolID:   String(agentPool.ID),\n\t\t})\n\t\tt.Cleanup(wTestCleanup)\n\n\t\tk, err := client.AgentPools.List(ctx, orgTest.Name, &AgentPoolListOptions{\n\t\t\tInclude: []AgentPoolIncludeOpt{AgentPoolWorkspaces},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, k.Items)\n\t\trequire.NotEmpty(t, k.Items[0].Workspaces)\n\t\tassert.NotNil(t, k.Items[0].Workspaces[0])\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tpools, err := client.AgentPools.List(ctx, orgTest.Name, &AgentPoolListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, pools.Items)\n\t\tassert.Equal(t, 999, pools.CurrentPage)\n\t\tassert.Equal(t, 1, pools.TotalCount)\n\t})\n\n\tt.Run(\"with sorting\", func(t *testing.T) {\n\t\tagentPool2, agentPoolCleanup2 := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(agentPoolCleanup2)\n\n\t\tpools, err := client.AgentPools.List(ctx, orgTest.Name, &AgentPoolListOptions{\n\t\t\tSort: \"created-at\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, pools)\n\t\trequire.Len(t, pools.Items, 2)\n\t\trequire.Equal(t, []string{agentPool.ID, agentPool2.ID}, []string{pools.Items[0].ID, pools.Items[1].ID})\n\n\t\tpools, err = client.AgentPools.List(ctx, orgTest.Name, &AgentPoolListOptions{\n\t\t\tSort: \"-created-at\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, pools)\n\t\trequire.Len(t, pools.Items, 2)\n\t\trequire.Equal(t, []string{agentPool2.ID, agentPool.ID}, []string{pools.Items[0].ID, pools.Items[1].ID})\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tpools, err := client.AgentPools.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, pools)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"with query options\", func(t *testing.T) {\n\t\tpools, err := client.AgentPools.List(ctx, orgTest.Name, &AgentPoolListOptions{\n\t\t\tQuery: agentPool.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, len(pools.Items), 1)\n\n\t\tpools, err = client.AgentPools.List(ctx, orgTest.Name, &AgentPoolListOptions{\n\t\t\tQuery: agentPool.Name + \"not_going_to_match\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, pools.Items)\n\t})\n\n\tt.Run(\"with allowed workspace name filter\", func(t *testing.T) {\n\t\tws1, ws1TestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(ws1TestCleanup)\n\n\t\tws2, ws2TestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(ws2TestCleanup)\n\n\t\torganizationScoped := false\n\t\tap, apCleanup := createAgentPoolWithOptions(t, client, orgTest, AgentPoolCreateOptions{\n\t\t\tName:               String(\"a-pool\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tAllowedWorkspaces:  []*Workspace{ws1},\n\t\t})\n\t\tt.Cleanup(apCleanup)\n\n\t\tap2, ap2Cleanup := createAgentPoolWithOptions(t, client, orgTest, AgentPoolCreateOptions{\n\t\t\tName:               String(\"b-pool\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tAllowedWorkspaces:  []*Workspace{ws2},\n\t\t})\n\t\tt.Cleanup(ap2Cleanup)\n\n\t\tpools, err := client.AgentPools.List(ctx, orgTest.Name, &AgentPoolListOptions{\n\t\t\tAllowedWorkspacesName: ws1.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, pools.Items)\n\t\tassert.Contains(t, pools.Items, ap)\n\t\tassert.Contains(t, pools.Items, agentPool)\n\t\tassert.Equal(t, 2, pools.TotalCount)\n\n\t\tpools, err = client.AgentPools.List(ctx, orgTest.Name, &AgentPoolListOptions{\n\t\t\tAllowedWorkspacesName: ws2.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, pools.Items)\n\t\tassert.Contains(t, pools.Items, agentPool)\n\t\tassert.Contains(t, pools.Items, ap2)\n\t\tassert.Equal(t, 2, pools.TotalCount)\n\t})\n\n\tt.Run(\"with allowed projects name filter\", func(t *testing.T) {\n\t\tproj1, proj1TestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(proj1TestCleanup)\n\n\t\tproj2, proj2TestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(proj2TestCleanup)\n\n\t\torganizationScoped := false\n\t\tap, apCleanup := createAgentPoolWithOptions(t, client, orgTest, AgentPoolCreateOptions{\n\t\t\tName:               String(\"a-pool\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tAllowedProjects:    []*Project{proj1},\n\t\t})\n\t\tt.Cleanup(apCleanup)\n\n\t\tap2, ap2Cleanup := createAgentPoolWithOptions(t, client, orgTest, AgentPoolCreateOptions{\n\t\t\tName:               String(\"b-pool\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tAllowedProjects:    []*Project{proj2},\n\t\t})\n\t\tt.Cleanup(ap2Cleanup)\n\n\t\tpools, err := client.AgentPools.List(ctx, orgTest.Name, &AgentPoolListOptions{\n\t\t\tAllowedProjectsName: proj1.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, pools.Items)\n\t\tassert.Contains(t, pools.Items, ap)\n\t\tassert.Contains(t, pools.Items, agentPool)\n\t\tassert.Equal(t, 2, pools.TotalCount)\n\n\t\tpools, err = client.AgentPools.List(ctx, orgTest.Name, &AgentPoolListOptions{\n\t\t\tAllowedProjectsName: proj2.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, pools.Items)\n\t\tassert.Contains(t, pools.Items, agentPool)\n\t\tassert.Contains(t, pools.Items, ap2)\n\t\tassert.Equal(t, 2, pools.TotalCount)\n\t})\n}\n\nfunc TestAgentPoolsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := AgentPoolCreateOptions{\n\t\t\tName: String(\"cool-pool\"),\n\t\t}\n\n\t\tpool, err := client.AgentPools.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.AgentPools.Read(ctx, pool.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*AgentPool{\n\t\t\tpool,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t}\n\t})\n\n\tt.Run(\"when options is missing name\", func(t *testing.T) {\n\t\tk, err := client.AgentPools.Create(ctx, \"foo\", AgentPoolCreateOptions{})\n\t\tassert.Nil(t, k)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"with an invalid organization\", func(t *testing.T) {\n\t\tpool, err := client.AgentPools.Create(ctx, badIdentifier, AgentPoolCreateOptions{\n\t\t\tName: String(\"cool-pool\"),\n\t\t})\n\t\tassert.Nil(t, pool)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"with allowed-workspaces options\", func(t *testing.T) {\n\t\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceTestCleanup)\n\n\t\torganizationScoped := false\n\t\toptions := AgentPoolCreateOptions{\n\t\t\tName:               String(\"a-pool\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tAllowedWorkspaces: []*Workspace{\n\t\t\t\tworkspaceTest,\n\t\t\t},\n\t\t}\n\n\t\tpool, err := client.AgentPools.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, 1, len(pool.AllowedWorkspaces))\n\t\tassert.Equal(t, workspaceTest.ID, pool.AllowedWorkspaces[0].ID)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.AgentPools.Read(ctx, pool.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*AgentPool{\n\t\t\tpool,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t}\n\t})\n\n\tt.Run(\"with allowed-projects options\", func(t *testing.T) {\n\t\tprojectTest, projectTestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(projectTestCleanup)\n\n\t\torganizationScoped := false\n\t\toptions := AgentPoolCreateOptions{\n\t\t\tName:               String(\"a-pool-2\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tAllowedProjects: []*Project{\n\t\t\t\tprojectTest,\n\t\t\t},\n\t\t}\n\n\t\tpool, err := client.AgentPools.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, 1, len(pool.AllowedProjects))\n\t\tassert.Equal(t, projectTest.ID, pool.AllowedProjects[0].ID)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.AgentPools.Read(ctx, pool.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*AgentPool{\n\t\t\tpool,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t}\n\t})\n\n\tt.Run(\"with excluded-workspaces options\", func(t *testing.T) {\n\t\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceTestCleanup)\n\n\t\torganizationScoped := false\n\t\toptions := AgentPoolCreateOptions{\n\t\t\tName:               String(\"a-pool-3\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tExcludedWorkspaces: []*Workspace{\n\t\t\t\tworkspaceTest,\n\t\t\t},\n\t\t}\n\n\t\tpool, err := client.AgentPools.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, 1, len(pool.ExcludedWorkspaces))\n\t\tassert.Equal(t, workspaceTest.ID, pool.ExcludedWorkspaces[0].ID)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.AgentPools.Read(ctx, pool.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*AgentPool{\n\t\t\tpool,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t}\n\t})\n}\n\nfunc TestAgentPoolsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tpool, poolCleanup := createAgentPool(t, client, orgTest)\n\tt.Cleanup(poolCleanup)\n\n\tt.Run(\"when the agent pool exists\", func(t *testing.T) {\n\t\tk, err := client.AgentPools.Read(ctx, pool.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, pool, k)\n\t})\n\n\tt.Run(\"when the agent pool does not exist\", func(t *testing.T) {\n\t\tk, err := client.AgentPools.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, k)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without a valid agent pool ID\", func(t *testing.T) {\n\t\tk, err := client.AgentPools.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, k)\n\t\tassert.EqualError(t, err, ErrInvalidAgentPoolID.Error())\n\t})\n\n\tt.Run(\"with Include option\", func(t *testing.T) {\n\t\t_, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName:          String(\"foo\"),\n\t\t\tExecutionMode: String(\"agent\"),\n\t\t\tAgentPoolID:   String(pool.ID),\n\t\t})\n\t\tt.Cleanup(wTestCleanup)\n\n\t\tk, err := client.AgentPools.ReadWithOptions(ctx, pool.ID, &AgentPoolReadOptions{\n\t\t\tInclude: []AgentPoolIncludeOpt{AgentPoolWorkspaces},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, k.Workspaces[0])\n\t})\n\n\tt.Run(\"read hyok configurations of an agent pool\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid agent pool ID that has HYOK configurations\n\t\thyokPoolID := os.Getenv(\"HYOK_POOL_ID\")\n\t\tif hyokPoolID == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_POOL_ID before running this test!\")\n\t\t}\n\n\t\tk, err := client.AgentPools.ReadWithOptions(ctx, hyokPoolID, &AgentPoolReadOptions{\n\t\t\tInclude: []AgentPoolIncludeOpt{AgentPoolHYOKConfigurations},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, k.HYOKConfigurations)\n\t})\n}\n\nfunc TestAgentPoolsReadCreatedAt(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpool, poolCleanup := createAgentPool(t, client, orgTest)\n\tdefer poolCleanup()\n\n\tk, err := client.AgentPools.Read(ctx, pool.ID)\n\tassert.NotEmpty(t, k.CreatedAt)\n\trequire.NoError(t, err)\n}\n\nfunc TestAgentPoolsUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tkAfter, err := client.AgentPools.Update(ctx, kBefore.ID, AgentPoolUpdateOptions{\n\t\t\tName: String(randomString(t)),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.ID, kAfter.ID)\n\t\tassert.NotEqual(t, kBefore.Name, kAfter.Name)\n\t})\n\n\tt.Run(\"when updating only the name\", func(t *testing.T) {\n\t\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceTestCleanup)\n\n\t\tprojectTest, projectTestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(projectTestCleanup)\n\n\t\texcludedWorkspaceTest, excludedWorkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(excludedWorkspaceTestCleanup)\n\n\t\torganizationScoped := false\n\t\toptions := AgentPoolCreateOptions{\n\t\t\tName:               String(\"a-pool\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tAllowedWorkspaces: []*Workspace{\n\t\t\t\tworkspaceTest,\n\t\t\t},\n\t\t\tAllowedProjects: []*Project{\n\t\t\t\tprojectTest,\n\t\t\t},\n\t\t\tExcludedWorkspaces: []*Workspace{\n\t\t\t\texcludedWorkspaceTest,\n\t\t\t},\n\t\t}\n\t\tkBefore, err := client.AgentPools.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tkAfter, err := client.AgentPools.Update(ctx, kBefore.ID, AgentPoolUpdateOptions{\n\t\t\tName: String(\"updated-key-name\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.ID, kAfter.ID)\n\t\tassert.Equal(t, \"updated-key-name\", kAfter.Name)\n\t\trequire.Equal(t, 1, len(kAfter.AllowedWorkspaces))\n\t\tassert.Equal(t, workspaceTest.ID, kAfter.AllowedWorkspaces[0].ID)\n\t\trequire.Equal(t, 1, len(kAfter.AllowedProjects))\n\t\tassert.Equal(t, projectTest.ID, kAfter.AllowedProjects[0].ID)\n\t\trequire.Equal(t, 1, len(kAfter.ExcludedWorkspaces))\n\t\tassert.Equal(t, excludedWorkspaceTest.ID, kAfter.ExcludedWorkspaces[0].ID)\n\t})\n\n\tt.Run(\"without a valid agent pool ID\", func(t *testing.T) {\n\t\tw, err := client.AgentPools.Update(ctx, badIdentifier, AgentPoolUpdateOptions{})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidAgentPoolID.Error())\n\t})\n\n\tt.Run(\"when updating organization scope\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\torganizationScoped := false\n\t\tkAfter, err := client.AgentPools.Update(ctx, kBefore.ID, AgentPoolUpdateOptions{\n\t\t\tName:               String(kBefore.Name),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEqual(t, kBefore.OrganizationScoped, kAfter.OrganizationScoped)\n\t\tassert.Equal(t, organizationScoped, kAfter.OrganizationScoped)\n\t})\n\n\tt.Run(\"when updating allowed-workspaces\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceTestCleanup)\n\n\t\tkAfter, err := client.AgentPools.Update(ctx, kBefore.ID, AgentPoolUpdateOptions{\n\t\t\tAllowedWorkspaces: []*Workspace{\n\t\t\t\tworkspaceTest,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.Name, kAfter.Name)\n\t\tassert.NotEqual(t, kBefore.AllowedWorkspaces, kAfter.AllowedWorkspaces)\n\t\trequire.Equal(t, 1, len(kAfter.AllowedWorkspaces))\n\t\tassert.Equal(t, workspaceTest.ID, kAfter.AllowedWorkspaces[0].ID)\n\t})\n\n\tt.Run(\"when updating allowed-projects\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tprojectTest, projectTestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(projectTestCleanup)\n\n\t\tkAfter, err := client.AgentPools.Update(ctx, kBefore.ID, AgentPoolUpdateOptions{\n\t\t\tAllowedProjects: []*Project{\n\t\t\t\tprojectTest,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.Name, kAfter.Name)\n\t\tassert.NotEqual(t, kBefore.AllowedProjects, kAfter.AllowedProjects)\n\t\trequire.Equal(t, 1, len(kAfter.AllowedProjects))\n\t\tassert.Equal(t, projectTest.ID, kAfter.AllowedProjects[0].ID)\n\t})\n\n\tt.Run(\"when updating excluded-workspaces\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceTestCleanup)\n\n\t\tkAfter, err := client.AgentPools.Update(ctx, kBefore.ID, AgentPoolUpdateOptions{\n\t\t\tExcludedWorkspaces: []*Workspace{\n\t\t\t\tworkspaceTest,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.Name, kAfter.Name)\n\t\tassert.NotEqual(t, kBefore.ExcludedWorkspaces, kAfter.ExcludedWorkspaces)\n\t\trequire.Equal(t, 1, len(kAfter.ExcludedWorkspaces))\n\t\tassert.Equal(t, workspaceTest.ID, kAfter.ExcludedWorkspaces[0].ID)\n\t})\n}\n\nfunc TestAgentPoolsUpdateAllowedWorkspaces(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tt.Run(\"when updating allowed-workspaces\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceTestCleanup)\n\n\t\tkAfter, err := client.AgentPools.UpdateAllowedWorkspaces(ctx, kBefore.ID, AgentPoolAllowedWorkspacesUpdateOptions{\n\t\t\tAllowedWorkspaces: []*Workspace{\n\t\t\t\tworkspaceTest,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.Name, kAfter.Name)\n\t\tassert.NotEqual(t, kBefore.AllowedWorkspaces, kAfter.AllowedWorkspaces)\n\t\trequire.Equal(t, 1, len(kAfter.AllowedWorkspaces))\n\t\tassert.Equal(t, workspaceTest.ID, kAfter.AllowedWorkspaces[0].ID)\n\t})\n\n\tt.Run(\"when removing all the allowed-workspaces\", func(t *testing.T) {\n\t\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceTestCleanup)\n\n\t\torganizationScoped := false\n\t\toptions := AgentPoolCreateOptions{\n\t\t\tName:               String(\"a-pool\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tAllowedWorkspaces: []*Workspace{\n\t\t\t\tworkspaceTest,\n\t\t\t},\n\t\t}\n\n\t\tkBefore, kTestCleanup := createAgentPoolWithOptions(t, client, orgTest, options)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tkAfter, err := client.AgentPools.UpdateAllowedWorkspaces(ctx, kBefore.ID, AgentPoolAllowedWorkspacesUpdateOptions{\n\t\t\tAllowedWorkspaces: []*Workspace{},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.ID, kAfter.ID)\n\t\tassert.Equal(t, \"a-pool\", kAfter.Name)\n\t\tassert.Empty(t, kAfter.AllowedWorkspaces)\n\t})\n}\n\nfunc TestAgentPoolsUpdateAllowedProjects(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tt.Run(\"when updating allowed-projects\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tprojectTest, projectTestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(projectTestCleanup)\n\n\t\tkAfter, err := client.AgentPools.UpdateAllowedProjects(ctx, kBefore.ID, AgentPoolAllowedProjectsUpdateOptions{\n\t\t\tAllowedProjects: []*Project{\n\t\t\t\tprojectTest,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.Name, kAfter.Name)\n\t\tassert.NotEqual(t, kBefore.AllowedProjects, kAfter.AllowedProjects)\n\t\trequire.Equal(t, 1, len(kAfter.AllowedProjects))\n\t\tassert.Equal(t, projectTest.ID, kAfter.AllowedProjects[0].ID)\n\t})\n\n\tt.Run(\"when removing all the allowed-projects\", func(t *testing.T) {\n\t\tprojectTest, projectTestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(projectTestCleanup)\n\n\t\torganizationScoped := false\n\t\toptions := AgentPoolCreateOptions{\n\t\t\tName:               String(\"a-pool\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tAllowedProjects: []*Project{\n\t\t\t\tprojectTest,\n\t\t\t},\n\t\t}\n\n\t\tkBefore, kTestCleanup := createAgentPoolWithOptions(t, client, orgTest, options)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tkAfter, err := client.AgentPools.UpdateAllowedProjects(ctx, kBefore.ID, AgentPoolAllowedProjectsUpdateOptions{\n\t\t\tAllowedProjects: []*Project{},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.ID, kAfter.ID)\n\t\tassert.Equal(t, \"a-pool\", kAfter.Name)\n\t\tassert.Empty(t, kAfter.AllowedProjects)\n\t})\n}\n\nfunc TestAgentPoolsUpdateExcludedWorkspaces(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tt.Run(\"when updating excluded-workspaces\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceTestCleanup)\n\n\t\tkAfter, err := client.AgentPools.UpdateExcludedWorkspaces(ctx, kBefore.ID, AgentPoolExcludedWorkspacesUpdateOptions{\n\t\t\tExcludedWorkspaces: []*Workspace{\n\t\t\t\tworkspaceTest,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.Name, kAfter.Name)\n\t\tassert.NotEqual(t, kBefore.ExcludedWorkspaces, kAfter.ExcludedWorkspaces)\n\t\trequire.Equal(t, 1, len(kAfter.ExcludedWorkspaces))\n\t\tassert.Equal(t, workspaceTest.ID, kAfter.ExcludedWorkspaces[0].ID)\n\t})\n\n\tt.Run(\"when removing all the excluded-workspaces\", func(t *testing.T) {\n\t\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceTestCleanup)\n\n\t\torganizationScoped := false\n\t\toptions := AgentPoolCreateOptions{\n\t\t\tName:               String(\"a-pool\"),\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t\tExcludedWorkspaces: []*Workspace{\n\t\t\t\tworkspaceTest,\n\t\t\t},\n\t\t}\n\n\t\tkBefore, kTestCleanup := createAgentPoolWithOptions(t, client, orgTest, options)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tkAfter, err := client.AgentPools.UpdateExcludedWorkspaces(ctx, kBefore.ID, AgentPoolExcludedWorkspacesUpdateOptions{\n\t\t\tExcludedWorkspaces: []*Workspace{},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.ID, kAfter.ID)\n\t\tassert.Equal(t, \"a-pool\", kAfter.Name)\n\t\tassert.Empty(t, kAfter.ExcludedWorkspaces)\n\t})\n}\n\nfunc TestAgentPoolsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tagentPool, _ := createAgentPool(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.AgentPools.Delete(ctx, agentPool.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the agent pool - it should fail.\n\t\t_, err = client.AgentPools.Read(ctx, agentPool.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the agent pool does not exist\", func(t *testing.T) {\n\t\terr := client.AgentPools.Delete(ctx, agentPool.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the agent pool ID is invalid\", func(t *testing.T) {\n\t\terr := client.AgentPools.Delete(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidAgentPoolID.Error())\n\t})\n}\n"
  },
  {
    "path": "agent_token.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ AgentTokens = (*agentTokens)(nil)\n\n// AgentTokens describes all the agent token related methods that the\n// HCP Terraform API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/agent-tokens\ntype AgentTokens interface {\n\t// List all the agent tokens of the given agent pool.\n\tList(ctx context.Context, agentPoolID string) (*AgentTokenList, error)\n\n\t// Create a new agent token with the given options.\n\tCreate(ctx context.Context, agentPoolID string, options AgentTokenCreateOptions) (*AgentToken, error)\n\n\t// Read an agent token by its ID.\n\tRead(ctx context.Context, agentTokenID string) (*AgentToken, error)\n\n\t// Delete an agent token by its ID.\n\tDelete(ctx context.Context, agentTokenID string) error\n}\n\n// agentTokens implements AgentTokens.\ntype agentTokens struct {\n\tclient *Client\n}\n\n// AgentToken represents a HCP Terraform agent token.\ntype AgentToken struct {\n\tID          string    `jsonapi:\"primary,authentication-tokens\"`\n\tCreatedAt   time.Time `jsonapi:\"attr,created-at,iso8601\"`\n\tDescription string    `jsonapi:\"attr,description\"`\n\tLastUsedAt  time.Time `jsonapi:\"attr,last-used-at,iso8601\"`\n\tToken       string    `jsonapi:\"attr,token\"`\n\n\t// Relations\n\tCreatedBy *User `jsonapi:\"relation,created-by\"`\n}\n\n// AgentTokenList represents a list of agent tokens.\ntype AgentTokenList struct {\n\t*Pagination\n\tItems []*AgentToken\n}\n\n// AgentTokenCreateOptions represents the options for creating an agent token.\ntype AgentTokenCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,agent-tokens\"`\n\n\t// Description of the token\n\tDescription *string `jsonapi:\"attr,description\"`\n}\n\n// List all the agent tokens of the given agent pool.\nfunc (s *agentTokens) List(ctx context.Context, agentPoolID string) (*AgentTokenList, error) {\n\tif !validStringID(&agentPoolID) {\n\t\treturn nil, ErrInvalidAgentPoolID\n\t}\n\n\tu := fmt.Sprintf(\"agent-pools/%s/authentication-tokens\", url.PathEscape(agentPoolID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttokenList := &AgentTokenList{}\n\terr = req.Do(ctx, tokenList)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tokenList, nil\n}\n\n// Create a new agent token with the given options.\nfunc (s *agentTokens) Create(ctx context.Context, agentPoolID string, options AgentTokenCreateOptions) (*AgentToken, error) {\n\tif !validStringID(&agentPoolID) {\n\t\treturn nil, ErrInvalidAgentPoolID\n\t}\n\n\tif !validString(options.Description) {\n\t\treturn nil, ErrAgentTokenDescription\n\t}\n\n\tu := fmt.Sprintf(\"agent-pools/%s/authentication-tokens\", url.PathEscape(agentPoolID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tat := &AgentToken{}\n\terr = req.Do(ctx, at)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn at, err\n}\n\n// Read an agent token by its ID.\nfunc (s *agentTokens) Read(ctx context.Context, agentTokenID string) (*AgentToken, error) {\n\tif !validStringID(&agentTokenID) {\n\t\treturn nil, ErrInvalidAgentTokenID\n\t}\n\n\tu := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(agentTokenID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tat := &AgentToken{}\n\terr = req.Do(ctx, at)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn at, err\n}\n\n// Delete an agent token by its ID.\nfunc (s *agentTokens) Delete(ctx context.Context, agentTokenID string) error {\n\tif !validStringID(&agentTokenID) {\n\t\treturn ErrInvalidAgentTokenID\n\t}\n\n\tu := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(agentTokenID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "agent_token_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAgentTokensList(t *testing.T) {\n\tt.Parallel()\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tapTest, apTestCleanup := createAgentPool(t, client, nil)\n\tdefer apTestCleanup()\n\n\tagentToken1, agentToken1Cleanup := createAgentToken(t, client, apTest)\n\tdefer agentToken1Cleanup()\n\t_, agentToken2Cleanup := createAgentToken(t, client, apTest)\n\tdefer agentToken2Cleanup()\n\n\tt.Run(\"with no list options\", func(t *testing.T) {\n\t\ttokenlist, err := client.AgentTokens.List(ctx, apTest.ID)\n\t\trequire.NoError(t, err)\n\t\tvar found bool\n\t\tfor _, j := range tokenlist.Items {\n\t\t\tif j.ID == agentToken1.ID {\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.Fatalf(\"agent token (%s) not found in token list\", agentToken1.ID)\n\t\t}\n\n\t\tassert.Equal(t, 1, tokenlist.CurrentPage)\n\t\tassert.Equal(t, 2, tokenlist.TotalCount)\n\t})\n\n\tt.Run(\"without a valid agent pool ID\", func(t *testing.T) {\n\t\ttokenlist, err := client.AgentTokens.List(ctx, badIdentifier)\n\t\tassert.Nil(t, tokenlist)\n\t\tassert.EqualError(t, err, ErrInvalidAgentPoolID.Error())\n\t})\n}\n\nfunc TestAgentTokensCreate(t *testing.T) {\n\tt.Parallel()\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tapTest, apTestCleanup := createAgentPool(t, client, nil)\n\tdefer apTestCleanup()\n\n\tt.Run(\"with valid description\", func(t *testing.T) {\n\t\ttoken, err := client.AgentTokens.Create(ctx, apTest.ID, AgentTokenCreateOptions{\n\t\t\tDescription: String(randomString(t)),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, token.Token)\n\t})\n\n\tt.Run(\"without valid description\", func(t *testing.T) {\n\t\tat, err := client.AgentTokens.Create(ctx, badIdentifier, AgentTokenCreateOptions{})\n\t\tassert.Nil(t, at)\n\t\tassert.EqualError(t, err, ErrInvalidAgentPoolID.Error())\n\t})\n\n\tt.Run(\"without valid agent pool ID\", func(t *testing.T) {\n\t\tat, err := client.AgentTokens.Create(ctx, badIdentifier, AgentTokenCreateOptions{\n\t\t\tDescription: String(randomString(t)),\n\t\t})\n\t\tassert.Nil(t, at)\n\t\tassert.EqualError(t, err, ErrInvalidAgentPoolID.Error())\n\t})\n}\nfunc TestAgentTokensRead(t *testing.T) {\n\tt.Parallel()\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tapTest, apTestCleanup := createAgentPool(t, client, nil)\n\tdefer apTestCleanup()\n\n\ttoken, tokenTestCleanup := createAgentToken(t, client, apTest)\n\tdefer tokenTestCleanup()\n\n\tt.Run(\"read token with valid token ID\", func(t *testing.T) {\n\t\tat, err := client.AgentTokens.Read(ctx, token.ID)\n\t\trequire.NoError(t, err)\n\t\t// The initial API call to create a token will return a value in the token\n\t\t// object. Empty that out for comparison\n\t\ttoken.Token = \"\"\n\t\tassert.Equal(t, token, at)\n\t})\n\n\tt.Run(\"read token without valid token ID\", func(t *testing.T) {\n\t\t_, err := client.AgentTokens.Read(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidAgentTokenID.Error())\n\t})\n}\n\nfunc TestAgentTokensReadCreatedBy(t *testing.T) {\n\tt.Parallel()\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tapTest, apTestCleanup := createAgentPool(t, client, nil)\n\tdefer apTestCleanup()\n\n\ttoken, tokenTestCleanup := createAgentToken(t, client, apTest)\n\tdefer tokenTestCleanup()\n\n\tat, err := client.AgentTokens.Read(ctx, token.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, at.CreatedBy)\n}\n\nfunc TestAgentTokensDelete(t *testing.T) {\n\tt.Parallel()\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tapTest, apTestCleanup := createAgentPool(t, client, nil)\n\tdefer apTestCleanup()\n\n\ttoken, _ := createAgentToken(t, client, apTest)\n\n\tt.Run(\"with valid token ID\", func(t *testing.T) {\n\t\terr := client.AgentTokens.Delete(ctx, token.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without valid token ID\", func(t *testing.T) {\n\t\terr := client.AgentTokens.Delete(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidAgentTokenID.Error())\n\t})\n}\n"
  },
  {
    "path": "apply.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Applies = (*applies)(nil)\n\n// Applies describes all the apply related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/applies\ntype Applies interface {\n\t// Read an apply by its ID.\n\tRead(ctx context.Context, applyID string) (*Apply, error)\n\n\t// Logs retrieves the logs of an apply.\n\tLogs(ctx context.Context, applyID string) (io.Reader, error)\n}\n\n// applies implements Applies interface.\ntype applies struct {\n\tclient *Client\n}\n\n// ApplyStatus represents an apply state.\ntype ApplyStatus string\n\n// List all available apply statuses.\nconst (\n\tApplyCanceled    ApplyStatus = \"canceled\"\n\tApplyCreated     ApplyStatus = \"created\"\n\tApplyErrored     ApplyStatus = \"errored\"\n\tApplyFinished    ApplyStatus = \"finished\"\n\tApplyMFAWaiting  ApplyStatus = \"mfa_waiting\"\n\tApplyPending     ApplyStatus = \"pending\"\n\tApplyQueued      ApplyStatus = \"queued\"\n\tApplyRunning     ApplyStatus = \"running\"\n\tApplyUnreachable ApplyStatus = \"unreachable\"\n)\n\n// Apply represents a Terraform Enterprise apply.\ntype Apply struct {\n\tID                   string                 `jsonapi:\"primary,applies\"`\n\tLogReadURL           string                 `jsonapi:\"attr,log-read-url\"`\n\tResourceAdditions    int                    `jsonapi:\"attr,resource-additions\"`\n\tResourceChanges      int                    `jsonapi:\"attr,resource-changes\"`\n\tResourceDestructions int                    `jsonapi:\"attr,resource-destructions\"`\n\tResourceImports      int                    `jsonapi:\"attr,resource-imports\"`\n\tStatus               ApplyStatus            `jsonapi:\"attr,status\"`\n\tStatusTimestamps     *ApplyStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n}\n\n// ApplyStatusTimestamps holds the timestamps for individual apply statuses.\ntype ApplyStatusTimestamps struct {\n\tCanceledAt      time.Time `jsonapi:\"attr,canceled-at,rfc3339\"`\n\tErroredAt       time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tFinishedAt      time.Time `jsonapi:\"attr,finished-at,rfc3339\"`\n\tForceCanceledAt time.Time `jsonapi:\"attr,force-canceled-at,rfc3339\"`\n\tQueuedAt        time.Time `jsonapi:\"attr,queued-at,rfc3339\"`\n\tStartedAt       time.Time `jsonapi:\"attr,started-at,rfc3339\"`\n}\n\n// Read an apply by its ID.\nfunc (s *applies) Read(ctx context.Context, applyID string) (*Apply, error) {\n\tif !validStringID(&applyID) {\n\t\treturn nil, ErrInvalidApplyID\n\t}\n\n\tu := fmt.Sprintf(\"applies/%s\", url.PathEscape(applyID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ta := &Apply{}\n\terr = req.Do(ctx, a)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn a, nil\n}\n\n// Logs retrieves the logs of an apply.\nfunc (s *applies) Logs(ctx context.Context, applyID string) (io.Reader, error) {\n\tif !validStringID(&applyID) {\n\t\treturn nil, ErrInvalidApplyID\n\t}\n\n\t// Get the apply to make sure it exists.\n\ta, err := s.Read(ctx, applyID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Return an error if the log URL is empty.\n\tif a.LogReadURL == \"\" {\n\t\treturn nil, fmt.Errorf(\"apply %s does not have a log URL\", applyID)\n\t}\n\n\tu, err := url.Parse(a.LogReadURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid log URL: %w\", err)\n\t}\n\n\tdone := func() (bool, error) {\n\t\ta, err := s.Read(ctx, a.ID)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tswitch a.Status {\n\t\tcase ApplyCanceled, ApplyErrored, ApplyFinished, ApplyUnreachable:\n\t\t\treturn true, nil\n\t\tdefault:\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\treturn &LogReader{\n\t\tclient: s.client,\n\t\tctx:    ctx,\n\t\tdone:   done,\n\t\tlogURL: u,\n\t}, nil\n}\n"
  },
  {
    "path": "apply_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAppliesRead_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\trTest, rTestCleanup := createRunApply(t, client, wTest)\n\tdefer rTestCleanup()\n\n\tt.Run(\"when the plan exists\", func(t *testing.T) {\n\t\ta, err := client.Applies.Read(ctx, rTest.Apply.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, a.LogReadURL)\n\t\tassert.Equal(t, a.Status, ApplyFinished)\n\t\tassert.NotEmpty(t, a.StatusTimestamps)\n\t})\n\n\tt.Run(\"when the apply does not exist\", func(t *testing.T) {\n\t\ta, err := client.Applies.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, a)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid apply ID\", func(t *testing.T) {\n\t\ta, err := client.Applies.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, a)\n\t\tassert.EqualError(t, err, ErrInvalidApplyID.Error())\n\t})\n}\n\nfunc TestAppliesLogs_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\trTest, rTestCleanup := createRunApply(t, client, nil)\n\tdefer rTestCleanup()\n\n\tt.Run(\"when the log exists\", func(t *testing.T) {\n\t\ta, err := client.Applies.Read(ctx, rTest.Apply.ID)\n\t\trequire.NoError(t, err)\n\n\t\tlogReader, err := client.Applies.Logs(ctx, a.ID)\n\t\trequire.NoError(t, err)\n\n\t\tlogs, err := io.ReadAll(logReader)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Contains(t, string(logs), \"1 added, 0 changed, 0 destroyed\")\n\t})\n\n\tt.Run(\"when the log does not exist\", func(t *testing.T) {\n\t\tlogs, err := client.Applies.Logs(ctx, \"nonexisting\")\n\t\tassert.Nil(t, logs)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestApplies_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"applies\",\n\t\t\t\"id\":   \"apply-47MBvjwzBG8YKc2v\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"log-read-url\":          \"hashicorp.com\",\n\t\t\t\t\"resource-additions\":    1,\n\t\t\t\t\"resource-changes\":      1,\n\t\t\t\t\"resource-destructions\": 1,\n\t\t\t\t\"status\":                ApplyCanceled,\n\t\t\t\t\"status-timestamps\": map[string]string{\n\t\t\t\t\t\"queued-at\":  \"2020-03-16T23:15:59+00:00\",\n\t\t\t\t\t\"errored-at\": \"2019-03-16T23:23:59+00:00\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\tresponseBody := bytes.NewReader(byteData)\n\n\tapply := &Apply{}\n\terr = unmarshalResponse(responseBody, apply)\n\trequire.NoError(t, err)\n\n\tqueuedParsedTime, err := time.Parse(time.RFC3339, \"2020-03-16T23:15:59+00:00\")\n\trequire.NoError(t, err)\n\terroredParsedTime, err := time.Parse(time.RFC3339, \"2019-03-16T23:23:59+00:00\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, apply.ID, \"apply-47MBvjwzBG8YKc2v\")\n\tassert.Equal(t, apply.ResourceAdditions, 1)\n\tassert.Equal(t, apply.ResourceChanges, 1)\n\tassert.Equal(t, apply.ResourceDestructions, 1)\n\tassert.Equal(t, apply.Status, ApplyCanceled)\n\tassert.Equal(t, apply.StatusTimestamps.QueuedAt, queuedParsedTime)\n\tassert.Equal(t, apply.StatusTimestamps.ErroredAt, erroredParsedTime)\n}\n"
  },
  {
    "path": "audit_trail.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/google/go-querystring/query\"\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n)\n\n// Compile-time proof of interface implementation\nvar _ AuditTrails = (*auditTrails)(nil)\n\n// AuditTrails describes all the audit event related methods that the HCP Terraform\n// API supports.\n// **Note:** These methods require the client to be configured with an organization token for\n// an organization in the Business tier. Furthermore, these methods are only available in HCP Terraform.\n//\n// HCP Terraform API Docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/audit-trails\ntype AuditTrails interface {\n\t// Read all the audit events in an organization.\n\tList(ctx context.Context, options *AuditTrailListOptions) (*AuditTrailList, error)\n}\n\n// auditTrails implements AuditTrails\ntype auditTrails struct {\n\tclient *Client\n}\n\n// AuditTrailRequest represents the request details of the audit event.\ntype AuditTrailRequest struct {\n\tID string `json:\"id\"`\n}\n\n// AuditTrailAuth represents the details of the actor that invoked the audit event.\ntype AuditTrailAuth struct {\n\tAccessorID     string  `json:\"accessor_id\"`\n\tDescription    string  `json:\"description\"`\n\tType           string  `json:\"type\"`\n\tImpersonatorID *string `json:\"impersonator_id\"`\n\tOrganizationID string  `json:\"organization_id\"`\n}\n\n// AuditTrailResource represents the details of the API resource in the audit event.\ntype AuditTrailResource struct {\n\tID     string                 `json:\"id\"`\n\tType   string                 `json:\"type\"`\n\tAction string                 `json:\"action\"`\n\tMeta   map[string]interface{} `json:\"meta\"`\n}\n\ntype AuditTrailPagination struct {\n\tCurrentPage  int `json:\"current_page\"`\n\tPreviousPage int `json:\"prev_page\"`\n\tNextPage     int `json:\"next_page\"`\n\tTotalPages   int `json:\"total_pages\"`\n\tTotalCount   int `json:\"total_count\"`\n}\n\n// AuditTrail represents an event in the HCP Terraform audit log.\ntype AuditTrail struct {\n\tID        string    `json:\"id\"`\n\tVersion   string    `json:\"version\"`\n\tType      string    `json:\"type\"`\n\tTimestamp time.Time `json:\"timestamp\"`\n\n\tAuth     AuditTrailAuth     `json:\"auth\"`\n\tRequest  AuditTrailRequest  `json:\"request\"`\n\tResource AuditTrailResource `json:\"resource\"`\n}\n\n// AuditTrailList represents a list of audit trails.\ntype AuditTrailList struct {\n\t*AuditTrailPagination `json:\"pagination\"`\n\tItems                 []*AuditTrail `json:\"data\"`\n}\n\n// AuditTrailListOptions represents the options for listing audit trails.\ntype AuditTrailListOptions struct {\n\t// Optional: Returns only audit trails created after this date\n\tSince time.Time `url:\"since,omitempty\"`\n\t*ListOptions\n}\n\n// List all the audit events in an organization.\nfunc (s *auditTrails) List(ctx context.Context, options *AuditTrailListOptions) (*AuditTrailList, error) {\n\tu, err := s.client.baseURL.Parse(\"/api/v2/organization/audit-trail\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\theaders := make(http.Header)\n\theaders.Set(\"User-Agent\", _userAgent)\n\theaders.Set(\"Authorization\", \"Bearer \"+s.client.token)\n\theaders.Set(\"Content-Type\", \"application/json\")\n\n\tif options != nil {\n\t\tq, err := query.Values(options)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tu.RawQuery = encodeQueryParams(q)\n\t}\n\n\treq, err := retryablehttp.NewRequest(\"GET\", u.String(), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Attach the headers to the request\n\tfor k, v := range headers {\n\t\treq.Header[k] = v\n\t}\n\n\tif err := s.client.limiter.Wait(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := s.client.http.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, ctx.Err()\n\t\tdefault:\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tdefer resp.Body.Close() //nolint:errcheck\n\n\tif err := checkResponseCode(resp); err != nil {\n\t\treturn nil, err\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tatl := &AuditTrailList{}\n\tif err := json.Unmarshal(body, atl); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn atl, nil\n}\n"
  },
  {
    "path": "audit_trail_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAuditTrailsList(t *testing.T) {\n\tt.Parallel()\n\tskipIfEnterprise(t)\n\n\tuserClient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, userClient)\n\tt.Cleanup(orgCleanup)\n\n\tauditTrailClient := testAuditTrailClient(t, userClient, org)\n\n\t// First let's generate some audit events in this test organization\n\tws, wkspace1Cleanup := createWorkspace(t, userClient, org)\n\tt.Cleanup(wkspace1Cleanup)\n\n\t_, err := userClient.Workspaces.Lock(ctx, ws.ID, WorkspaceLockOptions{})\n\trequire.NoError(t, err)\n\t_, err = userClient.Workspaces.Unlock(ctx, ws.ID)\n\trequire.NoError(t, err)\n\n\t_, err = userClient.Workspaces.Lock(ctx, ws.ID, WorkspaceLockOptions{})\n\trequire.NoError(t, err)\n\t_, err = userClient.Workspaces.Unlock(ctx, ws.ID)\n\trequire.NoError(t, err)\n\n\t_, err = userClient.Workspaces.Lock(ctx, ws.ID, WorkspaceLockOptions{})\n\trequire.NoError(t, err)\n\t_, err = userClient.Workspaces.Unlock(ctx, ws.ID)\n\trequire.NoError(t, err)\n\n\tt.Run(\"with no specified timeframe\", func(t *testing.T) {\n\t\tatl, err := auditTrailClient.AuditTrails.List(ctx, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, atl.Items)\n\n\t\trequire.Equal(t, len(atl.Items), 8)\n\n\t\tt.Run(\"pagination parameters\", func(t *testing.T) {\n\t\t\tpage1, err := auditTrailClient.AuditTrails.List(ctx, &AuditTrailListOptions{\n\t\t\t\tListOptions: &ListOptions{\n\t\t\t\t\tPageNumber: 1,\n\t\t\t\t\tPageSize:   4,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.NotEmpty(t, page1.Items)\n\t\t\tassert.Equal(t, 1, page1.CurrentPage)\n\t\t\tassert.Equal(t, 2, page1.TotalPages)\n\t\t\tassert.Equal(t, 8, page1.TotalCount)\n\n\t\t\tpage2, err := auditTrailClient.AuditTrails.List(ctx, &AuditTrailListOptions{\n\t\t\t\tListOptions: &ListOptions{\n\t\t\t\t\tPageNumber: 2,\n\t\t\t\t\tPageSize:   4,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.NotEmpty(t, page2.Items)\n\t\t\tassert.Equal(t, 2, page2.CurrentPage)\n\t\t\tassert.Equal(t, 0, page2.NextPage)\n\t\t})\n\n\t\tlog := atl.Items[0]\n\t\tassert.NotEmpty(t, log.ID)\n\t\tassert.NotEmpty(t, log.Timestamp)\n\t\tassert.NotEmpty(t, log.Type)\n\t\tassert.NotEmpty(t, log.Version)\n\t\trequire.NotNil(t, log.Resource)\n\t\trequire.NotNil(t, log.Auth)\n\t\trequire.NotNil(t, log.Request)\n\n\t\tt.Run(\"with resource deserialized correctly\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, log.Resource.ID)\n\t\t\tassert.NotEmpty(t, log.Resource.Type)\n\t\t\tassert.NotEmpty(t, log.Resource.Action)\n\n\t\t\t// we don't test against log.Resource.Meta since we don't know the nature\n\t\t\t// of the audit trail log we're testing against as it can be nil or contain a k-v map\n\t\t})\n\n\t\tt.Run(\"with auth deserialized correctly\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, log.Auth.AccessorID)\n\t\t\tassert.NotEmpty(t, log.Auth.Description)\n\t\t\tassert.NotEmpty(t, log.Auth.Type)\n\t\t\tassert.NotEmpty(t, log.Auth.OrganizationID)\n\t\t})\n\n\t\tt.Run(\"with request deserialized correctly\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, log.Request.ID)\n\t\t})\n\t})\n\n\tt.Run(\"using since query param\", func(t *testing.T) {\n\t\tsince := time.Now()\n\n\t\t// Wait some time before creating the event\n\t\t// otherwise comparing time values can be flaky\n\t\ttime.Sleep(1 * time.Second)\n\n\t\t// Let's create an event that is sent to the audit log\n\t\t_, wsCleanup := createWorkspace(t, userClient, org)\n\t\tt.Cleanup(wsCleanup)\n\n\t\tatl, err := auditTrailClient.AuditTrails.List(ctx, &AuditTrailListOptions{\n\t\t\tSince: since,\n\t\t\tListOptions: &ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   20,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.Greater(t, len(atl.Items), 0)\n\t\tassert.LessOrEqual(t, len(atl.Items), 20)\n\n\t\tfor _, log := range atl.Items {\n\t\t\tassert.True(t, log.Timestamp.After(since))\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "aws_oidc_configuration.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\nconst OIDCConfigPathFormat = \"oidc-configurations/%s\"\n\n// AWSOIDCConfigurations describes all the AWS OIDC configuration related methods that the HCP Terraform API supports.\n// HCP Terraform API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/aws\ntype AWSOIDCConfigurations interface {\n\tCreate(ctx context.Context, organization string, options AWSOIDCConfigurationCreateOptions) (*AWSOIDCConfiguration, error)\n\n\tRead(ctx context.Context, oidcID string) (*AWSOIDCConfiguration, error)\n\n\tUpdate(ctx context.Context, oidcID string, options AWSOIDCConfigurationUpdateOptions) (*AWSOIDCConfiguration, error)\n\n\tDelete(ctx context.Context, oidcID string) error\n}\n\ntype awsOIDCConfigurations struct {\n\tclient *Client\n}\n\nvar _ AWSOIDCConfigurations = &awsOIDCConfigurations{}\n\ntype AWSOIDCConfiguration struct {\n\tID      string `jsonapi:\"primary,aws-oidc-configurations\"`\n\tRoleARN string `jsonapi:\"attr,role-arn\"`\n\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n}\n\ntype AWSOIDCConfigurationCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,aws-oidc-configurations\"`\n\n\t// Attributes\n\tRoleARN string `jsonapi:\"attr,role-arn\"`\n}\n\ntype AWSOIDCConfigurationUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,aws-oidc-configurations\"`\n\n\t// Attributes\n\tRoleARN string `jsonapi:\"attr,role-arn\"`\n}\n\nfunc (o *AWSOIDCConfigurationCreateOptions) valid() error {\n\tif o.RoleARN == \"\" {\n\t\treturn ErrRequiredRoleARN\n\t}\n\n\treturn nil\n}\n\nfunc (o *AWSOIDCConfigurationUpdateOptions) valid() error {\n\tif o.RoleARN == \"\" {\n\t\treturn ErrRequiredRoleARN\n\t}\n\n\treturn nil\n}\n\nfunc (aoc *awsOIDCConfigurations) Create(ctx context.Context, organization string, options AWSOIDCConfigurationCreateOptions) (*AWSOIDCConfiguration, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := aoc.client.NewRequest(\"POST\", fmt.Sprintf(\"organizations/%s/oidc-configurations\", url.PathEscape(organization)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tawsOIDCConfiguration := &AWSOIDCConfiguration{}\n\terr = req.Do(ctx, awsOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn awsOIDCConfiguration, nil\n}\n\nfunc (aoc *awsOIDCConfigurations) Read(ctx context.Context, oidcID string) (*AWSOIDCConfiguration, error) {\n\treq, err := aoc.client.NewRequest(\"GET\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tawsOIDCConfiguration := &AWSOIDCConfiguration{}\n\terr = req.Do(ctx, awsOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn awsOIDCConfiguration, nil\n}\n\nfunc (aoc *awsOIDCConfigurations) Update(ctx context.Context, oidcID string, options AWSOIDCConfigurationUpdateOptions) (*AWSOIDCConfiguration, error) {\n\tif !validStringID(&oidcID) {\n\t\treturn nil, ErrInvalidOIDC\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := aoc.client.NewRequest(\"PATCH\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tawsOIDCConfiguration := &AWSOIDCConfiguration{}\n\terr = req.Do(ctx, awsOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn awsOIDCConfiguration, nil\n}\n\nfunc (aoc *awsOIDCConfigurations) Delete(ctx context.Context, oidcID string) error {\n\tif !validStringID(&oidcID) {\n\t\treturn ErrInvalidOIDC\n\t}\n\n\treq, err := aoc.client.NewRequest(\"DELETE\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "aws_oidc_configuration_integration_test.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// These tests are intended for local execution only, as OIDC configurations for HYOK requires specific conditions.\n// To run them locally, follow the instructions outlined in hyok_configuration_integration_test.go\n\nfunc TestAWSOIDCConfigurationCreateDelete(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\topts := AWSOIDCConfigurationCreateOptions{\n\t\t\tRoleARN: \"arn:aws:iam::123456789012:role/some-role\",\n\t\t}\n\n\t\toidcConfig, err := client.AWSOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, oidcConfig)\n\t\tassert.Equal(t, oidcConfig.RoleARN, opts.RoleARN)\n\n\t\t// delete the created configuration\n\t\terr = client.AWSOIDCConfigurations.Delete(ctx, oidcConfig.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"missing role ARN\", func(t *testing.T) {\n\t\topts := AWSOIDCConfigurationCreateOptions{}\n\n\t\t_, err := client.AWSOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\tassert.ErrorIs(t, err, ErrRequiredRoleARN)\n\t})\n}\n\nfunc TestAWSOIDCConfigurationRead(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\toidcConfig, oidcConfigCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\tt.Cleanup(oidcConfigCleanup)\n\n\tt.Run(\"fetch existing configuration\", func(t *testing.T) {\n\t\tfetched, err := client.AWSOIDCConfigurations.Read(ctx, oidcConfig.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, fetched)\n\t})\n\n\tt.Run(\"fetching non-existing configuration\", func(t *testing.T) {\n\t\t_, err := client.AWSOIDCConfigurations.Read(ctx, \"awsoidc-notreal\")\n\t\tassert.ErrorIs(t, err, ErrResourceNotFound)\n\t})\n}\n\nfunc TestAWSOIDCConfigurationsUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\toidcConfig, oidcConfigCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\tt.Cleanup(oidcConfigCleanup)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\topts := AWSOIDCConfigurationUpdateOptions{\n\t\t\tRoleARN: \"arn:aws:iam::123456789012:role/some-role-2\",\n\t\t}\n\t\tupdated, err := client.AWSOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, opts.RoleARN, updated.RoleARN)\n\t})\n\n\tt.Run(\"missing role ARN\", func(t *testing.T) {\n\t\topts := AWSOIDCConfigurationUpdateOptions{}\n\t\t_, err := client.AWSOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\tassert.ErrorIs(t, err, ErrRequiredRoleARN)\n\t})\n}\n"
  },
  {
    "path": "azure_oidc_configuration.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// AzureOIDCConfigurations describes all the Azure OIDC configuration related methods that the HCP Terraform API supports.\n// HCP Terraform API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/azure\ntype AzureOIDCConfigurations interface {\n\tCreate(ctx context.Context, organization string, options AzureOIDCConfigurationCreateOptions) (*AzureOIDCConfiguration, error)\n\n\tRead(ctx context.Context, oidcID string) (*AzureOIDCConfiguration, error)\n\n\tUpdate(ctx context.Context, oidcID string, options AzureOIDCConfigurationUpdateOptions) (*AzureOIDCConfiguration, error)\n\n\tDelete(ctx context.Context, oidcID string) error\n}\n\ntype azureOIDCConfigurations struct {\n\tclient *Client\n}\n\nvar _ AzureOIDCConfigurations = &azureOIDCConfigurations{}\n\ntype AzureOIDCConfiguration struct {\n\tID             string `jsonapi:\"primary,azure-oidc-configurations\"`\n\tClientID       string `jsonapi:\"attr,client-id\"`\n\tSubscriptionID string `jsonapi:\"attr,subscription-id\"`\n\tTenantID       string `jsonapi:\"attr,tenant-id\"`\n\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n}\n\ntype AzureOIDCConfigurationCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,azure-oidc-configurations\"`\n\n\t// Attributes\n\tClientID       string `jsonapi:\"attr,client-id\"`\n\tSubscriptionID string `jsonapi:\"attr,subscription-id\"`\n\tTenantID       string `jsonapi:\"attr,tenant-id\"`\n}\n\ntype AzureOIDCConfigurationUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,azure-oidc-configurations\"`\n\n\t// Attributes\n\tClientID       *string `jsonapi:\"attr,client-id,omitempty\"`\n\tSubscriptionID *string `jsonapi:\"attr,subscription-id,omitempty\"`\n\tTenantID       *string `jsonapi:\"attr,tenant-id,omitempty\"`\n}\n\nfunc (o *AzureOIDCConfigurationCreateOptions) valid() error {\n\tif o.ClientID == \"\" {\n\t\treturn ErrRequiredClientID\n\t}\n\n\tif o.SubscriptionID == \"\" {\n\t\treturn ErrRequiredSubscriptionID\n\t}\n\n\tif o.TenantID == \"\" {\n\t\treturn ErrRequiredTenantID\n\t}\n\n\treturn nil\n}\n\nfunc (aoc *azureOIDCConfigurations) Create(ctx context.Context, organization string, options AzureOIDCConfigurationCreateOptions) (*AzureOIDCConfiguration, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := aoc.client.NewRequest(\"POST\", fmt.Sprintf(\"organizations/%s/oidc-configurations\", url.PathEscape(organization)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tazureOIDCConfiguration := &AzureOIDCConfiguration{}\n\terr = req.Do(ctx, azureOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn azureOIDCConfiguration, nil\n}\n\nfunc (aoc *azureOIDCConfigurations) Read(ctx context.Context, oidcID string) (*AzureOIDCConfiguration, error) {\n\treq, err := aoc.client.NewRequest(\"GET\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tazureOIDCConfiguration := &AzureOIDCConfiguration{}\n\terr = req.Do(ctx, azureOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn azureOIDCConfiguration, nil\n}\n\nfunc (aoc *azureOIDCConfigurations) Update(ctx context.Context, oidcID string, options AzureOIDCConfigurationUpdateOptions) (*AzureOIDCConfiguration, error) {\n\tif !validStringID(&oidcID) {\n\t\treturn nil, ErrInvalidOIDC\n\t}\n\n\treq, err := aoc.client.NewRequest(\"PATCH\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tazureOIDCConfiguration := &AzureOIDCConfiguration{}\n\terr = req.Do(ctx, azureOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn azureOIDCConfiguration, nil\n}\n\nfunc (aoc *azureOIDCConfigurations) Delete(ctx context.Context, oidcID string) error {\n\tif !validStringID(&oidcID) {\n\t\treturn ErrInvalidOIDC\n\t}\n\n\treq, err := aoc.client.NewRequest(\"DELETE\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "azure_oidc_configuration_integration_test.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// These tests are intended for local execution only, as OIDC configurations for HYOK requires specific conditions.\n// To run them locally, follow the instructions outlined in hyok_configuration_integration_test.go\n\nfunc TestAzureOIDCConfigurationCreateDelete(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\topts := AzureOIDCConfigurationCreateOptions{\n\t\t\tClientID:       \"your-azure-client-id\",\n\t\t\tSubscriptionID: \"your-azure-subscription-id\",\n\t\t\tTenantID:       \"your-azure-tenant-id\",\n\t\t}\n\n\t\toidcConfig, err := client.AzureOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, oidcConfig)\n\t\tassert.Equal(t, oidcConfig.ClientID, opts.ClientID)\n\t\tassert.Equal(t, oidcConfig.SubscriptionID, opts.SubscriptionID)\n\t\tassert.Equal(t, oidcConfig.TenantID, opts.TenantID)\n\n\t\t// delete the created configuration\n\t\terr = client.AzureOIDCConfigurations.Delete(ctx, oidcConfig.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"missing client ID\", func(t *testing.T) {\n\t\topts := AzureOIDCConfigurationCreateOptions{\n\t\t\tSubscriptionID: \"your-azure-subscription-id\",\n\t\t\tTenantID:       \"your-azure-tenant-id\",\n\t\t}\n\n\t\t_, err := client.AzureOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\tassert.ErrorIs(t, err, ErrRequiredClientID)\n\t})\n\n\tt.Run(\"missing subscription ID\", func(t *testing.T) {\n\t\topts := AzureOIDCConfigurationCreateOptions{\n\t\t\tClientID: \"your-azure-client-id\",\n\t\t\tTenantID: \"your-azure-tenant-id\",\n\t\t}\n\n\t\t_, err := client.AzureOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\tassert.ErrorIs(t, err, ErrRequiredSubscriptionID)\n\t})\n\n\tt.Run(\"missing tenant ID\", func(t *testing.T) {\n\t\topts := AzureOIDCConfigurationCreateOptions{\n\t\t\tClientID:       \"your-azure-client-id\",\n\t\t\tSubscriptionID: \"your-azure-subscription-id\",\n\t\t}\n\n\t\t_, err := client.AzureOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\tassert.ErrorIs(t, err, ErrRequiredTenantID)\n\t})\n}\n\nfunc TestAzureOIDCConfigurationRead(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\toidcConfig, oidcConfigCleanup := createAzureOIDCConfiguration(t, client, orgTest)\n\tt.Cleanup(oidcConfigCleanup)\n\n\tt.Run(\"fetch existing configuration\", func(t *testing.T) {\n\t\tfetched, err := client.AzureOIDCConfigurations.Read(ctx, oidcConfig.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, fetched)\n\t})\n\n\tt.Run(\"fetching non-existing configuration\", func(t *testing.T) {\n\t\t_, err := client.AzureOIDCConfigurations.Read(ctx, \"azoidc-notreal\")\n\t\tassert.ErrorIs(t, err, ErrResourceNotFound)\n\t})\n}\n\nfunc TestAzureOIDCConfigurationUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tt.Run(\"update all fields\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createAzureOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\tclientID := \"your-azure-client-id\"\n\t\tsubscriptionID := \"your-azure-subscription-id\"\n\t\ttenantID := \"your-azure-tenant-id\"\n\n\t\topts := AzureOIDCConfigurationUpdateOptions{\n\t\t\tClientID:       &clientID,\n\t\t\tSubscriptionID: &subscriptionID,\n\t\t\tTenantID:       &tenantID,\n\t\t}\n\n\t\tupdated, err := client.AzureOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, *opts.ClientID, updated.ClientID)\n\t\tassert.Equal(t, *opts.SubscriptionID, updated.SubscriptionID)\n\t\tassert.Equal(t, *opts.TenantID, updated.TenantID)\n\t})\n\n\tt.Run(\"client ID not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createAzureOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\tsubscriptionID := \"your-azure-subscription-id\"\n\t\ttenantID := \"your-azure-tenant-id\"\n\n\t\topts := AzureOIDCConfigurationUpdateOptions{\n\t\t\tSubscriptionID: &subscriptionID,\n\t\t\tTenantID:       &tenantID,\n\t\t}\n\n\t\tupdated, err := client.AzureOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, oidcConfig.ClientID, updated.ClientID) // not updated\n\t\tassert.Equal(t, *opts.SubscriptionID, updated.SubscriptionID)\n\t\tassert.Equal(t, *opts.TenantID, updated.TenantID)\n\t})\n\n\tt.Run(\"subscription ID not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createAzureOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\tclientID := \"your-azure-client-id\"\n\t\ttenantID := \"your-azure-tenant-id\"\n\n\t\topts := AzureOIDCConfigurationUpdateOptions{\n\t\t\tClientID: &clientID,\n\t\t\tTenantID: &tenantID,\n\t\t}\n\n\t\tupdated, err := client.AzureOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, *opts.ClientID, updated.ClientID)\n\t\tassert.Equal(t, oidcConfig.SubscriptionID, updated.SubscriptionID) // not updated\n\t\tassert.Equal(t, *opts.TenantID, updated.TenantID)\n\t})\n\n\tt.Run(\"tenant ID not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createAzureOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\tclientID := \"your-azure-client-id\"\n\t\tsubscriptionID := \"your-azure-subscription-id\"\n\n\t\topts := AzureOIDCConfigurationUpdateOptions{\n\t\t\tClientID:       &clientID,\n\t\t\tSubscriptionID: &subscriptionID,\n\t\t}\n\n\t\tupdated, err := client.AzureOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, *opts.ClientID, updated.ClientID)\n\t\tassert.Equal(t, *opts.SubscriptionID, updated.SubscriptionID)\n\t\tassert.Equal(t, oidcConfig.TenantID, updated.TenantID) // not updated\n\t})\n}\n"
  },
  {
    "path": "comment.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Comments = (*comments)(nil)\n\n// Comments describes all the comment related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/comments\ntype Comments interface {\n\t// List all comments of the given run.\n\tList(ctx context.Context, runID string) (*CommentList, error)\n\n\t// Read a comment by its ID.\n\tRead(ctx context.Context, commentID string) (*Comment, error)\n\n\t// Create a new comment with the given options.\n\tCreate(ctx context.Context, runID string, options CommentCreateOptions) (*Comment, error)\n}\n\n// Comments implements Comments.\ntype comments struct {\n\tclient *Client\n}\n\n// CommentList represents a list of comments.\ntype CommentList struct {\n\t*Pagination\n\tItems []*Comment\n}\n\n// Comment represents a Terraform Enterprise comment.\ntype Comment struct {\n\tID   string `jsonapi:\"primary,comments\"`\n\tBody string `jsonapi:\"attr,body\"`\n}\n\ntype CommentCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,comments\"`\n\n\t// Required: Body of the comment.\n\tBody string `jsonapi:\"attr,body\"`\n}\n\n// List all comments of the given run.\nfunc (s *comments) List(ctx context.Context, runID string) (*CommentList, error) {\n\tif !validStringID(&runID) {\n\t\treturn nil, ErrInvalidRunID\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s/comments\", url.PathEscape(runID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcl := &CommentList{}\n\terr = req.Do(ctx, cl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cl, nil\n}\n\n// Create a new comment with the given options.\nfunc (s *comments) Create(ctx context.Context, runID string, options CommentCreateOptions) (*Comment, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !validStringID(&runID) {\n\t\treturn nil, ErrInvalidRunID\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s/comments\", url.PathEscape(runID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcomm := &Comment{}\n\terr = req.Do(ctx, comm)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn comm, err\n}\n\n// Read a comment by its ID.\nfunc (s *comments) Read(ctx context.Context, commentID string) (*Comment, error) {\n\tif !validStringID(&commentID) {\n\t\treturn nil, ErrInvalidCommentID\n\t}\n\n\tu := fmt.Sprintf(\"comments/%s\", url.PathEscape(commentID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcomm := &Comment{}\n\terr = req.Do(ctx, comm)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn comm, nil\n}\n\nfunc (o CommentCreateOptions) valid() error {\n\tif !validString(&o.Body) {\n\t\treturn ErrInvalidCommentBody\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "comment_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCommentsList_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest1, wTest1Cleanup := createWorkspace(t, client, orgTest)\n\tdefer wTest1Cleanup()\n\n\trTest, rTest1Cleanup := createRun(t, client, wTest1)\n\tdefer rTest1Cleanup()\n\tcommentBody1 := \"1st comment test\"\n\tcommentBody2 := \"2nd comment test\"\n\n\tt.Run(\"without comments\", func(t *testing.T) {\n\t\t_, err := client.Comments.List(ctx, rTest.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without a valid run\", func(t *testing.T) {\n\t\tcl, err := client.Comments.List(ctx, badIdentifier)\n\t\tassert.Nil(t, cl)\n\t\tassert.EqualError(t, err, ErrInvalidRunID.Error())\n\t})\n\n\tt.Run(\"create a comment\", func(t *testing.T) {\n\t\toptions := CommentCreateOptions{\n\t\t\tBody: commentBody1,\n\t\t}\n\t\tcl, err := client.Comments.Create(ctx, rTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, commentBody1, cl.Body)\n\t})\n\n\tt.Run(\"create 2nd comment\", func(t *testing.T) {\n\t\toptions := CommentCreateOptions{\n\t\t\tBody: commentBody2,\n\t\t}\n\t\tcl, err := client.Comments.Create(ctx, rTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, commentBody2, cl.Body)\n\t})\n\n\tt.Run(\"list comments\", func(t *testing.T) {\n\t\tcommentsList, err := client.Comments.List(ctx, rTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, commentsList.Items, 2)\n\t\tassert.Equal(t, true, commentItemsContainsBody(commentsList.Items, commentBody1))\n\t\tassert.Equal(t, true, commentItemsContainsBody(commentsList.Items, commentBody2))\n\t})\n}\n\nfunc commentItemsContainsBody(items []*Comment, body string) bool {\n\thasBody := false\n\tfor _, item := range items {\n\t\tif item.Body == body {\n\t\t\thasBody = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn hasBody\n}\n"
  },
  {
    "path": "configuration_version.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ ConfigurationVersions = (*configurationVersions)(nil)\n\n// ConfigurationVersions describes all the configuration version related\n// methods that the Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/configuration-versions\ntype ConfigurationVersions interface {\n\t// List returns all configuration versions of a workspace.\n\tList(ctx context.Context, workspaceID string, options *ConfigurationVersionListOptions) (*ConfigurationVersionList, error)\n\n\t// Create is used to create a new configuration version. The created\n\t// configuration version will be usable once data is uploaded to it.\n\tCreate(ctx context.Context, workspaceID string, options ConfigurationVersionCreateOptions) (*ConfigurationVersion, error)\n\n\t// CreateForRegistryModule is used to create a new configuration version\n\t// keyed to a registry module instead of a workspace. The created\n\t// configuration version will be usable once data is uploaded to it.\n\t//\n\t// **Note: This function is still in BETA and subject to change.**\n\tCreateForRegistryModule(ctx context.Context, moduleID RegistryModuleID) (*ConfigurationVersion, error)\n\n\t// Read a configuration version by its ID.\n\tRead(ctx context.Context, cvID string) (*ConfigurationVersion, error)\n\n\t// ReadWithOptions reads a configuration version by its ID using the options supplied\n\tReadWithOptions(ctx context.Context, cvID string, options *ConfigurationVersionReadOptions) (*ConfigurationVersion, error)\n\n\t// Upload packages and uploads Terraform configuration files. It requires\n\t// the upload URL from a configuration version and the full path to the\n\t// configuration files on disk.\n\tUpload(ctx context.Context, url string, path string) error\n\n\t// Upload a tar gzip archive to the specified configuration version upload URL.\n\tUploadTarGzip(ctx context.Context, url string, archive io.Reader) error\n\n\t// Archive a configuration version. This can only be done on configuration versions that\n\t// were created with the API or CLI, are in an uploaded state, and have no runs in progress.\n\tArchive(ctx context.Context, cvID string) error\n\n\t// Download a configuration version.  Only configuration versions in the uploaded state may be downloaded.\n\tDownload(ctx context.Context, cvID string) ([]byte, error)\n\n\t// SoftDeleteBackingData soft deletes the configuration version's backing data\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tSoftDeleteBackingData(ctx context.Context, svID string) error\n\n\t// RestoreBackingData restores a soft deleted configuration version's backing data\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tRestoreBackingData(ctx context.Context, svID string) error\n\n\t// PermanentlyDeleteBackingData permanently deletes a soft deleted configuration version's backing data\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tPermanentlyDeleteBackingData(ctx context.Context, svID string) error\n}\n\n// configurationVersions implements ConfigurationVersions.\ntype configurationVersions struct {\n\tclient *Client\n}\n\n// ConfigurationStatus represents a configuration version status.\ntype ConfigurationStatus string\n\n// List all available configuration version statuses.\nconst (\n\tConfigurationArchived ConfigurationStatus = \"archived\"\n\tConfigurationErrored  ConfigurationStatus = \"errored\"\n\tConfigurationFetching ConfigurationStatus = \"fetching\"\n\tConfigurationPending  ConfigurationStatus = \"pending\"\n\tConfigurationUploaded ConfigurationStatus = \"uploaded\"\n)\n\n// ConfigurationSource represents a source of a configuration version.\ntype ConfigurationSource string\n\n// List all available configuration version sources.\nconst (\n\tConfigurationSourceAPI       ConfigurationSource = \"tfe-api\"\n\tConfigurationSourceBitbucket ConfigurationSource = \"bitbucket\"\n\tConfigurationSourceGithub    ConfigurationSource = \"github\"\n\tConfigurationSourceGitlab    ConfigurationSource = \"gitlab\"\n\tConfigurationSourceAdo       ConfigurationSource = \"ado\"\n\tConfigurationSourceTerraform ConfigurationSource = \"terraform\"\n)\n\n// ConfigurationVersionList represents a list of configuration versions.\ntype ConfigurationVersionList struct {\n\t*Pagination\n\tItems []*ConfigurationVersion\n}\n\n// ConfigurationVersion is a representation of an uploaded or ingressed\n// Terraform configuration in TFE. A workspace must have at least one\n// configuration version before any runs may be queued on it.\ntype ConfigurationVersion struct {\n\tID               string              `jsonapi:\"primary,configuration-versions\"`\n\tAutoQueueRuns    bool                `jsonapi:\"attr,auto-queue-runs\"`\n\tError            string              `jsonapi:\"attr,error\"`\n\tErrorMessage     string              `jsonapi:\"attr,error-message\"`\n\tSource           ConfigurationSource `jsonapi:\"attr,source\"`\n\tSpeculative      bool                `jsonapi:\"attr,speculative\"`\n\tProvisional      bool                `jsonapi:\"attr,provisional\"`\n\tStatus           ConfigurationStatus `jsonapi:\"attr,status\"`\n\tStatusTimestamps *CVStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\tUploadURL        string              `jsonapi:\"attr,upload-url\"`\n\n\t// Relations\n\tIngressAttributes *IngressAttributes `jsonapi:\"relation,ingress-attributes\"`\n}\n\n// CVStatusTimestamps holds the timestamps for individual configuration version\n// statuses.\ntype CVStatusTimestamps struct {\n\tArchivedAt time.Time `jsonapi:\"attr,archived-at,rfc3339\"`\n\tFetchingAt time.Time `jsonapi:\"attr,fetching-at,rfc3339\"`\n\tFinishedAt time.Time `jsonapi:\"attr,finished-at,rfc3339\"`\n\tQueuedAt   time.Time `jsonapi:\"attr,queued-at,rfc3339\"`\n\tStartedAt  time.Time `jsonapi:\"attr,started-at,rfc3339\"`\n}\n\n// ConfigVerIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/configuration-versions#available-related-resources\ntype ConfigVerIncludeOpt string\n\nconst (\n\tConfigVerIngressAttributes ConfigVerIncludeOpt = \"ingress_attributes\"\n\tConfigVerRun               ConfigVerIncludeOpt = \"run\"\n)\n\n// ConfigurationVersionReadOptions represents the options for reading a configuration version.\ntype ConfigurationVersionReadOptions struct {\n\t// Optional: A list of relations to include. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/configuration-versions#available-related-resources\n\tInclude []ConfigVerIncludeOpt `url:\"include,omitempty\"`\n}\n\n// ConfigurationVersionListOptions represents the options for listing\n// configuration versions.\ntype ConfigurationVersionListOptions struct {\n\tListOptions\n\t// Optional: A list of relations to include. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/configuration-versions#available-related-resources\n\tInclude []ConfigVerIncludeOpt `url:\"include,omitempty\"`\n}\n\n// ConfigurationVersionCreateOptions represents the options for creating a\n// configuration version.\ntype ConfigurationVersionCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,configuration-versions\"`\n\n\t// Optional: When true, runs are queued automatically when the configuration version\n\t// is uploaded.\n\tAutoQueueRuns *bool `jsonapi:\"attr,auto-queue-runs,omitempty\"`\n\n\t// Optional: When true, this configuration version can only be used for planning.\n\tSpeculative *bool `jsonapi:\"attr,speculative,omitempty\"`\n\n\t// Optional: When true, does not become the workspace's current configuration until\n\t// a run referencing it is ultimately applied.\n\tProvisional *bool `jsonapi:\"attr,provisional,omitempty\"`\n}\n\n// IngressAttributes include commit information associated with configuration versions sourced from VCS.\ntype IngressAttributes struct {\n\tID                string `jsonapi:\"primary,ingress-attributes\"`\n\tBranch            string `jsonapi:\"attr,branch\"`\n\tCloneURL          string `jsonapi:\"attr,clone-url\"`\n\tCommitMessage     string `jsonapi:\"attr,commit-message\"`\n\tCommitSHA         string `jsonapi:\"attr,commit-sha\"`\n\tCommitURL         string `jsonapi:\"attr,commit-url\"`\n\tCompareURL        string `jsonapi:\"attr,compare-url\"`\n\tIdentifier        string `jsonapi:\"attr,identifier\"`\n\tIsPullRequest     bool   `jsonapi:\"attr,is-pull-request\"`\n\tOnDefaultBranch   bool   `jsonapi:\"attr,on-default-branch\"`\n\tPullRequestNumber int    `jsonapi:\"attr,pull-request-number\"`\n\tPullRequestURL    string `jsonapi:\"attr,pull-request-url\"`\n\tPullRequestTitle  string `jsonapi:\"attr,pull-request-title\"`\n\tPullRequestBody   string `jsonapi:\"attr,pull-request-body\"`\n\tTag               string `jsonapi:\"attr,tag\"`\n\tSenderUsername    string `jsonapi:\"attr,sender-username\"`\n\tSenderAvatarURL   string `jsonapi:\"attr,sender-avatar-url\"`\n\tSenderHTMLURL     string `jsonapi:\"attr,sender-html-url\"`\n\n\t// Links\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n}\n\n// List returns all configuration versions of a workspace.\nfunc (s *configurationVersions) List(ctx context.Context, workspaceID string, options *ConfigurationVersionListOptions) (*ConfigurationVersionList, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/configuration-versions\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcvl := &ConfigurationVersionList{}\n\terr = req.Do(ctx, cvl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cvl, nil\n}\n\n// Create is used to create a new configuration version. The created\n// configuration version will be usable once data is uploaded to it.\nfunc (s *configurationVersions) Create(ctx context.Context, workspaceID string, options ConfigurationVersionCreateOptions) (*ConfigurationVersion, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/configuration-versions\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcv := &ConfigurationVersion{}\n\terr = req.Do(ctx, cv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cv, nil\n}\n\nfunc (s *configurationVersions) CreateForRegistryModule(ctx context.Context, moduleID RegistryModuleID) (*ConfigurationVersion, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"%s/configuration-versions\", testRunsPath(moduleID))\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcv := &ConfigurationVersion{}\n\terr = req.Do(ctx, cv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cv, nil\n}\n\n// Read a configuration version by its ID.\nfunc (s *configurationVersions) Read(ctx context.Context, cvID string) (*ConfigurationVersion, error) {\n\treturn s.ReadWithOptions(ctx, cvID, nil)\n}\n\n// Read a configuration version by its ID with the given options.\nfunc (s *configurationVersions) ReadWithOptions(ctx context.Context, cvID string, options *ConfigurationVersionReadOptions) (*ConfigurationVersion, error) {\n\tif !validStringID(&cvID) {\n\t\treturn nil, ErrInvalidConfigVersionID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"configuration-versions/%s\", url.PathEscape(cvID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcv := &ConfigurationVersion{}\n\terr = req.Do(ctx, cv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cv, nil\n}\n\n// Upload packages and uploads Terraform configuration files. It requires the\n// upload URL from a configuration version and the path to the configuration\n// files on disk.\nfunc (s *configurationVersions) Upload(ctx context.Context, uploadURL, path string) error {\n\tbody, err := packContents(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn s.UploadTarGzip(ctx, uploadURL, body)\n}\n\n// UploadTarGzip is used to upload Terraform configuration files contained a tar gzip archive.\n// Any stream implementing io.Reader can be passed into this method. This method is also\n// particularly useful for tar streams created by non-default go-slug configurations.\n//\n// **Note**: This method does not validate the content being uploaded and is therefore the caller's\n// responsibility to ensure the raw content is a valid Terraform configuration.\nfunc (s *configurationVersions) UploadTarGzip(ctx context.Context, uploadURL string, archive io.Reader) error {\n\treturn s.client.doForeignPUTRequest(ctx, uploadURL, archive)\n}\n\n// Archive a configuration version. This can only be done on configuration versions that\n// were created with the API or CLI, are in an uploaded state, and have no runs in progress.\nfunc (s *configurationVersions) Archive(ctx context.Context, cvID string) error {\n\tif !validStringID(&cvID) {\n\t\treturn ErrInvalidConfigVersionID\n\t}\n\n\tbody := bytes.NewBuffer(nil)\n\n\tu := fmt.Sprintf(\"configuration-versions/%s/actions/archive\", url.PathEscape(cvID))\n\treq, err := s.client.NewRequest(\"POST\", u, body)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o *ConfigurationVersionReadOptions) valid() error {\n\treturn nil\n}\n\nfunc (o *ConfigurationVersionListOptions) valid() error {\n\treturn nil\n}\n\n// Download a configuration version.  Only configuration versions in the uploaded state may be downloaded.\nfunc (s *configurationVersions) Download(ctx context.Context, cvID string) ([]byte, error) {\n\tif !validStringID(&cvID) {\n\t\treturn nil, ErrInvalidConfigVersionID\n\t}\n\n\tu := fmt.Sprintf(\"configuration-versions/%s/download\", url.PathEscape(cvID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar buf bytes.Buffer\n\terr = req.Do(ctx, &buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\nfunc (s *configurationVersions) SoftDeleteBackingData(ctx context.Context, cvID string) error {\n\treturn s.manageBackingData(ctx, cvID, \"soft_delete_backing_data\")\n}\n\nfunc (s *configurationVersions) RestoreBackingData(ctx context.Context, cvID string) error {\n\treturn s.manageBackingData(ctx, cvID, \"restore_backing_data\")\n}\n\nfunc (s *configurationVersions) PermanentlyDeleteBackingData(ctx context.Context, cvID string) error {\n\treturn s.manageBackingData(ctx, cvID, \"permanently_delete_backing_data\")\n}\n\nfunc (s *configurationVersions) manageBackingData(ctx context.Context, cvID, action string) error {\n\tif !validStringID(&cvID) {\n\t\treturn ErrInvalidConfigVersionID\n\t}\n\n\tu := fmt.Sprintf(\"configuration-versions/%s/actions/%s\", cvID, action)\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "configuration_version_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\tslug \"github.com/hashicorp/go-slug\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestConfigurationVersionsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\tcvTest1, cvTest1Cleanup := createConfigurationVersion(t, client, wTest)\n\tdefer cvTest1Cleanup()\n\tcvTest2, cvTest2Cleanup := createConfigurationVersion(t, client, wTest)\n\tdefer cvTest2Cleanup()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tcvl, err := client.ConfigurationVersions.List(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// We need to strip the upload URL as that is a dynamic link.\n\t\tcvTest1.UploadURL = \"\"\n\t\tcvTest2.UploadURL = \"\"\n\n\t\t// And for the retrieved configuration versions as well.\n\t\tfor _, cv := range cvl.Items {\n\t\t\tcv.UploadURL = \"\"\n\t\t}\n\n\t\tassert.Contains(t, cvl.Items, cvTest1)\n\t\tassert.Contains(t, cvl.Items, cvTest2)\n\t\tassert.Equal(t, 1, cvl.CurrentPage)\n\t\tassert.Equal(t, 2, cvl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\toptions := &ConfigurationVersionListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t}\n\n\t\tcvl, err := client.ConfigurationVersions.List(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, cvl.Items)\n\t\tassert.Equal(t, 999, cvl.CurrentPage)\n\t\tassert.Equal(t, 2, cvl.TotalCount)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tcvl, err := client.ConfigurationVersions.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, cvl)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestConfigurationVersionsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tcv, err := client.ConfigurationVersions.Create(ctx,\n\t\t\twTest.ID,\n\t\t\tConfigurationVersionCreateOptions{},\n\t\t)\n\t\tassert.NotEmpty(t, cv.UploadURL)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view of the configuration version.\n\t\trefreshed, err := client.ConfigurationVersions.Read(ctx, cv.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, refreshed.UploadURL)\n\n\t\tfor _, item := range []*ConfigurationVersion{\n\t\t\tcv,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Empty(t, item.Error)\n\t\t\tassert.Equal(t, item.Source, ConfigurationSourceAPI)\n\t\t\tassert.Equal(t, item.Status, ConfigurationPending)\n\t\t}\n\t})\n\n\tt.Run(\"with invalid workspace id\", func(t *testing.T) {\n\t\tcv, err := client.ConfigurationVersions.Create(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tConfigurationVersionCreateOptions{},\n\t\t)\n\t\tassert.Nil(t, cv)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n\n\tt.Run(\"provisional\", func(t *testing.T) {\n\t\tcv, err := client.ConfigurationVersions.Create(ctx,\n\t\t\twTest.ID,\n\t\t\tConfigurationVersionCreateOptions{\n\t\t\t\tProvisional: Bool(true),\n\t\t\t},\n\t\t)\n\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, cv.Provisional)\n\n\t\tws, err := client.Workspaces.ReadByID(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Depends on \"with valid options\"\n\t\trequire.NotNil(t, ws.CurrentConfigurationVersion)\n\n\t\t// Provisional configuration version is not the current one\n\t\tassert.NotEqual(t, ws.CurrentConfigurationVersion.ID, cv.ID)\n\t})\n}\n\nfunc TestConfigurationVersionsCreateForRegistryModule(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, rmTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\tdefer rmTestCleanup()\n\n\tid := RegistryModuleID{\n\t\tOrganization: rmTest.Organization.Name,\n\t\tName:         rmTest.Name,\n\t\tProvider:     rmTest.Provider,\n\t\tNamespace:    rmTest.Namespace,\n\t\tRegistryName: rmTest.RegistryName,\n\t}\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tcv, err := client.ConfigurationVersions.CreateForRegistryModule(ctx, id)\n\t\tassert.NotEmpty(t, cv.UploadURL)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view of the configuration version.\n\t\trefreshed, err := client.ConfigurationVersions.Read(ctx, cv.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, refreshed.UploadURL)\n\n\t\tfor _, item := range []*ConfigurationVersion{\n\t\t\tcv,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Empty(t, item.Error)\n\t\t\tassert.Equal(t, item.Source, ConfigurationSourceAPI)\n\t\t\tassert.Equal(t, item.Status, ConfigurationPending)\n\t\t}\n\t})\n\n\tt.Run(\"with invalid workspace id\", func(t *testing.T) {\n\t\tcv, err := client.ConfigurationVersions.CreateForRegistryModule(\n\t\t\tctx,\n\t\t\tRegistryModuleID{},\n\t\t)\n\t\tassert.Nil(t, cv)\n\t\tassert.Equal(t, ErrRequiredName, err)\n\t})\n}\n\nfunc TestConfigurationVersionsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tcvTest, cvTestCleanup := createConfigurationVersion(t, client, nil)\n\tdefer cvTestCleanup()\n\n\tt.Run(\"when the configuration version exists\", func(t *testing.T) {\n\t\tcv, err := client.ConfigurationVersions.Read(ctx, cvTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Don't compare the UploadURL because it will be generated twice in\n\t\t// this test - once at creation of the configuration version, and\n\t\t// again during the GET.\n\t\tcvTest.UploadURL, cv.UploadURL = \"\", \"\"\n\n\t\tassert.Equal(t, cvTest, cv)\n\t})\n\n\tt.Run(\"when the configuration version does not exist\", func(t *testing.T) {\n\t\tcv, err := client.ConfigurationVersions.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, cv)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid configuration version id\", func(t *testing.T) {\n\t\tcv, err := client.ConfigurationVersions.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, cv)\n\t\tassert.EqualError(t, err, ErrInvalidConfigVersionID.Error())\n\t})\n}\n\nfunc TestConfigurationVersionsReadWithOptions(t *testing.T) {\n\tt.Parallel()\n\tt.Skip(\"Skipping due to persistent failures - see TF-31172\")\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, WorkspaceCreateOptions{QueueAllRuns: Bool(true)})\n\tdefer wTestCleanup()\n\n\tw, err := retry(func() (interface{}, error) {\n\t\tw, err := client.Workspaces.ReadByIDWithOptions(ctx, wTest.ID, &WorkspaceReadOptions{\n\t\t\tInclude: []WSIncludeOpt{WSCurrentRunConfigVer},\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif w.CurrentRun == nil {\n\t\t\treturn nil, errors.New(\"A run was expected to be found on this workspace as a test pre-condition\")\n\t\t}\n\n\t\treturn w, nil\n\t})\n\n\trequire.NoError(t, err)\n\n\tws, ok := w.(*Workspace)\n\trequire.True(t, ok, \"Expected Workspace, got %T\", w)\n\n\tcv := ws.CurrentRun.ConfigurationVersion\n\n\tt.Run(\"when the configuration version exists\", func(t *testing.T) {\n\t\toptions := &ConfigurationVersionReadOptions{\n\t\t\tInclude: []ConfigVerIncludeOpt{ConfigVerIngressAttributes},\n\t\t}\n\n\t\tcv, err := client.ConfigurationVersions.ReadWithOptions(ctx, cv.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotNil(t, cv.IngressAttributes)\n\t\tassert.NotZero(t, cv.IngressAttributes.CommitURL)\n\t\tassert.NotZero(t, cv.IngressAttributes.CommitSHA)\n\t})\n}\n\nfunc TestConfigurationVersionsUpload(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tcv, cvCleanup := createConfigurationVersion(t, client, nil)\n\tdefer cvCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.ConfigurationVersions.Upload(\n\t\t\tctx,\n\t\t\tcv.UploadURL,\n\t\t\t\"test-fixtures/config-version\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tWaitUntilStatus(t, client, cv, ConfigurationUploaded, 60)\n\t})\n\n\tt.Run(\"without a valid upload URL\", func(t *testing.T) {\n\t\terr := client.ConfigurationVersions.Upload(\n\t\t\tctx,\n\t\t\tcv.UploadURL[:len(cv.UploadURL)-10]+\"nonexisting\",\n\t\t\t\"test-fixtures/config-version\",\n\t\t)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"without a valid path\", func(t *testing.T) {\n\t\terr := client.ConfigurationVersions.Upload(\n\t\t\tctx,\n\t\t\tcv.UploadURL,\n\t\t\t\"nonexisting\",\n\t\t)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestConfigurationVersionsUploadTarGzip(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tcv, cvCleanup := createConfigurationVersion(t, client, nil)\n\tt.Cleanup(cvCleanup)\n\n\tt.Run(\"with custom go-slug\", func(t *testing.T) {\n\t\tpacker, err := slug.NewPacker(\n\t\t\tslug.DereferenceSymlinks(),\n\t\t\tslug.ApplyTerraformIgnore(),\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tbody := bytes.NewBuffer(nil)\n\t\t_, err = packer.Pack(\"test-fixtures/config-version\", body)\n\t\trequire.NoError(t, err)\n\n\t\terr = client.ConfigurationVersions.UploadTarGzip(ctx, cv.UploadURL, body)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with custom tar archive\", func(t *testing.T) {\n\t\tarchivePath := \"test-fixtures/config-archive.tar.gz\"\n\t\tcreateTarGzipArchive(t, []string{\"test-fixtures/config-version/main.tf\"}, archivePath)\n\n\t\tarchive, err := os.Open(archivePath)\n\t\trequire.NoError(t, err)\n\t\tdefer archive.Close()\n\n\t\terr = client.ConfigurationVersions.UploadTarGzip(ctx, cv.UploadURL, archive)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestConfigurationVersionsArchive(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tw, wCleanup := createWorkspace(t, client, nil)\n\tdefer wCleanup()\n\n\tcv, cvCleanup := createConfigurationVersion(t, client, w)\n\tdefer cvCleanup()\n\n\tt.Run(\"when the configuration version exists and has been uploaded\", func(t *testing.T) {\n\t\terr := client.ConfigurationVersions.Upload(\n\t\t\tctx,\n\t\t\tcv.UploadURL,\n\t\t\t\"test-fixtures/config-version\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tWaitUntilStatus(t, client, cv, ConfigurationUploaded, 60)\n\n\t\t// configuration version should not be archived, since it's the latest version\n\t\terr = client.ConfigurationVersions.Archive(ctx, cv.ID)\n\t\tassert.Error(t, err)\n\t\tassert.ErrorContains(t, err, \"transition not allowed\")\n\t\tassert.ErrorContains(t, err, \"configuration could not be archived because it is current\")\n\n\t\t// create subsequent version, since the latest configuration version cannot be archived\n\t\tnewCv, newCvCleanup := createConfigurationVersion(t, client, w)\n\t\terr = client.ConfigurationVersions.Upload(\n\t\t\tctx,\n\t\t\tnewCv.UploadURL,\n\t\t\t\"test-fixtures/config-version\",\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tdefer newCvCleanup()\n\t\tWaitUntilStatus(t, client, newCv, ConfigurationUploaded, 60)\n\n\t\terr = client.ConfigurationVersions.Archive(ctx, cv.ID)\n\t\trequire.NoError(t, err)\n\n\t\tWaitUntilStatus(t, client, cv, ConfigurationArchived, 60)\n\t})\n\n\tt.Run(\"when the configuration version does not exist\", func(t *testing.T) {\n\t\terr := client.ConfigurationVersions.Archive(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid configuration version id\", func(t *testing.T) {\n\t\terr := client.ConfigurationVersions.Archive(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidConfigVersionID.Error())\n\t})\n}\n\nfunc TestConfigurationVersionsDownload(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"with a valid ID for downloadable configuration version\", func(t *testing.T) {\n\t\tuploadedCv, uploadedCvCleanup := createUploadedConfigurationVersion(t, client, nil)\n\t\tdefer uploadedCvCleanup()\n\n\t\texpectedCvFile := bytes.NewBuffer(nil)\n\t\t_, expectedCvFileErr := slug.Pack(\"test-fixtures/config-version\", expectedCvFile, true)\n\t\tif expectedCvFileErr != nil {\n\t\t\tt.Fatal(expectedCvFileErr)\n\t\t}\n\n\t\tcvFile, err := client.ConfigurationVersions.Download(ctx, uploadedCv.ID)\n\n\t\tassert.NotNil(t, cvFile)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, bytes.Equal(cvFile, expectedCvFile.Bytes()), \"Configuration version should match\")\n\t})\n\n\tt.Run(\"with a valid ID for a non downloadable configuration version\", func(t *testing.T) {\n\t\tpendingCv, pendingCvCleanup := createConfigurationVersion(t, client, nil)\n\t\tdefer pendingCvCleanup()\n\n\t\tcvFile, err := client.ConfigurationVersions.Download(ctx, pendingCv.ID)\n\n\t\tassert.Nil(t, cvFile)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n\n\tt.Run(\"with an invalid ID\", func(t *testing.T) {\n\t\tcvFile, err := client.ConfigurationVersions.Download(ctx, \"nonexistent\")\n\t\tassert.Nil(t, cvFile)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n\nfunc TestConfigurationVersions_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"configuration-versions\",\n\t\t\t\"id\":   \"cv-ntv3HbhJqvFzamy7\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"auto-queue-runs\": true,\n\t\t\t\t\"error\":           \"bad error\",\n\t\t\t\t\"error-message\":   \"message\",\n\t\t\t\t\"source\":          ConfigurationSourceTerraform,\n\t\t\t\t\"status\":          ConfigurationUploaded,\n\t\t\t\t\"status-timestamps\": map[string]string{\n\t\t\t\t\t\"finished-at\": \"2020-03-16T23:15:59+00:00\",\n\t\t\t\t\t\"started-at\":  \"2019-03-16T23:23:59+00:00\",\n\t\t\t\t},\n\t\t\t\t\"speculative\": true,\n\t\t\t\t\"provisional\": true,\n\t\t\t},\n\t\t},\n\t}\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\tcv := &ConfigurationVersion{}\n\terr = unmarshalResponse(responseBody, cv)\n\trequire.NoError(t, err)\n\n\tfinishedParsedTime, err := time.Parse(time.RFC3339, \"2020-03-16T23:15:59+00:00\")\n\trequire.NoError(t, err)\n\tstartedParsedTime, err := time.Parse(time.RFC3339, \"2019-03-16T23:23:59+00:00\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, cv.ID, \"cv-ntv3HbhJqvFzamy7\")\n\tassert.Equal(t, cv.AutoQueueRuns, true)\n\tassert.Equal(t, cv.Error, \"bad error\")\n\tassert.Equal(t, cv.ErrorMessage, \"message\")\n\tassert.Equal(t, cv.Source, ConfigurationSourceTerraform)\n\tassert.Equal(t, cv.Status, ConfigurationUploaded)\n\tassert.Equal(t, cv.StatusTimestamps.FinishedAt, finishedParsedTime)\n\tassert.Equal(t, cv.StatusTimestamps.StartedAt, startedParsedTime)\n\tassert.Equal(t, cv.Provisional, true)\n\tassert.Equal(t, cv.Speculative, true)\n}\n\nfunc TestConfigurationVersions_ManageBackingData(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tworkspace, workspaceCleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(workspaceCleanup)\n\n\tnonCurrentCv, uploadedCvCleanup := createUploadedConfigurationVersion(t, client, workspace)\n\tdefer uploadedCvCleanup()\n\n\t_, uploadedCvCleanup = createUploadedConfigurationVersion(t, client, workspace)\n\tdefer uploadedCvCleanup()\n\n\tt.Run(\"soft delete backing data\", func(t *testing.T) {\n\t\terr := client.ConfigurationVersions.SoftDeleteBackingData(ctx, nonCurrentCv.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.ConfigurationVersions.Download(ctx, nonCurrentCv.ID)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"restore backing data\", func(t *testing.T) {\n\t\terr := client.ConfigurationVersions.RestoreBackingData(ctx, nonCurrentCv.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.ConfigurationVersions.Download(ctx, nonCurrentCv.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"permanently delete backing data\", func(t *testing.T) {\n\t\terr := client.ConfigurationVersions.SoftDeleteBackingData(ctx, nonCurrentCv.ID)\n\t\trequire.NoError(t, err)\n\n\t\terr = client.ConfigurationVersions.PermanentlyDeleteBackingData(ctx, nonCurrentCv.ID)\n\t\trequire.NoError(t, err)\n\n\t\terr = client.ConfigurationVersions.RestoreBackingData(ctx, nonCurrentCv.ID)\n\t\trequire.ErrorContainsf(t, err, \"transition not allowed\", \"Restore backing data should fail\")\n\n\t\t_, err = client.ConfigurationVersions.Download(ctx, nonCurrentCv.ID)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n}\n"
  },
  {
    "path": "const.go",
    "content": "// Copyright IBM Corp. 2018, 2026\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nconst (\n\t// AuthenticationTokensPath is the API path for authentication tokens.\n\tAuthenticationTokensPath = \"authentication-tokens/%s\"\n\n\t// AdminSCIMTokensPath is the API path for admin SCIM tokens.\n\tAdminSCIMTokensPath = \"admin/scim-tokens\"\n\n\t// AdminSCIMGroupsPath is the API path for admin SCIM groups.\n\tAdminSCIMGroupsPath = \"admin/scim-groups\"\n)\n"
  },
  {
    "path": "cost_estimate.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ CostEstimates = (*costEstimates)(nil)\n\n// CostEstimates describes all the costEstimate related methods that\n// the Terraform Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/cost-estimates\ntype CostEstimates interface {\n\t// Read a costEstimate by its ID.\n\tRead(ctx context.Context, costEstimateID string) (*CostEstimate, error)\n\n\t// Logs retrieves the logs of a costEstimate.\n\tLogs(ctx context.Context, costEstimateID string) (io.Reader, error)\n}\n\n// costEstimates implements CostEstimates.\ntype costEstimates struct {\n\tclient *Client\n}\n\n// CostEstimateStatus represents a costEstimate state.\ntype CostEstimateStatus string\n\n// List all available costEstimate statuses.\nconst (\n\tCostEstimateCanceled              CostEstimateStatus = \"canceled\"\n\tCostEstimateErrored               CostEstimateStatus = \"errored\"\n\tCostEstimateFinished              CostEstimateStatus = \"finished\"\n\tCostEstimatePending               CostEstimateStatus = \"pending\"\n\tCostEstimateQueued                CostEstimateStatus = \"queued\"\n\tCostEstimateSkippedDueToTargeting CostEstimateStatus = \"skipped_due_to_targeting\"\n)\n\n// CostEstimate represents a Terraform Enterprise costEstimate.\ntype CostEstimate struct {\n\tID                      string                        `jsonapi:\"primary,cost-estimates\"`\n\tDeltaMonthlyCost        string                        `jsonapi:\"attr,delta-monthly-cost\"`\n\tErrorMessage            string                        `jsonapi:\"attr,error-message\"`\n\tMatchedResourcesCount   int                           `jsonapi:\"attr,matched-resources-count\"`\n\tPriorMonthlyCost        string                        `jsonapi:\"attr,prior-monthly-cost\"`\n\tProposedMonthlyCost     string                        `jsonapi:\"attr,proposed-monthly-cost\"`\n\tResourcesCount          int                           `jsonapi:\"attr,resources-count\"`\n\tStatus                  CostEstimateStatus            `jsonapi:\"attr,status\"`\n\tStatusTimestamps        *CostEstimateStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\tUnmatchedResourcesCount int                           `jsonapi:\"attr,unmatched-resources-count\"`\n}\n\n// CostEstimateStatusTimestamps holds the timestamps for individual costEstimate statuses.\ntype CostEstimateStatusTimestamps struct {\n\tCanceledAt              time.Time `jsonapi:\"attr,canceled-at,rfc3339\"`\n\tErroredAt               time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tFinishedAt              time.Time `jsonapi:\"attr,finished-at,rfc3339\"`\n\tPendingAt               time.Time `jsonapi:\"attr,pending-at,rfc3339\"`\n\tQueuedAt                time.Time `jsonapi:\"attr,queued-at,rfc3339\"`\n\tSkippedDueToTargetingAt time.Time `jsonapi:\"attr,skipped-due-to-targeting-at,rfc3339\"`\n}\n\n// Read a costEstimate by its ID.\nfunc (s *costEstimates) Read(ctx context.Context, costEstimateID string) (*CostEstimate, error) {\n\tif !validStringID(&costEstimateID) {\n\t\treturn nil, ErrInvalidCostEstimateID\n\t}\n\n\tu := fmt.Sprintf(\"cost-estimates/%s\", url.PathEscape(costEstimateID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tce := &CostEstimate{}\n\terr = req.Do(ctx, ce)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ce, nil\n}\n\n// Logs retrieves the logs of a costEstimate.\nfunc (s *costEstimates) Logs(ctx context.Context, costEstimateID string) (io.Reader, error) {\n\tif !validStringID(&costEstimateID) {\n\t\treturn nil, ErrInvalidCostEstimateID\n\t}\n\n\t// Loop until the context is canceled or the cost estimate is finished\n\t// running. The cost estimate logs are not streamed and so only available\n\t// once the estimate is finished.\n\tfor {\n\t\t// Get the costEstimate to make sure it exists.\n\t\tce, err := s.Read(ctx, costEstimateID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tswitch ce.Status {\n\t\tcase CostEstimateQueued:\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn nil, ctx.Err()\n\t\t\tcase <-time.After(1000 * time.Millisecond):\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tu := fmt.Sprintf(\"cost-estimates/%s/output\", url.PathEscape(costEstimateID))\n\t\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlogs := bytes.NewBuffer(nil)\n\t\terr = req.Do(ctx, logs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn logs, nil\n\t}\n}\n"
  },
  {
    "path": "cost_estimate_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCostEstimatesRead_RunDependent(t *testing.T) {\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\t// Enable cost estimation for the test organization.\n\torgTest, err := client.Organizations.Update(\n\t\tctx,\n\t\torgTest.Name,\n\t\tOrganizationUpdateOptions{\n\t\t\tCostEstimationEnabled: Bool(true),\n\t\t},\n\t)\n\trequire.NoError(t, err)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\trTest, rTestCleanup := createCostEstimatedRun(t, client, wTest)\n\tdefer rTestCleanup()\n\n\tt.Run(\"when the costEstimate exists\", func(t *testing.T) {\n\t\tce, err := client.CostEstimates.Read(ctx, rTest.CostEstimate.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, ce.Status, CostEstimateFinished)\n\t\tassert.NotEmpty(t, ce.StatusTimestamps)\n\t})\n\n\tt.Run(\"when the costEstimate does not exist\", func(t *testing.T) {\n\t\tce, err := client.CostEstimates.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, ce)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"with invalid costEstimate ID\", func(t *testing.T) {\n\t\tce, err := client.CostEstimates.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, ce)\n\t\tassert.EqualError(t, err, ErrInvalidCostEstimateID.Error())\n\t})\n}\n\nfunc TestCostEsimate_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"cost-estimates\",\n\t\t\t\"id\":   \"ce-ntv3HbhJqvFzamy7\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"delta-monthly-cost\":      \"100\",\n\t\t\t\t\"error-message\":           \"message\",\n\t\t\t\t\"matched-resources-count\": 1,\n\t\t\t\t\"prior-monthly-cost\":      \"100\",\n\t\t\t\t\"proposed-monthly-cost\":   \"100\",\n\t\t\t\t\"resources-count\":         1,\n\t\t\t\t\"status\":                  CostEstimateCanceled,\n\t\t\t\t\"status-timestamps\": map[string]string{\n\t\t\t\t\t\"queued-at\":  \"2020-03-16T23:15:59+00:00\",\n\t\t\t\t\t\"errored-at\": \"2019-03-16T23:23:59+00:00\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tqueuedParsedTime, err := time.Parse(time.RFC3339, \"2020-03-16T23:15:59+00:00\")\n\trequire.NoError(t, err)\n\terroredParsedTime, err := time.Parse(time.RFC3339, \"2019-03-16T23:23:59+00:00\")\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\tce := &CostEstimate{}\n\terr = unmarshalResponse(responseBody, ce)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, ce.ID, \"ce-ntv3HbhJqvFzamy7\")\n\tassert.Equal(t, ce.DeltaMonthlyCost, \"100\")\n\tassert.Equal(t, ce.ErrorMessage, \"message\")\n\tassert.Equal(t, ce.MatchedResourcesCount, 1)\n\tassert.Equal(t, ce.PriorMonthlyCost, \"100\")\n\tassert.Equal(t, ce.ProposedMonthlyCost, \"100\")\n\tassert.Equal(t, ce.ResourcesCount, 1)\n\tassert.Equal(t, ce.Status, CostEstimateCanceled)\n\tassert.Equal(t, ce.StatusTimestamps.QueuedAt, queuedParsedTime)\n\tassert.Equal(t, ce.StatusTimestamps.ErroredAt, erroredParsedTime)\n}\n"
  },
  {
    "path": "data_retention_policy.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport \"regexp\"\n\n// DataRetentionPolicyChoice is a choice type struct that represents the possible types\n// of a drp returned by a polymorphic relationship. If a value is available, exactly one field\n// will be non-nil.\ntype DataRetentionPolicyChoice struct {\n\tDataRetentionPolicy            *DataRetentionPolicy\n\tDataRetentionPolicyDeleteOlder *DataRetentionPolicyDeleteOlder\n\tDataRetentionPolicyDontDelete  *DataRetentionPolicyDontDelete\n}\n\n// Returns whether one of the choices is populated\nfunc (d DataRetentionPolicyChoice) IsPopulated() bool {\n\treturn d.DataRetentionPolicy != nil ||\n\t\td.DataRetentionPolicyDeleteOlder != nil ||\n\t\td.DataRetentionPolicyDontDelete != nil\n}\n\n// Convert the DataRetentionPolicyChoice to the legacy DataRetentionPolicy struct\n// Returns nil if the policy cannot be represented by a legacy DataRetentionPolicy\nfunc (d *DataRetentionPolicyChoice) ConvertToLegacyStruct() *DataRetentionPolicy {\n\tif d == nil {\n\t\treturn nil\n\t}\n\tif d.DataRetentionPolicy != nil {\n\t\t// TFE v202311-1 and v202312-1 will return a deprecated DataRetentionPolicy in the DataRetentionPolicyChoice struct\n\t\treturn d.DataRetentionPolicy\n\t} else if d.DataRetentionPolicyDeleteOlder != nil {\n\t\t// DataRetentionPolicy was functionally replaced by DataRetentionPolicyDeleteOlder in TFE v202401\n\t\treturn &DataRetentionPolicy{\n\t\t\tID:                   d.DataRetentionPolicyDeleteOlder.ID,\n\t\t\tDeleteOlderThanNDays: d.DataRetentionPolicyDeleteOlder.DeleteOlderThanNDays,\n\t\t}\n\t}\n\treturn nil\n}\n\n// DataRetentionPolicy describes the retention policy of deleting records older than the specified number of days.\n//\n// Deprecated: Use DataRetentionPolicyDeleteOlder instead. This is the original representation of a\n// data retention policy, only present in TFE v202311-1 and v202312-1\ntype DataRetentionPolicy struct {\n\tID                   string `jsonapi:\"primary,data-retention-policies\"`\n\tDeleteOlderThanNDays int    `jsonapi:\"attr,delete-older-than-n-days\"`\n}\n\n// DataRetentionPolicySetOptions is the options for a creating a DataRetentionPolicy.\n//\n// Deprecated: Use DataRetentionPolicyDeleteOlder variations instead\ntype DataRetentionPolicySetOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,data-retention-policies\"`\n\n\t// DeleteOlderThanNDays is the number of days to retain records for.\n\tDeleteOlderThanNDays int `jsonapi:\"attr,delete-older-than-n-days\"`\n}\n\n// DataRetentionPolicyDeleteOlder describes the retention policy of deleting records older than the specified number of days.\ntype DataRetentionPolicyDeleteOlder struct {\n\tID string `jsonapi:\"primary,data-retention-policy-delete-olders\"`\n\n\t// DeleteOlderThanNDays is the number of days to retain records for.\n\tDeleteOlderThanNDays int `jsonapi:\"attr,delete-older-than-n-days\"`\n}\n\n// DataRetentionPolicyDontDelete describes the retention policy of never deleting records.\ntype DataRetentionPolicyDontDelete struct {\n\tID string `jsonapi:\"primary,data-retention-policy-dont-deletes\"`\n}\n\n// DataRetentionPolicyDeleteOlderSetOptions describes the options for a creating a DataRetentionPolicyDeleteOlder.\ntype DataRetentionPolicyDeleteOlderSetOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,data-retention-policy-delete-olders\"`\n\n\t// DeleteOlderThanNDays is the number of days records will be retained for after their creation.\n\tDeleteOlderThanNDays int `jsonapi:\"attr,delete-older-than-n-days\"`\n}\n\n// DataRetentionPolicyDontDeleteSetOptions describes the options for a creating a DataRetentionPolicyDontDelete.\ntype DataRetentionPolicyDontDeleteSetOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,data-retention-policy-dont-deletes\"`\n}\n\n// error we get when trying to unmarshal a data retention policy from TFE v202401+ into the deprecated DataRetentionPolicy struct\nvar drpUnmarshalEr = regexp.MustCompile(`Trying to Unmarshal an object of type \\\".+\\\", but \\\"data-retention-policies\\\" does not match`)\n"
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "content": "# Contributing to go-tfe\n\nIf you find an issue with this package, please create an issue in GitHub. If you'd like, we welcome any contributions. Fork this repository and submit a pull request.\n\n## Adding new functionality or fixing relevant bugs\n\nIf you are adding a new endpoint, make sure to update the [coverage list in README.md](../README.md#API-Coverage) where we keep a list of the HCP Terraform APIs that this SDK supports.\n\nIf you are making relevant changes that is worth communicating to our users, please include a note about it in our CHANGELOG.md. You can include it as part of the PR where you are submitting your changes.\n\nCHANGELOG.md should have the next minor version listed as `# v1.X.0 (Unreleased)` and any changes can go under there. But if you feel that your changes are better suited for a patch version (like a critical bug fix), you may list a new section for this version. You should repeat the same formatting style introduced by previous versions.\n\n### Scoping pull requests that add new resources\n\nThere are instances where several new resources being added (i.e Workspace Run Tasks and Organization Run Tasks) are coalesced into one PR. In order to keep the review process as efficient and least error prone as possible, we ask that you please scope each PR to an individual resource even if the multiple resources you're adding share similarities. If joining multiple related PRs into one single PR makes more sense logistically, we'd ask that you organize your commit history by resource. A general convention for this repository is one commit for the implementation of the resource's methods, one for the integration test, and one for cleanup and housekeeping (e.g modifying the changelog/docs, generating mocks, etc).\n\n**Note HashiCorp Employees Only:** When submitting a new set of endpoints please ensure that one of your respective team members approves the changes as well before merging.\n\n## Linting\n\nAfter opening a PR, our CI system will perform a series of code checks, one of which is linting. Linting is not strictly required for a change to be merged, but it helps smooth the review process and catch common mistakes early. If you'd like to run the linters manually, follow these steps:\n\n1. Ensure you have [installed golangci-lint](https://golangci-lint.run/welcome/install/#local-installation)\n2. Format your code by running `make fmt`\n3. Run lint checks using `make lint`\n\n## Writing Tests\n\nThe test suite contains many acceptance tests that are run against the latest version of Terraform Enterprise. You can read more about running the tests against your own Terraform Enterprise environment in [TESTS.md](TESTS.md). Our CI system (Github Actions) will not test your fork until a one-time approval takes place.\n\n## Editor Settings\n\nWe've included VSCode settings to assist with configuring the go extension. For other editors that integrate with the [Go Language Server](https://github.com/golang/tools/tree/master/gopls), the main thing to do is to add the `integration` build tags so that the test files are found by the language server. See `.vscode/settings.json` for more details.\n\n## Generating Mocks\nEnsure you have installed the [mockgen](https://github.com/uber-go/mock) tool.\n\nYou'll need to generate mocks if an existing endpoint method is modified or a new method is added. To generate mocks, simply run `./generate_mocks.sh`.\n\nIf you're adding a new API resource to go-tfe, you'll need to add a new command to `generate_mocks.sh`. For example if someone creates `example_resource.go`, you'll add:\n\n```\nmockgen -source=example_resource.go -destination=mocks/example_resource_mocks.go -package=mocks\n```\n\nYou can also use the Makefile target `mocks` to add the new command:\n\n```\nFILENAME=example_resource.go make mocks\n```\n\n## Adding API changes that are not generally available\n\nIn general, beta features should not be merged/released until generally available (GA). However, the maintainers recognize almost any reason to release beta features on a case-by-case basis. These could include: partial customer availability, software dependency, or any reason short of feature completeness.\n\nBeta features, if released, should be clearly commented:\n\n```\n// **Note: This field is still in BETA and subject to change.**\nExampleNewField *bool `jsonapi:\"attr,example-new-field,omitempty\"`\n```\n\nWhen adding test cases, you can temporarily use the skipUnlessBeta() test helper to omit beta features from running in CI.\n\n```\nt.Run(\"with nested changes trigger\", func (t *testing.T) {\n  skipUnlessBeta(t)\n  options := WorkspaceCreateOptions {\n     // rest of required fields here\n     ExampleNewField: Bool(true),\n   }\n  // the rest of your test logic here\n})\n```\n\n**Note**: After your PR has been merged, and the feature either reaches general availability, you should remove the `skipUnlessBeta()` flag.\n\n## Adding New Endpoints\n\n### Scaffolding a Resource\n\nWhen creating a new resource you can use the helper script `generate_resource` to quickly setup boilerplate code for adding a new set of endpoints related to that resource:\n\n#### Running the script directly\n```sh\ncd ./scripts/generate_resource\ngo run . example_resource\n```\n\n#### Running the Makefile target `generate`\n```sh\nRESOURCE=example_resource make generate\n```\n\n### Guidelines for Adding New Endpoints\n\n* An interface should cover one RESTful resource, which sometimes involves two or more endpoints.\n* We require that each resource interface provides compile-time proof that it has been implemented.\n* You'll need to add an integration test that covers each method of the resource's interface.\n* Option structs serve as a proxy for either passing query params or request bodies:\n    - `ListOptions` and `ReadOptions` are values passed as query parameters.\n    - `CreateOptions` and `UpdateOptions` represent the request body.\n* URL parameters should be defined as method parameters.\n* Any resource specific errors must be defined in `errors.go`\n\nHere is a more comprehensive example of what a resource looks like when implemented. The helper script `generate_resource` generates a subset of this example, focusing only on the core details that are required across all resources in go-tfe.\n\n```go\npackage tfe\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\nvar ErrInvalidExampleID = errors.New(\"invalid value for example ID\") // move this line to errors.go\n\n// Compile-time proof of interface implementation\nvar _ ExampleResource = (*example)(nil)\n\n// Example represents all the example methods in the context of an organization\n// that the HCP Terraform and Terraform Enterprise API supports.\n// If this API is in beta or pre-release state, include that warning here.\ntype ExampleResource interface {\n\t// Create an example for an organization\n\tCreate(ctx context.Context, organization string, options ExampleCreateOptions) (*Example, error)\n\n\t// List all examples for an organization\n\tList(ctx context.Context, organization string, options *ExampleListOptions) (*ExampleList, error)\n\n\t// Read an organization's example by ID\n\tRead(ctx context.Context, exampleID string) (*Example, error)\n\n\t// Read an organization's example by ID with given options\n\tReadWithOptions(ctx context.Context, exampleID string, options *ExampleReadOptions) (*Example, error)\n\n\t// Update an example for an organization\n\tUpdate(ctx context.Context, exampleID string, options ExampleUpdateOptions) (*Example, error)\n\n\t// Delete an organization's example\n\tDelete(ctx context.Context, exampleID string) error\n}\n\n// example implements Example\ntype example struct {\n\tclient *Client\n}\n\n// Example represents a HCP Terraform and Terraform Enterprise example resource\ntype Example struct {\n\tID            string  `jsonapi:\"primary,examples\"`\n\tName          string  `jsonapi:\"attr,name\"`\n\tURL           string  `jsonapi:\"attr,url\"`\n\tOptionalValue *string `jsonapi:\"attr,optional-value,omitempty\"`\n\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n}\n\n// ExampleCreateOptions represents the set of options for creating an example\ntype ExampleCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,examples\"`\n\n\t// Required: The name of the example\n\tName string `jsonapi:\"attr,name\"`\n\n\t// Required: The URL to send in the example\n\tURL string `jsonapi:\"attr,url\"`\n\n\t// Optional: An optional value that is omitted if empty\n\tOptionalValue *string `jsonapi:\"attr,optional-value,omitempty\"`\n}\n\n// ExampleIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/examples#list-examples (replace this URL with the actual documentation URL)\ntype ExampleIncludeOpt string\n\nconst (\n\tExampleOrganization ExampleIncludeOpt = \"organization\"\n\tExampleRun ExampleIncludeOpt = \"run\"\n)\n\n// ExampleListOptions represents the set of options for listing examples\ntype ExampleListOptions struct {\n\tListOptions\n\n\t// Optional: A list of relations to include with an example. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/examples#list-examples (replace this URL with the actual documentation URL)\n\tInclude []ExampleIncludeOpt `url:\"include,omitempty\"`\n}\n\n// ExampleList represents a list of examples\ntype ExampleList struct {\n\t*Pagination\n\tItems []*Example\n}\n\n// ExampleReadOptions represents the set of options for reading an example\ntype ExampleReadOptions struct {\n\t// Optional: A list of relations to include with an example. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/examples#list-examples (replace this URL with the actual documentation URL)\n\tInclude []RunTaskIncludeOpt `url:\"include,omitempty\"`\n}\n\n// ExampleUpdateOptions represents the set of options for updating an organization's examples\ntype ExampleUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,examples\"`\n\n\t// Optional: The name of the example, defaults to previous value\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: The URL to send a example payload, defaults to previous value\n\tURL *string `jsonapi:\"attr,url,omitempty\"`\n\n\t// Optional: An optional value\n\tOptionalValue *string `jsonapi:\"attr,optional-value,omitempty\"`\n}\n\n// Create is used to create a new example for an organization\nfunc (s *example) Create(ctx context.Context, organization string, options ExampleCreateOptions) (*Example, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/tasks\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &Example{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r, nil\n}\n\n// List all the examples for an organization\nfunc (s *example) List(ctx context.Context, organization string, options *ExampleListOptions) (*ExampleList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/examples\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tel := &ExampleList{}\n\terr = req.Do(ctx, el)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn el, nil\n}\n\n// Read is used to read an organization's example by ID\nfunc (s *example) Read(ctx context.Context, exampleID string) (*Example, error) {\n\treturn s.ReadWithOptions(ctx, exampleID, nil)\n}\n\n// Read is used to read an organization's example by ID with options\nfunc (s *example) ReadWithOptions(ctx context.Context, exampleID string, options *ExampleReadOptions) (*Example, error) {\n\tif !validStringID(&exampleID) {\n\t\treturn nil, ErrInvalidExampleID\n\t}\n\n\tu := fmt.Sprintf(\"examples/%s\", url.PathEscape(exampleID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te := &Example{}\n\terr = req.Do(ctx, e)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn e, nil\n}\n\n// Update an existing example for an organization by ID\nfunc (s *example) Update(ctx context.Context, exampleID string, options ExampleUpdateOptions) (*Example, error) {\n\tif !validStringID(&exampleID) {\n\t\treturn nil, ErrInvalidExampleID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"examples/%s\", url.PathEscape(exampleID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &Example{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r, nil\n}\n\n// Delete an existing example for an organization by ID\nfunc (s *example) Delete(ctx context.Context, exampleID string) error {\n\tif !validStringID(&exampleID) {\n\t\treturn ErrInvalidExampleID\n\t}\n\n\tu := fmt.Sprintf(\"examples/%s\", exampleID)\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o *ExampleUpdateOptions) valid() error {\n\tif o.Name != nil && !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\n\tif o.URL != nil && !validString(o.URL) {\n\t\treturn ErrInvalidRunTaskURL\n\t}\n\n\treturn nil\n}\n\nfunc (o *ExampleCreateOptions) valid() error {\n\tif !validString(&o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\n\tif !validString(&o.URL) {\n\t\treturn ErrInvalidRunTaskURL\n\t}\n\n\treturn nil\n}\n```\n\n## Rebasing a fork to trigger CI (Maintainers Only)\n\nPull requests that originate from a fork will not have access to this repository's secrets, thus resulting in the inability to test against our CI instance. In order to trigger the CI action workflow, there is a handy script `./scripts/rebase-fork.sh` that automates the steps for you. It will:\n\n* Checkout the fork PR locally onto your machine and create a new branch prefixed as follows: `local/{name_of_fork_branch}`\n* Push your newly created branch to Github, appending an empty commit stating the original branch that was rebased.\n* Copy the contents of the fork's pull request (title and description) and create a new pull request, triggering the CI workflow.\n\n**Important**: This script does not handle subsequent commits to the original PR and would require you to rebase them manually. Therefore, it is important that authors include test results in their description and changes are approved before this script is executed.\n\nThis script depends on `gh` and `jq`. It also requires you to `gh auth login`, providing a SSO-authorized personal access token with the following scopes enabled:\n\n- repo\n- read:org\n- read:discussion\n\n### Example Usage\n\n```sh\n./scripts/rebase-fork.sh 557\n```\n"
  },
  {
    "path": "docs/RELEASES.md",
    "content": "## Release Process\n\ngo-tfe can be released as often as required. Documentation updates and test fixes that only touch test files don't require a release or tag. You can just merge these changes into `main` once they have been approved.\n\n### Preparing a release\n\nStart by comparing the main branch with the last release in order to fully understand which changes are being released. Compare the last release tag with main ([example](https://github.com/hashicorp/go-tfe/compare/v1.5.0...main)). For each meaningful change, double check the following:\n\n1. Is the change added to CHANGELOG.md?\n2. Does the public package API follow all endpoint conventions, such as naming, pointer usage, and options availability? Once these are released, they are permanent in the current major release version.\n3. Are new features generally available in the HCP Terraform API? Or is there another considered reason to release them?\n\nSteps to prepare the changelog for a new release:\n\n1. Replace `# Unreleased` with the version you are releasing.\n2. Ensure there is a line with `# Unreleased` at the top of the changelog for future changes. Ideally we don't ask authors to add this line; this will make it clear where they should add their changelog entry.\n3. Ensure that each existing changelog entry for the new release has the author(s) attributed and a pull request linked, i.e `- Some new feature/bugfix by @some-github-user (#3)[link-to-pull-request]`\n4. Open a pull request with these changes titled `vX.XX.XX Changelog`. Once approved and merged, you can go ahead and create the release.\n\n### Creating a release\n\n1. [Create a new release in GitHub](https://help.github.com/en/github/administering-a-repository/creating-releases) by clicking on \"Releases\" and then \"Draft a new release\"\n2. Set the `Tag version` to a new tag, using [Semantic Versioning](https://semver.org/) as a guideline.\n3. Set the `Target` as `main`.\n4. Set the `Release title` to the tag you created, `vX.Y.Z`\n5. Use the description section to describe why you're releasing and what changes you've made. You should include links to merged PRs. Use the following headers in the description of your release:\n   - BREAKING CHANGES: Use this for any changes that aren't backwards compatible. Include details on how to handle these changes.\n   - FEATURES: Use this for any large new features added,\n   - ENHANCEMENTS: Use this for smaller new features added\n   - BUG FIXES: Use this for any bugs that were fixed.\n   - NOTES: Use this section if you need to include any additional notes on things like upgrading, upcoming deprecations, or any other information you might want to highlight.\n\n   Markdown example:\n\n   ```markdown\n   ENHANCEMENTS\n   * Add description of new small feature by @some-github-user (#3)[link-to-pull-request]\n\n   BUG FIXES\n   * Fix description of a bug by @some-github-user (#2)[link-to-pull-request]\n   * Fix description of another bug by @some-github-user (#1)[link-to-pull-request]\n   ```\n\n6. Don't attach any binaries. The zip and tar.gz assets are automatically created and attached after you publish your release.\n7. Click \"Publish release\" to save and publish your release.\n"
  },
  {
    "path": "docs/TESTS.md",
    "content": "# Running tests\n\ngo-tfe relies on acceptance tests against either the HCP Terraform and Terraform Enterprise APIs. go-tfe is tested against HCP Terraform by our CI environment, and against Terraform Enterprise prior to release or otherwise as needed.\n\n## 1. (Optional) Create repositories for policy sets and registry modules\n\nIf you are planning to run the full suite of tests or work on policy sets or registry modules, you'll need to set up repositories for them in GitHub.\n\nYour policy set repository will need the following:\n1. A policy set stored in a subdirectory `policy-sets/foo`\n1. A branch other than `main` named `policies`\n\nAlternatively, you can start with this [example repository for policy sets](https://github.com/hashicorp/test-policy-set) by forking the repository to your GitHub account, then setting `GITHUB_POLICY_SET_IDENTIFIER` to the forked repository identifier `your-github-handle/test-policy-set`.\n\nYour registry module repository will need to be a [valid module](https://developer.hashicorp.com/terraform/cloud-docs/registry/publish-modules#preparing-a-module-repository).\nIt will need the following:\n1. To be named `terraform-<PROVIDER>-<NAME>`\n1. At least one valid SemVer tag in the format `x.y.z`\n[terraform-random-module](https://github.com/caseylang/terraform-random-module) is a good example repo.\n\n## 2. Set up environment variables (ENVVARS)\n\nYou'll need to have environment variables setup in your environment to run the tests. There are different options to facilitate setting up environment variables, using the tool [envchain](https://github.com/sorah/envchain) is one option:\n   1. Install envchain - [refer to the envchain README for details](https://github.com/sorah/envchain#installation)\n   1. Run the script `./scripts/setup-test-envvars.sh` to setup the env vars. This script uses envchain, will use a default namespace of `go-tfe` and will prompt you for environment variable values. To run: `sh ./scripts/setup-test-envvars.sh`\n   1. Or manually, pick a namespace for storing your environment variables, such as: `go-tfe`. Then, for each environment variable you need to set, run the following command:\n      ```sh\n      envchain --set YOUR_NAMESPACE_HERE ENVIRONMENT_VARIABLE_HERE\n      ```\n      **OR**\n\n      Set all of the environment variables at once with the following command:\n      ```sh\n      envchain --set YOUR_NAMESPACE_HERE TFE_ADDRESS TFE_TOKEN OAUTH_CLIENT_GITHUB_TOKEN GITHUB_POLICY_SET_IDENTIFIER\n      ```\n\n### Required ENVVARS\n\nTests are run against an actual backend so they require a valid backend address and token:\n\n1. `TFE_ADDRESS` - URL of a HCP Terraform or Terraform Enterprise instance to be used for testing, including scheme. Example: `https://tfe.local`\n1. `TFE_TOKEN` - A [user API token](https://developer.hashicorp.com/terraform/cloud-docs/users-teams-organizations/users#tokens) for the HCP Terraform or Terraform Enterprise instance being used for testing.\n\n**Note:** Alternatively, you can set `TFE_HOSTNAME` which serves as a fallback for `TFE_ADDRESS`. It will only be used if `TFE_ADDRESS` is not set and will resolve the host to an `https` scheme. Example: `tfe.local` => resolves to `https://tfe.local`\n\n### Optional ENVVARS\n\n1. `OAUTH_CLIENT_GITHUB_TOKEN` - [GitHub personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). Required for running any tests that use VCS (OAuth clients, policy sets, etc).\n2. `GITHUB_POLICY_SET_IDENTIFIER` - GitHub policy set repository identifier in the format `username/repository`. Required for running policy set tests.\n3. `GITHUB_REGISTRY_MODULE_IDENTIFIER` - GitHub registry module repository identifier in the format `username/repository`. Required for running registry module tests.\n4. `ENABLE_TFE` - Some tests are only applicable to Terraform Enterprise or HCP Terraforrm. By setting `ENABLE_TFE=1` you will enable Terraform Enterprise only tests and disable HCP Terraform only tests. In CI `ENABLE_TFE` is not set so if you are writing enterprise only features you should manually test with `ENABLE_TFE=1` against a Terraform Enterprise instance.\n5. `ENABLE_BETA` - Some tests require access to beta features. By setting `ENABLE_BETA=1` you will enable tests that require access to beta features. IN CI `ENABLE_BETA` is not set so if you are writing beta only features you should manually test with `ENABLE_BETA=1` against a Terraform Enterprise instance with those features enabled.\n6. `TFC_RUN_TASK_URL` - Run task integration tests require a URL to use when creating run tasks. To learn more about the Run Task API, [read here](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run-tasks/run-tasks)\n7. `GITHUB_APP_INSTALLATION_ID` - Required for running any tests that use GitHub App as the VCS provider (workspace, policy sets, registry module). These tests are skipped in the automated CI pipeline because in order to use this variable, the user has to have a GitHub App Installation setup done using the [HCP Terraform UI](https://developer.hashicorp.com/terraform/enterprise/admin/application/github-app-integration). And then the value can be fetched either from the UI or from the `GET /github-app/installations` API. The test command is listed below.\n    ```sh\n      $ GITHUB_APP_INSTALLATION_ID=ghain-xxxx TFE_ADDRESS= https://tfe.local TFE_TOKEN=xxx GITHUB_POLICY_SET_IDENTIFIER=username/repository GITHUB_REGISTRY_MODULE_IDENTIFIER=username/repository go test -run \"(GHA|GithubApp)\" -v ./...\n    ```\n8. `GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER` - Required for running tests for workspaces using no-code modules.\n\n## 3. Make sure run queue settings are correct\n\nIn order for the tests relating to queuing and capacity to pass, FRQ (fair run queuing) should be\nenabled with a limit of 2 concurrent runs per organization on the HCP Terraform or Terraform Enterprise instance you are using for testing.\n\n## 4. Run tests\n\nFor most situations, it's recommended to run specific tests because it takes about 20 minutes to run all of the tests.\n\n### Running specific tests\n\nTypically, you'll want to run specific tests. The commands below use notification configurations as an example.\n\n#### With envchain:\n```sh\n$ envchain YOUR_NAMESPACE_HERE go test -run TestNotificationConfiguration -v ./...\n```\n\n#### Without envchain (Using TFE_ADDRESS):\n```sh\n$ TFE_TOKEN=xyz TFE_ADDRESS=https://tfe.local ENABLE_TFE=1 go test -run TestNotificationConfiguration -v ./...\n```\n\n#### Without envchain (Using TFE_HOSTNAME):\n```sh\n$ TFE_TOKEN=xyz TFE_HOSTNAME=tfe.local ENABLE_TFE=1 go test -run TestNotificationConfiguration -v ./...\n```\n\n#### Using Makefile target `test`\n```sh\nTFE_TOKEN=xyz TFE_ADDRESS=https://tfe.local TESTARGS=\"-run TestNotificationConfiguration\" make test\n```\n\n### Running all tests\nIt takes about 20 minutes to run all of the tests, so specify a larger timeout when you run the tests (_the default timeout is 10 minutes_):\n\n#### With envchain:\n```sh\n$ envchain YOUR_NAMESPACE_HERE go test ./... -timeout=30m\n```\n\n#### Without envchain  (Using TFE_ADDRESS):\n```sh\n$ TFE_TOKEN=xyz TFE_ADDRESS=https://tfe.local ENABLE_TFE=1 go test ./... -timeout=30m\n```\n\n#### Without envchain  (Using TFE_HOSTNAME):\n```sh\n$ TFE_TOKEN=xyz TFE_HOSTNAME=tfe.local ENABLE_TFE=1 go test ./... -timeout=30m\n```\n\n\n### Running tests for HCP Terraform features that require paid plans (HashiCorp Employees)\n\nYou can use the test helper `newSubscriptionUpdater()` to upgrade your test organization to a Business Plan, giving the organization access to all features in HCP Terraform. This method requires `TFE_TOKEN` to be a user token with administrator access in the target test environment. Furthermore, you **can not** have enterprise features enabled (`ENABLE_TFE=1`) in order to use this method since the API call fails against Terraform Enterprise test environments.\n"
  },
  {
    "path": "entitlement_helper_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"errors\"\n)\n\nfunc getOrgEntitlements(client *Client, organizationName string) (*Entitlements, error) {\n\tctx := context.Background()\n\torgEntitlements, err := client.Organizations.ReadEntitlements(ctx, organizationName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif orgEntitlements == nil {\n\t\treturn nil, errors.New(\"The organization entitlements are empty.\")\n\t}\n\treturn orgEntitlements, nil\n}\n\nfunc hasGlobalRunTasks(client *Client, organizationName string) (bool, error) {\n\toe, err := getOrgEntitlements(client, organizationName)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn oe.GlobalRunTasks, nil\n}\n\nfunc hasPrivateRunTasks(client *Client, organizationName string) (bool, error) {\n\toe, err := getOrgEntitlements(client, organizationName)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn oe.PrivateRunTasks, nil\n}\n\nfunc hasAuditLogging(client *Client, organizationName string) (bool, error) {\n\toe, err := getOrgEntitlements(client, organizationName)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn oe.AuditLogging, nil\n}\n"
  },
  {
    "path": "errors.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\n// Generic errors applicable to all resources.\nvar (\n\t// ErrUnauthorized is returned when receiving a 401.\n\tErrUnauthorized = errors.New(\"unauthorized\")\n\n\t// ErrResourceNotFound is returned when receiving a 404.\n\tErrResourceNotFound = errors.New(\"resource not found\")\n\n\t// ErrMissingDirectory is returned when the path does not have an existing directory.\n\tErrMissingDirectory = errors.New(\"path needs to be an existing directory\")\n\n\t// ErrNamespaceNotAuthorized is returned when a user attempts to perform an action\n\t// on a namespace (organization) they do not have access to.\n\tErrNamespaceNotAuthorized = errors.New(\"namespace not authorized\")\n)\n\n// Options/fields that cannot be defined\nvar (\n\tErrUnsupportedOperations = errors.New(\"operations is deprecated and cannot be specified when execution mode is used\")\n\n\tErrUnsupportedPrivateKey = errors.New(\"private Key can only be present with Azure DevOps Server service provider\")\n\n\tErrUnsupportedBothTagsRegexAndFileTriggersEnabled = errors.New(`\"TagsRegex\" cannot be populated when \"FileTriggersEnabled\" is true`)\n\n\tErrUnsupportedBothTagsRegexAndTriggerPatterns = errors.New(`\"TagsRegex\" and \"TriggerPrefixes\" cannot be populated at the same time`)\n\n\tErrUnsupportedBothTagsRegexAndTriggerPrefixes = errors.New(`\"TagsRegex\" and \"TriggerPatterns\" cannot be populated at the same time`)\n\n\tErrUnsupportedRunTriggerType = errors.New(`\"RunTriggerType\" must be \"inbound\" when requesting \"include\" query params`)\n\n\tErrUnsupportedBothTriggerPatternsAndPrefixes = errors.New(`\"TriggerPatterns\" and \"TriggerPrefixes\" cannot be populated at the same time`)\n\n\tErrUnsupportedBothNamespaceAndPrivateRegistryName = errors.New(`\"Namespace\" cannot be populated when \"RegistryName\" is \"private\"`)\n)\n\n// Library errors that usually indicate a bug in the implementation of go-tfe\nvar (\n\tErrItemsMustBeSlice = errors.New(`model field \"Items\" must be a slice`) // ErrItemsMustBeSlice is returned when an API response attribute called Items is not a slice\n\n\tErrInvalidRequestBody = errors.New(\"go-tfe bug: DELETE/PATCH/POST body must be nil, ptr, or ptr slice\") // ErrInvalidRequestBody is returned when a request body for DELETE/PATCH/POST is not a reference type\n\n\tErrInvalidStructFormat = errors.New(\"go-tfe bug: struct can't use both json and jsonapi attributes\") // ErrInvalidStructFormat is returned when a mix of json and jsonapi tagged fields are used in the same struct\n)\n\n// Resource Errors\nvar (\n\t// ErrWorkspaceLocked is returned when trying to lock a locked workspace.\n\tErrWorkspaceLocked = errors.New(\"workspace already locked\")\n\n\t// ErrWorkspaceNotLocked is returned when trying to unlock a unlocked workspace.\n\tErrWorkspaceNotLocked = errors.New(\"workspace already unlocked\")\n\n\t// ErrWorkspaceLockedByRun is returned when trying to unlock a workspace locked by a run.\n\tErrWorkspaceLockedByRun = errors.New(\"unable to unlock workspace locked by run\")\n\n\t// ErrWorkspaceLockedByTeam is returned when trying to unlock a workspace locked by a team.\n\tErrWorkspaceLockedByTeam = errors.New(\"unable to unlock workspace locked by team\")\n\n\t// ErrWorkspaceLockedByUser is returned when trying to unlock a workspace locked by a user.\n\tErrWorkspaceLockedByUser = errors.New(\"unable to unlock workspace locked by user\")\n\n\t// ErrWorkspaceLockedStateVersionStillPending is returned when trying to unlock whose\n\t// latest state version is still pending.\n\tErrWorkspaceLockedStateVersionStillPending = errors.New(\"unable to unlock workspace while state version upload is still pending\")\n\n\t// ErrWorkspaceStillProcessing is returned when a workspace is still processing state\n\t// to determine if it is safe to delete. \"conflict\" followed by newline is used to\n\t// preserve go-tfe version compatibility with the error constructed at runtime before it was\n\t// defined here.\n\tErrWorkspaceStillProcessing = errors.New(\"conflict\\nLatest workspace state is being processed to discover resources, please try again later\")\n\n\t// ErrWorkspaceNotSafeToDelete is returned when a workspace has processed state and\n\t// is determined to still have resources present. \"conflict\" followed by newline is used to\n\t// preserve go-tfe version compatibility with the error constructed at runtime before it was\n\t// defined here.\n\tErrWorkspaceNotSafeToDelete = errors.New(\"conflict\\nworkspace cannot be safely deleted because it is still managing resources\")\n\n\t// ErrWorkspaceLockedCannotDelete is returned when a workspace cannot be safely deleted when\n\t// it is locked. \"conflict\" followed by newline is used to preserve go-tfe version\n\t// compatibility with the error constructed at runtime before it was defined here.\n\tErrWorkspaceLockedCannotDelete = errors.New(\"conflict\\nWorkspace is currently locked. Workspace must be unlocked before it can be safely deleted\")\n\n\t// ErrHYOKCannotBeDisabled is returned when attempting to disable HYOK on a workspace that already has it enabled.\n\tErrHYOKCannotBeDisabled = errors.New(\"bad request\\n\\nhyok may not be disabled once it has been turned on for a workspace\")\n)\n\n// Invalid values for resources/struct fields\nvar (\n\tErrInvalidWorkspaceID = errors.New(\"invalid value for workspace ID\")\n\n\tErrInvalidWorkspaceValue = errors.New(\"invalid value for workspace\")\n\n\tErrInvalidTerraformVersionID = errors.New(\"invalid value for terraform version ID\")\n\n\tErrInvalidTerraformVersionType = errors.New(\"invalid type for terraform version. Please use 'terraform-version'\")\n\n\tErrInvalidOPAVersionID = errors.New(\"invalid value for OPA version ID\")\n\n\tErrInvalidSentinelVersionID = errors.New(\"invalid value for Sentinel version ID\")\n\n\tErrInvalidConfigVersionID = errors.New(\"invalid value for configuration version ID\")\n\n\tErrInvalidCostEstimateID = errors.New(\"invalid value for cost estimate ID\")\n\n\tErrInvalidSMTPAuth = errors.New(\"invalid smtp auth type\")\n\n\tErrInvalidAgentPoolID = errors.New(\"invalid value for agent pool ID\")\n\n\tErrInvalidAgentTokenID = errors.New(\"invalid value for agent token ID\")\n\n\tErrInvalidRunID = errors.New(\"invalid value for run ID\")\n\n\tErrInvalidRunEventID = errors.New(\"invalid value for run event ID\")\n\n\tErrInvalidProjectID = errors.New(\"invalid value for project ID\")\n\n\tErrInvalidPagination = errors.New(\"invalid value for page size or number\")\n\n\tErrInvalidReservedTagKeyID = errors.New(\"invalid value for reserved tag key ID\")\n\n\tErrInvalidRunTaskCategory = errors.New(`category must be \"task\"`)\n\n\tErrInvalidRunTaskID = errors.New(\"invalid value for run task ID\")\n\n\tErrInvalidRunTaskURL = errors.New(\"invalid url for run task URL\")\n\n\tErrInvalidWorkspaceRunTaskID = errors.New(\"invalid value for workspace run task ID\")\n\n\tErrInvalidWorkspaceRunTaskType = errors.New(`invalid value for type, please use \"workspace-tasks\"`)\n\n\tErrInvalidTaskResultID = errors.New(\"invalid value for task result ID\")\n\n\tErrInvalidTaskStageID = errors.New(\"invalid value for task stage ID\")\n\n\tErrInvalidApplyID = errors.New(\"invalid value for apply ID\")\n\n\tErrInvalidOrg = errors.New(\"invalid value for organization\")\n\n\tErrInvalidName = errors.New(\"invalid value for name\")\n\n\tErrInvalidNotificationConfigID = errors.New(\"invalid value for notification configuration ID\")\n\n\tErrInvalidMembership = errors.New(\"invalid value for membership\")\n\n\tErrInvalidMembershipIDs = errors.New(\"invalid value for organization membership ids\")\n\n\tErrInvalidOauthClientID = errors.New(\"invalid value for OAuth client ID\")\n\n\tErrInvalidOauthTokenID = errors.New(\"invalid value for OAuth token ID\")\n\n\tErrInvalidPolicySetID = errors.New(\"invalid value for policy set ID\")\n\n\tErrInvalidPolicyCheckID = errors.New(\"invalid value for policy check ID\")\n\n\tErrInvalidPolicyEvaluationID = errors.New(\"invalid value for policy evaluation ID\")\n\n\tErrInvalidPolicySetOutcomeID = errors.New(\"invalid value for policy set outcome ID\")\n\n\tErrInvalidTag = errors.New(\"invalid tag id\")\n\n\tErrInvalidPlanExportID = errors.New(\"invalid value for plan export ID\")\n\n\tErrInvalidPlanID = errors.New(\"invalid value for plan ID\")\n\n\tErrInvalidParamID = errors.New(\"invalid value for parameter ID\")\n\n\tErrInvalidPolicyID = errors.New(\"invalid value for policy ID\")\n\n\tErrInvalidProvider = errors.New(\"invalid value for provider\")\n\n\tErrInvalidVersion = errors.New(\"invalid value for version\")\n\n\tErrInvalidRunTriggerID = errors.New(\"invalid value for run trigger ID\")\n\n\tErrInvalidRunTriggerType = errors.New(`invalid value or no value for RunTriggerType. It must be either \"inbound\" or \"outbound\"`)\n\n\tErrInvalidIncludeValue = errors.New(`invalid value for \"include\" field`)\n\n\tErrInvalidSHHKeyID = errors.New(\"invalid value for SSH key ID\")\n\n\tErrInvalidStateVerID = errors.New(\"invalid value for state version ID\")\n\n\tErrInvalidOutputID = errors.New(\"invalid value for state version output ID\")\n\n\tErrInvalidAccessTeamID = errors.New(\"invalid value for team access ID\")\n\n\tErrInvalidTeamProjectAccessID = errors.New(\"invalid value for team project access ID\")\n\n\tErrInvalidTeamProjectAccessType = errors.New(\"invalid type for team project access\")\n\n\tErrInvalidTeamID = errors.New(\"invalid value for team ID\")\n\n\tErrInvalidUsernames = errors.New(\"invalid value for usernames\")\n\n\tErrInvalidUserID = errors.New(\"invalid value for user ID\")\n\n\tErrInvalidUserValue = errors.New(\"invalid value for user\")\n\n\tErrInvalidTokenID = errors.New(\"invalid value for token ID\")\n\n\tErrInvalidCategory = errors.New(\"category must be policy-set\")\n\n\tErrInvalidPolicies = errors.New(\"must provide at least one policy\")\n\n\tErrInvalidVariableID = errors.New(\"invalid value for variable ID\")\n\n\tErrInvalidNotificationTrigger = errors.New(\"invalid value for notification trigger\")\n\n\tErrInvalidVariableSetID = errors.New(\"invalid variable set ID\")\n\n\tErrInvalidCommentID = errors.New(\"invalid value for comment ID\")\n\n\tErrInvalidCommentBody = errors.New(\"invalid value for comment body\")\n\n\tErrInvalidNamespace = errors.New(\"invalid value for namespace\")\n\n\tErrInvalidKeyID = errors.New(\"invalid value for key-id\")\n\n\tErrInvalidOS = errors.New(\"invalid value for OS\")\n\n\tErrInvalidArch = errors.New(\"invalid value for arch\")\n\n\tErrInvalidAgentID = errors.New(\"invalid value for Agent ID\")\n\n\tErrInvalidModuleID = errors.New(\"invalid value for module ID\")\n\n\tErrInvalidRegistryName = errors.New(`invalid value for registry-name. It must be either \"private\" or \"public\"`)\n\n\tErrInvalidCallbackURL = errors.New(\"invalid value for callback URL\")\n\n\tErrInvalidAccessToken = errors.New(\"invalid value for access token\")\n\n\tErrInvalidTaskResultsCallbackStatus = fmt.Errorf(\"invalid value for task result status. Must be either `%s`, `%s`, or `%s`\", TaskFailed, TaskPassed, TaskRunning)\n\n\tErrInvalidDescriptionConflict = errors.New(\"invalid attributes\\n\\nValidation failed: Description has already been taken\")\n\n\tErrInvalidOIDC = errors.New(\"invalid value for OIDC configuration ID\")\n\n\tErrInvalidHYOK = errors.New(\"invalid value for HYOK configuration ID\")\n\n\tErrInvalidHYOKCustomerKeyVersion = errors.New(\"invalid value for HYOK Customer key version ID\")\n\n\tErrInvalidHYOKEncryptedDataKey = errors.New(\"invalid value for HYOK encrypted data key ID\")\n\n\tErrInvalidStackID = errors.New(\"invalid value for stack ID\")\n\n\tErrInvalidRemoteStateOptions = errors.New(\"invalid attribute\\n\\nProject remote state cannot be enabled when global remote state sharing is enabled\")\n\n\tErrInvalidSAMLProviderType = errors.New(\"invalid SAML provider type\")\n)\n\nvar (\n\tErrRequiredAccess = errors.New(\"access is required\")\n\n\tErrRequiredAgentPoolID = errors.New(\"'agent' execution mode requires an agent pool ID to be specified\")\n\n\tErrRequiredAgentMode                      = errors.New(\"specifying an agent pool ID requires 'agent' execution mode\")\n\tErrRequiredBranchWhenTestsEnabled         = errors.New(\"VCS branch is required when enabling tests\")\n\tErrBranchMustBeEmptyWhenTagsEnabled       = errors.New(\"VCS branch must be empty to enable tags\")\n\tErrRequiredCategory                       = errors.New(\"category is required\")\n\tErrAgentPoolNotRequiredForRemoteExecution = errors.New(\"'remote' execution mode does not support agent pool IDs\")\n\tErrRequiredDestinationType                = errors.New(\"destination type is required\")\n\n\tErrRequiredDataType = errors.New(\"data type is required\")\n\n\tErrRequiredKey = errors.New(\"key is required\")\n\n\tErrRequiredName = errors.New(\"name is required\")\n\n\tErrRequiredQuery = errors.New(\"query cannot be empty\")\n\n\tErrRequiredEnabled = errors.New(\"enabled is required\")\n\n\tErrRequiredEnforce = errors.New(\"enforce or enforcement-level is required\")\n\n\tErrConflictingEnforceEnforcementLevel = errors.New(\"enforce and enforcement-level may not both be specified together\")\n\n\tErrRequiredEnforcementPath = errors.New(\"enforcement path is required\")\n\n\tErrRequiredEnforcementMode = errors.New(\"enforcement mode is required\")\n\n\tErrRequiredEmail = errors.New(\"email is required\")\n\n\tErrRequiredM5 = errors.New(\"MD5 is required\")\n\n\tErrRequiredURL = errors.New(\"url is required\")\n\n\tErrRequiredArchsOrURLAndSha = errors.New(\"valid archs or url and sha are required\")\n\n\tErrRequiredAPIURL = errors.New(\"API URL is required\")\n\n\tErrRequiredHTTPURL = errors.New(\"HTTP URL is required\")\n\n\tErrRequiredServiceProvider = errors.New(\"service provider is required\")\n\n\tErrRequiredProvider = errors.New(\"provider is required\")\n\n\tErrRequiredOauthToken = errors.New(\"OAuth token is required\")\n\n\tErrRequiredOauthTokenOrGithubAppInstallationID = errors.New(\"either oauth token ID or github app installation ID is required\")\n\n\tErrRequiredTestNumber = errors.New(\"TestNumber is required\")\n\n\tErrMissingTagIdentifier = errors.New(\"must specify at least one tag by ID or name\")\n\n\tErrAgentTokenDescription = errors.New(\"agent token description can't be blank\")\n\n\tErrRequiredTagID = errors.New(\"you must specify at least one tag id to remove\")\n\n\tErrRequiredTagWorkspaceID = errors.New(\"you must specify at least one workspace to add tag to\")\n\n\tErrRequiredWorkspace = errors.New(\"workspace is required\")\n\n\tErrRequiredProject = errors.New(\"project is required\")\n\n\tErrRequiredWorkspaceID = errors.New(\"workspace ID is required\")\n\n\tErrRequiredProjectID = errors.New(\"project ID is required\")\n\n\tErrRequiredStackID = errors.New(\"stack ID is required\")\n\n\tErrWorkspacesRequired = errors.New(\"workspaces is required\")\n\n\tErrWorkspaceMinLimit = errors.New(\"must provide at least one workspace\")\n\n\tErrProjectMinLimit = errors.New(\"must provide at least one project\")\n\n\tErrRequiredPlan = errors.New(\"plan is required\")\n\n\tErrRequiredPolicies = errors.New(\"policies is required\")\n\n\tErrRequiredVersion = errors.New(\"version is required\")\n\n\tErrRequiredVCSRepo = errors.New(\"vcs repo is required\")\n\n\tErrRequiredIdentifier = errors.New(\"identifier is required\")\n\n\tErrRequiredDisplayIdentifier = errors.New(\"display identifier is required\")\n\n\tErrRequiredSha = errors.New(\"sha is required\")\n\n\tErrRequiredSourceable = errors.New(\"sourceable is required\")\n\n\tErrRequiredValue = errors.New(\"value is required\")\n\n\tErrRequiredOrg = errors.New(\"organization is required\")\n\n\tErrRequiredTeam = errors.New(\"team is required\")\n\n\tErrRequiredStateVerListOps = errors.New(\"StateVersionListOptions is required\")\n\n\tErrRequiredTeamAccessListOps = errors.New(\"TeamAccessListOptions is required\")\n\n\tErrRequiredTeamProjectAccessListOps = errors.New(\"TeamProjectAccessListOptions is required\")\n\n\tErrRequiredRunTriggerListOps = errors.New(\"RunTriggerListOptions is required\")\n\n\tErrRequiredTFVerCreateOps = errors.New(\"version, URL and sha is required for AdminTerraformVersionCreateOptions\")\n\n\tErrRequiredOPAVerCreateOps = errors.New(\"version, URL and sha is required for AdminOPAVersionCreateOptions\")\n\n\tErrRequiredSentinelVerCreateOps = errors.New(\"version, URL and sha is required for AdminSentinelVersionCreateOptions\")\n\n\tErrRequiredSerial = errors.New(\"serial is required\")\n\n\tErrRequiredState = errors.New(\"state is required\")\n\n\tErrRequiredSHHKeyID = errors.New(\"SSH key ID is required\")\n\n\tErrRequiredOnlyOneField = errors.New(\"only one of usernames or organization membership ids can be provided\")\n\n\tErrRequiredUsernameOrMembershipIds = errors.New(\"usernames or organization membership ids are required\")\n\n\tErrRequiredGlobalFlag = errors.New(\"global flag is required\")\n\n\tErrRequiredWorkspacesList = errors.New(\"no workspaces list provided\")\n\n\tErrRequiredStacksList = errors.New(\"no stacks list provided\")\n\n\tErrCommentBody = errors.New(\"comment body is required\")\n\n\tErrEmptyTeamName = errors.New(\"team name can not be empty\")\n\n\tErrInvalidEmail = errors.New(\"email is invalid\")\n\n\tErrRequiredPrivateRegistry = errors.New(\"only private registry is allowed\")\n\n\tErrRequiredOS = errors.New(\"OS is required\")\n\n\tErrRequiredArch = errors.New(\"arch is required\")\n\n\tErrRequiredShasum = errors.New(\"shasum is required\")\n\n\tErrRequiredFilename = errors.New(\"filename is required\")\n\n\tErrInvalidAsciiArmor = errors.New(\"ASCII Armor is invalid\")\n\n\tErrRequiredNamespace = errors.New(\"namespace is required for public registry\")\n\n\tErrRequiredRegistryModule = errors.New(\"registry module is required\")\n\n\tErrRequiredTagBindings = errors.New(\"TagBindings are required\")\n\n\tErrInvalidTestRunID = errors.New(\"invalid value for test run id\")\n\n\tErrInvalidQueryRunID = errors.New(\"invalid value for query run id\")\n\n\tErrTerraformVersionValidForPlanOnly = errors.New(\"setting terraform-version is only valid when plan-only is set to true\")\n\n\tErrStateMustBeOmitted = errors.New(\"when uploading state, the State and JSONState strings must be omitted from options\")\n\n\tErrRequiredRawState = errors.New(\"RawState is required\")\n\n\tErrStateVersionUploadNotSupported = errors.New(\"upload not supported by this version of Terraform Enterprise\")\n\n\tErrSanitizedStateUploadURLMissing = errors.New(\"sanitized state upload URL is missing\")\n\n\tErrRequiredRoleARN = errors.New(\"role-arn is required for AWS OIDC configuration\")\n\n\tErrRequiredServiceAccountEmail = errors.New(\"service-account-email is required for GCP OIDC configuration\")\n\n\tErrRequiredProjectNumber = errors.New(\"project-number is required for GCP OIDC configuration\")\n\n\tErrRequiredWorkloadProviderName = errors.New(\"workload-provider-name is required for GCP OIDC configuration\")\n\n\tErrRequiredClientID = errors.New(\"client-id is required for Azure OIDC configuration\")\n\n\tErrRequiredSubscriptionID = errors.New(\"subscription-id is required for Azure OIDC configuration\")\n\n\tErrRequiredTenantID = errors.New(\"tenant-id is required for Azure OIDC configuration\")\n\n\tErrRequiredVaultAddress = errors.New(\"address is required for Vault OIDC configuration\")\n\n\tErrRequiredRoleName = errors.New(\"role is required for Vault OIDC configuration\")\n\n\tErrRequiredKEKID = errors.New(\"kek-id is required for HYOK configuration\")\n\n\tErrRequiredOIDCConfiguration = errors.New(\"oidc-configuration is required for HYOK configuration\")\n\n\tErrRequiredAgentPool = errors.New(\"agent-pool is required for HYOK configuration\")\n\n\tErrRequiredKMSOptions = errors.New(\"kms-options is required for HYOK configuration\")\n\n\tErrRequiredKMSOptionsKeyRegion = errors.New(\"kms-options.key-region is required for HYOK configuration with AWS OIDC\")\n\n\tErrRequiredKMSOptionsKeyLocation = errors.New(\"kms-options.key-location is required for HYOK configuration with GCP OIDC\")\n\n\tErrRequiredKMSOptionsKeyRingID = errors.New(\"kms-options.key-ring-id is required for HYOK configuration with GCP OIDC\")\n\n\tErrSCIMTokenDescription = errors.New(\"SCIM token description can't be blank\")\n)\n"
  },
  {
    "path": "example_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tslug \"github.com/hashicorp/go-slug\"\n)\n\nfunc ExampleOrganizations() {\n\tconfig := &Config{\n\t\tToken:             \"insert-your-token-here\",\n\t\tRetryServerErrors: true,\n\t}\n\n\tclient, err := NewClient(config)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a context\n\tctx := context.Background()\n\n\t// Create a new organization\n\toptions := OrganizationCreateOptions{\n\t\tName:  String(\"example\"),\n\t\tEmail: String(\"info@example.com\"),\n\t}\n\n\torg, err := client.Organizations.Create(ctx, options)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Delete an organization\n\terr = client.Organizations.Delete(ctx, org.Name)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc ExampleWorkspaces() {\n\tconfig := &Config{\n\t\tToken:             \"insert-your-token-here\",\n\t\tRetryServerErrors: true,\n\t}\n\n\tclient, err := NewClient(config)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a context\n\tctx := context.Background()\n\n\t// Create a new workspace\n\tw, err := client.Workspaces.Create(ctx, \"org-name\", WorkspaceCreateOptions{\n\t\tName: String(\"my-app-tst\"),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Update the workspace\n\tw, err = client.Workspaces.Update(ctx, \"org-name\", w.Name, WorkspaceUpdateOptions{\n\t\tAutoApply:        Bool(false),\n\t\tTerraformVersion: String(\"0.11.1\"),\n\t\tWorkingDirectory: String(\"my-app/infra\"),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc ExampleConfigurationVersions_UploadTarGzip() {\n\tctx := context.Background()\n\tclient, err := NewClient(&Config{\n\t\tToken:             \"insert-your-token-here\",\n\t\tRetryServerErrors: true,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tpacker, err := slug.NewPacker(\n\t\tslug.DereferenceSymlinks(),  // dereferences symlinks\n\t\tslug.ApplyTerraformIgnore(), // ignores paths specified in .terraformignore\n\t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\trawConfig := bytes.NewBuffer(nil)\n\t// Pass in a path\n\t_, err = packer.Pack(\"test-fixtures/config\", rawConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a configuration version\n\tcv, err := client.ConfigurationVersions.Create(ctx, \"ws-12345678\", ConfigurationVersionCreateOptions{\n\t\tAutoQueueRuns: Bool(false),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Upload the buffer\n\terr = client.ConfigurationVersions.UploadTarGzip(ctx, cv.UploadURL, rawConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc ExampleRegistryModules_UploadTarGzip() {\n\tctx := context.Background()\n\tclient, err := NewClient(&Config{\n\t\tToken:             \"insert-your-token-here\",\n\t\tRetryServerErrors: true,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tpacker, err := slug.NewPacker(\n\t\tslug.DereferenceSymlinks(),  // dereferences symlinks\n\t\tslug.ApplyTerraformIgnore(), // ignores paths specified in .terraformignore\n\t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\trawConfig := bytes.NewBuffer(nil)\n\t// Pass in a path\n\t_, err = packer.Pack(\"test-fixtures/config\", rawConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a registry module\n\trm, err := client.RegistryModules.Create(ctx, \"hashicorp\", RegistryModuleCreateOptions{\n\t\tName:         String(\"my-module\"),\n\t\tProvider:     String(\"provider\"),\n\t\tRegistryName: PrivateRegistry,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\topts := RegistryModuleCreateVersionOptions{\n\t\tVersion: String(\"1.1.0\"),\n\t}\n\n\t// Create a registry module version\n\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\tOrganization: \"hashicorp\",\n\t\tName:         rm.Name,\n\t\tProvider:     rm.Provider,\n\t}, opts)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tuploadURL, ok := rmv.Links[\"upload\"].(string)\n\tif !ok {\n\t\tlog.Fatal(\"upload url must be a valid string\")\n\t}\n\t// Upload the buffer\n\terr = client.RegistryModules.UploadTarGzip(ctx, uploadURL, rawConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc ExampleStateVersions_Upload() {\n\tctx := context.Background()\n\tclient, err := NewClient(&Config{\n\t\tToken:             \"insert-your-token-here\",\n\t\tRetryServerErrors: true,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Lock the workspace\n\tif _, err = client.Workspaces.Lock(ctx, \"ws-12345678\", WorkspaceLockOptions{}); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tstate, err := os.ReadFile(\"state.json\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create upload options that does not contain a State attribute within the create options\n\toptions := StateVersionUploadOptions{\n\t\tStateVersionCreateOptions: StateVersionCreateOptions{\n\t\t\tLineage: String(\"493f7758-da5e-229e-7872-ea1f78ebe50a\"),\n\t\t\tSerial:  Int64(int64(2)),\n\t\t\tMD5:     String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tForce:   Bool(false),\n\t\t},\n\t\tRawState: state,\n\t}\n\n\t// Upload a state version\n\tif _, err = client.StateVersions.Upload(ctx, \"ws-12345678\", options); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Unlock the workspace\n\tif _, err = client.Workspaces.Unlock(ctx, \"ws-12345678\"); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/backing_data/main.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\ttfe \"github.com/hashicorp/go-tfe\"\n\t\"log\"\n\t\"strings\"\n)\n\nfunc main() {\n\taction := flag.String(\"action\", \"\", \"Action (soft-delete|restore|permanently-delete\")\n\texternalId := flag.String(\"external-id\", \"\", \"External ID of StateVersion or ConfigurationVersion\")\n\n\tflag.Parse()\n\n\tif action == nil || *action == \"\" {\n\t\tlog.Fatal(\"No Action provided\")\n\t}\n\n\tif externalId == nil || *externalId == \"\" {\n\t\tlog.Fatal(\"No external ID provided\")\n\t}\n\n\tctx := context.Background()\n\tclient, err := tfe.NewClient(&tfe.Config{\n\t\tRetryServerErrors: true,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\terr = performAction(ctx, client, *action, *externalId)\n\tif err != nil {\n\t\tlog.Fatalf(\"Error performing action: %v\", err)\n\t}\n}\n\nfunc performAction(ctx context.Context, client *tfe.Client, action string, id string) error {\n\texternalIdParts := strings.Split(id, \"-\")\n\tswitch externalIdParts[0] {\n\tcase \"cv\":\n\t\tswitch action {\n\t\tcase \"soft-delete\":\n\t\t\treturn client.ConfigurationVersions.SoftDeleteBackingData(ctx, id)\n\t\tcase \"restore\":\n\t\t\treturn client.ConfigurationVersions.RestoreBackingData(ctx, id)\n\t\tcase \"permanently-delete\":\n\t\t\treturn client.ConfigurationVersions.PermanentlyDeleteBackingData(ctx, id)\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported action: %s\", action)\n\t\t}\n\tcase \"sv\":\n\t\tswitch action {\n\t\tcase \"soft-delete\":\n\t\t\treturn client.StateVersions.SoftDeleteBackingData(ctx, id)\n\t\tcase \"restore\":\n\t\t\treturn client.StateVersions.RestoreBackingData(ctx, id)\n\t\tcase \"permanently-delete\":\n\t\t\treturn client.StateVersions.PermanentlyDeleteBackingData(ctx, id)\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported action: %s\", action)\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported external ID: %s\", id)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "examples/configuration_versions/main.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"log\"\n\n\t\"github.com/hashicorp/go-slug\"\n\ttfe \"github.com/hashicorp/go-tfe\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\tclient, err := tfe.NewClient(&tfe.Config{\n\t\tRetryServerErrors: true,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tpacker, err := slug.NewPacker(\n\t\tslug.DereferenceSymlinks(),  // dereferences symlinks\n\t\tslug.ApplyTerraformIgnore(), // ignores paths specified in .terraformignore\n\t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\trawConfig := bytes.NewBuffer(nil)\n\t// Pass in a path\n\t_, err = packer.Pack(\"test-fixtures/config\", rawConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a configuration version\n\tcv, err := client.ConfigurationVersions.Create(ctx, \"ws-12345678\", tfe.ConfigurationVersionCreateOptions{\n\t\tAutoQueueRuns: tfe.Bool(false),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Upload the configuration\n\terr = client.ConfigurationVersions.UploadTarGzip(ctx, cv.UploadURL, rawConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/organizations/main.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n)\n\nfunc main() {\n\tconfig := &tfe.Config{\n\t\tToken:             \"insert-your-token-here\",\n\t\tRetryServerErrors: true,\n\t}\n\n\tclient, err := tfe.NewClient(config)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a context\n\tctx := context.Background()\n\n\t// Create a new organization\n\toptions := tfe.OrganizationCreateOptions{\n\t\tName:  tfe.String(\"example\"),\n\t\tEmail: tfe.String(\"info@example.com\"),\n\t}\n\n\torg, err := client.Organizations.Create(ctx, options)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Delete an organization\n\terr = client.Organizations.Delete(ctx, org.Name)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/projects/main.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\n\t\"github.com/hashicorp/jsonapi\"\n)\n\nfunc main() {\n\tconfig := &tfe.Config{\n\t\tToken:             \"insert-your-token-here\",\n\t\tRetryServerErrors: true,\n\t}\n\n\tclient, err := tfe.NewClient(config)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a context\n\tctx := context.Background()\n\n\t// Create a new project\n\tp, err := client.Projects.Create(ctx, \"org-test\", tfe.ProjectCreateOptions{\n\t\tName: \"my-app-tst\",\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Update the project auto destroy activity duration\n\tp, err = client.Projects.Update(ctx, p.ID, tfe.ProjectUpdateOptions{\n\t\tAutoDestroyActivityDuration: jsonapi.NewNullableAttrWithValue(\"3d\"),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Disable auto destroy\n\tp, err = client.Projects.Update(ctx, p.ID, tfe.ProjectUpdateOptions{\n\t\tAutoDestroyActivityDuration: jsonapi.NewNullNullableAttr[string](),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\terr = client.Projects.Delete(ctx, p.ID)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/registry_modules/main.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"log\"\n\n\t\"github.com/hashicorp/go-slug\"\n\ttfe \"github.com/hashicorp/go-tfe\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\tclient, err := tfe.NewClient(&tfe.Config{\n\t\tRetryServerErrors: true,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tpacker, err := slug.NewPacker(\n\t\tslug.DereferenceSymlinks(),  // dereferences symlinks\n\t\tslug.ApplyTerraformIgnore(), // ignores paths specified in .terraformignore\n\t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\trawConfig := bytes.NewBuffer(nil)\n\t// Pass in the configuration path\n\t_, err = packer.Pack(\"test-fixtures/config\", rawConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a registry module\n\trm, err := client.RegistryModules.Create(ctx, \"hashicorp\", tfe.RegistryModuleCreateOptions{\n\t\tName:         tfe.String(\"my-module\"),\n\t\tProvider:     tfe.String(\"provider\"),\n\t\tRegistryName: tfe.PrivateRegistry,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\topts := tfe.RegistryModuleCreateVersionOptions{\n\t\tVersion: tfe.String(\"1.1.0\"),\n\t}\n\n\t// Create a registry module version\n\trmv, err := client.RegistryModules.CreateVersion(ctx, tfe.RegistryModuleID{\n\t\tOrganization: \"hashicorp\",\n\t\tName:         rm.Name,\n\t\tProvider:     rm.Provider,\n\t}, opts)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tuploadURL, ok := rmv.Links[\"upload\"].(string)\n\tif !ok {\n\t\tlog.Fatal(\"upload url must be a valid string\")\n\t}\n\t// Upload the buffer\n\terr = client.RegistryModules.UploadTarGzip(ctx, uploadURL, rawConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/run_errors/README.md",
    "content": "## Example: Parsing Run Errors\n\nIn this example, you'll use terraform to create a run with errors on HCP Terraform, then\nexecute the command to read the plan log and filter it for errors. It's important to use\nTerraform to create the run, otherwise you will not get the structured log that this code\nexample requires.\n\n#### Instructions\n\n1. Change to the terraform directory, and run terraform init using Terraform 1.3+\n\n`cd terraform`\n`TF_CLOUD_ORGANIZATION=\"yourorg\" terraform init`\n\n2. Apply the changes (You should see an error \"Error making request\" or similar)\n\n`TF_CLOUD_ORGANIZATION=\"yourorg\" terraform apply`\n\n3. Notice the run ID in the URL (it begins with \"run-\") and execute the example with the run ID as a flag:\n\n`cd ../`\n`TFE_TOKEN=\"YOURTOKEN\" go run main.go run-RUN_ID_FROM_URL_ABOVE`\n"
  },
  {
    "path": "examples/run_errors/main.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n)\n\nvar (\n\tpollInterval = 500 * time.Millisecond\n)\n\n// Diagnostic represents a diagnostic type message from Terraform, which is how errors\n// are usually represented.\ntype Diagnostic struct {\n\tSeverity string           `json:\"severity\"`\n\tSummary  string           `json:\"summary\"`\n\tDetail   string           `json:\"detail\"`\n\tAddress  string           `json:\"address,omitempty\"`\n\tRange    *DiagnosticRange `json:\"range,omitempty\"`\n}\n\n// Pos represents a position in the source code.\ntype Pos struct {\n\t// Line is a one-based count for the line in the indicated file.\n\tLine int `json:\"line\"`\n\n\t// Column is a one-based count of Unicode characters from the start of the line.\n\tColumn int `json:\"column\"`\n\n\t// Byte is a zero-based offset into the indicated file.\n\tByte int `json:\"byte\"`\n}\n\n// DiagnosticRange represents the filename and position of the diagnostic subject.\ntype DiagnosticRange struct {\n\tFilename string `json:\"filename\"`\n\tStart    Pos    `json:\"start\"`\n\tEnd      Pos    `json:\"end\"`\n}\n\n// For full decoding, see https://github.com/hashicorp/terraform/blob/main/internal/command/jsonformat/renderer.go\ntype JSONLog struct {\n\tMessage    string      `json:\"@message\"`\n\tLevel      string      `json:\"@level\"`\n\tTimestamp  string      `json:\"@timestamp\"`\n\tType       string      `json:\"type\"`\n\tDiagnostic *Diagnostic `json:\"diagnostic\"`\n}\n\n// Given a\nfunc logErrorsOnly(reader io.Reader) {\n\tscanner := bufio.NewScanner(reader)\n\tfor scanner.Scan() {\n\t\tvar jsonLog JSONLog\n\t\terr := json.Unmarshal([]byte(scanner.Text()), &jsonLog)\n\t\t// It's possible this log is not encoded as JSON at all, so errors will be ignored.\n\t\tif err == nil && jsonLog.Level == \"error\" {\n\t\t\tfmt.Println()\n\t\t\tfmt.Println(\"--- Error Message\")\n\t\t\tfmt.Println(jsonLog.Message)\n\t\t\tfmt.Println(\"---\")\n\t\t\tfmt.Println()\n\t\t\tif jsonLog.Type == \"diagnostic\" {\n\t\t\t\tfmt.Println(\"--- Diagnostic Details\")\n\t\t\t\tfmt.Println(jsonLog.Diagnostic.Detail)\n\t\t\t\tfmt.Println(\"---\")\n\t\t\t\tfmt.Println()\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc logRunErrors(ctx context.Context, client *tfe.Client, run *tfe.Run) {\n\tvar reader io.Reader\n\tvar err error\n\n\tif run.Apply != nil && run.Apply.Status == tfe.ApplyErrored {\n\t\tlog.Printf(\"Reading apply logs from %q\", run.Apply.LogReadURL)\n\t\treader, err = client.Applies.Logs(ctx, run.Apply.ID)\n\t} else if run.Plan != nil && run.Plan.Status == tfe.PlanErrored {\n\t\tlog.Printf(\"Reading apply logs from %q\", run.Plan.LogReadURL)\n\t\treader, err = client.Plans.Logs(ctx, run.Plan.ID)\n\t} else {\n\t\tlog.Fatal(\"Failed to find an errored plan or apply.\")\n\t}\n\n\tif err != nil {\n\t\tlog.Fatal(\"Failed to read error log: \", err)\n\t}\n\n\tlogErrorsOnly(reader)\n}\n\nfunc readRun(ctx context.Context, client *tfe.Client, id string) *tfe.Run {\n\tr, err := client.Runs.ReadWithOptions(ctx, id, &tfe.RunReadOptions{\n\t\tInclude: []tfe.RunIncludeOpt{tfe.RunApply, tfe.RunPlan},\n\t})\n\tif err != nil {\n\t\tlog.Fatal(\"Failed to read specified run: \", err)\n\t}\n\treturn r\n}\n\nfunc main() {\n\tif len(os.Args) < 2 {\n\t\tfmt.Println(\"Usage:\")\n\t\tfmt.Printf(\"\\t%s <run ID>\\n\", os.Args[0])\n\t\tos.Exit(1)\n\t}\n\n\tctx := context.Background()\n\tclient, err := tfe.NewClient(&tfe.Config{\n\t\tAddress:           \"https://app.terraform.io\",\n\t\tRetryServerErrors: true,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(\"Failed to initialize client: \", err)\n\t}\n\n\tr := readRun(ctx, client, os.Args[1])\n\npoll:\n\tfor {\n\t\t<-time.After(pollInterval)\n\n\t\tr := readRun(ctx, client, r.ID)\n\n\t\tswitch r.Status {\n\t\tcase tfe.RunApplied:\n\t\t\tfmt.Println(\"Run finished!\")\n\t\tcase tfe.RunErrored:\n\t\t\tfmt.Println(\"Run had errors!\")\n\t\t\tlogRunErrors(ctx, client, r)\n\t\t\tbreak poll\n\t\tdefault:\n\t\t\tfmt.Printf(\"Waiting for run to error... Run status was %q...\\n\", r.Status)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/run_errors/terraform/main.tf",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\nterraform {\n  cloud {\n    workspaces {\n      name = \"go-tfe-examples-run_errors\"\n    }\n  }\n}\n\n# The following example should return an error\ndata \"http\" \"example_head\" {\n  url    = \"https://this-shall-not-exist.hashicorp.com/example\"\n  method = \"GET\"\n}\n"
  },
  {
    "path": "examples/state_versions/main.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\tclient, err := tfe.NewClient(&tfe.Config{\n\t\tRetryServerErrors: true,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Lock the workspace\n\tif _, err = client.Workspaces.Lock(ctx, \"ws-12345678\", tfe.WorkspaceLockOptions{}); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tstate, err := os.ReadFile(\"state.json\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create upload options that does not contain a State attribute within the create options\n\toptions := tfe.StateVersionUploadOptions{\n\t\tStateVersionCreateOptions: tfe.StateVersionCreateOptions{\n\t\t\tLineage: tfe.String(\"493f7758-da5e-229e-7872-ea1f78ebe50a\"),\n\t\t\tSerial:  tfe.Int64(int64(2)),\n\t\t\tMD5:     tfe.String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tForce:   tfe.Bool(false),\n\t\t},\n\t\tRawState: state,\n\t}\n\n\t// Upload a state version\n\tif _, err = client.StateVersions.Upload(ctx, \"ws-12345678\", options); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Unlock the workspace\n\tif _, err = client.Workspaces.Unlock(ctx, \"ws-12345678\"); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "examples/state_versions/state.json",
    "content": "{\n  \"version\": 4,\n  \"terraform_version\": \"1.3.9\",\n  \"serial\": 2,\n  \"lineage\": \"493f7758-da5e-229e-7872-ea1f78ebe50a\",\n  \"outputs\": {\n    \"name\": {\n      \"value\": \"\",\n      \"type\": \"string\"\n    }\n  },\n  \"resources\": [\n    {\n      \"mode\": \"managed\",\n      \"type\": \"null_resource\",\n      \"name\": \"null\",\n      \"provider\": \"provider[\\\"registry.terraform.io/hashicorp/null\\\"]\",\n      \"instances\": [\n        {\n          \"schema_version\": 0,\n          \"attributes\": {\n            \"id\": \"6593301963468675161\",\n            \"triggers\": {\n              \"creating\": \"ai-generated content\",\n              \"critical\": \"code\",\n              \"even\": \"[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50]\",\n              \"happy\": \"gilmore\",\n              \"key\": \"value2\",\n              \"napkin\": \"piano\",\n              \"odd\": \"[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49]\",\n              \"product\": \"type\",\n              \"responsible\": \"damages to your mainframe\",\n              \"slam\": \"dunk\",\n              \"super\": \"heroes\",\n              \"system\": \"or otherwise\",\n              \"warning\": \"do not operate\"\n            }\n          },\n          \"sensitive_attributes\": []\n        }\n      ]\n    }\n  ],\n  \"check_results\": null\n}\n"
  },
  {
    "path": "examples/users/main.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n)\n\nfunc main() {\n\tconfig := &tfe.Config{\n\t\tToken:             \"insert Your user token here\",\n\t\tRetryServerErrors: true,\n\t}\n\n\tclient, err := tfe.NewClient(config)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a context\n\tctx := context.Background()\n\n\t// Read Current User Details\n\tuser, err := client.Users.ReadCurrent(ctx)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tlog.Printf(\"%v\", user)\n}\n"
  },
  {
    "path": "examples/workspaces/main.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n)\n\nfunc main() {\n\tconfig := &tfe.Config{\n\t\tToken:             \"insert-your-token-here\",\n\t\tRetryServerErrors: true,\n\t}\n\n\tclient, err := tfe.NewClient(config)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Create a context\n\tctx := context.Background()\n\n\t// Create a new workspace\n\tw, err := client.Workspaces.Create(ctx, \"org-name\", tfe.WorkspaceCreateOptions{\n\t\tName:                       tfe.String(\"my-app-tst\"),\n\t\tAutoDestroyAt:              tfe.NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),\n\t\tInheritsProjectAutoDestroy: tfe.Bool(false),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Update the workspace\n\tw, err = client.Workspaces.Update(ctx, \"org-name\", w.Name, tfe.WorkspaceUpdateOptions{\n\t\tAutoApply:                  tfe.Bool(false),\n\t\tTerraformVersion:           tfe.String(\"0.11.1\"),\n\t\tWorkingDirectory:           tfe.String(\"my-app/infra\"),\n\t\tAutoDestroyAt:              tfe.NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),\n\t\tInheritsProjectAutoDestroy: tfe.Bool(false),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Disable auto destroy\n\tw, err = client.Workspaces.Update(ctx, \"org-name\", w.Name, tfe.WorkspaceUpdateOptions{\n\t\tAutoDestroyAt: tfe.NullTime(),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "gcp_oidc_configuration.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// GCPOIDCConfigurations describes all the GCP OIDC configuration related methods that the HCP Terraform API supports.\n// HCP Terraform API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/gcp\ntype GCPOIDCConfigurations interface {\n\tCreate(ctx context.Context, organization string, options GCPOIDCConfigurationCreateOptions) (*GCPOIDCConfiguration, error)\n\n\tRead(ctx context.Context, oidcID string) (*GCPOIDCConfiguration, error)\n\n\tUpdate(ctx context.Context, oidcID string, options GCPOIDCConfigurationUpdateOptions) (*GCPOIDCConfiguration, error)\n\n\tDelete(ctx context.Context, oidcID string) error\n}\n\ntype gcpOIDCConfigurations struct {\n\tclient *Client\n}\n\nvar _ GCPOIDCConfigurations = &gcpOIDCConfigurations{}\n\ntype GCPOIDCConfiguration struct {\n\tID                   string `jsonapi:\"primary,gcp-oidc-configurations\"`\n\tServiceAccountEmail  string `jsonapi:\"attr,service-account-email\"`\n\tProjectNumber        string `jsonapi:\"attr,project-number\"`\n\tWorkloadProviderName string `jsonapi:\"attr,workload-provider-name\"`\n\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n}\n\ntype GCPOIDCConfigurationCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,gcp-oidc-configurations\"`\n\n\t// Attributes\n\tServiceAccountEmail  string `jsonapi:\"attr,service-account-email\"`\n\tProjectNumber        string `jsonapi:\"attr,project-number\"`\n\tWorkloadProviderName string `jsonapi:\"attr,workload-provider-name\"`\n}\n\ntype GCPOIDCConfigurationUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,gcp-oidc-configurations\"`\n\n\t// Attributes\n\tServiceAccountEmail  *string `jsonapi:\"attr,service-account-email,omitempty\"`\n\tProjectNumber        *string `jsonapi:\"attr,project-number,omitempty\"`\n\tWorkloadProviderName *string `jsonapi:\"attr,workload-provider-name,omitempty\"`\n}\n\nfunc (o *GCPOIDCConfigurationCreateOptions) valid() error {\n\tif o.ServiceAccountEmail == \"\" {\n\t\treturn ErrRequiredServiceAccountEmail\n\t}\n\n\tif o.ProjectNumber == \"\" {\n\t\treturn ErrRequiredProjectNumber\n\t}\n\n\tif o.WorkloadProviderName == \"\" {\n\t\treturn ErrRequiredWorkloadProviderName\n\t}\n\n\treturn nil\n}\n\nfunc (goc *gcpOIDCConfigurations) Create(ctx context.Context, organization string, options GCPOIDCConfigurationCreateOptions) (*GCPOIDCConfiguration, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := goc.client.NewRequest(\"POST\", fmt.Sprintf(\"organizations/%s/oidc-configurations\", url.PathEscape(organization)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgcpOIDCConfiguration := &GCPOIDCConfiguration{}\n\terr = req.Do(ctx, gcpOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn gcpOIDCConfiguration, nil\n}\n\nfunc (goc *gcpOIDCConfigurations) Read(ctx context.Context, oidcID string) (*GCPOIDCConfiguration, error) {\n\treq, err := goc.client.NewRequest(\"GET\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgcpOIDCConfiguration := &GCPOIDCConfiguration{}\n\terr = req.Do(ctx, gcpOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn gcpOIDCConfiguration, nil\n}\n\nfunc (goc *gcpOIDCConfigurations) Update(ctx context.Context, oidcID string, options GCPOIDCConfigurationUpdateOptions) (*GCPOIDCConfiguration, error) {\n\tif !validStringID(&oidcID) {\n\t\treturn nil, ErrInvalidOIDC\n\t}\n\n\treq, err := goc.client.NewRequest(\"PATCH\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgcpOIDCConfiguration := &GCPOIDCConfiguration{}\n\terr = req.Do(ctx, gcpOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn gcpOIDCConfiguration, nil\n}\n\nfunc (goc *gcpOIDCConfigurations) Delete(ctx context.Context, oidcID string) error {\n\tif !validStringID(&oidcID) {\n\t\treturn ErrInvalidOIDC\n\t}\n\n\treq, err := goc.client.NewRequest(\"DELETE\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "gcp_oidc_configuration_integration_test.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// These tests are intended for local execution only, as OIDC configurations for HYOK requires specific conditions.\n// To run them locally, follow the instructions outlined in hyok_configuration_integration_test.go\n\nfunc TestGCPOIDCConfigurationCreateDelete(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\topts := GCPOIDCConfigurationCreateOptions{\n\t\t\tServiceAccountEmail:  \"updated-service-account@example.iam.gserviceaccount.com\",\n\t\t\tProjectNumber:        \"123456789012\",\n\t\t\tWorkloadProviderName: randomString(t),\n\t\t}\n\n\t\toidcConfig, err := client.GCPOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, oidcConfig)\n\t\tassert.Equal(t, oidcConfig.ServiceAccountEmail, opts.ServiceAccountEmail)\n\t\tassert.Equal(t, oidcConfig.ProjectNumber, opts.ProjectNumber)\n\t\tassert.Equal(t, oidcConfig.WorkloadProviderName, opts.WorkloadProviderName)\n\n\t\t// delete the created configuration\n\t\terr = client.GCPOIDCConfigurations.Delete(ctx, oidcConfig.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"missing workload provider name\", func(t *testing.T) {\n\t\topts := GCPOIDCConfigurationCreateOptions{\n\t\t\tServiceAccountEmail: \"updated-service-account@example.iam.gserviceaccount.com\",\n\t\t\tProjectNumber:       \"123456789012\",\n\t\t}\n\n\t\t_, err := client.GCPOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\tassert.ErrorIs(t, err, ErrRequiredWorkloadProviderName)\n\t})\n\n\tt.Run(\"missing service account email\", func(t *testing.T) {\n\t\topts := GCPOIDCConfigurationCreateOptions{\n\t\t\tProjectNumber:        \"123456789012\",\n\t\t\tWorkloadProviderName: randomString(t),\n\t\t}\n\n\t\t_, err := client.GCPOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\tassert.ErrorIs(t, err, ErrRequiredServiceAccountEmail)\n\t})\n\n\tt.Run(\"missing project number\", func(t *testing.T) {\n\t\topts := GCPOIDCConfigurationCreateOptions{\n\t\t\tServiceAccountEmail:  \"updated-service-account@example.iam.gserviceaccount.com\",\n\t\t\tWorkloadProviderName: randomString(t),\n\t\t}\n\n\t\t_, err := client.GCPOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\tassert.ErrorIs(t, err, ErrRequiredProjectNumber)\n\t})\n}\n\nfunc TestGCPOIDCConfigurationRead(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\toidcConfig, oidcConfigCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\tt.Cleanup(oidcConfigCleanup)\n\n\tt.Run(\"fetch existing configuration\", func(t *testing.T) {\n\t\tfetched, err := client.GCPOIDCConfigurations.Read(ctx, oidcConfig.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, fetched)\n\t})\n\n\tt.Run(\"fetching non-existing configuration\", func(t *testing.T) {\n\t\t_, err := client.GCPOIDCConfigurations.Read(ctx, \"gcpoidc-notreal\")\n\t\tassert.ErrorIs(t, err, ErrResourceNotFound)\n\t})\n}\n\nfunc TestGCPOIDCConfigurationUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tt.Run(\"update all fields\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\tserviceAccountEmail := \"updated-service-account@example.iam.gserviceaccount.com\"\n\t\tprojectNumber := \"123456789012\"\n\t\tworkloadProviderName := randomString(t)\n\n\t\topts := GCPOIDCConfigurationUpdateOptions{\n\t\t\tServiceAccountEmail:  &serviceAccountEmail,\n\t\t\tProjectNumber:        &projectNumber,\n\t\t\tWorkloadProviderName: &workloadProviderName,\n\t\t}\n\n\t\tupdated, err := client.GCPOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, updated)\n\t\tassert.Equal(t, *opts.ServiceAccountEmail, updated.ServiceAccountEmail)\n\t\tassert.Equal(t, *opts.ProjectNumber, updated.ProjectNumber)\n\t\tassert.Equal(t, *opts.WorkloadProviderName, updated.WorkloadProviderName)\n\t})\n\n\tt.Run(\"workload provider name not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\tserviceAccountEmail := \"updated-service-account@example.iam.gserviceaccount.com\"\n\t\tprojectNumber := \"123456789012\"\n\n\t\topts := GCPOIDCConfigurationUpdateOptions{\n\t\t\tServiceAccountEmail: &serviceAccountEmail,\n\t\t\tProjectNumber:       &projectNumber,\n\t\t}\n\n\t\tupdated, err := client.GCPOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, updated)\n\t\tassert.Equal(t, *opts.ServiceAccountEmail, updated.ServiceAccountEmail)\n\t\tassert.Equal(t, *opts.ProjectNumber, updated.ProjectNumber)\n\t\tassert.Equal(t, oidcConfig.WorkloadProviderName, updated.WorkloadProviderName) // not updated\n\t})\n\n\tt.Run(\"service account email not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\tprojectNumber := \"123456789012\"\n\t\tworkloadProviderName := randomString(t)\n\n\t\topts := GCPOIDCConfigurationUpdateOptions{\n\t\t\tProjectNumber:        &projectNumber,\n\t\t\tWorkloadProviderName: &workloadProviderName,\n\t\t}\n\n\t\tupdated, err := client.GCPOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, updated)\n\t\tassert.Equal(t, oidcConfig.ServiceAccountEmail, updated.ServiceAccountEmail) // not updated\n\t\tassert.Equal(t, *opts.ProjectNumber, updated.ProjectNumber)\n\t\tassert.Equal(t, *opts.WorkloadProviderName, updated.WorkloadProviderName)\n\t})\n\n\tt.Run(\"project number not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\tserviceAccountEmail := \"updated-service-account@example.iam.gserviceaccount.com\"\n\t\tworkloadProviderName := randomString(t)\n\n\t\topts := GCPOIDCConfigurationUpdateOptions{\n\t\t\tServiceAccountEmail:  &serviceAccountEmail,\n\t\t\tWorkloadProviderName: &workloadProviderName,\n\t\t}\n\n\t\tupdated, err := client.GCPOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, updated)\n\t\tassert.Equal(t, *opts.ServiceAccountEmail, updated.ServiceAccountEmail)\n\t\tassert.Equal(t, oidcConfig.ProjectNumber, updated.ProjectNumber) // not updated\n\t\tassert.Equal(t, *opts.WorkloadProviderName, updated.WorkloadProviderName)\n\t})\n}\n"
  },
  {
    "path": "generate_mocks.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2018, 2026\n# SPDX-License-Identifier: MPL-2.0\n\nset -euf -o pipefail\n\nmockgen -source=admin_opa_version.go -destination=mocks/admin_opa_version_mocks.go -package=mocks\nmockgen -source=admin_organization.go -destination=mocks/admin_organization_mocks.go -package=mocks\nmockgen -source=admin_run.go -destination=mocks/admin_run_mocks.go -package=mocks\nmockgen -source=admin_sentinel_version.go -destination=mocks/admin_sentinel_version_mocks.go -package=mocks\nmockgen -source=admin_setting.go -destination=mocks/admin_setting_mocks.go -package=mocks\nmockgen -source=admin_setting_cost_estimation.go -destination=mocks/admin_setting_cost_estimation_mocks.go -package=mocks\nmockgen -source=admin_setting_customization.go -destination=mocks/admin_setting_customization_mocks.go -package=mocks\nmockgen -source=admin_setting_general.go -destination=mocks/admin_setting_general_mocks.go -package=mocks\nmockgen -source=admin_setting_oidc.go -destination=mocks/admin_setting_oidc_mocks.go -package=mocks\nmockgen -source=admin_setting_saml.go -destination=mocks/admin_setting_saml_mocks.go -package=mocks\nmockgen -source=admin_setting_scim.go -destination=mocks/admin_setting_scim_mocks.go -package=mocks\nmockgen -source=admin_setting_scim_token.go -destination=mocks/admin_setting_scim_token_mocks.go -package=mocks\nmockgen -source=admin_setting_scim_groups.go -destination=mocks/admin_setting_scim_groups_mocks.go -package=mocks\nmockgen -source=admin_setting_smtp.go -destination=mocks/admin_setting_smtp_mocks.go -package=mocks\nmockgen -source=admin_setting_twilio.go -destination=mocks/admin_setting_twilio_mocks.go -package=mocks\nmockgen -source=admin_terraform_version.go -destination=mocks/admin_terraform_version_mocks.go -package=mocks\nmockgen -source=admin_user.go -destination=mocks/admin_user_mocks.go -package=mocks\nmockgen -source=admin_workspace.go -destination=mocks/admin_workspace_mocks.go -package=mocks\nmockgen -source=agent.go -destination=mocks/agents.go -package=mocks\nmockgen -source=agent_pool.go -destination=mocks/agent_pool_mocks.go -package=mocks\nmockgen -source=agent_token.go -destination=mocks/agent_token_mocks.go -package=mocks\nmockgen -source=apply.go -destination=mocks/apply_mocks.go -package=mocks\nmockgen -source=audit_trail.go -destination=mocks/audit_trail_mocks.go -package=mocks\nmockgen -source=comment.go -destination=mocks/comment_mocks.go -package=mocks\nmockgen -source=configuration_version.go -destination=mocks/configuration_version_mocks.go -package=mocks\nmockgen -source=cost_estimate.go -destination=mocks/cost_estimate_mocks.go -package=mocks\nmockgen -source=github_app_installation.go -destination=mocks/github_app_installation_mocks.go -package=mocks\nmockgen -source=gpg_key.go -destination=mocks/gpg_key_mocks.go -package=mocks\nmockgen -source=ip_ranges.go -destination=mocks/ip_ranges_mocks.go -package=mocks\nmockgen -source=logreader.go -destination=mocks/logreader_mocks.go -package=mocks\nmockgen -source=notification_configuration.go -destination=mocks/notification_configuration_mocks.go -package=mocks\nmockgen -source=oauth_client.go -destination=mocks/oauth_client_mocks.go -package=mocks\nmockgen -source=oauth_token.go -destination=mocks/oauth_token_mocks.go -package=mocks\nmockgen -source=organization.go -destination=mocks/organization_mocks.go -package=mocks\nmockgen -source=organization_membership.go -destination=mocks/organization_membership_mocks.go -package=mocks\nmockgen -source=organization_token.go -destination=mocks/organization_token_mocks.go -package=mocks\nmockgen -source=plan.go -destination=mocks/plan_mocks.go -package=mocks\nmockgen -source=plan_export.go -destination=mocks/plan_export_mocks.go -package=mocks\nmockgen -source=policy.go -destination=mocks/policy_mocks.go -package=mocks\nmockgen -source=policy_check.go -destination=mocks/policy_check_mocks.go -package=mocks\nmockgen -source=policy_set.go -destination=mocks/policy_set_mocks.go -package=mocks\nmockgen -source=policy_set_parameter.go -destination=mocks/policy_set_parameter_mocks.go -package=mocks\nmockgen -source=policy_set_version.go -destination=mocks/policy_set_version_mocks.go -package=mocks\nmockgen -source=registry_module.go -destination=mocks/registry_module_mocks.go -package=mocks\nmockgen -source=registry_provider.go -destination=mocks/registry_provider_mocks.go -package=mocks\nmockgen -source=registry_provider_platform.go -destination=mocks/registry_provider_platform_mocks.go -package=mocks\nmockgen -source=registry_provider_version.go -destination=mocks/registry_provider_version_mocks.go -package=mocks\nmockgen -source=query_runs.go -destination=mocks/query_runs_mocks.go -package=mocks\nmockgen -source=run.go -destination=mocks/run_mocks.go -package=mocks\nmockgen -source=run_event.go -destination=mocks/run_events_mocks.go -package=mocks\nmockgen -source=run_task.go -destination=mocks/run_tasks_mocks.go -package=mocks\nmockgen -source=run_trigger.go -destination=mocks/run_trigger_mocks.go -package=mocks\nmockgen -source=ssh_key.go -destination=mocks/ssh_key_mocks.go -package=mocks\nmockgen -source=state_version.go -destination=mocks/state_version_mocks.go -package=mocks\nmockgen -source=state_version_output.go -destination=mocks/state_version_output_mocks.go -package=mocks\nmockgen -source=tag.go -destination=mocks/tag_mocks.go -package=mocks\nmockgen -source=task_result.go -destination=mocks/task_result_mocks.go -package=mocks\nmockgen -source=task_stages.go -destination=mocks/task_stages_mocks.go -package=mocks\nmockgen -source=team.go -destination=mocks/team_mocks.go -package=mocks\nmockgen -source=team_access.go -destination=mocks/team_access_mocks.go -package=mocks\nmockgen -source=team_member.go -destination=mocks/team_member_mocks.go -package=mocks\nmockgen -source=team_project_access.go -destination=mocks/team_project_access_mocks.go -package=mocks\nmockgen -source=team_token.go -destination=mocks/team_token_mocks.go -package=mocks\nmockgen -source=test_run.go -destination=mocks/test_run_mocks.go -package=mocks\nmockgen -source=test_variables.go -destination=mocks/test_variables_mocks.go -package=mocks\nmockgen -source=user.go -destination=mocks/user_mocks.go -package=mocks\nmockgen -source=user_token.go -destination=mocks/user_token_mocks.go -package=mocks\nmockgen -source=variable.go -destination=mocks/variable_mocks.go -package=mocks\nmockgen -source=variable_set.go -destination=mocks/variable_set_mocks.go -package=mocks\nmockgen -source=variable_set_variable.go -destination=mocks/variable_set_variable_mocks.go -package=mocks\nmockgen -source=workspace.go -destination=mocks/workspace_mocks.go -package=mocks\nmockgen -source=workspace_run_task.go -destination=mocks/workspace_run_tasks_mocks.go -package=mocks\nmockgen -source=policy_evaluation.go -destination=mocks/policy_evaluation.go -package=mocks\nmockgen -source=project.go -destination=mocks/project_mocks.go -package=mocks\nmockgen -source=registry_no_code_module.go -destination=mocks/registry_no_code_module_mocks.go -package=mocks\nmockgen -source=registry_module.go -destination=mocks/registry_module_mocks.go -package=mocks\nmockgen -source=workspace_resources.go -destination=mocks/workspace_resources.go -package=mocks\n"
  },
  {
    "path": "github_app_installation.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ GHAInstallations = (*gHAInstallations)(nil)\n\n// GHAInstallations describes all the GitHub App Installation related methods that the\n// Terraform Enterprise API supports. The APIs require the user token for the user who\n// already has the GitHub App Installation set up via the UI.\n// (https://developer.hashicorp.com/terraform/enterprise/admin/application/github-app-integration)\ntype GHAInstallations interface {\n\t// List all the GitHub App Installations for the user.\n\tList(ctx context.Context, options *GHAInstallationListOptions) (*GHAInstallationList, error)\n\n\t// Read a GitHub App Installations by its external id.\n\tRead(ctx context.Context, GHAInstallationID string) (*GHAInstallation, error)\n}\n\n// gHAInstallations implements GHAInstallations.\ntype gHAInstallations struct {\n\tclient *Client\n}\n\n// GHAInstallationList represents a list of github installations.\ntype GHAInstallationList struct {\n\t*Pagination\n\tItems []*GHAInstallation\n}\n\n// GHAInstallation represents a github app installation\ntype GHAInstallation struct {\n\tID               *string `jsonapi:\"primary,github-app-installations\"`\n\tIconURL          *string `jsonapi:\"attr,icon-url\"`\n\tInstallationID   *int    `jsonapi:\"attr,installation-id\"`\n\tInstallationType *string `jsonapi:\"attr,installation-type\"`\n\tInstallationURL  *string `jsonapi:\"attr,installation-url\"`\n\tName             *string `jsonapi:\"attr,name\"`\n}\n\n// GHAInstallationListOptions represents the options for listing.\ntype GHAInstallationListOptions struct {\n\tListOptions\n}\n\n// List all the github app installations.\nfunc (s *gHAInstallations) List(ctx context.Context, options *GHAInstallationListOptions) (*GHAInstallationList, error) {\n\tu := \"github-app/installations\"\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tghil := &GHAInstallationList{}\n\n\terr = req.Do(ctx, ghil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ghil, nil\n}\n\n// Read a GitHub App Installations by its ID.\nfunc (s *gHAInstallations) Read(ctx context.Context, id string) (*GHAInstallation, error) {\n\tif !validStringID(&id) {\n\t\treturn nil, ErrInvalidOauthClientID\n\t}\n\n\tu := fmt.Sprintf(\"github-app/installation/%s\", url.PathEscape(id))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tghi := &GHAInstallation{}\n\terr = req.Do(ctx, ghi)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ghi, err\n}\n"
  },
  {
    "path": "github_app_installation_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGHAInstallationList(t *testing.T) {\n\tt.Parallel()\n\tgHAInstallationID := os.Getenv(\"GITHUB_APP_INSTALLATION_ID\")\n\n\tif gHAInstallationID == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_APP_INSTALLATION_ID before running this test!\")\n\t}\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\t_, err := client.GHAInstallations.List(ctx, nil)\n\t\tassert.NoError(t, err)\n\t})\n}\nfunc TestGHAInstallationRead(t *testing.T) {\n\tt.Parallel()\n\tgHAInstallationID := os.Getenv(\"GITHUB_APP_INSTALLATION_ID\")\n\n\tif gHAInstallationID == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_APP_INSTALLATION_ID before running this test!\")\n\t}\n\n\tvar GHAInstallationID = string(gHAInstallationID)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"when installation id exists\", func(t *testing.T) {\n\t\tghais, err := client.GHAInstallations.Read(ctx, GHAInstallationID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, ghais.IconURL)\n\t\tassert.NotEmpty(t, ghais.ID)\n\t\tassert.NotEmpty(t, ghais.InstallationID)\n\t\tassert.NotEmpty(t, ghais.InstallationType)\n\t\tassert.NotEmpty(t, ghais.InstallationURL)\n\t\tassert.NotEmpty(t, ghais.Name)\n\t\tassert.Equal(t, *ghais.ID, gHAInstallationID)\n\t})\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/hashicorp/go-tfe\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/google/go-querystring v1.2.0\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2\n\tgithub.com/hashicorp/go-retryablehttp v0.7.8\n\tgithub.com/hashicorp/go-slug v0.16.8\n\tgithub.com/hashicorp/go-uuid v1.0.3\n\tgithub.com/hashicorp/go-version v1.8.0\n\tgithub.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e\n\tgithub.com/stretchr/testify v1.11.1\n\tgo.uber.org/mock v0.4.0\n\tgolang.org/x/sync v0.19.0\n\tgolang.org/x/time v0.14.0\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgolang.org/x/sys v0.35.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=\ngithub.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=\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/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-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=\ngithub.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=\ngithub.com/hashicorp/go-slug v0.16.8 h1:f4/sDZqRsxx006HrE6e9BE5xO9lWXydKhVoH6Kb0v1M=\ngithub.com/hashicorp/go-slug v0.16.8/go.mod h1:hB4mUcVHl4RPu0205s0fwmB9i31MxQgeafGkko3FD+Y=\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/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=\ngithub.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e h1:xwy/1T0cxHWaLx2MM0g4BlaQc1BXn/9835mPrBqwSPU=\ngithub.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e/go.mod h1:kWfdn49yCjQvbpnvY1dxxAuAFzISwrrMDQOcu6NsFoM=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngo.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=\ngo.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=\ngolang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\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": "gpg_key.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation\nvar _ GPGKeys = (*gpgKeys)(nil)\n\n// GPGKeys describes all the GPG key related methods that the Terraform Private Registry API supports.\n//\n// TFE API Docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/gpg-keys\ntype GPGKeys interface {\n\t// Lists GPG keys in a private registry.\n\tListPrivate(ctx context.Context, options GPGKeyListOptions) (*GPGKeyList, error)\n\n\t// Uploads a GPG Key to a private registry scoped with a namespace.\n\tCreate(ctx context.Context, registryName RegistryName, options GPGKeyCreateOptions) (*GPGKey, error)\n\n\t// Read a GPG key.\n\tRead(ctx context.Context, keyID GPGKeyID) (*GPGKey, error)\n\n\t// Update a GPG key.\n\tUpdate(ctx context.Context, keyID GPGKeyID, options GPGKeyUpdateOptions) (*GPGKey, error)\n\n\t// Delete a GPG key.\n\tDelete(ctx context.Context, keyID GPGKeyID) error\n}\n\n// gpgKeys implements GPGKeys\ntype gpgKeys struct {\n\tclient *Client\n}\n\n// GPGKeyList represents a list of GPG keys.\ntype GPGKeyList struct {\n\t*Pagination\n\tItems []*GPGKey\n}\n\n// GPGKey represents a signed GPG key for a HCP Terraform or Terraform Enterprise private provider.\ntype GPGKey struct {\n\tID             string    `jsonapi:\"primary,gpg-keys\"`\n\tAsciiArmor     string    `jsonapi:\"attr,ascii-armor\"`\n\tCreatedAt      time.Time `jsonapi:\"attr,created-at,iso8601\"`\n\tKeyID          string    `jsonapi:\"attr,key-id\"`\n\tNamespace      string    `jsonapi:\"attr,namespace\"`\n\tSource         string    `jsonapi:\"attr,source\"`\n\tSourceURL      *string   `jsonapi:\"attr,source-url\"`\n\tTrustSignature string    `jsonapi:\"attr,trust-signature\"`\n\tUpdatedAt      time.Time `jsonapi:\"attr,updated-at,iso8601\"`\n}\n\n// GPGKeyID represents the set of identifiers used to fetch a GPG key.\ntype GPGKeyID struct {\n\tRegistryName RegistryName\n\tNamespace    string\n\tKeyID        string\n}\n\n// GPGKeyListOptions represents all the available options to list keys in a registry.\ntype GPGKeyListOptions struct {\n\tListOptions\n\n\t// Required: A list of one or more namespaces. Must be authorized HCP Terraform or Terraform Enterprise organization names.\n\tNamespaces []string `url:\"filter[namespace]\"`\n}\n\n// GPGKeyCreateOptions represents all the available options used to create a GPG key.\ntype GPGKeyCreateOptions struct {\n\tType       string `jsonapi:\"primary,gpg-keys\"`\n\tNamespace  string `jsonapi:\"attr,namespace\"`\n\tAsciiArmor string `jsonapi:\"attr,ascii-armor\"`\n}\n\n// GPGKeyCreateOptions represents all the available options used to update a GPG key.\ntype GPGKeyUpdateOptions struct {\n\tType      string `jsonapi:\"primary,gpg-keys\"`\n\tNamespace string `jsonapi:\"attr,namespace\"`\n}\n\n// ListPrivate lists the private registry GPG keys for specified namespaces.\nfunc (s *gpgKeys) ListPrivate(ctx context.Context, options GPGKeyListOptions) (*GPGKeyList, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"/api/registry/%s/v2/gpg-keys\", url.PathEscape(string(PrivateRegistry)))\n\treq, err := s.client.NewRequest(\"GET\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkeyl := &GPGKeyList{}\n\terr = req.Do(ctx, keyl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn keyl, nil\n}\n\nfunc (s *gpgKeys) Create(ctx context.Context, registryName RegistryName, options GPGKeyCreateOptions) (*GPGKey, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif registryName != PrivateRegistry {\n\t\treturn nil, ErrInvalidRegistryName\n\t}\n\n\tu := fmt.Sprintf(\"/api/registry/%s/v2/gpg-keys\", url.PathEscape(string(registryName)))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tg := &GPGKey{}\n\terr = req.Do(ctx, g)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn g, nil\n}\n\nfunc (s *gpgKeys) Read(ctx context.Context, keyID GPGKeyID) (*GPGKey, error) {\n\tif err := keyID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"/api/registry/%s/v2/gpg-keys/%s/%s\",\n\t\turl.PathEscape(string(keyID.RegistryName)),\n\t\turl.PathEscape(keyID.Namespace),\n\t\turl.PathEscape(keyID.KeyID),\n\t)\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tg := &GPGKey{}\n\terr = req.Do(ctx, g)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn g, nil\n}\n\nfunc (s *gpgKeys) Update(ctx context.Context, keyID GPGKeyID, options GPGKeyUpdateOptions) (*GPGKey, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := keyID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"/api/registry/%s/v2/gpg-keys/%s/%s\",\n\t\turl.PathEscape(string(keyID.RegistryName)),\n\t\turl.PathEscape(keyID.Namespace),\n\t\turl.PathEscape(keyID.KeyID),\n\t)\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tg := &GPGKey{}\n\terr = req.Do(ctx, g)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"namespace not authorized\") {\n\t\t\treturn nil, ErrNamespaceNotAuthorized\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn g, nil\n}\n\nfunc (s *gpgKeys) Delete(ctx context.Context, keyID GPGKeyID) error {\n\tif err := keyID.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"/api/registry/%s/v2/gpg-keys/%s/%s\",\n\t\turl.PathEscape(string(keyID.RegistryName)),\n\t\turl.PathEscape(keyID.Namespace),\n\t\turl.PathEscape(keyID.KeyID),\n\t)\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o GPGKeyID) valid() error {\n\tif o.RegistryName != PrivateRegistry {\n\t\treturn ErrInvalidRegistryName\n\t}\n\n\tif !validString(&o.Namespace) {\n\t\treturn ErrInvalidNamespace\n\t}\n\n\tif !validString(&o.KeyID) {\n\t\treturn ErrInvalidKeyID\n\t}\n\n\treturn nil\n}\n\nfunc (o *GPGKeyListOptions) valid() error {\n\tif len(o.Namespaces) == 0 {\n\t\treturn ErrInvalidNamespace\n\t}\n\n\tfor _, namespace := range o.Namespaces {\n\t\tif namespace == \"\" || !validString(&namespace) {\n\t\t\treturn ErrInvalidNamespace\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (o GPGKeyCreateOptions) valid() error {\n\tif !validString(&o.Namespace) {\n\t\treturn ErrInvalidNamespace\n\t}\n\n\tif !validString(&o.AsciiArmor) {\n\t\treturn ErrInvalidAsciiArmor\n\t}\n\n\treturn nil\n}\n\nfunc (o GPGKeyUpdateOptions) valid() error {\n\tif !validString(&o.Namespace) {\n\t\treturn ErrInvalidNamespace\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "gpg_key_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGPGKeyList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg1, org1Cleanup := createOrganization(t, client)\n\tt.Cleanup(org1Cleanup)\n\n\torg2, org2Cleanup := createOrganization(t, client)\n\tt.Cleanup(org2Cleanup)\n\n\tprovider1, provider1Cleanup := createRegistryProvider(t, client, org1, PrivateRegistry)\n\tt.Cleanup(provider1Cleanup)\n\n\tprovider2, provider2Cleanup := createRegistryProvider(t, client, org2, PrivateRegistry)\n\tt.Cleanup(provider2Cleanup)\n\n\tgpgKey1, gpgKey1Cleanup := createGPGKey(t, client, org1, provider1)\n\tt.Cleanup(gpgKey1Cleanup)\n\n\tgpgKey2, gpgKey2Cleanup := createGPGKey(t, client, org2, provider2)\n\tt.Cleanup(gpgKey2Cleanup)\n\n\tt.Run(\"with single namespace\", func(t *testing.T) {\n\t\topts := GPGKeyListOptions{\n\t\t\tNamespaces: []string{org1.Name},\n\t\t}\n\n\t\tkeyl, err := client.GPGKeys.ListPrivate(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Len(t, keyl.Items, 1)\n\t\tassert.Equal(t, gpgKey1.ID, keyl.Items[0].ID)\n\t\tassert.Equal(t, gpgKey1.KeyID, keyl.Items[0].KeyID)\n\t})\n\n\tt.Run(\"with multiple namespaces\", func(t *testing.T) {\n\t\tt.Skip(\"Skipping due to GPG Key API not returning keys for multiple namespaces\")\n\n\t\topts := GPGKeyListOptions{\n\t\t\tNamespaces: []string{org1.Name, org2.Name},\n\t\t}\n\n\t\tkeyl, err := client.GPGKeys.ListPrivate(ctx, opts)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Len(t, keyl.Items, 2)\n\t\tfor i, key := range []*GPGKey{\n\t\t\tgpgKey1,\n\t\t\tgpgKey2,\n\t\t} {\n\t\t\tassert.Equal(t, key.ID, keyl.Items[i].ID)\n\t\t\tassert.Equal(t, key.KeyID, keyl.Items[i].KeyID)\n\t\t}\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\topts := GPGKeyListOptions{\n\t\t\tNamespaces: []string{org1.Name},\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t}\n\n\t\tkeyl, err := client.GPGKeys.ListPrivate(ctx, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, keyl.Items)\n\t\tassert.Equal(t, 999, keyl.CurrentPage)\n\t\tassert.Equal(t, 1, keyl.TotalCount)\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\tt.Run(\"invalid namespace\", func(t *testing.T) {\n\t\t\topts := GPGKeyListOptions{\n\t\t\t\tNamespaces: []string{},\n\t\t\t}\n\t\t\t_, err := client.GPGKeys.ListPrivate(ctx, opts)\n\t\t\trequire.EqualError(t, err, ErrInvalidNamespace.Error())\n\t\t})\n\t})\n}\n\nfunc TestGPGKeyCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tt.Cleanup(orgCleanup)\n\n\tprovider, providerCleanup := createRegistryProvider(t, client, org, PrivateRegistry)\n\tt.Cleanup(providerCleanup)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\topts := GPGKeyCreateOptions{\n\t\t\tNamespace:  provider.Organization.Name,\n\t\t\tAsciiArmor: testGpgArmor,\n\t\t}\n\n\t\tgpgKey, err := client.GPGKeys.Create(ctx, PrivateRegistry, opts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, gpgKey.ID)\n\t\tassert.Equal(t, gpgKey.AsciiArmor, opts.AsciiArmor)\n\t\tassert.Equal(t, gpgKey.Namespace, opts.Namespace)\n\t\tassert.NotEmpty(t, gpgKey.CreatedAt)\n\t\tassert.NotEmpty(t, gpgKey.UpdatedAt)\n\n\t\t// The default value for these two fields is an empty string\n\t\tassert.Empty(t, gpgKey.Source)\n\t\tassert.Empty(t, gpgKey.TrustSignature)\n\t})\n\n\tt.Run(\"with invalid registry name\", func(t *testing.T) {\n\t\topts := GPGKeyCreateOptions{\n\t\t\tNamespace:  provider.Organization.Name,\n\t\t\tAsciiArmor: testGpgArmor,\n\t\t}\n\n\t\t_, err := client.GPGKeys.Create(ctx, \"foobar\", opts)\n\t\tassert.ErrorIs(t, err, ErrInvalidRegistryName)\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\tmissingNamespaceOpts := GPGKeyCreateOptions{\n\t\t\tNamespace:  \"\",\n\t\t\tAsciiArmor: testGpgArmor,\n\t\t}\n\t\t_, err := client.GPGKeys.Create(ctx, PrivateRegistry, missingNamespaceOpts)\n\t\tassert.ErrorIs(t, err, ErrInvalidNamespace)\n\n\t\tmissingAsciiArmorOpts := GPGKeyCreateOptions{\n\t\t\tNamespace:  provider.Organization.Name,\n\t\t\tAsciiArmor: \"\",\n\t\t}\n\t\t_, err = client.GPGKeys.Create(ctx, PrivateRegistry, missingAsciiArmorOpts)\n\t\tassert.ErrorIs(t, err, ErrInvalidAsciiArmor)\n\t})\n}\n\nfunc TestGPGKeyRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tt.Cleanup(orgCleanup)\n\n\tprovider, providerCleanup := createRegistryProvider(t, client, org, PrivateRegistry)\n\tt.Cleanup(providerCleanup)\n\n\tgpgKey, gpgKeyCleanup := createGPGKey(t, client, org, provider)\n\tt.Cleanup(gpgKeyCleanup)\n\n\tt.Run(\"when the gpg key exists\", func(t *testing.T) {\n\t\tfetched, err := client.GPGKeys.Read(ctx, GPGKeyID{\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    provider.Organization.Name,\n\t\t\tKeyID:        gpgKey.KeyID,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, gpgKey.ID)\n\t\tassert.NotEmpty(t, gpgKey.KeyID)\n\t\tassert.Greater(t, len(gpgKey.AsciiArmor), 0)\n\t\tassert.Equal(t, fetched.Namespace, provider.Organization.Name)\n\t})\n\n\tt.Run(\"when the key does not exist\", func(t *testing.T) {\n\t\t_, err := client.GPGKeys.Read(ctx, GPGKeyID{\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    provider.Organization.Name,\n\t\t\tKeyID:        \"foobar\",\n\t\t})\n\t\tassert.ErrorIs(t, err, ErrResourceNotFound)\n\t})\n}\n\nfunc TestGPGKeyUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tt.Cleanup(orgCleanup)\n\n\tprovider, providerCleanup := createRegistryProvider(t, client, org, PrivateRegistry)\n\tt.Cleanup(providerCleanup)\n\n\t// We won't use the cleanup method here as the namespace\n\t// is used to identify a key and that will change due to the update\n\t// call. We'll need to manually delete the key.\n\tgpgKey, _ := createGPGKey(t, client, org, provider)\n\n\tt.Run(\"when using an invalid namespace\", func(t *testing.T) {\n\t\tkeyID := GPGKeyID{\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    provider.Organization.Name,\n\t\t\tKeyID:        gpgKey.KeyID,\n\t\t}\n\t\topts := GPGKeyUpdateOptions{\n\t\t\tNamespace: \"invalid_namespace_org\",\n\t\t}\n\t\t_, err := client.GPGKeys.Update(ctx, keyID, opts)\n\t\tassert.ErrorIs(t, err, ErrNamespaceNotAuthorized)\n\t})\n\n\tt.Run(\"when updating to a valid namespace\", func(t *testing.T) {\n\t\t// Create a new namespace to update the key with\n\t\torg2, org2Cleanup := createOrganization(t, client)\n\t\tt.Cleanup(org2Cleanup)\n\n\t\tprovider2, provider2Cleanup := createRegistryProvider(t, client, org2, PrivateRegistry)\n\t\tt.Cleanup(provider2Cleanup)\n\n\t\tkeyID := GPGKeyID{\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    provider.Organization.Name,\n\t\t\tKeyID:        gpgKey.KeyID,\n\t\t}\n\t\topts := GPGKeyUpdateOptions{\n\t\t\tNamespace: provider2.Organization.Name,\n\t\t}\n\n\t\tupdatedKey, err := client.GPGKeys.Update(ctx, keyID, opts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, gpgKey.KeyID, updatedKey.KeyID)\n\t\tassert.Equal(t, updatedKey.Namespace, provider2.Organization.Name)\n\n\t\t// Cleanup\n\t\terr = client.GPGKeys.Delete(ctx, GPGKeyID{\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    provider2.Organization.Name,\n\t\t\tKeyID:        updatedKey.KeyID,\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestGPGKeyDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgCleanup := createOrganization(t, client)\n\tt.Cleanup(orgCleanup)\n\n\tprovider, providerCleanup := createRegistryProvider(t, client, org, PrivateRegistry)\n\tt.Cleanup(providerCleanup)\n\n\tgpgKey, _ := createGPGKey(t, client, org, provider)\n\n\tt.Run(\"when a key exists\", func(t *testing.T) {\n\t\terr := client.GPGKeys.Delete(ctx, GPGKeyID{\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    provider.Organization.Name,\n\t\t\tKeyID:        gpgKey.KeyID,\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n}\n"
  },
  {
    "path": "helper_test.go",
    "content": "// Copyright IBM Corp. 2018, 2026\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"archive/tar\"\n\t\"archive/zip\"\n\t\"compress/gzip\"\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/md5\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n\tuuid \"github.com/hashicorp/go-uuid\"\n)\n\nconst badIdentifier = \"! / nope\" //nolint\nconst agentVersion = \"1.3.0\"\nconst testInitialClientToken = \"insert-your-token-here\"\nconst testTaskResultCallbackToken = \"this-is-task-result-callback-token\"\nconst defaultTokenExpirationYears = 2\n\nvar _testAccountDetails *TestAccountDetails\n\nfunc testClient(t *testing.T) *Client {\n\tclient, err := NewClient(&Config{\n\t\tRetryServerErrors: true,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn client\n}\n\ntype adminRoleType string\n\nconst (\n\tsiteAdmin                adminRoleType = \"site-admin\"\n\tconfigurationAdmin       adminRoleType = \"configuration\"\n\tprovisionLicensesAdmin   adminRoleType = \"provision-licenses\"\n\tsubscriptionAdmin        adminRoleType = \"subscription\"\n\tsupportAdmin             adminRoleType = \"support\"\n\tsecurityMaintenanceAdmin adminRoleType = \"security-maintenance\"\n\tversionMaintenanceAdmin  adminRoleType = \"version-maintenance\"\n)\n\nfunc getTokenForAdminRole(adminRole adminRoleType) string {\n\ttoken := \"\"\n\n\tswitch adminRole {\n\tcase siteAdmin:\n\t\ttoken = os.Getenv(\"TFE_ADMIN_SITE_ADMIN_TOKEN\")\n\tcase configurationAdmin:\n\t\ttoken = os.Getenv(\"TFE_ADMIN_CONFIGURATION_TOKEN\")\n\tcase provisionLicensesAdmin:\n\t\ttoken = os.Getenv(\"TFE_ADMIN_PROVISION_LICENSES_TOKEN\")\n\tcase subscriptionAdmin:\n\t\ttoken = os.Getenv(\"TFE_ADMIN_SUBSCRIPTION_TOKEN\")\n\tcase supportAdmin:\n\t\ttoken = os.Getenv(\"TFE_ADMIN_SUPPORT_TOKEN\")\n\tcase securityMaintenanceAdmin:\n\t\ttoken = os.Getenv(\"TFE_ADMIN_SECURITY_MAINTENANCE_TOKEN\")\n\tcase versionMaintenanceAdmin:\n\t\ttoken = os.Getenv(\"TFE_ADMIN_VERSION_MAINTENANCE_TOKEN\")\n\t}\n\n\treturn token\n}\n\nfunc testAdminClient(t *testing.T, adminRole adminRoleType) *Client {\n\ttoken := getTokenForAdminRole(adminRole)\n\tif token == \"\" {\n\t\tt.Fatal(\"missing API token for admin role \" + adminRole)\n\t}\n\n\tclient, err := NewClient(&Config{\n\t\tToken:             token,\n\t\tRetryServerErrors: true,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn client\n}\n\nfunc testAuditTrailClient(t *testing.T, userClient *Client, org *Organization) *Client {\n\tupgradeOrganizationSubscription(t, userClient, org)\n\n\torgToken, orgTokenCleanup := createOrganizationToken(t, userClient, org)\n\tt.Cleanup(orgTokenCleanup)\n\n\tclient, err := NewClient(&Config{\n\t\tToken: orgToken.Token,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn client\n}\n\n// TestAccountDetails represents the basic account information\n// of a Terraform Enterprise or HCP Terraform user.\n//\n// See FetchTestAccountDetails for more information.\ntype TestAccountDetails struct {\n\tID       string `jsonapi:\"primary,users\"`\n\tUsername string `jsonapi:\"attr,username\"`\n\tEmail    string `jsonapi:\"attr,email\"`\n}\n\nfunc fetchTestAccountDetails(t *testing.T, client *Client) *TestAccountDetails {\n\tt.Helper()\n\n\tif _testAccountDetails == nil {\n\t\t_testAccountDetails = &TestAccountDetails{}\n\t\treq, err := client.NewRequest(\"GET\", \"account/details\", nil)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"could not create account details request: %v\", err)\n\t\t}\n\n\t\tctx := context.Background()\n\t\terr = req.Do(ctx, _testAccountDetails)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"could not fetch test user details: %v\", err)\n\t\t}\n\t}\n\n\treturn _testAccountDetails\n}\n\nfunc downloadFile(filePath, fileURL string) error {\n\t// Get the data\n\tresp, err := http.Get(fileURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close() //nolint:errcheck\n\n\t// Create the file\n\tout, err := os.Create(filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer out.Close()\n\n\t// Write the body to file\n\t_, err = io.Copy(out, resp.Body)\n\treturn err\n}\n\nfunc unzip(src, dest string) error {\n\tr, err := zip.OpenReader(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := r.Close(); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\n\tif err := os.MkdirAll(dest, 0o755); err != nil {\n\t\treturn err\n\t}\n\n\t// Closure to address file descriptors issue with all the deferred .Close() methods\n\textractAndWriteFile := func(zf *zip.File) error {\n\t\trc, err := zf.Open()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := rc.Close(); err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t}()\n\n\t\tpath := filepath.Join(dest, zf.Name)\n\n\t\t// Check for ZipSlip (Directory traversal)\n\t\tif !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {\n\t\t\treturn fmt.Errorf(\"illegal file path: %s\", path)\n\t\t}\n\n\t\tif zf.FileInfo().IsDir() {\n\t\t\treturn os.MkdirAll(path, zf.Mode())\n\t\t}\n\t\tif err := os.MkdirAll(filepath.Dir(path), zf.Mode()); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tf, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, zf.Mode())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := f.Close(); err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t}()\n\n\t\t_, err = io.Copy(f, rc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfor _, f := range r.File {\n\t\terr := extractAndWriteFile(f)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc downloadTFCAgent(t *testing.T) (string, error) {\n\tt.Helper()\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"tfc-agent\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot create temp dir: %w\", err)\n\t}\n\tt.Cleanup(func() {\n\t\tfmt.Printf(\"cleaning up %s \\n\", tmpDir)\n\t\tos.RemoveAll(tmpDir)\n\t})\n\tagentPath := fmt.Sprintf(\"https://releases.hashicorp.com/tfc-agent/%s/tfc-agent_%s_linux_amd64.zip\", agentVersion, agentVersion)\n\tzipFile := fmt.Sprintf(\"%s/agent.zip\", tmpDir)\n\n\tif err = downloadFile(zipFile, agentPath); err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot download agent file: %w\", err)\n\t}\n\n\tif err = unzip(zipFile, tmpDir); err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot unzip file: %w\", err)\n\t}\n\treturn fmt.Sprintf(\"%s/tfc-agent\", tmpDir), nil\n}\n\nfunc createAgent(t *testing.T, client *Client, org *Organization) (*Agent, *AgentPool, func()) {\n\tvar orgCleanup func()\n\tvar agentPoolTokenCleanup func()\n\tvar agent *Agent\n\tvar ok bool\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tagentPool, agentPoolCleanup := createAgentPool(t, client, org)\n\n\tupgradeOrganizationSubscription(t, client, org)\n\n\tagentPoolToken, agentPoolTokenCleanup := createAgentToken(t, client, agentPool)\n\n\tcleanup := func() {\n\t\tagentPoolTokenCleanup()\n\n\t\tif agentPoolCleanup != nil {\n\t\t\tagentPoolCleanup()\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n\n\tagentPath, err := downloadTFCAgent(t)\n\tif err != nil {\n\t\treturn agent, agentPool, cleanup\n\t}\n\n\tctx := context.Background()\n\n\tcmd := exec.Command(agentPath)\n\tcmd.Env = os.Environ()\n\tcmd.Env = append(cmd.Env,\n\t\t\"TFC_AGENT_TOKEN=\"+agentPoolToken.Token,\n\t\t\"TFC_AGENT_NAME=\"+\"test-agent\",\n\t\t\"TFC_ADDRESS=\"+DefaultConfig().Address,\n\t)\n\n\tgo func() {\n\t\t_, err := cmd.CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Logf(\"Could not run container: %s\", err)\n\t\t}\n\t}()\n\n\tt.Cleanup(func() {\n\t\tif err := cmd.Process.Kill(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t})\n\n\ti, err := retry(func() (interface{}, error) {\n\t\tagentList, err := client.Agents.List(ctx, agentPool.ID, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif agentList != nil && len(agentList.Items) > 0 {\n\t\t\treturn agentList.Items[0], nil\n\t\t}\n\t\treturn nil, errors.New(\"no agent found\")\n\t})\n\n\tif err != nil {\n\t\tt.Fatalf(\"Could not return an agent %s\", err)\n\t}\n\n\tagent, ok = i.(*Agent)\n\tif !ok {\n\t\tt.Fatalf(\"Expected type to be *Agent but got %T\", agent)\n\t}\n\n\treturn agent, agentPool, cleanup\n}\n\nfunc createAgentPool(t *testing.T, client *Client, org *Organization) (*AgentPool, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\tpool, err := client.AgentPools.Create(ctx, org.Name, AgentPoolCreateOptions{\n\t\tName: String(randomString(t)),\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn pool, func() {\n\t\tif err := client.AgentPools.Delete(ctx, pool.ID); err != nil {\n\t\t\tt.Logf(\"Error destroying agent pool! WARNING: Dangling resources \"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Agent pool ID: %s\\nError: %s\", pool.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createAgentPoolWithOptions(t *testing.T, client *Client, org *Organization, opts AgentPoolCreateOptions) (*AgentPool, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\tpool, err := client.AgentPools.Create(ctx, org.Name, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn pool, func() {\n\t\tif err := client.AgentPools.Delete(ctx, pool.ID); err != nil {\n\t\t\tt.Logf(\"Error destroying agent pool! WARNING: Dangling resources \"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Agent pool ID: %s\\nError: %s\", pool.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createAgentToken(t *testing.T, client *Client, ap *AgentPool) (*AgentToken, func()) {\n\tvar apCleanup func()\n\n\tif ap == nil {\n\t\tap, apCleanup = createAgentPool(t, client, nil)\n\t}\n\n\tctx := context.Background()\n\tat, err := client.AgentTokens.Create(ctx, ap.ID, AgentTokenCreateOptions{\n\t\tDescription: String(randomString(t)),\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn at, func() {\n\t\tif err := client.AgentTokens.Delete(ctx, at.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying agent token! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"AgentToken: %s\\nError: %s\", at.ID, err)\n\t\t}\n\n\t\tif apCleanup != nil {\n\t\t\tapCleanup()\n\t\t}\n\t}\n}\n\nfunc createConfigurationVersion(t *testing.T, client *Client, w *Workspace) (*ConfigurationVersion, func()) {\n\tvar wCleanup func()\n\n\tif w == nil {\n\t\tw, wCleanup = createWorkspace(t, client, nil)\n\t}\n\n\tctx := context.Background()\n\tcv, err := client.ConfigurationVersions.Create(\n\t\tctx,\n\t\tw.ID,\n\t\tConfigurationVersionCreateOptions{AutoQueueRuns: Bool(false)},\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn cv, func() {\n\t\tif wCleanup != nil {\n\t\t\twCleanup()\n\t\t}\n\t}\n}\n\nfunc createUploadedConfigurationVersion(t *testing.T, client *Client, w *Workspace) (*ConfigurationVersion, func()) {\n\tcv, cvCleanup := createConfigurationVersion(t, client, w)\n\n\tctx := context.Background()\n\terr := client.ConfigurationVersions.Upload(ctx, cv.UploadURL, \"test-fixtures/config-version\")\n\tif err != nil {\n\t\tcvCleanup()\n\t\tt.Fatal(err)\n\t}\n\n\tWaitUntilStatus(t, client, cv, ConfigurationUploaded, 15)\n\n\treturn cv, cvCleanup\n}\n\nfunc createTestRunConfigurationVersion(t *testing.T, client *Client, rm *RegistryModule) (*ConfigurationVersion, func()) {\n\tvar rmCleanup func()\n\n\tif rm == nil {\n\t\trm, rmCleanup = createRegistryModuleWithVersion(t, client, nil)\n\t}\n\n\tctx := context.Background()\n\tcv, err := client.ConfigurationVersions.CreateForRegistryModule(\n\t\tctx,\n\t\tRegistryModuleID{\n\t\t\tOrganization: rm.Organization.Name,\n\t\t\tName:         rm.Name,\n\t\t\tProvider:     rm.Provider,\n\t\t\tNamespace:    rm.Namespace,\n\t\t\tRegistryName: rm.RegistryName,\n\t\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn cv, func() {\n\t\tif rmCleanup != nil {\n\t\t\trmCleanup()\n\t\t}\n\t}\n}\n\nfunc createUploadedTestRunConfigurationVersion(t *testing.T, client *Client, rm *RegistryModule) (*ConfigurationVersion, func()) {\n\tcv, cvCleanup := createTestRunConfigurationVersion(t, client, rm)\n\n\tctx := context.Background()\n\terr := client.ConfigurationVersions.Upload(ctx, cv.UploadURL, \"test-fixtures/config-version-with-test\")\n\tif err != nil {\n\t\tcvCleanup()\n\t\tt.Fatal(err)\n\t}\n\n\tWaitUntilStatus(t, client, cv, ConfigurationUploaded, 15)\n\n\treturn cv, cvCleanup\n}\n\n// helper to wait until a configuration version has reached a certain status\nfunc WaitUntilStatus(t *testing.T, client *Client, cv *ConfigurationVersion, desiredStatus ConfigurationStatus, timeoutSeconds int) {\n\tctx := context.Background()\n\n\tfor i := 0; ; i++ {\n\t\trefreshed, err := client.ConfigurationVersions.Read(ctx, cv.ID)\n\t\trequire.NoError(t, err)\n\n\t\tif refreshed.Status == desiredStatus {\n\t\t\tbreak\n\t\t}\n\n\t\tif i > timeoutSeconds {\n\t\t\tt.Fatal(\"Timeout waiting for the configuration version to be archived\")\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc createGPGKey(t *testing.T, client *Client, org *Organization, provider *RegistryProvider) (*GPGKey, func()) {\n\tvar orgCleanup func()\n\tvar providerCleanup func()\n\n\tctx := context.Background()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t\tupgradeOrganizationSubscription(t, client, org)\n\t}\n\n\tif provider == nil {\n\t\tprovider, providerCleanup = createRegistryProvider(t, client, org, PrivateRegistry)\n\t}\n\n\tgpgKey, err := client.GPGKeys.Create(ctx, PrivateRegistry, GPGKeyCreateOptions{\n\t\tNamespace:  provider.Organization.Name,\n\t\tAsciiArmor: testGpgArmor,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn gpgKey, func() {\n\t\tif err := client.GPGKeys.Delete(ctx, GPGKeyID{\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    provider.Organization.Name,\n\t\t\tKeyID:        gpgKey.KeyID,\n\t\t}); err != nil {\n\t\t\tt.Errorf(\"Error removing GPG key! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"GPGKey: %s\\nError: %s\", gpgKey.KeyID, err)\n\t\t}\n\n\t\tif providerCleanup != nil {\n\t\t\tproviderCleanup()\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createAWSOIDCConfiguration(t *testing.T, client *Client, org *Organization) (*AWSOIDCConfiguration, func()) {\n\tvar orgCleanup func()\n\n\tctx := context.Background()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\topts := AWSOIDCConfigurationCreateOptions{\n\t\tRoleARN: fmt.Sprintf(\"arn:aws:iam::123456789012:role/%s\", randomString(t)),\n\t}\n\n\toidcConfig, err := client.AWSOIDCConfigurations.Create(ctx, org.Name, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn oidcConfig, func() {\n\t\tif err := client.AWSOIDCConfigurations.Delete(ctx, oidcConfig.ID); err != nil {\n\t\t\tt.Errorf(\"Error removing AWS OIDC Configuration! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"AWSOIDCConfigurations: %s\\nError: %s\", oidcConfig.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc (a *AWSOIDCConfiguration) createHYOKConfiguration(t *testing.T, client *Client, org *Organization, agentPool *AgentPool) (*HYOKConfiguration, func()) {\n\tvar orgCleanup func()\n\n\tctx := context.Background()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\topts := HYOKConfigurationsCreateOptions{\n\t\tKEKID:             \"arn:aws:kms:us-east-1:123456789012:key/this-is-not-a-real-key\",\n\t\tName:              randomStringWithoutSpecialChar(t),\n\t\tKMSOptions:        &KMSOptions{KeyRegion: \"us-east-1\"},\n\t\tAgentPool:         agentPool,\n\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{AWSOIDCConfiguration: a},\n\t}\n\n\thyokConfig, err := client.HYOKConfigurations.Create(ctx, org.Name, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn hyokConfig, func() {\n\t\tcleanupHYOKConfiguration(t, ctx, client, hyokConfig.ID)\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createAzureOIDCConfiguration(t *testing.T, client *Client, org *Organization) (*AzureOIDCConfiguration, func()) {\n\tvar orgCleanup func()\n\n\tctx := context.Background()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\topts := AzureOIDCConfigurationCreateOptions{\n\t\tClientID:       randomString(t),\n\t\tSubscriptionID: randomString(t),\n\t\tTenantID:       randomString(t),\n\t}\n\n\toidcConfig, err := client.AzureOIDCConfigurations.Create(ctx, org.Name, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn oidcConfig, func() {\n\t\tif err := client.AzureOIDCConfigurations.Delete(ctx, oidcConfig.ID); err != nil {\n\t\t\tt.Errorf(\"Error removing Azure OIDC Configuration! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"AzureOIDCConfigurations: %s\\nError: %s\", oidcConfig.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc (a *AzureOIDCConfiguration) createHYOKConfiguration(t *testing.T, client *Client, org *Organization, agentPool *AgentPool) (*HYOKConfiguration, func()) {\n\tvar orgCleanup func()\n\n\tctx := context.Background()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\topts := HYOKConfigurationsCreateOptions{\n\t\tKEKID:             \"https://vault-name.vault.azure.net/keys/key-name\",\n\t\tName:              randomStringWithoutSpecialChar(t),\n\t\tAgentPool:         agentPool,\n\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{AzureOIDCConfiguration: a},\n\t}\n\n\thyokConfig, err := client.HYOKConfigurations.Create(ctx, org.Name, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn hyokConfig, func() {\n\t\tcleanupHYOKConfiguration(t, ctx, client, hyokConfig.ID)\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createGCPOIDCConfiguration(t *testing.T, client *Client, org *Organization) (*GCPOIDCConfiguration, func()) {\n\tvar orgCleanup func()\n\n\tctx := context.Background()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\topts := GCPOIDCConfigurationCreateOptions{\n\t\tServiceAccountEmail:  randomString(t),\n\t\tProjectNumber:        \"123456789012\",\n\t\tWorkloadProviderName: randomString(t),\n\t}\n\n\toidcConfig, err := client.GCPOIDCConfigurations.Create(ctx, org.Name, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn oidcConfig, func() {\n\t\tif err := client.GCPOIDCConfigurations.Delete(ctx, oidcConfig.ID); err != nil {\n\t\t\tt.Errorf(\"Error removing GCP OIDC Configuration! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"GCPOIDCConfigurations: %s\\nError: %s\", oidcConfig.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc (g *GCPOIDCConfiguration) createHYOKConfiguration(t *testing.T, client *Client, org *Organization, agentPool *AgentPool) (*HYOKConfiguration, func()) {\n\tvar orgCleanup func()\n\n\tctx := context.Background()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\topts := HYOKConfigurationsCreateOptions{\n\t\tKEKID:             randomStringWithoutSpecialChar(t),\n\t\tName:              randomStringWithoutSpecialChar(t),\n\t\tKMSOptions:        &KMSOptions{KeyLocation: \"global\", KeyRingID: randomString(t)},\n\t\tAgentPool:         agentPool,\n\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{GCPOIDCConfiguration: g},\n\t}\n\n\thyokConfig, err := client.HYOKConfigurations.Create(ctx, org.Name, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn hyokConfig, func() {\n\t\tcleanupHYOKConfiguration(t, ctx, client, hyokConfig.ID)\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createVaultOIDCConfiguration(t *testing.T, client *Client, org *Organization) (*VaultOIDCConfiguration, func()) {\n\tvar orgCleanup func()\n\n\tctx := context.Background()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\topts := VaultOIDCConfigurationCreateOptions{\n\t\tAddress:          \"https://vault.example.com\",\n\t\tRoleName:         randomString(t),\n\t\tNamespace:        randomString(t),\n\t\tJWTAuthPath:      \"jwt\",\n\t\tTLSCACertificate: randomString(t),\n\t}\n\n\toidcConfig, err := client.VaultOIDCConfigurations.Create(ctx, org.Name, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn oidcConfig, func() {\n\t\tif err := client.VaultOIDCConfigurations.Delete(ctx, oidcConfig.ID); err != nil {\n\t\t\tt.Errorf(\"Error removing Vault OIDC Configuration! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"VaultOIDCConfigurations: %s\\nError: %s\", oidcConfig.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc (v *VaultOIDCConfiguration) createHYOKConfiguration(t *testing.T, client *Client, org *Organization, agentPool *AgentPool) (*HYOKConfiguration, func()) {\n\tvar orgCleanup func()\n\n\tctx := context.Background()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\topts := HYOKConfigurationsCreateOptions{\n\t\tKEKID:             randomStringWithoutSpecialChar(t),\n\t\tName:              randomStringWithoutSpecialChar(t),\n\t\tAgentPool:         agentPool,\n\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{VaultOIDCConfiguration: v},\n\t}\n\n\thyokConfig, err := client.HYOKConfigurations.Create(ctx, org.Name, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn hyokConfig, func() {\n\t\tcleanupHYOKConfiguration(t, ctx, client, hyokConfig.ID)\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc waitForHYOKConfigurationStatus(t *testing.T, ctx context.Context, client *Client, hyokID string, status HYOKConfigurationStatus) (interface{}, error) {\n\tt.Helper()\n\n\treturn retryPatiently(func() (interface{}, error) {\n\t\tfetched, err := client.HYOKConfigurations.Read(ctx, hyokID, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif fetched.Status == status {\n\t\t\treturn fetched, nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\"HYOK Configuration is not %s! HYOKConfiguration: %s\\nStatus: %s\", status, hyokID, fetched.Status)\n\t})\n}\n\nfunc cleanupHYOKConfiguration(t *testing.T, ctx context.Context, client *Client, hyokID string) {\n\t_, err := waitForHYOKConfigurationStatus(t, ctx, client, hyokID, HYOKConfigurationTestFailed)\n\tif err != nil {\n\t\tt.Errorf(\"Timed out waiting for HYOK configuration %s to fail test\", hyokID)\n\t}\n\n\tif err = client.HYOKConfigurations.Revoke(ctx, hyokID); err != nil {\n\t\tt.Errorf(\"Error removing HYOK Configuration! WARNING: Dangling resources\\n\"+\n\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\"HYOKConfigurations: %s\\nError: %s\", hyokID, err)\n\t}\n\n\t_, err = waitForHYOKConfigurationStatus(t, ctx, client, hyokID, HYOKConfigurationRevoked)\n\tif err != nil {\n\t\tt.Errorf(\"Timed out waiting for HYOK configuration %s to revoke\", hyokID)\n\t}\n\n\tif err := client.HYOKConfigurations.Delete(ctx, hyokID); err != nil {\n\t\tt.Errorf(\"Error removing HYOK Configuration! WARNING: Dangling resources\\n\"+\n\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\"HYOKConfigurations: %s\\nError: %s\", hyokID, err)\n\t}\n}\n\nfunc createNotificationConfiguration(t *testing.T, client *Client, w *Workspace, options *NotificationConfigurationCreateOptions) (*NotificationConfiguration, func()) {\n\tvar wCleanup func()\n\n\tif w == nil {\n\t\tw, wCleanup = createWorkspace(t, client, nil)\n\t}\n\n\trunTaskURL := os.Getenv(\"TFC_RUN_TASK_URL\")\n\tif runTaskURL == \"\" {\n\t\tt.Skip(\"Cannot create a notification configuration with an empty URL. You must set TFC_RUN_TASK_URL for run task related tests.\")\n\t}\n\n\tif options == nil {\n\t\toptions = &NotificationConfigurationCreateOptions{\n\t\t\tDestinationType: NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\tEnabled:         Bool(false),\n\t\t\tName:            String(randomString(t)),\n\t\t\tToken:           String(randomString(t)),\n\t\t\tURL:             String(runTaskURL),\n\t\t\tTriggers:        []NotificationTriggerType{NotificationTriggerCreated},\n\t\t}\n\t}\n\n\tctx := context.Background()\n\tnc, err := client.NotificationConfigurations.Create(\n\t\tctx,\n\t\tw.ID,\n\t\t*options,\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn nc, func() {\n\t\tif err := client.NotificationConfigurations.Delete(ctx, nc.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying notification configuration! WARNING: Dangling\\n\"+\n\t\t\t\t\"resources may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"NotificationConfiguration: %s\\nError: %s\", nc.ID, err)\n\t\t}\n\n\t\tif wCleanup != nil {\n\t\t\twCleanup()\n\t\t}\n\t}\n}\n\nfunc createTeamNotificationConfiguration(t *testing.T, client *Client, team *Team, options *NotificationConfigurationCreateOptions) (*NotificationConfiguration, func()) {\n\tvar tCleanup func()\n\n\tif team == nil {\n\t\tteam, tCleanup = createTeam(t, client, nil)\n\t}\n\n\t// Team notification configurations do not actually require a run task, but we'll\n\t// reuse this as a URL that returns a 200.\n\trunTaskURL := os.Getenv(\"TFC_RUN_TASK_URL\")\n\tif runTaskURL == \"\" {\n\t\tt.Error(\"You must set TFC_RUN_TASK_URL for run task related tests.\")\n\t}\n\n\tif options == nil {\n\t\toptions = &NotificationConfigurationCreateOptions{\n\t\t\tDestinationType:    NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\tEnabled:            Bool(false),\n\t\t\tName:               String(randomString(t)),\n\t\t\tToken:              String(randomString(t)),\n\t\t\tURL:                String(runTaskURL),\n\t\t\tTriggers:           []NotificationTriggerType{NotificationTriggerChangeRequestCreated},\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: team},\n\t\t}\n\t}\n\n\tctx := context.Background()\n\tnc, err := client.NotificationConfigurations.Create(\n\t\tctx,\n\t\tteam.ID,\n\t\t*options,\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn nc, func() {\n\t\tif err := client.NotificationConfigurations.Delete(ctx, nc.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying team notification configuration! WARNING: Dangling\\n\"+\n\t\t\t\t\"resources may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"NotificationConfiguration: %s\\nError: %s\", nc.ID, err)\n\t\t}\n\n\t\tif tCleanup != nil {\n\t\t\ttCleanup()\n\t\t}\n\t}\n}\n\nfunc createPolicySetParameter(t *testing.T, client *Client, ps *PolicySet) (*PolicySetParameter, func()) {\n\tvar psCleanup func()\n\n\tif ps == nil {\n\t\tps, psCleanup = createPolicySet(t, client, nil, nil, nil, nil, nil, \"\")\n\t}\n\n\tctx := context.Background()\n\n\tv, err := client.PolicySetParameters.Create(ctx, ps.ID, PolicySetParameterCreateOptions{\n\t\tKey:      String(randomKeyValue(t)),\n\t\tValue:    String(randomKeyValue(t)),\n\t\tCategory: Category(CategoryPolicySet),\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn v, func() {\n\t\tif err := client.PolicySetParameters.Delete(ctx, ps.ID, v.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying variable! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Parameter: %s\\nError: %s\", v.Key, err)\n\t\t}\n\n\t\tif psCleanup != nil {\n\t\t\tpsCleanup()\n\t\t}\n\t}\n}\n\nfunc createPolicySet(t *testing.T, client *Client, org *Organization, policies []*Policy, workspaces []*Workspace,\n\texcludedWorkspace []*Workspace, projects []*Project, kind PolicyKind) (*PolicySet, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\tps, err := client.PolicySets.Create(ctx, org.Name, PolicySetCreateOptions{\n\t\tName:                String(randomString(t)),\n\t\tPolicies:            policies,\n\t\tWorkspaces:          workspaces,\n\t\tWorkspaceExclusions: excludedWorkspace,\n\t\tProjects:            projects,\n\t\tKind:                kind,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn ps, func() {\n\t\tif err := client.PolicySets.Delete(ctx, ps.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying policy set! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"PolicySet: %s\\nError: %s\", ps.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createPolicySetWithOptions(t *testing.T, client *Client, org *Organization, policies []*Policy, workspaces, excludedWorkspace []*Workspace, projects []*Project, opts PolicySetCreateOptions) (*PolicySet, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\tps, err := client.PolicySets.Create(ctx, org.Name, PolicySetCreateOptions{\n\t\tName:                String(randomString(t)),\n\t\tPolicies:            policies,\n\t\tWorkspaces:          workspaces,\n\t\tWorkspaceExclusions: excludedWorkspace,\n\t\tProjects:            projects,\n\t\tKind:                opts.Kind,\n\t\tOverridable:         opts.Overridable,\n\t\tAgentEnabled:        opts.AgentEnabled,\n\t\tPolicyToolVersion:   opts.PolicyToolVersion,\n\t\tProjectExclusions:   opts.ProjectExclusions,\n\t\tGlobal:              opts.Global,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn ps, func() {\n\t\tif err := client.PolicySets.Delete(ctx, ps.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying policy set! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"PolicySet: %s\\nError: %s\", ps.ID, err)\n\t\t}\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createPolicySetVersion(t *testing.T, client *Client, ps *PolicySet) (*PolicySetVersion, func()) {\n\tvar psCleanup func()\n\n\tif ps == nil {\n\t\tps, psCleanup = createPolicySet(t, client, nil, nil, nil, nil, nil, \"\")\n\t}\n\n\tctx := context.Background()\n\tpsv, err := client.PolicySetVersions.Create(ctx, ps.ID)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn psv, func() {\n\t\t// Deleting a Policy Set Version is done through deleting a Policy Set.\n\t\tif psCleanup != nil {\n\t\t\tpsCleanup()\n\t\t}\n\t}\n}\n\nfunc createPolicy(t *testing.T, client *Client, org *Organization) (*Policy, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tname := randomString(t)\n\toptions := PolicyCreateOptions{\n\t\tName: String(name),\n\t\tEnforce: []*EnforcementOptions{\n\t\t\t{\n\t\t\t\tPath: String(name + \".sentinel\"),\n\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t},\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tp, err := client.Policies.Create(ctx, org.Name, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn p, func() {\n\t\tif err := client.Policies.Delete(ctx, p.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying policy! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Policy: %s\\nError: %s\", p.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createPolicyWithOptions(t *testing.T, client *Client, org *Organization, opts PolicyCreateOptions) (*Policy, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tname := randomString(t)\n\toptions := PolicyCreateOptions{\n\t\tName:             String(name),\n\t\tKind:             opts.Kind,\n\t\tQuery:            opts.Query,\n\t\tEnforcementLevel: opts.EnforcementLevel,\n\t}\n\n\tif len(opts.Enforce) > 0 {\n\t\tpath := name + \".sentinel\"\n\t\tif opts.Kind == OPA {\n\t\t\tpath = name + \".rego\"\n\t\t}\n\t\toptions.Enforce = []*EnforcementOptions{\n\t\t\t{\n\t\t\t\tPath: String(path),\n\t\t\t\tMode: opts.Enforce[0].Mode,\n\t\t\t},\n\t\t}\n\t}\n\n\tctx := context.Background()\n\tp, err := client.Policies.Create(ctx, org.Name, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn p, func() {\n\t\tif err := client.Policies.Delete(ctx, p.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying policy! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Policy: %s\\nError: %s\", p.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createUploadedPolicy(t *testing.T, client *Client, pass bool, org *Organization) (*Policy, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tp, pCleanup := createPolicy(t, client, org)\n\n\tctx := context.Background()\n\terr := client.Policies.Upload(ctx, p.ID, []byte(fmt.Sprintf(\"main = rule { %t }\", pass)))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tp, err = client.Policies.Read(ctx, p.ID)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn p, func() {\n\t\tpCleanup()\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createUploadedPolicyWithOptions(t *testing.T, client *Client, pass bool, org *Organization, opts PolicyCreateOptions) (*Policy, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tp, pCleanup := createPolicyWithOptions(t, client, org, opts)\n\n\tctx := context.Background()\n\tpolicy := fmt.Sprintf(\"main = rule { %t }\", pass)\n\tif opts.Kind == OPA {\n\t\tpolicy = `package example rule[\"not allowed\"] { false }`\n\t\tif !pass {\n\t\t\tpolicy = `package example rule[\"not allowed\"] { true }`\n\t\t}\n\t}\n\terr := client.Policies.Upload(ctx, p.ID, []byte(policy))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tp, err = client.Policies.Read(ctx, p.ID)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn p, func() {\n\t\tpCleanup()\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createOAuthClient(t *testing.T, client *Client, org *Organization, projects []*Project) (*OAuthClient, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tgithubToken := os.Getenv(\"OAUTH_CLIENT_GITHUB_TOKEN\")\n\tif githubToken == \"\" {\n\t\tt.Skip(\"Export a valid OAUTH_CLIENT_GITHUB_TOKEN before running this test!\")\n\t}\n\n\toptions := OAuthClientCreateOptions{\n\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\tHTTPURL:         String(\"https://github.com\"),\n\t\tOAuthToken:      String(githubToken),\n\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\tProjects:        projects,\n\t}\n\n\tctx := context.Background()\n\toc, err := client.OAuthClients.Create(ctx, org.Name, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// This currently panics as the token will not be there when the client is\n\t// created. To get a token, the client needs to be connected through the UI\n\t// first. So the test using this (TestOAuthTokensList) is currently disabled.\n\treturn oc, func() {\n\t\tif err := client.OAuthClients.Delete(ctx, oc.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying OAuth client! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"OAuthClient: %s\\nError: %s\", oc.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createOAuthToken(t *testing.T, client *Client, org *Organization) (*OAuthToken, func()) {\n\tocTest, ocTestCleanup := createOAuthClient(t, client, org, nil)\n\treturn ocTest.OAuthTokens[0], ocTestCleanup\n}\n\n// createOrganization creates an organization for tests using the special prefix\n// \"tst-\" that the API uses especially to grant access to orgs for testing.\n// Don't change this prefix unless we refactor the code!\nfunc createOrganization(t *testing.T, client *Client) (*Organization, func()) {\n\treturn createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\tName:                  String(\"tst-\" + randomString(t)),\n\t\tEmail:                 String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\tCostEstimationEnabled: Bool(true),\n\t\tStacksEnabled:         Bool(true),\n\t})\n}\n\nfunc createOrganizationWithOptions(t *testing.T, client *Client, options OrganizationCreateOptions) (*Organization, func()) {\n\tctx := context.Background()\n\torg, err := client.Organizations.Create(ctx, options)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create organization: %s\", err)\n\t}\n\n\treturn org, func() {\n\t\tif err := client.Organizations.Delete(ctx, org.Name); err != nil {\n\t\t\tt.Logf(\"Error destroying organization! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Organization: %s\\nError: %s\", org.Name, err)\n\t\t}\n\t}\n}\n\nfunc createOrganizationWithDefaultAgentPool(t *testing.T, client *Client) (*Organization, func()) {\n\tctx := context.Background()\n\torg, orgCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\tName:                  String(\"tst-\" + randomString(t)),\n\t\tEmail:                 String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\tCostEstimationEnabled: Bool(true),\n\t})\n\n\tagentPool, _ := createAgentPool(t, client, org)\n\n\torg, err := client.Organizations.Update(ctx, org.Name, OrganizationUpdateOptions{\n\t\tDefaultExecutionMode: String(\"agent\"),\n\t\tDefaultAgentPool:     agentPool,\n\t})\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn org, func() {\n\t\t// delete the org\n\t\torgCleanup()\n\t}\n}\nfunc createOrganizationMembership(t *testing.T, client *Client, org *Organization) (*OrganizationMembership, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\tmem, err := client.OrganizationMemberships.Create(ctx, org.Name, OrganizationMembershipCreateOptions{\n\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn mem, func() {\n\t\tif err := client.OrganizationMemberships.Delete(ctx, mem.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying membership! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Membership: %s\\nError: %s\", mem.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createOrganizationToken(t *testing.T, client *Client, org *Organization) (*OrganizationToken, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\ttk, err := client.OrganizationTokens.Create(ctx, org.Name)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn tk, func() {\n\t\tif err := client.OrganizationTokens.Delete(ctx, org.Name); err != nil {\n\t\t\tt.Errorf(\"Error destroying organization token! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"OrganizationToken: %s\\nError: %s\", tk.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createOrganizationTokenWithOptions(t *testing.T, client *Client, org *Organization, options OrganizationTokenCreateOptions) (*OrganizationToken, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\ttk, err := client.OrganizationTokens.CreateWithOptions(ctx, org.Name, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn tk, func() {\n\t\tif err := client.OrganizationTokens.Delete(ctx, org.Name); err != nil {\n\t\t\tt.Errorf(\"Error destroying organization token! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"OrganizationToken: %s\\nError: %s\", tk.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createRunTrigger(t *testing.T, client *Client, w, sourceable *Workspace) (*RunTrigger, func()) {\n\tvar wCleanup func()\n\tvar sourceableCleanup func()\n\n\tif w == nil {\n\t\tw, wCleanup = createWorkspace(t, client, nil)\n\t}\n\n\tif sourceable == nil {\n\t\tsourceable, sourceableCleanup = createWorkspace(t, client, nil)\n\t}\n\n\tctx := context.Background()\n\trt, err := client.RunTriggers.Create(\n\t\tctx,\n\t\tw.ID,\n\t\tRunTriggerCreateOptions{\n\t\t\tSourceable: sourceable,\n\t\t},\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn rt, func() {\n\t\tif err := client.RunTriggers.Delete(ctx, rt.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying run trigger! WARNING: Dangling\\n\"+\n\t\t\t\t\"resources may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"RunTrigger: %s\\nError: %s\", rt.ID, err)\n\t\t}\n\n\t\tif wCleanup != nil {\n\t\t\twCleanup()\n\t\t}\n\n\t\tif sourceableCleanup != nil {\n\t\t\tsourceableCleanup()\n\t\t}\n\t}\n}\n\nfunc createPolicyCheckedRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {\n\treturn createRunWaitForAnyStatuses(t, client, w, []RunStatus{RunPolicyChecked, RunPolicyOverride})\n}\n\nfunc createPlannedRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {\n\treturn createRunWaitForAnyStatuses(t, client, w, []RunStatus{RunCostEstimated, RunPlanned, RunPostPlanCompleted})\n}\n\nfunc createCostEstimatedRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {\n\treturn createRunWaitForStatus(t, client, w, RunCostEstimated)\n}\n\nfunc createRunApply(t *testing.T, client *Client, w *Workspace) (*Run, func()) {\n\tctx := context.Background()\n\trun, rCleanup := createRunUnapplied(t, client, w)\n\ttimeout := 2 * time.Minute\n\n\t// If the run was not in error, it must be applyable\n\tapplyRun(t, client, ctx, run)\n\n\tctxPollRunApplied, cancelPollApplied := context.WithTimeout(ctx, timeout)\n\n\trun = pollRunStatus(t, client, ctxPollRunApplied, run, []RunStatus{RunApplied, RunErrored})\n\tif run.Status == RunErrored {\n\t\tfatalDumpRunLog(t, client, ctx, run)\n\t}\n\n\treturn run, func() {\n\t\trCleanup()\n\t\tcancelPollApplied()\n\t}\n}\n\nfunc createRunUnapplied(t *testing.T, client *Client, w *Workspace) (*Run, func()) {\n\tvar rCleanup func()\n\tctx := context.Background()\n\tr, rCleanup := createRun(t, client, w)\n\n\ttimeout := 2 * time.Minute\n\n\tctxPollRunReady, cancelPollRunReady := context.WithTimeout(ctx, timeout)\n\n\trun := pollRunStatus(\n\t\tt,\n\t\tclient,\n\t\tctxPollRunReady,\n\t\tr,\n\t\tappend(applyableStatuses(r), RunErrored),\n\t)\n\n\tif run.Status == RunErrored {\n\t\tfatalDumpRunLog(t, client, ctx, run)\n\t}\n\n\treturn run, func() {\n\t\trCleanup()\n\t\tcancelPollRunReady()\n\t}\n}\n\nfunc createRunWaitForStatus(t *testing.T, client *Client, w *Workspace, status RunStatus) (*Run, func()) {\n\treturn createRunWaitForAnyStatuses(t, client, w, []RunStatus{status})\n}\n\nfunc createRunWaitForAnyStatuses(t *testing.T, client *Client, w *Workspace, statuses []RunStatus) (*Run, func()) {\n\tvar rCleanup func()\n\tctx := context.Background()\n\tr, rCleanup := createRun(t, client, w)\n\n\ttimeout := 2 * time.Minute\n\n\tctxPollRunReady, cancelPollRunReady := context.WithTimeout(ctx, timeout)\n\n\trun := pollRunStatus(\n\t\tt,\n\t\tclient,\n\t\tctxPollRunReady,\n\t\tr,\n\t\tappend(statuses, RunErrored),\n\t)\n\n\tif run.Status == RunErrored {\n\t\tfatalDumpRunLog(t, client, ctx, run)\n\t}\n\n\treturn run, func() {\n\t\trCleanup()\n\t\tcancelPollRunReady()\n\t}\n}\n\nfunc createQueryRunWaitForAnyStatuses(t *testing.T, client *Client, w *Workspace, statuses []QueryRunStatus) (*QueryRun, func()) {\n\tctx := context.Background()\n\tqr := createQueryRun(t, client, w)\n\n\ttimeout := 2 * time.Minute\n\n\tctxPollQueryRunReady, cancelPollQueryRunReady := context.WithTimeout(ctx, timeout)\n\n\trun := pollQueryRunStatus(\n\t\tt,\n\t\tclient,\n\t\tctxPollQueryRunReady,\n\t\tqr,\n\t\tappend(statuses, QueryRunErrored),\n\t)\n\n\treturn run, func() {\n\t\tcancelPollQueryRunReady()\n\t}\n}\n\nfunc applyableStatuses(r *Run) []RunStatus {\n\tif len(r.PolicyChecks) > 0 {\n\t\treturn []RunStatus{\n\t\t\tRunPolicyChecked,\n\t\t\tRunPolicyOverride,\n\t\t}\n\t} else if r.CostEstimate != nil {\n\t\treturn []RunStatus{RunCostEstimated}\n\t}\n\n\treturn []RunStatus{RunPlanned, RunPostPlanCompleted}\n}\n\n// pollRunStatus will poll the given run until its status matches one of the given run statuses or the given context\n// times out.\nfunc pollRunStatus(t *testing.T, client *Client, ctx context.Context, r *Run, rss []RunStatus) *Run {\n\tdeadline, ok := ctx.Deadline()\n\tif !ok {\n\t\tt.Logf(\"No deadline was set to poll run %q which could result in an infinite loop\", r.ID)\n\t}\n\n\tt.Logf(\"Polling run %q for status included in %q with deadline of %s\", r.ID, rss, deadline)\n\n\tticker := time.NewTicker(2 * time.Second)\n\tdefer ticker.Stop()\n\n\tfor finished := false; !finished; {\n\t\tt.Log(\"...\")\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatalf(\"Run %q had status %q at deadline\", r.ID, r.Status)\n\t\tcase <-ticker.C:\n\t\t\tr = readRun(t, client, ctx, r)\n\t\t\tt.Logf(\"Run %q had status %q\", r.ID, r.Status)\n\t\t\tfor _, rs := range rss {\n\t\t\t\tif rs == r.Status {\n\t\t\t\t\tfinished = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn r\n}\n\n// pollQueryRunStatus will poll the given query run until its status matches one of the given run statuses or the given context\n// times out.\nfunc pollQueryRunStatus(t *testing.T, client *Client, ctx context.Context, q *QueryRun, rss []QueryRunStatus) *QueryRun {\n\tdeadline, ok := ctx.Deadline()\n\tif !ok {\n\t\tt.Logf(\"No deadline was set to poll query run %q which could result in an infinite loop\", q.ID)\n\t}\n\n\tt.Logf(\"Polling query run %q for status included in %q with deadline of %s\", q.ID, rss, deadline)\n\n\tticker := time.NewTicker(2 * time.Second)\n\tdefer ticker.Stop()\n\n\tfor finished := false; !finished; {\n\t\tt.Log(\"...\")\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatalf(\"Run %q had status %q at deadline\", q.ID, q.Status)\n\t\tcase <-ticker.C:\n\t\t\tq = readQueryRun(t, client, ctx, q)\n\t\t\tt.Logf(\"Query Run %q had status %q\", q.ID, q.Status)\n\t\t\tfor _, rs := range rss {\n\t\t\t\tif rs == q.Status {\n\t\t\t\t\tfinished = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn q\n}\n\n// pollStateVersionStatus will poll the given state version until its status\n// matches one of the given statuses or the given context times out.\nfunc pollStateVersionStatus(t *testing.T, client *Client, ctx context.Context, sv *StateVersion, statuses []StateVersionStatus) *StateVersion {\n\tdeadline, ok := ctx.Deadline()\n\tif !ok {\n\t\tt.Logf(\"No deadline was set to poll state version %q which could result in an infinite loop\", sv.ID)\n\t}\n\n\tt.Logf(\"Polling state version %q for status included in %q with deadline of %s\", sv.ID, statuses, deadline)\n\n\tticker := time.NewTicker(2 * time.Second)\n\tdefer ticker.Stop()\n\tvar err error\n\n\tfor finished := false; !finished; {\n\t\tt.Log(\"...\")\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatalf(\"State version %q had status %q at deadline\", sv.ID, sv.Status)\n\t\tcase <-ticker.C:\n\t\t\tsv, err = client.StateVersions.Read(ctx, sv.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Could not read state version %q: %s\", sv.ID, err)\n\t\t\t}\n\t\t\tt.Logf(\"State version %q had status %q\", sv.ID, sv.Status)\n\t\t\tfor _, svst := range statuses {\n\t\t\t\tif svst == sv.Status {\n\t\t\t\t\tfinished = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn sv\n}\n\n// readRun will re-read the given run.\nfunc readRun(t *testing.T, client *Client, ctx context.Context, r *Run) *Run {\n\tt.Logf(\"Reading run %q\", r.ID)\n\n\trr, err := client.Runs.Read(ctx, r.ID)\n\tif err != nil {\n\t\tt.Fatalf(\"Could not read run %q: %s\", r.ID, err)\n\t}\n\n\treturn rr\n}\n\n// readQueryRun will re-read the given query run.\nfunc readQueryRun(t *testing.T, client *Client, ctx context.Context, r *QueryRun) *QueryRun {\n\tt.Logf(\"Reading query run %q\", r.ID)\n\n\tqr, err := client.QueryRuns.Read(ctx, r.ID)\n\tif err != nil {\n\t\tt.Fatalf(\"Could not read run %q: %s\", r.ID, err)\n\t}\n\n\treturn qr\n}\n\n// applyRun will apply the given run.\nfunc applyRun(t *testing.T, client *Client, ctx context.Context, r *Run) {\n\trunDependentTestNameValidator(t)\n\tt.Logf(\"Applying run %q\", r.ID)\n\n\tif err := client.Runs.Apply(ctx, r.ID, RunApplyOptions{}); err != nil {\n\t\tt.Fatalf(\"Could not apply run %q: %s\", r.ID, err)\n\t}\n}\n\n// readPlan will read the given plan.\nfunc readPlan(t *testing.T, client *Client, ctx context.Context, p *Plan) *Plan {\n\tt.Logf(\"Reading plan %q\", p.ID)\n\n\trp, err := client.Plans.Read(ctx, p.ID)\n\tif err != nil {\n\t\tt.Fatalf(\"Could not read plan %q: %s\", p.ID, err)\n\t}\n\n\treturn rp\n}\n\n// readPlanLogs will read the logs of the given plan.\nfunc readPlanLogs(t *testing.T, client *Client, ctx context.Context, p *Plan) io.Reader {\n\tt.Logf(\"Reading logs of plan %q\", p.ID)\n\n\tr, err := client.Plans.Logs(ctx, p.ID)\n\tif err != nil {\n\t\tt.Fatalf(\"Could not retrieve logs of plan %q: %s\", p.ID, err)\n\t}\n\n\treturn r\n}\n\nfunc fatalDumpRunLog(t *testing.T, client *Client, ctx context.Context, run *Run) {\n\tt.Helper()\n\tp := readPlan(t, client, ctx, run.Plan)\n\tr := readPlanLogs(t, client, ctx, p)\n\n\tl, err := io.ReadAll(r)\n\tif err != nil {\n\t\tt.Fatalf(\"Could not read logs of plan %q: %v\", p.ID, err)\n\t}\n\n\tt.Log(\"Run errored - here's some logs to help figure out what happened\")\n\tt.Logf(\"---Start of logs---\\n%s\\n---End of logs---\", l)\n\n\tt.Fatalf(\"Run %q unexpectedly errored\", run.ID)\n}\n\nfunc createRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {\n\trunDependentTestNameValidator(t)\n\tvar wCleanup func()\n\n\tif w == nil {\n\t\tw, wCleanup = createWorkspace(t, client, nil)\n\t}\n\n\tcv, cvCleanup := createUploadedConfigurationVersion(t, client, w)\n\n\tctx := context.Background()\n\tr, err := client.Runs.Create(ctx, RunCreateOptions{\n\t\tConfigurationVersion: cv,\n\t\tWorkspace:            w,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn r, func() {\n\t\tcvCleanup()\n\n\t\tif wCleanup != nil {\n\t\t\twCleanup()\n\t\t}\n\t}\n}\n\nfunc createTestRun(t *testing.T, client *Client, rm *RegistryModule, variables ...*RunVariable) (*TestRun, func()) {\n\tvar rmCleanup func()\n\n\tif rm == nil {\n\t\trm, rmCleanup = createBranchBasedRegistryModule(t, client, nil)\n\t}\n\n\tcv, cvCleanup := createUploadedTestRunConfigurationVersion(t, client, rm)\n\n\tctx := context.Background()\n\ttr, err := client.TestRuns.Create(ctx, TestRunCreateOptions{\n\t\tVariables:            variables,\n\t\tConfigurationVersion: cv,\n\t\tRegistryModule:       rm,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn tr, func() {\n\t\tcvCleanup()\n\n\t\tif rmCleanup != nil {\n\t\t\trmCleanup()\n\t\t}\n\t}\n}\n\nfunc createTestVariable(t *testing.T, client *Client, rm *RegistryModule) (*Variable, func()) {\n\tvar rmCleanup func()\n\n\tif rm == nil {\n\t\trm, rmCleanup = createBranchBasedRegistryModule(t, client, nil)\n\t}\n\trmID := RegistryModuleID{\n\t\tOrganization: rm.Organization.Name,\n\t\tName:         rm.Name,\n\t\tProvider:     rm.Provider,\n\t\tNamespace:    rm.Namespace,\n\t\tRegistryName: rm.RegistryName,\n\t}\n\n\tctx := context.Background()\n\tv, err := client.TestVariables.Create(ctx, rmID, VariableCreateOptions{\n\t\tKey:         String(randomKeyValue(t)),\n\t\tValue:       String(randomStringWithoutSpecialChar(t)),\n\t\tCategory:    Category(CategoryEnv),\n\t\tDescription: String(randomStringWithoutSpecialChar(t)),\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn v, func() {\n\t\tif err := client.TestVariables.Delete(ctx, rmID, v.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying variable! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Variable: %s\\nError: %s\", v.Key, err)\n\t\t}\n\n\t\tif rmCleanup != nil {\n\t\t\trmCleanup()\n\t\t}\n\t}\n}\n\n// helper to wait until a test run has reached a certain status\nfunc waitUntilTestRunStatus(t *testing.T, client *Client, rm RegistryModuleID, tr *TestRun, desiredStatus TestRunStatus, timeoutSeconds int) {\n\tctx := context.Background()\n\n\tfor i := 0; ; i++ {\n\t\trefreshed, err := client.TestRuns.Read(ctx, rm, tr.ID)\n\t\trequire.NoError(t, err)\n\n\t\tif refreshed.Status == desiredStatus {\n\t\t\tbreak\n\t\t}\n\n\t\tif i > timeoutSeconds {\n\t\t\tt.Fatalf(\"Timeout waiting for the test run status %s\", string(desiredStatus))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc createPlanExport(t *testing.T, client *Client, r *Run) (*PlanExport, func()) {\n\tvar rCleanup func()\n\n\tif r == nil {\n\t\tr, rCleanup = createRunApply(t, client, nil)\n\t}\n\n\tctx := context.Background()\n\tpe, err := client.PlanExports.Create(ctx, PlanExportCreateOptions{\n\t\tPlan:     r.Plan,\n\t\tDataType: PlanExportType(PlanExportSentinelMockBundleV0),\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttimeout := 10 * time.Minute\n\n\tctxPollExportReady, cancelPollExportReady := context.WithTimeout(ctx, timeout)\n\tt.Cleanup(cancelPollExportReady)\n\n\tticker := time.NewTicker(3 * time.Second)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tt.Log(\"...\")\n\t\tselect {\n\t\tcase <-ctxPollExportReady.Done():\n\t\t\trCleanup()\n\t\t\tt.Fatalf(\"Run %q had status %q at deadline\", r.ID, r.Status)\n\t\tcase <-ticker.C:\n\t\t\tpe, err := client.PlanExports.Read(ctxPollExportReady, pe.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tswitch pe.Status {\n\t\t\tcase PlanExportFinished, PlanExportQueued:\n\t\t\t\treturn pe, func() {\n\t\t\t\t\tif rCleanup != nil {\n\t\t\t\t\t\trCleanup()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase PlanExportErrored:\n\t\t\t\tt.Fatal(\"Plan export failed\")\n\n\t\t\tdefault:\n\t\t\t\tt.Logf(\"Waiting for plan export finished or queued but was %s\", pe.Status)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc createBranchBasedRegistryModule(t *testing.T, client *Client, org *Organization) (*RegistryModule, func()) {\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\tgithubBranch := os.Getenv(\"GITHUB_REGISTRY_MODULE_BRANCH\")\n\tif githubBranch == \"\" {\n\t\tgithubBranch = \"main\"\n\t}\n\n\tvar orgCleanup func()\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, org)\n\n\tctx := context.Background()\n\n\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, RegistryModuleCreateWithVCSConnectionOptions{\n\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\tOrganizationName:  String(org.Name),\n\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\tBranch:            String(githubBranch),\n\t\t},\n\t\tInitialVersion: String(\"1.0.0\"),\n\t})\n\n\tif err != nil {\n\t\toauthTokenTestCleanup()\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\n\t\tt.Fatal(err)\n\t}\n\n\treturn rm, func() {\n\t\tif err := client.RegistryModules.Delete(ctx, org.Name, rm.Name); err != nil {\n\t\t\tt.Errorf(\"Error destroying registry module! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Registry Module: %s\\nError: %s\", rm.Name, err)\n\t\t}\n\n\t\toauthTokenTestCleanup()\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createBranchBasedRegistryModuleWithTests(t *testing.T, client *Client, org *Organization) (*RegistryModule, func()) {\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\tgithubBranch := os.Getenv(\"GITHUB_REGISTRY_MODULE_BRANCH\")\n\tif githubBranch == \"\" {\n\t\tgithubBranch = \"main\"\n\t}\n\n\tvar orgCleanup func()\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, org)\n\n\tctx := context.Background()\n\n\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, RegistryModuleCreateWithVCSConnectionOptions{\n\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\tOrganizationName:  String(org.Name),\n\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\tBranch:            String(githubBranch),\n\t\t},\n\t\tInitialVersion: String(\"1.0.0\"),\n\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\tTestsEnabled: Bool(true),\n\t\t},\n\t})\n\n\tif err != nil {\n\t\toauthTokenTestCleanup()\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\n\t\tt.Fatal(err)\n\t}\n\n\treturn rm, func() {\n\t\tif err := client.RegistryModules.Delete(ctx, org.Name, rm.Name); err != nil {\n\t\t\tt.Errorf(\"Error destroying registry module! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Registry Module: %s\\nError: %s\", rm.Name, err)\n\t\t}\n\n\t\toauthTokenTestCleanup()\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createRegistryModule(t *testing.T, client *Client, org *Organization, registryName RegistryName) (*RegistryModule, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\n\toptions := RegistryModuleCreateOptions{\n\t\tName:         String(randomString(t)),\n\t\tProvider:     String(\"provider\"),\n\t\tRegistryName: registryName,\n\t}\n\n\tif registryName == PublicRegistry {\n\t\toptions.Namespace = \"namespace\"\n\t}\n\n\trm, err := client.RegistryModules.Create(ctx, org.Name, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn rm, func() {\n\t\tif err := client.RegistryModules.Delete(ctx, org.Name, rm.Name); err != nil {\n\t\t\tt.Errorf(\"Error destroying registry module! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Registry Module: %s\\nError: %s\", rm.Name, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createRegistryModuleWithVersion(t *testing.T, client *Client, org *Organization) (*RegistryModule, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\n\toptions := RegistryModuleCreateOptions{\n\t\tName:     String(\"name\"),\n\t\tProvider: String(\"provider\"),\n\t}\n\trm, err := client.RegistryModules.Create(ctx, org.Name, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\toptionsModuleVersion := RegistryModuleCreateVersionOptions{\n\t\tVersion: String(\"1.0.0\"),\n\t}\n\trmID := RegistryModuleID{\n\t\tOrganization: org.Name,\n\t\tName:         rm.Name,\n\t\tProvider:     rm.Provider,\n\t}\n\t_, err = client.RegistryModules.CreateVersion(ctx, rmID, optionsModuleVersion)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trm, err = client.RegistryModules.Read(ctx, rmID)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn rm, func() {\n\t\tif err := client.RegistryModules.Delete(ctx, org.Name, rm.Name); err != nil {\n\t\t\tt.Errorf(\"Error destroying registry module! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Registry Module: %s\\nError: %s\", rm.Name, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createRunTask(t *testing.T, client *Client, org *Organization) (*RunTask, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\trunTaskURL := os.Getenv(\"TFC_RUN_TASK_URL\")\n\tif runTaskURL == \"\" {\n\t\tt.Error(\"Cannot create a run task with an empty URL. You must set TFC_RUN_TASK_URL for run task related tests.\")\n\t}\n\n\tctx := context.Background()\n\tdescription := randomString(t)\n\tr, err := client.RunTasks.Create(ctx, org.Name, RunTaskCreateOptions{\n\t\tName:        \"tst-\" + randomString(t),\n\t\tURL:         runTaskURL,\n\t\tDescription: &description,\n\t\tCategory:    \"task\",\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn r, func() {\n\t\tif err := client.RunTasks.Delete(ctx, r.ID); err != nil {\n\t\t\tt.Errorf(\"Error removing Run Task! WARNING: Run task limit\\n\"+\n\t\t\t\t\"may be reached if not deleted! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Run Task: %s\\nError: %s\", r.Name, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createRegistryProvider(t *testing.T, client *Client, org *Organization, registryName RegistryName) (*RegistryProvider, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tif (registryName != PublicRegistry) && (registryName != PrivateRegistry) {\n\t\tt.Fatal(\"RegistryName must be public or private\")\n\t}\n\n\tctx := context.Background()\n\n\tnamespaceName := \"test-namespace-\" + randomString(t)\n\tif registryName == PrivateRegistry {\n\t\tnamespaceName = org.Name\n\t}\n\n\toptions := RegistryProviderCreateOptions{\n\t\tName:         \"test-registry-provider-\" + randomString(t),\n\t\tNamespace:    namespaceName,\n\t\tRegistryName: registryName,\n\t}\n\n\tprv, err := client.RegistryProviders.Create(ctx, org.Name, options)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tprv.Organization = org\n\n\treturn prv, func() {\n\t\tid := RegistryProviderID{\n\t\t\tOrganizationName: org.Name,\n\t\t\tRegistryName:     prv.RegistryName,\n\t\t\tNamespace:        prv.Namespace,\n\t\t\tName:             prv.Name,\n\t\t}\n\n\t\tif err := client.RegistryProviders.Delete(ctx, id); err != nil {\n\t\t\tt.Errorf(\"Error destroying registry provider! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Registry Provider: %s/%s\\nError: %s\", prv.Namespace, prv.Name, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createRegistryProviderPlatform(t *testing.T, client *Client, provider *RegistryProvider, version *RegistryProviderVersion, targetOS, arch string) (*RegistryProviderPlatform, func()) {\n\tvar providerCleanup func()\n\tvar versionCleanup func()\n\n\tif provider == nil {\n\t\tprovider, providerCleanup = createRegistryProvider(t, client, nil, PrivateRegistry)\n\t}\n\n\tproviderID := RegistryProviderID{\n\t\tOrganizationName: provider.Organization.Name,\n\t\tRegistryName:     provider.RegistryName,\n\t\tNamespace:        provider.Namespace,\n\t\tName:             provider.Name,\n\t}\n\n\tif version == nil {\n\t\tversion, versionCleanup = createRegistryProviderVersion(t, client, provider)\n\t}\n\n\tversionID := RegistryProviderVersionID{\n\t\tRegistryProviderID: providerID,\n\t\tVersion:            version.Version,\n\t}\n\n\tctx := context.Background()\n\n\toptions := RegistryProviderPlatformCreateOptions{\n\t\tOS:       targetOS,\n\t\tArch:     arch,\n\t\tShasum:   genSha(t),\n\t\tFilename: randomString(t),\n\t}\n\n\tif targetOS == \"\" {\n\t\toptions.OS = \"linux\"\n\t}\n\n\tif arch == \"\" {\n\t\toptions.Arch = \"amd64\"\n\t}\n\n\trpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn rpp, func() {\n\t\tplatformID := RegistryProviderPlatformID{\n\t\t\tRegistryProviderVersionID: versionID,\n\t\t\tOS:                        rpp.OS,\n\t\t\tArch:                      rpp.Arch,\n\t\t}\n\n\t\tif err := client.RegistryProviderPlatforms.Delete(ctx, platformID); err != nil {\n\t\t\tt.Errorf(\"Error destroying registry provider platform! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Registry Provider Version: %s/%s/%s/%s\\nError: %s\", rpp.RegistryProviderVersion.RegistryProvider.Namespace, rpp.RegistryProviderVersion.RegistryProvider.Name, rpp.OS, rpp.Arch, err)\n\t\t}\n\n\t\tif versionCleanup != nil {\n\t\t\tversionCleanup()\n\t\t}\n\t\tif providerCleanup != nil {\n\t\t\tproviderCleanup()\n\t\t}\n\t}\n}\n\nfunc createRegistryProviderVersion(t *testing.T, client *Client, provider *RegistryProvider) (*RegistryProviderVersion, func()) {\n\tvar providerCleanup func()\n\n\tif provider == nil {\n\t\tprovider, providerCleanup = createRegistryProvider(t, client, nil, PrivateRegistry)\n\t}\n\n\tproviderID := RegistryProviderID{\n\t\tOrganizationName: provider.Organization.Name,\n\t\tRegistryName:     provider.RegistryName,\n\t\tNamespace:        provider.Namespace,\n\t\tName:             provider.Name,\n\t}\n\n\tctx := context.Background()\n\n\toptions := RegistryProviderVersionCreateOptions{\n\t\tVersion:   randomSemver(t),\n\t\tKeyID:     randomString(t),\n\t\tProtocols: []string{\"4.0\", \"5.0\", \"6.0\"},\n\t}\n\n\tprvv, err := client.RegistryProviderVersions.Create(ctx, providerID, options)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tprvv.RegistryProvider = provider\n\n\treturn prvv, func() {\n\t\tid := RegistryProviderVersionID{\n\t\t\tVersion:            options.Version,\n\t\t\tRegistryProviderID: providerID,\n\t\t}\n\n\t\tif err := client.RegistryProviderVersions.Delete(ctx, id); err != nil {\n\t\t\tt.Errorf(\"Error destroying registry provider version! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Registry Provider Version: %s/%s/%s\\nError: %s\", prvv.RegistryProvider.Namespace, prvv.RegistryProvider.Name, prvv.Version, err)\n\t\t}\n\n\t\tif providerCleanup != nil {\n\t\t\tproviderCleanup()\n\t\t}\n\t}\n}\n\nfunc createSSHKey(t *testing.T, client *Client, org *Organization) (*SSHKey, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\tkey, err := client.SSHKeys.Create(ctx, org.Name, SSHKeyCreateOptions{\n\t\tName:  String(randomString(t)),\n\t\tValue: String(randomString(t)),\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn key, func() {\n\t\tif err := client.SSHKeys.Delete(ctx, key.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying SSH key! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"SSHKey: %s\\nError: %s\", key.Name, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createStateVersion(t *testing.T, client *Client, serial int64, w *Workspace) (*StateVersion, func()) {\n\tvar wCleanup func()\n\n\tif w == nil {\n\t\tw, wCleanup = createWorkspace(t, client, nil)\n\t}\n\n\tstate, err := os.ReadFile(\"test-fixtures/state-version/terraform.tfstate\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tctx := context.Background()\n\n\t_, err = client.Workspaces.Lock(ctx, w.ID, WorkspaceLockOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_, err := client.Workspaces.Unlock(ctx, w.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}()\n\n\tsv, err := client.StateVersions.Create(ctx, w.ID, StateVersionCreateOptions{\n\t\tMD5:    String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\tSerial: Int64(serial),\n\t\tState:  String(base64.StdEncoding.EncodeToString(state)),\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Download URL may not be immediately available after creation\n\t// Poll until download URL is ready\n\tsv, err = retryPatientlyIf(\n\t\tfunc() (any, error) {\n\t\t\treturn client.StateVersions.Read(ctx, sv.ID)\n\t\t},\n\t\tfunc(nsv *StateVersion) bool {\n\t\t\treturn nsv.DownloadURL == \"\"\n\t\t},\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn sv, func() {\n\t\t// There currently isn't a way to delete a state, so we\n\t\t// can only cleanup by deleting the workspace.\n\t\tif wCleanup != nil {\n\t\t\twCleanup()\n\t\t}\n\t}\n}\n\nfunc createTeam(t *testing.T, client *Client, org *Organization) (*Team, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\ttm, err := client.Teams.Create(ctx, org.Name, TeamCreateOptions{\n\t\tName: String(randomString(t)),\n\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\tManagePolicies:          Bool(true),\n\t\t\tManagePolicyOverrides:   Bool(true),\n\t\t\tDelegatePolicyOverrides: Bool(true),\n\t\t\tManageProviders:         Bool(true),\n\t\t\tManageModules:           Bool(true),\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn tm, func() {\n\t\tif err := client.Teams.Delete(ctx, tm.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying team! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Team: %s\\nError: %s\", tm.Name, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createTeamAccess(t *testing.T, client *Client, tm *Team, w *Workspace, org *Organization) (*TeamAccess, func()) {\n\tvar orgCleanup, tmCleanup, wCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tif tm == nil {\n\t\ttm, tmCleanup = createTeam(t, client, org)\n\t}\n\n\tif w == nil {\n\t\tw, wCleanup = createWorkspace(t, client, org)\n\t}\n\n\tctx := context.Background()\n\tta, err := client.TeamAccess.Add(ctx, TeamAccessAddOptions{\n\t\tAccess:    Access(AccessAdmin),\n\t\tTeam:      tm,\n\t\tWorkspace: w,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn ta, func() {\n\t\tif err := client.TeamAccess.Remove(ctx, ta.ID); err != nil {\n\t\t\tt.Errorf(\"Error removing team access! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"TeamAccess: %s\\nError: %s\", ta.ID, err)\n\t\t}\n\n\t\tif tmCleanup != nil {\n\t\t\ttmCleanup()\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\n\t\tif wCleanup != nil {\n\t\t\twCleanup()\n\t\t}\n\t}\n}\n\nfunc createTeamProjectAccess(t *testing.T, client *Client, tm *Team, p *Project, org *Organization) (*TeamProjectAccess, func()) {\n\tvar orgCleanup, tmCleanup, pCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tif tm == nil {\n\t\ttm, tmCleanup = createTeam(t, client, org)\n\t}\n\n\tif p == nil {\n\t\tp, pCleanup = createProject(t, client, org)\n\t}\n\n\tctx := context.Background()\n\ttpa, err := client.TeamProjectAccess.Add(ctx, TeamProjectAccessAddOptions{\n\t\tAccess:  *ProjectAccess(TeamProjectAccessAdmin),\n\t\tTeam:    tm,\n\t\tProject: p,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn tpa, func() {\n\t\tif err := client.TeamProjectAccess.Remove(ctx, tpa.ID); err != nil {\n\t\t\tt.Errorf(\"Error removing team access! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"TeamAccess: %s\\nError: %s\", tpa.ID, err)\n\t\t}\n\n\t\tif tmCleanup != nil {\n\t\t\ttmCleanup()\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\n\t\tif pCleanup != nil {\n\t\t\tpCleanup()\n\t\t}\n\t}\n}\n\nfunc createTeamToken(t *testing.T, client *Client, tm *Team) (*TeamToken, func()) {\n\tvar tmCleanup func()\n\n\tif tm == nil {\n\t\ttm, tmCleanup = createTeam(t, client, nil)\n\t}\n\n\tctx := context.Background()\n\ttt, err := client.TeamTokens.Create(ctx, tm.ID)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn tt, func() {\n\t\tif err := client.TeamTokens.Delete(ctx, tm.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying team token! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"TeamToken: %s\\nError: %s\", tm.ID, err)\n\t\t}\n\n\t\tif tmCleanup != nil {\n\t\t\ttmCleanup()\n\t\t}\n\t}\n}\n\nfunc createTeamTokenWithOptions(t *testing.T, client *Client, tm *Team, options TeamTokenCreateOptions) (*TeamToken, func()) {\n\tvar tmCleanup func()\n\n\tif tm == nil {\n\t\ttm, tmCleanup = createTeam(t, client, nil)\n\t}\n\n\tctx := context.Background()\n\ttt, err := client.TeamTokens.CreateWithOptions(ctx, tm.ID, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn tt, func() {\n\t\tif err := client.TeamTokens.DeleteByID(ctx, tt.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying team token! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"TeamToken: %s\\nError: %s\", tm.ID, err)\n\t\t}\n\n\t\tif tmCleanup != nil {\n\t\t\ttmCleanup()\n\t\t}\n\t}\n}\n\nfunc createVariable(t *testing.T, client *Client, w *Workspace) (*Variable, func()) {\n\toptions := VariableCreateOptions{\n\t\tKey:         String(randomString(t)),\n\t\tValue:       String(randomString(t)),\n\t\tCategory:    Category(CategoryTerraform),\n\t\tDescription: String(randomString(t)),\n\t}\n\treturn createVariableWithOptions(t, client, w, options)\n}\n\nfunc createVariableWithOptions(t *testing.T, client *Client, w *Workspace, options VariableCreateOptions) (*Variable, func()) {\n\tvar wCleanup func()\n\n\tif w == nil {\n\t\tw, wCleanup = createWorkspace(t, client, nil)\n\t}\n\n\tif options.Key == nil {\n\t\toptions.Key = String(randomString(t))\n\t}\n\n\tif options.Value == nil {\n\t\toptions.Value = String(randomString(t))\n\t}\n\n\tif options.Description == nil {\n\t\toptions.Description = String(randomString(t))\n\t}\n\n\tif options.Category == nil {\n\t\toptions.Category = Category(CategoryTerraform)\n\t}\n\n\tif options.HCL == nil {\n\t\toptions.HCL = Bool(false)\n\t}\n\n\tif options.Sensitive == nil {\n\t\toptions.Sensitive = Bool(false)\n\t}\n\n\tctx := context.Background()\n\tv, err := client.Variables.Create(ctx, w.ID, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn v, func() {\n\t\tif err := client.Variables.Delete(ctx, w.ID, v.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying variable! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Variable: %s\\nError: %s\", v.Key, err)\n\t\t}\n\n\t\tif wCleanup != nil {\n\t\t\twCleanup()\n\t\t}\n\t}\n}\n\nfunc createWorkspace(t *testing.T, client *Client, org *Organization) (*Workspace, func()) {\n\treturn createWorkspaceWithOptions(t, client, org, WorkspaceCreateOptions{\n\t\tName: String(randomString(t)),\n\t})\n}\n\nfunc createWorkspaceWithOptions(t *testing.T, client *Client, org *Organization, options WorkspaceCreateOptions) (*Workspace, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\tw, err := client.Workspaces.Create(ctx, org.Name, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn w, func() {\n\t\tif err := client.Workspaces.DeleteByID(ctx, w.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying workspace! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Workspace: %s\\nError: %s\", w.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\n// queueAllRuns: Whether runs should be queued immediately after workspace creation. When set to\n// false, runs triggered by a VCS change will not be queued until at least one run is manually\n// queued. If set to true, a run will be automatically started after the configuration is ingressed\n// from VCS.\nfunc createWorkspaceWithVCS(t *testing.T, client *Client, org *Organization, options WorkspaceCreateOptions) (*Workspace, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\toc, ocCleanup := createOAuthToken(t, client, org)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_POLICY_SET_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Fatal(\"Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test!\")\n\t}\n\n\tif options.Name == nil {\n\t\toptions.Name = String(randomString(t))\n\t}\n\n\tif options.VCSRepo == nil {\n\t\toptions.VCSRepo = &VCSRepoOptions{}\n\t}\n\n\toptions.VCSRepo.Identifier = String(githubIdentifier)\n\toptions.VCSRepo.OAuthTokenID = String(oc.ID)\n\n\tctx := context.Background()\n\tw, err := client.Workspaces.Create(ctx, org.Name, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn w, func() {\n\t\tif err := client.Workspaces.Delete(ctx, org.Name, w.Name); err != nil {\n\t\t\tt.Errorf(\"Error destroying workspace! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Workspace: %s\\nError: %s\", w.Name, err)\n\t\t}\n\n\t\tif ocCleanup != nil {\n\t\t\tocCleanup()\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\n// This function is added to test setting up workspace's VCS connection via Github App Installation in place of\n// Oauth token. For now the value of GHAInstallationID has to manually set to the correct value by the user.\nfunc createWorkspaceWithGithubApp(t *testing.T, client *Client, org *Organization, options WorkspaceCreateOptions) (*Workspace, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tgHAInstallationID := os.Getenv(\"GITHUB_APP_INSTALLATION_ID\")\n\n\tif gHAInstallationID == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_APP_INSTALLATION_ID before running this test!\")\n\t}\n\n\toptions.VCSRepo.GHAInstallationID = String(gHAInstallationID)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_POLICY_SET_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Fatal(\"Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test!\")\n\t}\n\n\tif options.Name == nil {\n\t\toptions.Name = String(randomString(t))\n\t}\n\n\tif options.VCSRepo == nil {\n\t\toptions.VCSRepo = &VCSRepoOptions{}\n\t}\n\n\toptions.VCSRepo.Identifier = String(githubIdentifier)\n\n\tctx := context.Background()\n\tw, err := client.Workspaces.Create(ctx, org.Name, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn w, func() {\n\t\tif err := client.Workspaces.Delete(ctx, org.Name, w.Name); err != nil {\n\t\t\tt.Errorf(\"Error destroying workspace! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Workspace: %s\\nError: %s\", w.Name, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createWorkspaceRunTask(t *testing.T, client *Client, workspace *Workspace, runTask *RunTask) (*WorkspaceRunTask, func()) {\n\tvar organization *Organization\n\tvar runTaskCleanup func()\n\tvar workspaceCleanup func()\n\tvar orgCleanup func()\n\n\tif workspace == nil {\n\t\torganization, orgCleanup = createOrganization(t, client)\n\t\tworkspace, workspaceCleanup = createWorkspace(t, client, organization)\n\t}\n\n\tif runTask == nil {\n\t\trunTask, runTaskCleanup = createRunTask(t, client, organization)\n\t}\n\n\tctx := context.Background()\n\twr, err := client.WorkspaceRunTasks.Create(ctx, workspace.ID, WorkspaceRunTaskCreateOptions{\n\t\tEnforcementLevel: Advisory,\n\t\tRunTask:          runTask,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn wr, func() {\n\t\tif err := client.WorkspaceRunTasks.Delete(ctx, workspace.ID, wr.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying workspace run task!\\n\"+\n\t\t\t\t\"Workspace: %s\\n\"+\n\t\t\t\t\"Workspace Run Task: %s\\n\"+\n\t\t\t\t\"Error: %s\", workspace.ID, wr.ID, err)\n\t\t}\n\n\t\tif runTaskCleanup != nil {\n\t\t\trunTaskCleanup()\n\t\t}\n\n\t\tif workspaceCleanup != nil {\n\t\t\tworkspaceCleanup()\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createVariableSet(t *testing.T, client *Client, org *Organization, options VariableSetCreateOptions) (*VariableSet, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tif options.Name == nil {\n\t\toptions.Name = String(randomString(t))\n\t}\n\n\tif options.Global == nil {\n\t\toptions.Global = Bool(false)\n\t}\n\n\tctx := context.Background()\n\tvs, err := client.VariableSets.Create(ctx, org.Name, &options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn vs, func() {\n\t\tif err := client.VariableSets.Delete(ctx, vs.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying variable set! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"VariableSet: %s\\nError: %s\", vs.Name, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc applyVariableSetToWorkspace(t *testing.T, client *Client, vsID, wsID string) {\n\tif vsID == \"\" {\n\t\tt.Fatal(\"variable set ID must not be empty\")\n\t}\n\n\tif wsID == \"\" {\n\t\tt.Fatal(\"workspace ID must not be empty\")\n\t}\n\n\topts := &VariableSetApplyToWorkspacesOptions{}\n\topts.Workspaces = append(opts.Workspaces, &Workspace{ID: wsID})\n\n\tctx := context.Background()\n\tif err := client.VariableSets.ApplyToWorkspaces(ctx, vsID, opts); err != nil {\n\t\tt.Fatalf(\"Error applying variable set %s to workspace %s: %v\", vsID, wsID, err)\n\t}\n\n\tt.Cleanup(func() {\n\t\tremoveOpts := &VariableSetRemoveFromWorkspacesOptions{}\n\t\tremoveOpts.Workspaces = append(removeOpts.Workspaces, &Workspace{ID: wsID})\n\t\tif err := client.VariableSets.RemoveFromWorkspaces(ctx, vsID, removeOpts); err != nil {\n\t\t\tt.Errorf(\"Error removing variable set from workspace! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"VariableSet ID: %s\\nError: %s\", vsID, err)\n\t\t}\n\t})\n}\n\nfunc applyVariableSetToProject(t *testing.T, client *Client, vsID, prjID string) {\n\tt.Helper()\n\tif vsID == \"\" {\n\t\tt.Fatal(\"variable set ID must not be empty\")\n\t}\n\n\tif prjID == \"\" {\n\t\tt.Fatal(\"project ID must not be empty\")\n\t}\n\n\topts := VariableSetApplyToProjectsOptions{}\n\topts.Projects = append(opts.Projects, &Project{ID: prjID})\n\n\tctx := context.Background()\n\tif err := client.VariableSets.ApplyToProjects(ctx, vsID, opts); err != nil {\n\t\tt.Fatalf(\"Error applying variable set %s to project %s: %v\", vsID, prjID, err)\n\t}\n\n\tt.Cleanup(func() {\n\t\tremoveOpts := VariableSetRemoveFromProjectsOptions{}\n\t\tremoveOpts.Projects = append(removeOpts.Projects, &Project{ID: prjID})\n\t\tif err := client.VariableSets.RemoveFromProjects(ctx, vsID, removeOpts); err != nil {\n\t\t\tt.Errorf(\"Error removing variable set from project! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"VariableSet ID: %s\\nError: %s\", vsID, err)\n\t\t}\n\t})\n}\n\nfunc createVariableSetVariable(t *testing.T, client *Client, vs *VariableSet, options VariableSetVariableCreateOptions) (*VariableSetVariable, func()) {\n\tvar vsCleanup func()\n\n\tif vs == nil {\n\t\tvs, vsCleanup = createVariableSet(t, client, nil, VariableSetCreateOptions{})\n\t}\n\n\tif options.Key == nil {\n\t\toptions.Key = String(randomString(t))\n\t}\n\n\tif options.Value == nil {\n\t\toptions.Value = String(randomString(t))\n\t}\n\n\tif options.Description == nil {\n\t\toptions.Description = String(\"\")\n\t}\n\n\tif options.Category == nil {\n\t\toptions.Category = Category(CategoryTerraform)\n\t}\n\n\tif options.HCL == nil {\n\t\toptions.HCL = Bool(false)\n\t}\n\n\tif options.Sensitive == nil {\n\t\toptions.Sensitive = Bool(false)\n\t}\n\n\tctx := context.Background()\n\tv, err := client.VariableSetVariables.Create(ctx, vs.ID, &options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn v, func() {\n\t\tif err := client.VariableSetVariables.Delete(ctx, vs.ID, v.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying variable! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Variable: %s\\nError: %s\", v.Key, err)\n\t\t}\n\n\t\tif vsCleanup != nil {\n\t\t\tvsCleanup()\n\t\t}\n\t}\n}\n\n// Attempts to upgrade an organization to the business plan. Requires a user token with admin access.\n// DEPRECATED : Please use the newSubscriptionUpdater instead.\nfunc upgradeOrganizationSubscription(t *testing.T, _ *Client, organization *Organization) {\n\tnewSubscriptionUpdater(organization).WithBusinessPlan().Update(t)\n}\n\nfunc createProject(t *testing.T, client *Client, org *Organization) (*Project, func()) {\n\treturn createProjectWithOptions(t, client, org, ProjectCreateOptions{\n\t\tName: randomStringWithoutSpecialChar(t),\n\t})\n}\n\nfunc createProjectWithOptions(t *testing.T, client *Client, org *Organization, options ProjectCreateOptions) (*Project, func()) {\n\tvar orgCleanup func()\n\n\tif org == nil {\n\t\torg, orgCleanup = createOrganization(t, client)\n\t}\n\n\tctx := context.Background()\n\tp, err := client.Projects.Create(ctx, org.Name, options)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn p, func() {\n\t\tif err := client.Projects.Delete(ctx, p.ID); err != nil {\n\t\t\tt.Logf(\"Error destroying project! WARNING: Dangling resources \"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Project ID: %s\\nError: %s\", p.ID, err)\n\t\t}\n\n\t\tif orgCleanup != nil {\n\t\t\torgCleanup()\n\t\t}\n\t}\n}\n\nfunc createTarGzipArchive(t *testing.T, files []string, outputPath string) {\n\tif len(files) == 0 {\n\t\tt.Fatal(\"files to archive are empty\")\n\t}\n\n\tout, err := os.Create(outputPath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer out.Close()\n\n\tgw := gzip.NewWriter(out)\n\tdefer gw.Close()\n\n\ttw := tar.NewWriter(gw)\n\tdefer tw.Close()\n\n\tfor _, filename := range files {\n\t\tfunc() {\n\t\t\tfile, err := os.Open(filename)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer file.Close()\n\n\t\t\tinfo, err := file.Stat()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\theader, err := tar.FileInfoHeader(info, info.Name())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\theader.Name = filename\n\t\t\terr = tw.WriteHeader(header)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t_, err = io.Copy(tw, file)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}()\n\t}\n\n\tt.Cleanup(func() {\n\t\terr := os.Remove(outputPath)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"failed to delete archive: %w\", err)\n\t\t}\n\t})\n}\n\nfunc waitForSVOutputs(t *testing.T, client *Client, svID string) {\n\tt.Helper()\n\n\t_, err := retryPatiently(func() (interface{}, error) {\n\t\toutputs, err := client.StateVersions.ListOutputs(context.Background(), svID, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif len(outputs.Items) == 0 {\n\t\t\treturn nil, errors.New(\"no state version outputs found\")\n\t\t}\n\n\t\treturn outputs, nil\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc waitForRunLock(t *testing.T, client *Client, workspaceID string) {\n\tt.Helper()\n\t_, err := retry(func() (interface{}, error) {\n\t\tws, err := client.Workspaces.ReadByID(context.Background(), workspaceID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif !ws.Locked {\n\t\t\treturn nil, errors.New(\"workspace is not locked by run\")\n\t\t}\n\n\t\treturn ws, nil\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc retryTimes(maxRetries, secondsBetween int, f retryableFn) (interface{}, error) {\n\ttick := time.NewTicker(time.Duration(secondsBetween) * time.Second)\n\tretries := 0\n\n\tdefer tick.Stop()\n\n\tfor { //nolint\n\t\tselect {\n\t\tcase <-tick.C:\n\t\t\tres, err := f()\n\t\t\tif err == nil {\n\t\t\t\treturn res, nil\n\t\t\t}\n\n\t\t\tif retries >= maxRetries {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tretries += 1\n\t\t}\n\t}\n}\n\nfunc retryTimesIf[T any](maxRetries, secondsBetween int, f retryableFn, c func(T) bool) (T, error) {\n\ttick := time.NewTicker(time.Duration(secondsBetween) * time.Second)\n\tretries := 0\n\n\tdefer tick.Stop()\n\n\tvar zero T\n\tfor {\n\t\t<-tick.C\n\n\t\tres, err := f()\n\n\t\tobj, ok := res.(T)\n\t\tif !ok {\n\t\t\treturn zero, fmt.Errorf(\"type assertion failed in retryTimesIf\")\n\t\t}\n\n\t\tif err == nil && !c(obj) {\n\t\t\treturn obj, nil\n\t\t}\n\n\t\tif retries >= maxRetries {\n\t\t\treturn zero, err\n\t\t}\n\n\t\tretries += 1\n\t}\n}\n\nfunc retryPatiently(f retryableFn) (interface{}, error) {\n\treturn retryTimes(39, 3, f) // 40 attempts over 120 seconds\n}\n\nfunc retry(f retryableFn) (interface{}, error) { //nolint\n\treturn retryTimes(9, 3, f) // 10 attempts over 30 seconds\n}\n\nfunc retryPatientlyIf[T any](f retryableFn, c func(T) bool) (T, error) {\n\treturn retryTimesIf[T](39, 3, f, c) // 40 attempts over 120 seconds\n}\n\nfunc retryIf[T any](f retryableFn, c func(T) bool) (T, error) {\n\treturn retryTimesIf[T](9, 3, f, c) // 10 attempts over 30 seconds\n}\n\nfunc genSha(t *testing.T) string {\n\tt.Helper()\n\th := hmac.New(sha256.New, []byte(\"secret\"))\n\t_, err := h.Write([]byte(\"data\"))\n\tif err != nil {\n\t\tt.Fatalf(\"error writing hmac: %s\", err)\n\t}\n\tsha := hex.EncodeToString(h.Sum(nil))\n\treturn sha\n}\n\n// genSafeRandomTerraformVersion returns a random version number of the form\n// `1.0.<RANDOM>`, which HCP Terraform won't ever select as the latest available\n// Terraform. (At the time of writing, a fresh HCP Terraform instance will include\n// official Terraforms 1.2 and higher.) This is necessary because newly created\n// workspaces default to the latest available version, and there's nothing\n// preventing unrelated processes from creating workspaces during these tests.\nfunc genSafeRandomTerraformVersion() string {\n\trInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()\n\t// Avoid colliding with an official Terraform version. Highest 1.0 was\n\t// 1.0.11, so add a little padding and call it good.\n\tfor rInt < 20 {\n\t\trInt = rand.New(rand.NewSource(time.Now().UnixNano())).Int()\n\t}\n\treturn fmt.Sprintf(\"1.0.%d\", rInt)\n}\n\n// createAdminSentinelVersion returns a random version number of the form\n// `0.0.<RANDOM>`\nfunc createAdminSentinelVersion() string {\n\trInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()\n\treturn fmt.Sprintf(\"0.0.%d\", rInt)\n}\n\n// createAdminOPAVersion returns a random OPA version number of the form\n// `0.0.<RANDOM>`\nfunc createAdminOPAVersion() string {\n\trInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()\n\treturn fmt.Sprintf(\"0.0.%d\", rInt)\n}\n\nfunc randomString(t *testing.T) string {\n\tv, err := uuid.GenerateUUID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn v\n}\n\nfunc randomStringWithoutSpecialChar(t *testing.T) string {\n\tv, err := uuid.GenerateUUID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tuuidWithoutHyphens := strings.ReplaceAll(v, \"-\", \"\")\n\treturn uuidWithoutHyphens\n}\n\nfunc randomKeyValue(t *testing.T) string {\n\tv, err := uuid.GenerateUUID()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tuuidWithoutHyphens := strings.ReplaceAll(v, \"-\", \"\")\n\treturn \"t\" + uuidWithoutHyphens\n}\n\nfunc containsProject(pl []*Project, str string) bool {\n\tfor _, p := range pl {\n\t\tif p.Name == str {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc randomSemver(t *testing.T) string {\n\tt.Helper()\n\treturn fmt.Sprintf(\"%d.%d.%d\", rand.Intn(99)+3, rand.Intn(99)+1, rand.Intn(99)+1)\n}\n\n// skips a test if the environment is for HCP Terraform.\nfunc skipUnlessEnterprise(t *testing.T) {\n\tt.Helper()\n\tif !enterpriseEnabled() {\n\t\tt.Skip(\"Skipping test related to HCP Terraform. Set ENABLE_TFE=1 to run.\")\n\t}\n}\n\n// skips a test if the environment is for Terraform Enterprise\nfunc skipIfEnterprise(t *testing.T) {\n\tt.Helper()\n\tif enterpriseEnabled() {\n\t\tt.Skip(\"Skipping test related to Terraform Enterprise. Set ENABLE_TFE=0 to run.\")\n\t}\n}\n\nfunc skipHYOKIntegrationTests(t *testing.T) {\n\tt.Helper()\n\tif !hyokIntegrationTestsEnabled() {\n\t\tt.Skip(\"Skipping test related to HYOK features. Set ENABLE_HYOK_INTEGRATION_TESTS=1 to run.\")\n\t}\n}\n\n// skips a test if the underlying beta feature is not available.\n// **Note: ENABLE_BETA is always disabled in CI, so ensure you:\n//\n//  1. Run tests locally and paste the test output in the resulting pull request\n//  2. Remove the beta requirements of your feature from go-tfe once the feature is generally available.\n//\n// See CONTRIBUTING.md for details\nfunc skipUnlessBeta(t *testing.T) {\n\tt.Helper()\n\tif !betaFeaturesEnabled() {\n\t\tt.Skip(\"Skipping test related to a HCP Terraform beta feature. Set ENABLE_BETA=1 to run.\")\n\t}\n}\n\n// skips a test if the architecture is not linux_amd64\nfunc skipUnlessLinuxAMD64(t *testing.T) {\n\tt.Helper()\n\tif !linuxAmd64() {\n\t\tt.Skip(\"Skipping test if architecture is not linux_amd64\")\n\t}\n}\n\n// Temporarily skip a test that may be experiencing API errors. This method\n// purposefully errors after the set date to remind contributors to remove this check\n// and verify that the API errors are no longer occurring.\nfunc skipUnlessAfterDate(t *testing.T, d time.Time) {\n\ttoday := time.Now()\n\tif today.After(d) {\n\t\tt.Fatalf(\"This test was temporarily skipped and has now expired. Remove this check to run this test.\")\n\t} else {\n\t\tt.Skipf(\"Temporarily skipping test due to external issues: %s\", t.Name())\n\t}\n}\n\nfunc linuxAmd64() bool {\n\treturn runtime.GOOS == \"linux\" && runtime.GOARCH == \"amd64\"\n}\n\n// Checks to see if ENABLE_TFE is set to 1, thereby enabling enterprise tests.\nfunc enterpriseEnabled() bool {\n\treturn os.Getenv(\"ENABLE_TFE\") == \"1\"\n}\n\n// Checks to see if ENABLE_BETA is set to 1, thereby enabling tests for beta features.\nfunc betaFeaturesEnabled() bool {\n\treturn os.Getenv(\"ENABLE_BETA\") == \"1\"\n}\n\n// Checks to see if HYOK_INTEGRATION_TESTS is set to 1, thereby enabling tests for HYOK features.\nfunc hyokIntegrationTestsEnabled() bool {\n\treturn os.Getenv(\"ENABLE_HYOK_INTEGRATION_TESTS\") == \"1\"\n}\n\nfunc runDependentTestNameValidator(t *testing.T) {\n\tt.Helper()\n\n\ttestName := t.Name()\n\ttestNameParts := strings.Split(testName, \"/\")\n\n\tif len(testNameParts) == 0 {\n\t\treturn\n\t}\n\n\trootTestName := testNameParts[0]\n\tif rootTestName != \"\" && !strings.HasSuffix(rootTestName, \"_RunDependent\") {\n\t\tt.Fatalf(\"Tests that start runs must have names ending with '_RunDependent', but got: %s\", rootTestName)\n\t}\n}\n\nfunc testHyokOrganization(t *testing.T, client *Client) *Organization {\n\tctx := context.Background()\n\n\t// replace the environment variable with a valid organization name that has desired test configurations\n\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\tif hyokOrganizationName == \"\" {\n\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t}\n\n\torgTest, err := client.Organizations.Read(ctx, hyokOrganizationName)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn orgTest\n}\n\n// isEmpty gets whether the specified object is considered empty or not.\nfunc isEmpty(object interface{}) bool {\n\t// get nil case out of the way\n\tif object == nil {\n\t\treturn true\n\t}\n\n\tobjValue := reflect.ValueOf(object)\n\n\tswitch objValue.Kind() {\n\t// collection types are empty when they have no element\n\tcase reflect.Chan, reflect.Map, reflect.Slice:\n\t\treturn objValue.Len() == 0\n\t// pointers are empty if nil or if the value they point to is empty\n\tcase reflect.Ptr:\n\t\tif objValue.IsNil() {\n\t\t\treturn true\n\t\t}\n\t\tderef := objValue.Elem().Interface()\n\t\treturn isEmpty(deref)\n\t// for all other types, compare against the zero value\n\t// array types are empty when they match their zero-initialized state\n\tdefault:\n\t\tzero := reflect.Zero(objValue.Type())\n\t\treturn reflect.DeepEqual(object, zero.Interface())\n\t}\n}\n\n// requireExactlyOneNotEmpty accepts any number of values and calls t.Fatal if\n// less or more than one is empty.\nfunc requireExactlyOneNotEmpty(t *testing.T, v ...any) {\n\tif len(v) == 0 {\n\t\tt.Fatal(\"Expected some values for requireExactlyOneNotEmpty, but received none\")\n\t}\n\n\tempty := 0\n\tfor _, value := range v {\n\t\tif isEmpty(value) {\n\t\t\tempty += 1\n\t\t}\n\t}\n\n\tif empty != len(v)-1 {\n\t\tt.Fatalf(\"Expected exactly one value to not be empty, but found %d empty values\", empty)\n\t}\n}\n\nfunc runTaskCallbackMockServer(t *testing.T) *httptest.Server {\n\treturn httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Method == http.MethodGet {\n\t\t\treturn\n\t\t}\n\t\tif r.Header.Get(\"Accept\") != ContentTypeJSONAPI {\n\t\t\tt.Fatalf(\"unexpected accept header: %q\", r.Header.Get(\"Accept\"))\n\t\t}\n\t\tif r.Header.Get(\"Authorization\") != fmt.Sprintf(\"Bearer %s\", testTaskResultCallbackToken) {\n\t\t\tt.Fatalf(\"unexpected authorization header: %q\", r.Header.Get(\"Authorization\"))\n\t\t}\n\t\tif r.Header.Get(\"Authorization\") == fmt.Sprintf(\"Bearer %s\", testInitialClientToken) {\n\t\t\tt.Fatalf(\"authorization header is still the initial one: %q\", r.Header.Get(\"Authorization\"))\n\t\t}\n\t\tif r.Header.Get(\"User-Agent\") != \"go-tfe\" {\n\t\t\tt.Fatalf(\"unexpected user agent header: %q\", r.Header.Get(\"User-Agent\"))\n\t\t}\n\t}))\n}\n\nfunc enableSAML(ctx context.Context, t *testing.T, client *Client, enable bool) {\n\tt.Helper()\n\tvar options AdminSAMLSettingsUpdateOptions\n\tif enable {\n\t\toptions = AdminSAMLSettingsUpdateOptions{\n\t\t\tEnabled:        Bool(true),\n\t\t\tSLOEndpointURL: String(\"https://example.com/slo\"),\n\t\t\tSSOEndpointURL: String(\"https://example.com/sso\"),\n\t\t\tCertificate:    String(\"testCert\"),\n\t\t\tIDPCert:        String(\"testCert\"),\n\t\t}\n\t} else {\n\t\toptions = AdminSAMLSettingsUpdateOptions{\n\t\t\tEnabled: Bool(false),\n\t\t}\n\t}\n\t_, err := client.Admin.Settings.SAML.Update(ctx, options)\n\trequire.NoError(t, err)\n}\n\nfunc enableSCIM(ctx context.Context, t *testing.T, client *Client, enable bool) {\n\tt.Helper()\n\n\tif enable {\n\t\tenableSAML(ctx, t, client, true)\n\n\t\terr := setSAMLProviderType(ctx, t, client, true)\n\t\trequire.NoError(t, err, \"error setting SAML provider type\")\n\n\t\t_, err = client.Admin.Settings.SCIM.Update(ctx, AdminSCIMSettingUpdateOptions{\n\t\t\tEnabled: Bool(true),\n\t\t})\n\t\trequire.NoError(t, err, \"error enabling SCIM\")\n\t} else {\n\t\terr := client.Admin.Settings.SCIM.Delete(ctx)\n\t\trequire.NoError(t, err, \"error disabling SCIM\")\n\n\t\terr = setSAMLProviderType(ctx, t, client, false)\n\t\trequire.NoError(t, err, \"error clearing SAML provider type\")\n\n\t\tenableSAML(ctx, t, client, false)\n\t}\n}\n\nfunc setSAMLProviderType(ctx context.Context, t *testing.T, client *Client, setProvider bool) error {\n\tt.Helper()\n\tvar provider SAMLProviderType\n\tif setProvider {\n\t\tprovider = SAMLProviderTypeGeneric\n\t} else {\n\t\tprovider = SAMLProviderTypeUnknown\n\t}\n\n\t_, err := client.Admin.Settings.SAML.Update(ctx, AdminSAMLSettingsUpdateOptions{ProviderType: &provider})\n\treturn err\n}\n\nfunc createSCIMGroup(ctx context.Context, t *testing.T, client *Client, groupName, scimToken string) string {\n\tt.Helper()\n\n\tpayload := struct {\n\t\tDisplayName string   `json:\"displayName\"`\n\t\tSchemas     []string `json:\"schemas\"`\n\t}{\n\t\tDisplayName: groupName,\n\t\tSchemas:     []string{\"urn:ietf:params:scim:schemas:core:2.0:Group\"},\n\t}\n\n\tbody, err := serializeRequestBody(&payload)\n\trequire.NoError(t, err)\n\n\tu := client.BaseURL()\n\tu.Path = \"/scim/v2/Groups\"\n\n\treq, err := retryablehttp.NewRequest(\"POST\", u.String(), body)\n\trequire.NoError(t, err)\n\treq.Header = client.headers.Clone()\n\treq.Header.Set(\"Authorization\", \"Bearer \"+scimToken)\n\treq.Header.Set(\"Accept\", \"application/scim+json\")\n\treq.Header.Set(\"Content-Type\", \"application/scim+json; charset=utf-8\")\n\n\tvar res struct {\n\t\tID string `json:\"id\"`\n\t}\n\terr = (&ClientRequest{\n\t\tretryableRequest: req,\n\t\thttp:             client.http,\n\t\tlimiter:          client.limiter,\n\t\tHeader:           req.Header,\n\t}).DoJSON(ctx, &res)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, res.ID)\n\n\treturn res.ID\n}\n\nfunc deleteSCIMGroup(ctx context.Context, t *testing.T, client *Client, groupID, scimToken string) {\n\tt.Helper()\n\n\tu := client.BaseURL()\n\tu.Path = \"/scim/v2/Groups/\" + url.PathEscape(groupID)\n\n\treq, err := retryablehttp.NewRequest(\"DELETE\", u.String(), nil)\n\trequire.NoError(t, err)\n\treq.Header = client.headers.Clone()\n\treq.Header.Set(\"Authorization\", \"Bearer \"+scimToken)\n\n\terr = (&ClientRequest{\n\t\tretryableRequest: req,\n\t\thttp:             client.http,\n\t\tlimiter:          client.limiter,\n\t\tHeader:           req.Header,\n\t}).Do(ctx, nil)\n\trequire.NoError(t, err)\n}\n\n// Useless key but enough to pass validation in the API\nconst testGpgArmor string = `\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGKnWEYBEACsTJ9HEUrXBaBvQvXZAXEIMWloG96MVAdCj547jJviSS4TqMIQ\nEST2pzDq7lEpqL+JkW3ptyLEAeQs6gJJeuhODGm2EcxjJ9/JM4ZH+p9zq2wBeXVe\n0XJcP3HD8/7MesjMyGSsoX7tR7TcIhs5Y7zS+/L1xnoReYUsBgC6QdqjQwkuntaq\n2y6yxdYG4gVlxb4yA0Ga6Qfy0VGIKjbCdPqCRyJ76YHE3t+Skq9oDCOV3VSiwKsU\nV/ivf/MVZ1GyE03anW0+poVK38Ekogsd2+34uEjusbuoJGmHzh/20IDS8VnxQHIY\nqdVwcZrW+a3O6nexL4dJJGMfXMbCdS87FxpSnC1FDGMSJ2c5cxlMuKuDboTpbRy5\nDd80p6voJQcLcpr0hKYIwwDGJYE336KMFqf/apCc6HbCFfN8kCYg3K7+4yganRWu\nh/9qIhP0QaYOYEQl4RdjJTSyJSP3srAJ3F5OmrAhRXlHlLo1p00zxFxG7ZcJER6l\n+uRubtL9WN2kgGbr9NDJbz/HeOTjJhCASdQuzstcL8RrFMDftE/P2K8LnkxUNIbT\ndhZtwvkhnyIwOZIHwsQddeJboeHD445SlHJ+4vFsPKRTuNu5u9GhVSyZhoHmdeH0\nFheD8p43+BKZ7KmD4xd+zfCQE1xO2cO9ZrCNV2hs9UVFbgZfjokqWkuHJQARAQAB\ntBNmb28gPGFkbWluQHRmZS5jb20+iQJRBBMBCAA7FiEE/2esSrAATXzEQSanE9/s\nyjtYzkoFAmKnWEYCGwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQE9/s\nyjtYzkq01g/9EgnW0NBD4DdtQSHg5jya0lx5iNHLK+umwL2x7abcSQ9iTIylhbHP\n+he6jS/p4yzK7Gf7S+W3D9EZ58KrTMhu85iLr0uZ947pEbC0kDlQGkIfiK0CAyq2\nIDj1RFgmeM0E2LkPOYCM+JPeBC9nZduFMYY9eFhCZXJ3ua1DP37ZBdZbjuImbiQ5\nabt75a89NbQI3KRaACzqEjFpRYuoxbh8RznkTFf57AFzt4yMWy+4l47GSXTE8boS\n1P7ZOfvJPuh2RRN9sSe0eTPCYnnSxPPo0LvgqSnLSk9yc65nkPZmlSXVdswV5Le+\n7LlKG+rTwXljfGwLmj0VNn2gGCKe5IHs8FKt3parSiQOu4MXHCHshSQDEvXyIugJ\ni2V2pcw4Hi6f2Znh3YYJamL6fDwCpDcTOCxZbvFi4OuBzbWcDLP1k52k3ZyYce92\n1CK84HWtoRseNlVt1rieClPZH5T4b0HMPBWKK39/r+RABJDAfdGtn2ulKXK2JugH\nAYXlhY9xh9+r1O7tsqExGkEYnp7nI0ArauJhIUWZybpGpPYP99kK4F64E4DRu1si\n/3eeYoqKY1jAHoebRzn3XcRg5kro/lJYQQIhT4fHt5sAc/e8gDdaQaDPIftsmu7K\nw4e6pMyztiMfRw7w0ZSjGlPsl0NiXA3nuG966gx4Bnx/ddJIHrghAi25Ag0EYqdY\nRgEQAOGONFP+z45+9gvnT1yd9sJLqxYhtj5QRxKkXkLARPd0Yjdyff/lVd1YPtZ7\nslLuEGlBDKdB6aIeu3b1C95Ie3qbTIwIp6ZYKGqUEwGW/0sPtBqqXanVrQkrY4ho\nlqejgPraFgF6sDGrSxG7b8W985NJwKcm8Lx1/x4ZwvpUrQlCL4UajJcECmjVqU/e\nofjWZFZl7eR2oYh2BBzvA8mwkVKXs6kTGWLkK7VDeR2lCRl2fk4+5DydbOMIZXxT\njmYR8iu2Mr+gt//VmvvBjlFMI05kwD9iG3SRYBwpYEXETKCE12KKqcbhP/bwahIB\nbcsaQkoky9jgtp7tizduPOkjkGhT9kF8L1O0VGxek40L7+QIDEnVHMAH5hSLmgau\nvJF+Bd0W/TRZbmAJXoWPreftVTmWH7xH4N7v+3dvWziIJPt+N/1HHeZXBojJJAVk\n6C+t1KpsSwGzzOjdsQVCklT7D4PmWtzz6FAjImPSbk5LbiVWis/lH+SEVZS4sG7j\npR3vRjUZTjCi/8CmHTjiWXL7g9kkt//a5Av3iArQq0pv0QNPG/uPeN2QTnkz5DAo\nkM/qUx/G59i8AfEH2myh9oPCOzb3yFOsK9G/2Sy05cfdLozddHwt+hJVPx1Od9Nr\nHAJMQspr9AaZPB9FnAa0Bv/RNEGJv6LJwzVWJkezL2wQAZdlABEBAAGJAjYEGAEI\nACAWIQT/Z6xKsABNfMRBJqcT3+zKO1jOSgUCYqdYRgIbDAAKCRAT3+zKO1jOSq9E\nD/4hlNaCwY/etk7ZvMe4pupQATzrZF58d2qjx4niMd3CvCWmbrWMmoNxBjECXc8H\nkp+0NURFFc/wiCn/Q6dhrMxKVCpsWpHA1Doi/vtzQtM081Ib6uIX6L6liyUexW1l\ntvJwPurqJJVBW3ikOjICCnv70tp2zaS47uQjyFGTnzglIU961EXCWdNjH1vm8bFJ\nBxXN87gHXhUUw8GZ3d2V75TAJIEqRVV+eI4flXcJ4Ld+Zbt2EiMwtQ05XCc8bgsc\nQzZFizw936bC5Py7Iu6aEaShFlZlz8LgYcId32UYh5PG1xGNZv0C9Z/PJECx5zcx\nRJszDpm3erpmdkkJf9UBuhjjTdQ9gheFjZRDi/rVJ0JPVxD7HTzEAWd5MqFXqh0V\nj2xG1FhtfxSaMf9rsJjtwewLPyZylSuz2erz1j80Hx3Q6eSIDsNjnDTtfh9Z8gXz\ngvF7mSC0lZu/RvDSRyHfCw4zCQ04HieIvq3hZLy+QS11ykJTSKAePKk77EmwtoLd\nJe9n9FCKhLknUp1/dsu0lsznvttOLwYy6xFP4JNPgiq6iYlVHs417oib67DrGlsI\n3Ki44OESW/vL3WAC091TOF4OYgGw+TMauB8SxZo0PLXrIwKeBsQEB4tf6bX66OvJ\nUFpas2r53xTaraRDpu6+u66hLY+/XV9Uf5YzETuPQnX/nw==\n=bBSS\n-----END PGP PUBLIC KEY BLOCK-----\n`\n"
  },
  {
    "path": "hyok_configuration.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// HYOKConfigurations describes all the HYOK configuration related methods that the HCP Terraform API supports.\n// HCP Terraform API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/configurations\ntype HYOKConfigurations interface {\n\tList(ctx context.Context, organization string, options *HYOKConfigurationsListOptions) (*HYOKConfigurationsList, error)\n\n\tCreate(ctx context.Context, organization string, options HYOKConfigurationsCreateOptions) (*HYOKConfiguration, error)\n\n\tRead(ctx context.Context, hyokID string, options *HYOKConfigurationsReadOptions) (*HYOKConfiguration, error)\n\n\tUpdate(ctx context.Context, hyokID string, options HYOKConfigurationsUpdateOptions) (*HYOKConfiguration, error)\n\n\tDelete(ctx context.Context, hyokID string) error\n\n\t// Test checks the HYOK configuration and returns success if the configuration is valid.\n\t// It returns an error along with the error message if any issues are found.\n\tTest(ctx context.Context, hyokID string) error\n\n\tRevoke(ctx context.Context, hyokID string) error\n}\n\ntype hyokConfigurations struct {\n\tclient *Client\n}\n\nvar _ HYOKConfigurations = &hyokConfigurations{}\n\ntype HYOKConfigurationStatus string\n\nconst (\n\tHYOKConfigurationUntested   HYOKConfigurationStatus = \"untested\"\n\tHYOKConfigurationTesting    HYOKConfigurationStatus = \"testing\"\n\tHYOKConfigurationTestFailed HYOKConfigurationStatus = \"test_failed\"\n\tHYOKConfigurationAvailable  HYOKConfigurationStatus = \"available\"\n\tHYOKConfigurationErrored    HYOKConfigurationStatus = \"errored\"\n\tHYOKConfigurationRevoking   HYOKConfigurationStatus = \"revoking\"\n\tHYOKConfigurationRevoked    HYOKConfigurationStatus = \"revoked\"\n)\n\ntype OIDCConfigurationTypeChoice struct {\n\tAWSOIDCConfiguration   *AWSOIDCConfiguration\n\tGCPOIDCConfiguration   *GCPOIDCConfiguration\n\tAzureOIDCConfiguration *AzureOIDCConfiguration\n\tVaultOIDCConfiguration *VaultOIDCConfiguration\n}\n\ntype KMSOptions struct {\n\t// AWS\n\tKeyRegion string `jsonapi:\"attr,key-region,omitempty\"`\n\t// GCP\n\tKeyLocation string `jsonapi:\"attr,key-location,omitempty\"`\n\tKeyRingID   string `jsonapi:\"attr,key-ring-id,omitempty\"`\n}\n\ntype HYOKConfiguration struct {\n\tID string `jsonapi:\"primary,hyok-configurations\"`\n\n\t// Attributes\n\tKEKID      string                  `jsonapi:\"attr,kek-id\"`\n\tKMSOptions *KMSOptions             `jsonapi:\"attr,kms-options,omitempty\"`\n\tName       string                  `jsonapi:\"attr,name\"`\n\tPrimary    bool                    `jsonapi:\"attr,primary\"`\n\tStatus     HYOKConfigurationStatus `jsonapi:\"attr,status\"`\n\tError      *string                 `jsonapi:\"attr,error\"`\n\n\t// Relationships\n\tOrganization      *Organization                `jsonapi:\"relation,organization\"`\n\tOIDCConfiguration *OIDCConfigurationTypeChoice `jsonapi:\"polyrelation,oidc-configuration\"`\n\tAgentPool         *AgentPool                   `jsonapi:\"relation,agent-pool\"`\n\tKeyVersions       []*HYOKCustomerKeyVersion    `jsonapi:\"relation,hyok-customer-key-versions\"`\n}\n\ntype HYOKConfigurationsList struct {\n\t*Pagination\n\tItems []*HYOKConfiguration\n}\n\ntype HYOKConfigurationsIncludeOpt string\n\nconst (\n\tHYOKConfigurationsIncludeHYOKCustomerKeyVersions HYOKConfigurationsIncludeOpt = \"hyok_customer_key_versions\"\n\tHYOKConfigurationsIncludeOIDCConfiguration       HYOKConfigurationsIncludeOpt = \"oidc_configuration\"\n)\n\ntype HYOKConfigurationsListOptions struct {\n\tListOptions\n\tSearchQuery string                         `url:\"q,omitempty\"`\n\tInclude     []HYOKConfigurationsIncludeOpt `url:\"include,omitempty\"`\n}\n\ntype HYOKConfigurationsCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,hyok-configurations\"`\n\n\t// Attributes\n\tKEKID      string      `jsonapi:\"attr,kek-id\"`\n\tKMSOptions *KMSOptions `jsonapi:\"attr,kms-options\"`\n\tName       string      `jsonapi:\"attr,name\"`\n\n\t// Relationships\n\tOIDCConfiguration *OIDCConfigurationTypeChoice `jsonapi:\"polyrelation,oidc-configuration\"`\n\tAgentPool         *AgentPool                   `jsonapi:\"relation,agent-pool\"`\n}\n\ntype HYOKConfigurationsReadOptions struct {\n\tInclude []HYOKConfigurationsIncludeOpt `url:\"include,omitempty\"`\n}\n\ntype HYOKConfigurationsUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,hyok-configurations\"`\n\n\t// Attributes\n\tKEKID      *string     `jsonapi:\"attr,kek-id,omitempty\"`\n\tKMSOptions *KMSOptions `jsonapi:\"attr,kms-options,omitempty\"`\n\tName       *string     `jsonapi:\"attr,name,omitempty\"`\n\tPrimary    *bool       `jsonapi:\"attr,primary,omitempty\"`\n\n\t// Relationships\n\tAgentPool *AgentPool `jsonapi:\"relation,agent-pool,omitempty\"`\n}\n\nfunc (h hyokConfigurations) List(ctx context.Context, organization string, options *HYOKConfigurationsListOptions) (*HYOKConfigurationsList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\treq, err := h.client.NewRequest(\"GET\", fmt.Sprintf(\"organizations/%s/hyok-configurations\", url.PathEscape(organization)), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thyokConfigurationList := &HYOKConfigurationsList{}\n\terr = req.Do(ctx, hyokConfigurationList)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn hyokConfigurationList, nil\n}\n\nfunc (h hyokConfigurations) Read(ctx context.Context, hyokID string, options *HYOKConfigurationsReadOptions) (*HYOKConfiguration, error) {\n\tif !validStringID(&hyokID) {\n\t\treturn nil, ErrInvalidHYOK\n\t}\n\n\treq, err := h.client.NewRequest(\"GET\", fmt.Sprintf(\"hyok-configurations/%s\", url.PathEscape(hyokID)), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thyokConfiguration := &HYOKConfiguration{}\n\terr = req.Do(ctx, hyokConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn hyokConfiguration, nil\n}\n\nfunc (h *HYOKConfigurationsCreateOptions) valid() error {\n\tif h.KEKID == \"\" {\n\t\treturn ErrRequiredKEKID\n\t}\n\n\tif h.Name == \"\" {\n\t\treturn ErrRequiredName\n\t}\n\n\tif h.OIDCConfiguration == nil {\n\t\treturn ErrRequiredOIDCConfiguration\n\t}\n\n\tif h.AgentPool == nil {\n\t\treturn ErrRequiredAgentPool\n\t}\n\n\tif h.OIDCConfiguration.AWSOIDCConfiguration != nil {\n\t\tif h.KMSOptions == nil {\n\t\t\treturn ErrRequiredKMSOptions\n\t\t}\n\n\t\tif h.KMSOptions.KeyRegion == \"\" {\n\t\t\treturn ErrRequiredKMSOptionsKeyRegion\n\t\t}\n\t}\n\n\tif h.OIDCConfiguration.GCPOIDCConfiguration != nil {\n\t\tif h.KMSOptions == nil {\n\t\t\treturn ErrRequiredKMSOptions\n\t\t}\n\n\t\tif h.KMSOptions.KeyLocation == \"\" {\n\t\t\treturn ErrRequiredKMSOptionsKeyLocation\n\t\t}\n\n\t\tif h.KMSOptions.KeyRingID == \"\" {\n\t\t\treturn ErrRequiredKMSOptionsKeyRingID\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (h hyokConfigurations) Create(ctx context.Context, organization string, options HYOKConfigurationsCreateOptions) (*HYOKConfiguration, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := h.client.NewRequest(\"POST\", fmt.Sprintf(\"organizations/%s/hyok-configurations\", url.PathEscape(organization)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thyokConfiguration := &HYOKConfiguration{}\n\terr = req.Do(ctx, hyokConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn hyokConfiguration, nil\n}\n\nfunc (h hyokConfigurations) Update(ctx context.Context, hyokID string, options HYOKConfigurationsUpdateOptions) (*HYOKConfiguration, error) {\n\tif !validStringID(&hyokID) {\n\t\treturn nil, ErrInvalidHYOK\n\t}\n\n\treq, err := h.client.NewRequest(\"PATCH\", fmt.Sprintf(\"hyok-configurations/%s\", url.PathEscape(hyokID)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thyokConfiguration := &HYOKConfiguration{}\n\terr = req.Do(ctx, hyokConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn hyokConfiguration, nil\n}\n\nfunc (h hyokConfigurations) Delete(ctx context.Context, hyokID string) error {\n\tif !validStringID(&hyokID) {\n\t\treturn ErrInvalidHYOK\n\t}\n\n\treq, err := h.client.NewRequest(\"DELETE\", fmt.Sprintf(\"hyok-configurations/%s\", url.PathEscape(hyokID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (h hyokConfigurations) Test(ctx context.Context, hyokID string) error {\n\tif !validStringID(&hyokID) {\n\t\treturn ErrInvalidHYOK\n\t}\n\n\treq, err := h.client.NewRequest(\"POST\", fmt.Sprintf(\"hyok-configurations/%s/actions/test\", url.PathEscape(hyokID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (h hyokConfigurations) Revoke(ctx context.Context, hyokID string) error {\n\tif !validStringID(&hyokID) {\n\t\treturn ErrInvalidHYOK\n\t}\n\n\treq, err := h.client.NewRequest(\"POST\", fmt.Sprintf(\"hyok-configurations/%s/actions/revoke\", url.PathEscape(hyokID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "hyok_configuration_integration_test.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHYOKConfigurationCreateRevokeDelete(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tagentPool, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\tt.Cleanup(agentPoolCleanup)\n\n\tt.Run(\"AWS with valid options\", func(t *testing.T) {\n\t\tawsOIDCConfig, configCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(configCleanup)\n\n\t\tkeyRegion := \"us-east-1\"\n\t\topts := HYOKConfigurationsCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tKMSOptions: &KMSOptions{\n\t\t\t\tKeyRegion: keyRegion,\n\t\t\t},\n\t\t\tKEKID:     \"arn:aws:kms:us-east-1:123456789012:key/this-is-not-a-real-key\",\n\t\t\tAgentPool: agentPool,\n\t\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{\n\t\t\t\tAWSOIDCConfiguration: awsOIDCConfig,\n\t\t\t},\n\t\t}\n\n\t\tcreated, err := client.HYOKConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, created)\n\t\tassert.Equal(t, opts.Name, created.Name)\n\t\tassert.Equal(t, opts.KEKID, created.KEKID)\n\t\tassert.Equal(t, opts.KMSOptions.KeyRegion, created.KMSOptions.KeyRegion)\n\t\tassert.Equal(t, opts.AgentPool.ID, created.AgentPool.ID)\n\t\tassert.Equal(t, opts.OIDCConfiguration.AWSOIDCConfiguration.ID, created.OIDCConfiguration.AWSOIDCConfiguration.ID)\n\n\t\t// Must first wait for test_failed status before revoking and deleting the HYOK config or else OIDC configs cannot be cleaned up\n\t\t_, err = waitForHYOKConfigurationStatus(t, ctx, client, created.ID, HYOKConfigurationTestFailed)\n\t\trequire.NoError(t, err)\n\t\terr = client.HYOKConfigurations.Revoke(ctx, created.ID)\n\t\trequire.NoError(t, err)\n\t\t_, err = waitForHYOKConfigurationStatus(t, ctx, client, created.ID, HYOKConfigurationRevoked)\n\t\trequire.NoError(t, err, \"Timed out waiting for HYOK configuration %s to revoke\", created.ID)\n\n\t\terr = client.HYOKConfigurations.Delete(ctx, created.ID)\n\t\trequire.NoError(t, err)\n\t\t_, err = client.HYOKConfigurations.Read(ctx, created.ID, nil)\n\t\trequire.ErrorIs(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"AWS with missing key region\", func(t *testing.T) {\n\t\tawsOIDCConfig, configCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(configCleanup)\n\n\t\topts := HYOKConfigurationsCreateOptions{\n\t\t\tName:       randomStringWithoutSpecialChar(t),\n\t\t\tKMSOptions: &KMSOptions{},\n\t\t\tKEKID:      \"arn:aws:kms:us-east-1:123456789012:key/this-is-not-a-real-key\",\n\t\t\tAgentPool:  agentPool,\n\t\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{\n\t\t\t\tAWSOIDCConfiguration: awsOIDCConfig,\n\t\t\t},\n\t\t}\n\n\t\t_, err := client.HYOKConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.ErrorIs(t, err, ErrRequiredKMSOptionsKeyRegion)\n\t})\n\n\tt.Run(\"GCP with valid options\", func(t *testing.T) {\n\t\tgcpOIDCConfig, configCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(configCleanup)\n\n\t\tkeyLocation := \"global\"\n\t\tkeyRingID := randomStringWithoutSpecialChar(t)\n\n\t\topts := HYOKConfigurationsCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tKMSOptions: &KMSOptions{\n\t\t\t\tKeyLocation: keyLocation,\n\t\t\t\tKeyRingID:   keyRingID,\n\t\t\t},\n\t\t\tKEKID:     randomStringWithoutSpecialChar(t),\n\t\t\tAgentPool: agentPool,\n\t\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{\n\t\t\t\tGCPOIDCConfiguration: gcpOIDCConfig,\n\t\t\t},\n\t\t}\n\n\t\tcreated, err := client.HYOKConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, created)\n\t\tassert.Equal(t, opts.Name, created.Name)\n\t\tassert.Equal(t, opts.KEKID, created.KEKID)\n\t\tassert.Equal(t, opts.KMSOptions.KeyLocation, created.KMSOptions.KeyLocation)\n\t\tassert.Equal(t, opts.KMSOptions.KeyRingID, created.KMSOptions.KeyRingID)\n\t\tassert.Equal(t, opts.AgentPool.ID, created.AgentPool.ID)\n\t\tassert.Equal(t, opts.OIDCConfiguration.GCPOIDCConfiguration.ID, created.OIDCConfiguration.GCPOIDCConfiguration.ID)\n\n\t\t// Must first wait for test_failed status before revoking and deleting the HYOK config or else OIDC configs cannot be cleaned up\n\t\t_, err = waitForHYOKConfigurationStatus(t, ctx, client, created.ID, HYOKConfigurationTestFailed)\n\t\trequire.NoError(t, err)\n\t\terr = client.HYOKConfigurations.Revoke(ctx, created.ID)\n\t\trequire.NoError(t, err)\n\t\t_, err = waitForHYOKConfigurationStatus(t, ctx, client, created.ID, HYOKConfigurationRevoked)\n\t\trequire.NoError(t, err, \"Timed out waiting for HYOK configuration %s to revoke\", created.ID)\n\n\t\terr = client.HYOKConfigurations.Delete(ctx, created.ID)\n\t\trequire.NoError(t, err)\n\t\t_, err = client.HYOKConfigurations.Read(ctx, created.ID, nil)\n\t\trequire.ErrorIs(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"GCP with missing key location\", func(t *testing.T) {\n\t\tgcpOIDCConfig, configCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(configCleanup)\n\n\t\tkeyRingID := randomStringWithoutSpecialChar(t)\n\t\topts := HYOKConfigurationsCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tKMSOptions: &KMSOptions{\n\t\t\t\tKeyRingID: keyRingID,\n\t\t\t},\n\t\t\tKEKID:     randomStringWithoutSpecialChar(t),\n\t\t\tAgentPool: agentPool,\n\t\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{\n\t\t\t\tGCPOIDCConfiguration: gcpOIDCConfig,\n\t\t\t},\n\t\t}\n\n\t\t_, err := client.HYOKConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.ErrorIs(t, err, ErrRequiredKMSOptionsKeyLocation)\n\t})\n\n\tt.Run(\"GCP with missing key ring ID\", func(t *testing.T) {\n\t\tgcpOIDCConfig, configCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(configCleanup)\n\n\t\tkeyLocation := \"global\"\n\n\t\topts := HYOKConfigurationsCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tKMSOptions: &KMSOptions{\n\t\t\t\tKeyLocation: keyLocation,\n\t\t\t},\n\t\t\tKEKID:     randomStringWithoutSpecialChar(t),\n\t\t\tAgentPool: agentPool,\n\t\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{\n\t\t\t\tGCPOIDCConfiguration: gcpOIDCConfig,\n\t\t\t},\n\t\t}\n\n\t\t_, err := client.HYOKConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.ErrorIs(t, err, ErrRequiredKMSOptionsKeyRingID)\n\t})\n\n\tt.Run(\"Vault with valid options\", func(t *testing.T) {\n\t\tvaultOIDCConfig, configCleanup := createVaultOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(configCleanup)\n\n\t\topts := HYOKConfigurationsCreateOptions{\n\t\t\tName:      randomStringWithoutSpecialChar(t),\n\t\t\tKEKID:     randomStringWithoutSpecialChar(t),\n\t\t\tAgentPool: agentPool,\n\t\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{\n\t\t\t\tVaultOIDCConfiguration: vaultOIDCConfig,\n\t\t\t},\n\t\t}\n\n\t\tcreated, err := client.HYOKConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, created)\n\t\tassert.Equal(t, opts.Name, created.Name)\n\t\tassert.Equal(t, opts.KEKID, created.KEKID)\n\t\tassert.Equal(t, opts.AgentPool.ID, created.AgentPool.ID)\n\t\tassert.Equal(t, opts.OIDCConfiguration.VaultOIDCConfiguration.ID, created.OIDCConfiguration.VaultOIDCConfiguration.ID)\n\n\t\t// Must first wait for test_failed status before revoking and deleting the HYOK config or else OIDC configs cannot be cleaned up\n\t\t_, err = waitForHYOKConfigurationStatus(t, ctx, client, created.ID, HYOKConfigurationTestFailed)\n\t\trequire.NoError(t, err)\n\t\terr = client.HYOKConfigurations.Revoke(ctx, created.ID)\n\t\trequire.NoError(t, err)\n\t\t_, err = waitForHYOKConfigurationStatus(t, ctx, client, created.ID, HYOKConfigurationRevoked)\n\t\trequire.NoError(t, err, \"Timed out waiting for HYOK configuration %s to revoke\", created.ID)\n\n\t\terr = client.HYOKConfigurations.Delete(ctx, created.ID)\n\t\trequire.NoError(t, err)\n\t\t_, err = client.HYOKConfigurations.Read(ctx, created.ID, nil)\n\t\trequire.ErrorIs(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"Azure with valid options\", func(t *testing.T) {\n\t\tazureOIDCConfig, configCleanup := createAzureOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(configCleanup)\n\n\t\topts := HYOKConfigurationsCreateOptions{\n\t\t\tName:      randomStringWithoutSpecialChar(t),\n\t\t\tKEKID:     \"https://random.vault.azure.net/keys/some-key\",\n\t\t\tAgentPool: agentPool,\n\t\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{\n\t\t\t\tAzureOIDCConfiguration: azureOIDCConfig,\n\t\t\t},\n\t\t}\n\n\t\tcreated, err := client.HYOKConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, created)\n\t\tassert.Equal(t, opts.Name, created.Name)\n\t\tassert.Equal(t, opts.KEKID, created.KEKID)\n\t\tassert.Equal(t, opts.AgentPool.ID, created.AgentPool.ID)\n\t\tassert.Equal(t, opts.OIDCConfiguration.AzureOIDCConfiguration.ID, created.OIDCConfiguration.AzureOIDCConfiguration.ID)\n\n\t\t// Must first wait for test_failed status before revoking and deleting the HYOK config or else OIDC configs cannot be cleaned up\n\t\t_, err = waitForHYOKConfigurationStatus(t, ctx, client, created.ID, HYOKConfigurationTestFailed)\n\t\trequire.NoError(t, err)\n\t\terr = client.HYOKConfigurations.Revoke(ctx, created.ID)\n\t\trequire.NoError(t, err)\n\t\t_, err = waitForHYOKConfigurationStatus(t, ctx, client, created.ID, HYOKConfigurationRevoked)\n\t\trequire.NoError(t, err, \"Timed out waiting for HYOK configuration %s to revoke\", created.ID)\n\n\t\terr = client.HYOKConfigurations.Delete(ctx, created.ID)\n\t\trequire.NoError(t, err)\n\t\t_, err = client.HYOKConfigurations.Read(ctx, created.ID, nil)\n\t\trequire.ErrorIs(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with missing KEK ID\", func(t *testing.T) {\n\t\tawsOIDCConfig, configCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(configCleanup)\n\n\t\tkeyRegion := \"us-east-1\"\n\n\t\topts := HYOKConfigurationsCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tKMSOptions: &KMSOptions{\n\t\t\t\tKeyRegion: keyRegion,\n\t\t\t},\n\t\t\tAgentPool: agentPool,\n\t\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{\n\t\t\t\tAWSOIDCConfiguration: awsOIDCConfig,\n\t\t\t},\n\t\t}\n\n\t\t_, err := client.HYOKConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.ErrorIs(t, err, ErrRequiredKEKID)\n\t})\n\n\tt.Run(\"with missing agent pool\", func(t *testing.T) {\n\t\tawsOIDCConfig, configCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(configCleanup)\n\n\t\tkeyRegion := \"us-east-1\"\n\n\t\topts := HYOKConfigurationsCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tKMSOptions: &KMSOptions{\n\t\t\t\tKeyRegion: keyRegion,\n\t\t\t},\n\t\t\tKEKID: randomStringWithoutSpecialChar(t),\n\t\t\tOIDCConfiguration: &OIDCConfigurationTypeChoice{\n\t\t\t\tAWSOIDCConfiguration: awsOIDCConfig,\n\t\t\t},\n\t\t}\n\n\t\t_, err := client.HYOKConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.ErrorIs(t, err, ErrRequiredAgentPool)\n\t})\n\n\tt.Run(\"with missing OIDC config\", func(t *testing.T) {\n\t\tkeyRegion := \"us-east-1\"\n\n\t\topts := HYOKConfigurationsCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tKMSOptions: &KMSOptions{\n\t\t\t\tKeyRegion: keyRegion,\n\t\t\t},\n\t\t\tKEKID:     randomStringWithoutSpecialChar(t),\n\t\t\tAgentPool: agentPool,\n\t\t}\n\n\t\t_, err := client.HYOKConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.ErrorIs(t, err, ErrRequiredOIDCConfiguration)\n\t})\n}\n\nfunc TestHyokConfigurationList(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tagentPool, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\tt.Cleanup(agentPoolCleanup)\n\n\tazureOIDC, azureOIDCCleanup := createAzureOIDCConfiguration(t, client, orgTest)\n\tt.Cleanup(azureOIDCCleanup)\n\thyok1, hyokCleanup1 := azureOIDC.createHYOKConfiguration(t, client, orgTest, agentPool)\n\tt.Cleanup(hyokCleanup1)\n\n\tawsOIDC, awsOIDCCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\tt.Cleanup(awsOIDCCleanup)\n\thyok2, hyokCleanup2 := awsOIDC.createHYOKConfiguration(t, client, orgTest, agentPool)\n\tt.Cleanup(hyokCleanup2)\n\n\tgcpOIDC, gcpOIDCCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\tt.Cleanup(gcpOIDCCleanup)\n\thyok3, hyokCleanup3 := gcpOIDC.createHYOKConfiguration(t, client, orgTest, agentPool)\n\tt.Cleanup(hyokCleanup3)\n\n\tvaultOIDC, vaultOIDCCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\tt.Cleanup(vaultOIDCCleanup)\n\thyok4, hyokCleanup4 := vaultOIDC.createHYOKConfiguration(t, client, orgTest, agentPool)\n\tt.Cleanup(hyokCleanup4)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tresults, err := client.HYOKConfigurations.List(ctx, orgTest.Name, nil)\n\n\t\tvar resultingIDs []string\n\t\tfor _, r := range results.Items {\n\t\t\tresultingIDs = append(resultingIDs, r.ID)\n\t\t}\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, resultingIDs, hyok1.ID)\n\t\tassert.Contains(t, resultingIDs, hyok2.ID)\n\t\tassert.Contains(t, resultingIDs, hyok3.ID)\n\t\tassert.Contains(t, resultingIDs, hyok4.ID)\n\t})\n}\n\nfunc TestHyokConfigurationRead(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tagentPool, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\tt.Cleanup(agentPoolCleanup)\n\n\tt.Run(\"AWS\", func(t *testing.T) {\n\t\toidc, oidcCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcCleanup)\n\t\thyok, hyokCleanup := oidc.createHYOKConfiguration(t, client, orgTest, agentPool)\n\t\tt.Cleanup(hyokCleanup)\n\n\t\tfetched, err := client.HYOKConfigurations.Read(ctx, hyok.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, fetched)\n\t\tassert.Equal(t, hyok.Name, fetched.Name)\n\t\tassert.Equal(t, hyok.KEKID, fetched.KEKID)\n\t\tassert.Equal(t, hyok.KMSOptions.KeyRegion, fetched.KMSOptions.KeyRegion)\n\t\tassert.Equal(t, hyok.Organization.Name, fetched.Organization.Name)\n\t\tassert.Equal(t, hyok.AgentPool.ID, fetched.AgentPool.ID)\n\t\tassert.Equal(t, hyok.OIDCConfiguration.AWSOIDCConfiguration.ID, fetched.OIDCConfiguration.AWSOIDCConfiguration.ID)\n\t})\n\n\tt.Run(\"Azure\", func(t *testing.T) {\n\t\toidc, oidcCleanup := createAzureOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcCleanup)\n\t\thyok, hyokCleanup := oidc.createHYOKConfiguration(t, client, orgTest, agentPool)\n\t\tt.Cleanup(hyokCleanup)\n\n\t\tfetched, err := client.HYOKConfigurations.Read(ctx, hyok.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, fetched)\n\t\tassert.Equal(t, hyok.Name, fetched.Name)\n\t\tassert.Equal(t, hyok.KEKID, fetched.KEKID)\n\t\tassert.Equal(t, hyok.KMSOptions, fetched.KMSOptions)\n\t\tassert.Equal(t, hyok.Organization.Name, fetched.Organization.Name)\n\t\tassert.Equal(t, hyok.AgentPool.ID, fetched.AgentPool.ID)\n\t\tassert.Equal(t, hyok.OIDCConfiguration.AzureOIDCConfiguration.ID, fetched.OIDCConfiguration.AzureOIDCConfiguration.ID)\n\t})\n\n\tt.Run(\"GCP\", func(t *testing.T) {\n\t\toidc, oidcCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcCleanup)\n\t\thyok, hyokCleanup := oidc.createHYOKConfiguration(t, client, orgTest, agentPool)\n\t\tt.Cleanup(hyokCleanup)\n\n\t\tfetched, err := client.HYOKConfigurations.Read(ctx, hyok.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, fetched)\n\t\tassert.Equal(t, hyok.Name, fetched.Name)\n\t\tassert.Equal(t, hyok.KEKID, fetched.KEKID)\n\t\tassert.Equal(t, hyok.KMSOptions.KeyLocation, fetched.KMSOptions.KeyLocation)\n\t\tassert.Equal(t, hyok.KMSOptions.KeyRingID, fetched.KMSOptions.KeyRingID)\n\t\tassert.Equal(t, hyok.Organization.Name, fetched.Organization.Name)\n\t\tassert.Equal(t, hyok.AgentPool.ID, fetched.AgentPool.ID)\n\t\tassert.Equal(t, hyok.OIDCConfiguration.GCPOIDCConfiguration.ID, fetched.OIDCConfiguration.GCPOIDCConfiguration.ID)\n\t})\n\n\tt.Run(\"Vault\", func(t *testing.T) {\n\t\toidc, oidcCleanup := createVaultOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcCleanup)\n\t\thyok, hyokCleanup := oidc.createHYOKConfiguration(t, client, orgTest, agentPool)\n\t\tt.Cleanup(hyokCleanup)\n\n\t\tfetched, err := client.HYOKConfigurations.Read(ctx, hyok.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, fetched)\n\t\tassert.Equal(t, hyok.Name, fetched.Name)\n\t\tassert.Equal(t, hyok.KEKID, fetched.KEKID)\n\t\tassert.Equal(t, hyok.KMSOptions, fetched.KMSOptions)\n\t\tassert.Equal(t, hyok.Organization.Name, fetched.Organization.Name)\n\t\tassert.Equal(t, hyok.AgentPool.ID, fetched.AgentPool.ID)\n\t\tassert.Equal(t, hyok.OIDCConfiguration.VaultOIDCConfiguration.ID, fetched.OIDCConfiguration.VaultOIDCConfiguration.ID)\n\t})\n\n\tt.Run(\"fetching non-existing configuration\", func(t *testing.T) {\n\t\t_, err := client.HYOKConfigurations.Read(ctx, \"hyokc-notreal\", nil)\n\t\tassert.ErrorIs(t, err, ErrResourceNotFound)\n\t})\n}\n\nfunc TestHYOKConfigurationUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tagentPool, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\tt.Cleanup(agentPoolCleanup)\n\n\tt.Run(\"AWS with valid options\", func(t *testing.T) {\n\t\toidc, oidcCleanup := createAWSOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcCleanup)\n\t\thyok, hyokCleanup := oidc.createHYOKConfiguration(t, client, orgTest, agentPool)\n\t\tt.Cleanup(hyokCleanup)\n\n\t\tname := randomStringWithoutSpecialChar(t)\n\t\tkekID := \"arn:aws:kms:us-east-1:123456789012:key/this-is-a-bad-key\"\n\n\t\topts := HYOKConfigurationsUpdateOptions{\n\t\t\tName: &name,\n\t\t\tKMSOptions: &KMSOptions{\n\t\t\t\tKeyRegion: \"us-east-2\",\n\t\t\t},\n\t\t\tKEKID:     &kekID,\n\t\t\tAgentPool: agentPool,\n\t\t}\n\n\t\tupdated, err := client.HYOKConfigurations.Update(ctx, hyok.ID, opts)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, *opts.Name, updated.Name)\n\t\tassert.Equal(t, *opts.KEKID, updated.KEKID)\n\t\tassert.Equal(t, opts.KMSOptions.KeyRegion, updated.KMSOptions.KeyRegion)\n\t})\n\n\tt.Run(\"GCP with valid options\", func(t *testing.T) {\n\t\toidc, oidcCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcCleanup)\n\t\thyok, hyokCleanup := oidc.createHYOKConfiguration(t, client, orgTest, agentPool)\n\t\tt.Cleanup(hyokCleanup)\n\n\t\tname := randomStringWithoutSpecialChar(t)\n\t\tkekID := randomStringWithoutSpecialChar(t)\n\n\t\topts := HYOKConfigurationsUpdateOptions{\n\t\t\tName: &name,\n\t\t\tKMSOptions: &KMSOptions{\n\t\t\t\tKeyLocation: \"ca\",\n\t\t\t\tKeyRingID:   randomStringWithoutSpecialChar(t),\n\t\t\t},\n\t\t\tKEKID:     &kekID,\n\t\t\tAgentPool: agentPool,\n\t\t}\n\n\t\tupdated, err := client.HYOKConfigurations.Update(ctx, hyok.ID, opts)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, *opts.Name, updated.Name)\n\t\tassert.Equal(t, *opts.KEKID, updated.KEKID)\n\t\tassert.Equal(t, opts.KMSOptions.KeyLocation, updated.KMSOptions.KeyLocation)\n\t\tassert.Equal(t, opts.KMSOptions.KeyRingID, updated.KMSOptions.KeyRingID)\n\t})\n\n\tt.Run(\"Vault with valid options\", func(t *testing.T) {\n\t\toidc, oidcCleanup := createVaultOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcCleanup)\n\t\thyok, hyokCleanup := oidc.createHYOKConfiguration(t, client, orgTest, agentPool)\n\t\tt.Cleanup(hyokCleanup)\n\n\t\tname := randomStringWithoutSpecialChar(t)\n\t\tkekID := randomStringWithoutSpecialChar(t)\n\n\t\topts := HYOKConfigurationsUpdateOptions{\n\t\t\tName:      &name,\n\t\t\tKEKID:     &kekID,\n\t\t\tAgentPool: agentPool,\n\t\t}\n\n\t\tupdated, err := client.HYOKConfigurations.Update(ctx, hyok.ID, opts)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, *opts.Name, updated.Name)\n\t\tassert.Equal(t, *opts.KEKID, updated.KEKID)\n\t\tassert.Equal(t, opts.AgentPool.ID, updated.AgentPool.ID)\n\t})\n\n\tt.Run(\"Azure with valid options\", func(t *testing.T) {\n\t\toidc, oidcCleanup := createAzureOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcCleanup)\n\t\thyok, hyokCleanup := oidc.createHYOKConfiguration(t, client, orgTest, agentPool)\n\t\tt.Cleanup(hyokCleanup)\n\n\t\tname := randomStringWithoutSpecialChar(t)\n\t\tkekID := \"https://random.vault.azure.net/keys/some-key-2\"\n\n\t\topts := HYOKConfigurationsUpdateOptions{\n\t\t\tName:      &name,\n\t\t\tKEKID:     &kekID,\n\t\t\tAgentPool: agentPool,\n\t\t}\n\n\t\tupdated, err := client.HYOKConfigurations.Update(ctx, hyok.ID, opts)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, *opts.Name, updated.Name)\n\t\tassert.Equal(t, *opts.KEKID, updated.KEKID)\n\t\tassert.Equal(t, opts.AgentPool.ID, updated.AgentPool.ID)\n\t})\n}\n"
  },
  {
    "path": "hyok_customer_key_version.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\nvar _ HYOKCustomerKeyVersions = (*hyokCustomerKeyVersions)(nil)\n\n// HYOKCustomerKeyVersions describes all the hyok customer key version related methods that the HCP Terraform API supports.\n// HCP Terraform API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/key-versions\ntype HYOKCustomerKeyVersions interface {\n\t// List all hyok customer key versions associated to a HYOK configuration.\n\tList(ctx context.Context, hyokConfigurationID string, options *HYOKCustomerKeyVersionListOptions) (*HYOKCustomerKeyVersionList, error)\n\n\t// Read a hyok customer key version by its ID.\n\tRead(ctx context.Context, hyokCustomerKeyVersionID string) (*HYOKCustomerKeyVersion, error)\n\n\t// Revoke a hyok customer key version.\n\tRevoke(ctx context.Context, hyokCustomerKeyVersionID string) error\n\n\t// Delete a hyok customer key version.\n\tDelete(ctx context.Context, hyokCustomerKeyVersionID string) error\n}\n\n// hyokCustomerKeyVersions implements HYOKCustomerKeyVersions\ntype hyokCustomerKeyVersions struct {\n\tclient *Client\n}\n\n// HYOKCustomerKeyVersionList represents a list of hyok customer key versions\ntype HYOKCustomerKeyVersionList struct {\n\t*Pagination\n\tItems []*HYOKCustomerKeyVersion\n}\n\n// HYOKCustomerKeyVersion represents the resource\ntype HYOKCustomerKeyVersion struct {\n\t// Attributes\n\tID                string               `jsonapi:\"primary,hyok-customer-key-versions\"`\n\tKeyVersion        string               `jsonapi:\"attr,key-version\"`\n\tCreatedAt         time.Time            `jsonapi:\"attr,created-at,iso8601\"`\n\tStatus            HYOKKeyVersionStatus `jsonapi:\"attr,status\"`\n\tWorkspacesSecured int                  `jsonapi:\"attr,workspaces-secured\"`\n\tError             string               `jsonapi:\"attr,error\"`\n\n\t// Relationships\n\tHYOKConfiguration *HYOKConfiguration `jsonapi:\"relation,hyok-configuration\"`\n}\n\n// HYOKKeyVersionStatus represents a key version status.\ntype HYOKKeyVersionStatus string\n\n// List all available configuration version statuses.\nconst (\n\tKeyVersionStatusAvailable        HYOKKeyVersionStatus = \"available\"\n\tKeyVersionStatusRevoking         HYOKKeyVersionStatus = \"revoking\"\n\tKeyVersionStatusRevoked          HYOKKeyVersionStatus = \"revoked\"\n\tKeyVersionStatusRevocationFailed HYOKKeyVersionStatus = \"revocation_failed\"\n)\n\n// HYOKCustomerKeyVersionListOptions represents the options for listing hyok customer key versions\ntype HYOKCustomerKeyVersionListOptions struct {\n\tListOptions\n\tRefresh bool `url:\"refresh,omitempty\"`\n}\n\n// List all hyok customer key versions.\nfunc (s *hyokCustomerKeyVersions) List(ctx context.Context, hyokConfigurationID string, options *HYOKCustomerKeyVersionListOptions) (*HYOKCustomerKeyVersionList, error) {\n\tif !validStringID(&hyokConfigurationID) {\n\t\treturn nil, ErrInvalidHYOK\n\t}\n\n\tpath := fmt.Sprintf(\"hyok-configurations/%s/hyok-customer-key-versions\", url.PathEscape(hyokConfigurationID))\n\treq, err := s.client.NewRequest(\"GET\", path, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkvs := &HYOKCustomerKeyVersionList{}\n\terr = req.Do(ctx, kvs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn kvs, nil\n}\n\n// Read a hyok customer key version by its ID.\nfunc (s *hyokCustomerKeyVersions) Read(ctx context.Context, hyokCustomerKeyVersionID string) (*HYOKCustomerKeyVersion, error) {\n\tif !validStringID(&hyokCustomerKeyVersionID) {\n\t\treturn nil, ErrInvalidHYOKCustomerKeyVersion\n\t}\n\n\tpath := fmt.Sprintf(\"hyok-customer-key-versions/%s\", url.PathEscape(hyokCustomerKeyVersionID))\n\treq, err := s.client.NewRequest(\"GET\", path, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkv := &HYOKCustomerKeyVersion{}\n\terr = req.Do(ctx, kv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn kv, nil\n}\n\n// Revoke a hyok customer key version. This process is asynchronous.\n// Returns `error` if there was a problem triggering the revocation. Otherwise revocation has been triggered successfully.\nfunc (s *hyokCustomerKeyVersions) Revoke(ctx context.Context, hyokCustomerKeyVersionID string) error {\n\tif !validStringID(&hyokCustomerKeyVersionID) {\n\t\treturn ErrInvalidHYOKCustomerKeyVersion\n\t}\n\n\tpath := fmt.Sprintf(\"hyok-customer-key-versions/%s/actions/revoke\", url.PathEscape(hyokCustomerKeyVersionID))\n\treq, err := s.client.NewRequest(\"POST\", path, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Delete a hyok customer key version.\nfunc (s *hyokCustomerKeyVersions) Delete(ctx context.Context, hyokCustomerKeyVersionID string) error {\n\tif !validStringID(&hyokCustomerKeyVersionID) {\n\t\treturn ErrInvalidHYOKCustomerKeyVersion\n\t}\n\n\tpath := fmt.Sprintf(\"hyok-customer-key-versions/%s\", url.PathEscape(hyokCustomerKeyVersionID))\n\treq, err := s.client.NewRequest(\"DELETE\", path, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "hyok_customer_key_version_integration_test.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// These tests are intended for local execution only, as key versions for HYOK requires specific conditions\n// for tests to run successfully. To test locally:\n// 1. Follow the instructions outlined in hyok_configuration_integration_test.go.\n// 2. Set hyokCustomerKeyVersionID to the ID of an existing HYOK customer key version\n\nfunc TestHYOKCustomerKeyVersionsList(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tagentPool, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\tt.Cleanup(agentPoolCleanup)\n\n\toidc, oidcCleanup := createGCPOIDCConfiguration(t, client, orgTest)\n\tt.Cleanup(oidcCleanup)\n\thyok, hyokCleanup := oidc.createHYOKConfiguration(t, client, orgTest, agentPool)\n\tt.Cleanup(hyokCleanup)\n\n\tt.Run(\"with no list options\", func(t *testing.T) {\n\t\t_, err := client.HYOKCustomerKeyVersions.List(ctx, hyok.ID, nil)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestHYOKCustomerKeyVersionsRead(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"read an existing key version\", func(t *testing.T) {\n\t\thyokCustomerKeyVersionID := os.Getenv(\"HYOK_CUSTOMER_KEY_VERSION_ID\")\n\t\tif hyokCustomerKeyVersionID == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_CUSTOMER_KEY_VERSION_ID before running this test!\")\n\t\t}\n\n\t\t_, err := client.HYOKCustomerKeyVersions.Read(ctx, hyokCustomerKeyVersionID)\n\t\trequire.NoError(t, err)\n\t})\n}\n"
  },
  {
    "path": "hyok_encrypted_data_key.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\nvar _ HYOKEncryptedDataKeys = (*hyokEncryptedDataKeys)(nil)\n\n// HYOKEncryptedDataKeys describes all the hyok customer key version related methods that the HCP Terraform API supports.\n// HCP Terraform API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/encrypted-data-keys\ntype HYOKEncryptedDataKeys interface {\n\t// Read a HYOK encrypted data key by its ID.\n\tRead(ctx context.Context, hyokEncryptedDataKeyID string) (*HYOKEncryptedDataKey, error)\n}\n\n// hyokEncryptedDataKeys implements HYOKEncryptedDataKeys\ntype hyokEncryptedDataKeys struct {\n\tclient *Client\n}\n\n// HYOKEncryptedDataKey represents the resource\ntype HYOKEncryptedDataKey struct {\n\t// Attributes\n\tID              string    `jsonapi:\"primary,hyok-encrypted-data-keys\"`\n\tEncryptedDEK    string    `jsonapi:\"attr,encrypted-dek\"`\n\tCustomerKeyName string    `jsonapi:\"attr,customer-key-name\"`\n\tCreatedAt       time.Time `jsonapi:\"attr,created-at,iso8601\"`\n\n\t// Relationships\n\tKeyVersion *HYOKCustomerKeyVersion `jsonapi:\"relation,hyok-customer-key-versions\"`\n}\n\n// Read a HYOK encrypted data key by its ID.\nfunc (h hyokEncryptedDataKeys) Read(ctx context.Context, hyokEncryptedDataKeyID string) (*HYOKEncryptedDataKey, error) {\n\tif !validStringID(&hyokEncryptedDataKeyID) {\n\t\treturn nil, ErrInvalidHYOKEncryptedDataKey\n\t}\n\n\tpath := fmt.Sprintf(\"hyok-encrypted-data-keys/%s\", url.PathEscape(hyokEncryptedDataKeyID))\n\treq, err := h.client.NewRequest(\"GET\", path, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdek := &HYOKEncryptedDataKey{}\n\terr = req.Do(ctx, dek)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dek, nil\n}\n"
  },
  {
    "path": "hyok_encrypted_data_key_integration_test.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// These tests are intended for local execution only, as data encryption keys for HYOK requires specific conditions\n// for tests to run successfully. To test locally:\n// 1. Follow the instructions outlined in hyok_configuration_integration_test.go.\n// 2. Set hyokEncryptedDataKeyID to the ID of an existing data encryption key\n\nfunc TestHYOKEncryptedDataKeyRead(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"read an existing encrypted data key\", func(t *testing.T) {\n\t\thyokEncryptedDataKeyID := os.Getenv(\"HYOK_ENCRYPTED_DATA_KEY_ID\")\n\t\tif hyokEncryptedDataKeyID == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ENCRYPTED_DATA_KEY_ID before running this test!\")\n\t\t}\n\n\t\t_, err := client.HYOKEncryptedDataKeys.Read(ctx, hyokEncryptedDataKeyID)\n\t\trequire.NoError(t, err)\n\t})\n}\n"
  },
  {
    "path": "internal_run_task.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\n// A private struct we need for unmarshalling\ntype internalRunTask struct {\n\tID          string                 `jsonapi:\"primary,tasks\"`\n\tName        string                 `jsonapi:\"attr,name\"`\n\tURL         string                 `jsonapi:\"attr,url\"`\n\tDescription string                 `jsonapi:\"attr,description\"`\n\tCategory    string                 `jsonapi:\"attr,category\"`\n\tHMACKey     *string                `jsonapi:\"attr,hmac-key,omitempty\"`\n\tEnabled     bool                   `jsonapi:\"attr,enabled\"`\n\tRawGlobal   map[string]interface{} `jsonapi:\"attr,global-configuration,omitempty\"`\n\n\tOrganization      *Organization               `jsonapi:\"relation,organization\"`\n\tWorkspaceRunTasks []*internalWorkspaceRunTask `jsonapi:\"relation,workspace-tasks\"`\n}\n\n// Due to https://github.com/google/jsonapi/issues/74 we must first unmarshall using map[string]interface{}\n// and then perform our own conversion from the map into a GlobalRunTask struct\nfunc (irt internalRunTask) ToRunTask() *RunTask {\n\tobj := RunTask{\n\t\tID:          irt.ID,\n\t\tName:        irt.Name,\n\t\tURL:         irt.URL,\n\t\tDescription: irt.Description,\n\t\tCategory:    irt.Category,\n\t\tHMACKey:     irt.HMACKey,\n\t\tEnabled:     irt.Enabled,\n\n\t\tOrganization: irt.Organization,\n\t}\n\n\t// Convert the WorkspaceRunTasks\n\tworkspaceTasks := make([]*WorkspaceRunTask, len(irt.WorkspaceRunTasks))\n\tfor idx, rawTask := range irt.WorkspaceRunTasks {\n\t\tif rawTask != nil {\n\t\t\tworkspaceTasks[idx] = rawTask.ToWorkspaceRunTask()\n\t\t}\n\t}\n\tobj.WorkspaceRunTasks = workspaceTasks\n\n\tvar boolVal bool\n\t// Check if the global configuration exists\n\tif val, ok := irt.RawGlobal[\"enabled\"]; !ok {\n\t\t// The enabled property is required so we can assume now that the\n\t\t// global configuration was not supplied\n\t\treturn &obj\n\t} else if boolVal, ok = val.(bool); !ok {\n\t\t// The enabled property exists but it is invalid (Couldn't cast to boolean)\n\t\t// so assume the global configuration was not supplied\n\t\treturn &obj\n\t}\n\n\tobj.Global = &GlobalRunTask{\n\t\tEnabled: boolVal,\n\t}\n\n\t// Global Enforcement Level\n\tif val, ok := irt.RawGlobal[\"enforcement-level\"]; ok {\n\t\tif stringVal, ok := val.(string); ok {\n\t\t\tobj.Global.EnforcementLevel = TaskEnforcementLevel(stringVal)\n\t\t}\n\t}\n\n\t// Global Stages\n\tif val, ok := irt.RawGlobal[\"stages\"]; ok {\n\t\tif stringsVal, ok := val.([]interface{}); ok {\n\t\t\tobj.Global.Stages = make([]Stage, len(stringsVal))\n\t\t\tfor idx, stageName := range stringsVal {\n\t\t\t\tif stringVal, ok := stageName.(string); ok {\n\t\t\t\t\tobj.Global.Stages[idx] = Stage(stringVal)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &obj\n}\n\n// A private struct we need for unmarshalling\ntype internalRunTaskList struct {\n\t*Pagination\n\tItems []*internalRunTask\n}\n\n// Due to https://github.com/google/jsonapi/issues/74 we must first unmarshall using\n// the internal RunTask struct and convert that a RunTask\nfunc (irt internalRunTaskList) ToRunTaskList() *RunTaskList {\n\tobj := RunTaskList{\n\t\tPagination: irt.Pagination,\n\t\tItems:      make([]*RunTask, len(irt.Items)),\n\t}\n\n\tfor idx, src := range irt.Items {\n\t\tif src != nil {\n\t\t\tobj.Items[idx] = src.ToRunTask()\n\t\t}\n\t}\n\n\treturn &obj\n}\n"
  },
  {
    "path": "internal_workspace_run_task.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\n// A private struct we need for unmarshalling\ntype internalWorkspaceRunTask struct {\n\tID               string               `jsonapi:\"primary,workspace-tasks\"`\n\tEnforcementLevel TaskEnforcementLevel `jsonapi:\"attr,enforcement-level\"`\n\tStage            Stage                `jsonapi:\"attr,stage\"`\n\tStages           []string             `jsonapi:\"attr,stages\"`\n\n\tRunTask   *RunTask   `jsonapi:\"relation,task\"`\n\tWorkspace *Workspace `jsonapi:\"relation,workspace\"`\n}\n\n// Due to https://github.com/google/jsonapi/issues/74 we must first unmarshall using map[string]interface{}\n// and then perform our own conversion for the Stages\nfunc (irt internalWorkspaceRunTask) ToWorkspaceRunTask() *WorkspaceRunTask {\n\tobj := WorkspaceRunTask{\n\t\tID:               irt.ID,\n\t\tEnforcementLevel: irt.EnforcementLevel,\n\t\tStage:            irt.Stage,\n\t\tStages:           make([]Stage, len(irt.Stages)),\n\t\tRunTask:          irt.RunTask,\n\t\tWorkspace:        irt.Workspace,\n\t}\n\n\tfor idx, val := range irt.Stages {\n\t\tobj.Stages[idx] = Stage(val)\n\t}\n\n\treturn &obj\n}\n\n// A private struct we need for unmarshalling\ntype internalWorkspaceRunTaskList struct {\n\t*Pagination\n\tItems []*internalWorkspaceRunTask\n}\n\n// Due to https://github.com/google/jsonapi/issues/74 we must first unmarshall using\n// the internal WorkspaceRunTask struct and convert that a WorkspaceRunTask\nfunc (irt internalWorkspaceRunTaskList) ToWorkspaceRunTaskList() *WorkspaceRunTaskList {\n\tobj := WorkspaceRunTaskList{\n\t\tPagination: irt.Pagination,\n\t\tItems:      make([]*WorkspaceRunTask, len(irt.Items)),\n\t}\n\n\tfor idx, src := range irt.Items {\n\t\tif src != nil {\n\t\t\tobj.Items[idx] = src.ToWorkspaceRunTask()\n\t\t}\n\t}\n\n\treturn &obj\n}\n"
  },
  {
    "path": "ip_ranges.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ IPRanges = (*ipRanges)(nil)\n\n// IP Ranges provides a list of HCP Terraform or Terraform Enterprise's IP ranges.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/ip-ranges\ntype IPRanges interface {\n\t// Retrieve HCP Terraform IP ranges. If `modifiedSince` is not an empty string\n\t// then it will only return the IP ranges changes since that date.\n\t// The format for `modifiedSince` can be found here:\n\t// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since\n\tRead(ctx context.Context, modifiedSince string) (*IPRange, error)\n}\n\n// ipRanges implements IPRanges interface.\ntype ipRanges struct {\n\tclient *Client\n}\n\n// IPRange represents a list of HCP Terraform's IP ranges\ntype IPRange struct {\n\t// List of IP ranges in CIDR notation used for connections from user site to HCP Terraform APIs\n\tAPI []string `json:\"api\"`\n\t// List of IP ranges in CIDR notation used for notifications\n\tNotifications []string `json:\"notifications\"`\n\t// List of IP ranges in CIDR notation used for outbound requests from Sentinel policies\n\tSentinel []string `json:\"sentinel\"`\n\t// List of IP ranges in CIDR notation used for connecting to VCS providers\n\tVCS []string `json:\"vcs\"`\n}\n\n// Read an IPRange that was not modified since the specified date.\nfunc (i *ipRanges) Read(ctx context.Context, modifiedSince string) (*IPRange, error) {\n\treq, err := i.client.NewRequest(\"GET\", \"/api/meta/ip-ranges\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif modifiedSince != \"\" {\n\t\treq.Header.Add(\"If-Modified-Since\", modifiedSince)\n\t}\n\n\tir := &IPRange{}\n\terr = req.DoJSON(ctx, ir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ir, nil\n}\n"
  },
  {
    "path": "ip_ranges_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestIPRangesRead(t *testing.T) {\n\tt.Parallel()\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"without modifiedSince\", func(t *testing.T) {\n\t\tr, err := client.Meta.IPRanges.Read(ctx, \"\")\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, r.API)\n\t\tassert.NotEmpty(t, r.Notifications)\n\t\tassert.NotEmpty(t, r.Sentinel)\n\t\tassert.NotEmpty(t, r.VCS)\n\t})\n\n\tt.Run(\"with future modifiedSince\", func(t *testing.T) {\n\t\tts := time.Now().Add(48 * time.Hour)\n\t\tmodifiedSince := ts.Format(\"Mon, 02 Jan 2006 00:00:00 GMT\")\n\t\tr, err := client.Meta.IPRanges.Read(ctx, modifiedSince)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, r.API)\n\t\tassert.Empty(t, r.Notifications)\n\t\tassert.Empty(t, r.Sentinel)\n\t\tassert.Empty(t, r.VCS)\n\t})\n}\n"
  },
  {
    "path": "logreader.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// LogReader implements io.Reader for streaming logs.\ntype LogReader struct {\n\tclient      *Client\n\tctx         context.Context\n\tdone        func() (bool, error)\n\tlogURL      *url.URL\n\toffset      int64\n\treads       int\n\tstartOfText bool\n\tendOfText   bool\n}\n\nfunc (r *LogReader) Read(l []byte) (int, error) {\n\tif written, err := r.read(l); !errors.Is(err, io.ErrNoProgress) {\n\t\treturn written, err\n\t}\n\n\t// Loop until we can any data, the context is canceled or the\n\t// run is finsished. If we would return right away without any\n\t// data, we could end up causing a io.ErrNoProgress error.\n\tfor r.reads = 1; ; r.reads++ {\n\t\tselect {\n\t\tcase <-r.ctx.Done():\n\t\t\treturn 0, r.ctx.Err()\n\t\tcase <-time.After(backoff(500, 2000, r.reads)):\n\t\t\tif written, err := r.read(l); !errors.Is(err, io.ErrNoProgress) {\n\t\t\t\treturn written, err\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *LogReader) read(l []byte) (int, error) {\n\t// Update the query string.\n\tr.logURL.RawQuery = fmt.Sprintf(\"limit=%d&offset=%d\", len(l), r.offset)\n\n\t// Create a new request.\n\treq, err := http.NewRequest(\"GET\", r.logURL.String(), nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treq = req.WithContext(r.ctx)\n\n\t// Attach the default headers.\n\tfor k, v := range r.client.headers {\n\t\treq.Header[k] = v\n\t}\n\n\t// Retrieve the next chunk.\n\tresp, err := r.client.http.HTTPClient.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer resp.Body.Close() //nolint:errcheck\n\n\t// Basic response checking.\n\tif err := checkResponseCode(resp); err != nil {\n\t\treturn 0, err\n\t}\n\n\t// Read the retrieved chunk.\n\twritten, err := resp.Body.Read(l)\n\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t// Ignore io.EOF errors returned when reading from the response\n\t\t// body as this indicates the end of the chunk and not the end\n\t\t// of the logfile.\n\t\treturn written, err\n\t}\n\n\tif written > 0 {\n\t\t// Check for an STX (Start of Text) ASCII control marker.\n\t\tif !r.startOfText && l[0] == byte(2) {\n\t\t\tr.startOfText = true\n\n\t\t\t// Remove the STX marker from the received chunk.\n\t\t\tcopy(l[:written-1], l[1:])\n\t\t\tl[written-1] = byte(0)\n\t\t\tr.offset++\n\t\t\twritten--\n\n\t\t\t// Return early if we only received the STX marker.\n\t\t\tif written == 0 {\n\t\t\t\treturn 0, io.ErrNoProgress\n\t\t\t}\n\t\t}\n\n\t\t// If we found an STX ASCII control character, start looking for\n\t\t// the ETX (End of Text) control character.\n\t\tif r.startOfText && l[written-1] == byte(3) {\n\t\t\tr.endOfText = true\n\n\t\t\t// Remove the ETX marker from the received chunk.\n\t\t\tl[written-1] = byte(0)\n\t\t\tr.offset++\n\t\t\twritten--\n\t\t}\n\t}\n\n\t// Check if we need to continue the loop and wait 500 miliseconds\n\t// before checking if there is a new chunk available or that the\n\t// run is finished and we are done reading all chunks.\n\tif written != 0 {\n\t\t// Update the offset for the next read.\n\t\tr.offset += int64(written)\n\t\treturn written, nil\n\t}\n\n\tif (r.startOfText && r.endOfText) || // The logstream finished without issues.\n\t\t(r.startOfText && r.reads%10 == 0) || // The logstream terminated unexpectedly.\n\t\t(!r.startOfText && r.reads > 1) { // The logstream doesn't support STX/ETX.\n\t\tdone, err := r.done()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tif done {\n\t\t\treturn 0, io.EOF\n\t\t}\n\t}\n\treturn 0, io.ErrNoProgress\n}\n\n// backoff will perform exponential backoff based on the iteration and\n// limited by the provided minimum and maximum (in milliseconds) durations.\nfunc backoff(minimum, maximum float64, iter int) time.Duration {\n\tbackoff := math.Pow(2, float64(iter)/5) * minimum\n\tif backoff > maximum {\n\t\tbackoff = maximum\n\t}\n\treturn time.Duration(backoff) * time.Millisecond\n}\n"
  },
  {
    "path": "logreader_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n)\n\n// checkedWrite writes message to w and fails the test if there's an error.\nfunc checkedWrite(t *testing.T, w io.Writer, message []byte) {\n\t_, err := w.Write(message)\n\tif err != nil {\n\t\tt.Fatalf(\"error writing response: %s\", err)\n\t}\n}\n\nfunc testLogReader(t *testing.T, h http.HandlerFunc) (*httptest.Server, *LogReader) {\n\tts := httptest.NewServer(h)\n\n\tcfg := &Config{\n\t\tAddress:    ts.URL,\n\t\tToken:      \"dummy-token\",\n\t\tHTTPClient: ts.Client(),\n\t}\n\n\tclient, err := NewClient(cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tlogURL, err := url.Parse(ts.URL)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tlr := &LogReader{\n\t\tclient: client,\n\t\tctx:    context.Background(),\n\t\tlogURL: logURL,\n\t}\n\n\treturn ts, lr\n}\n\nfunc TestLogReader_withMarkersSingle(t *testing.T) {\n\tt.Parallel()\n\n\tlogReads := 0\n\tts, lr := testLogReader(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tlogReads++\n\t\tswitch logReads {\n\t\tcase 2:\n\t\t\tcheckedWrite(t, w, []byte(\"\\x02Terraform run started - logs - Terraform run finished\\x03\"))\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tdoneReads := 0\n\tlr.done = func() (bool, error) {\n\t\tdoneReads++\n\t\tif logReads >= 2 {\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, nil\n\t}\n\n\tlogs, err := io.ReadAll(lr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := \"Terraform run started - logs - Terraform run finished\"\n\tif string(logs) != expected {\n\t\tt.Fatalf(\"expected %s, got: %s\", expected, string(logs))\n\t}\n\tif doneReads != 1 {\n\t\tt.Fatalf(\"expected 1 done reads, got %d reads\", doneReads)\n\t}\n\tif logReads != 3 {\n\t\tt.Fatalf(\"expected 3 log reads, got %d reads\", logReads)\n\t}\n}\n\nfunc TestLogReader_withMarkersDouble(t *testing.T) {\n\tt.Parallel()\n\n\tlogReads := 0\n\tts, lr := testLogReader(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tlogReads++\n\t\tswitch logReads {\n\t\tcase 2:\n\t\t\tcheckedWrite(t, w, []byte(\"\\x02Terraform run started\"))\n\t\tcase 3:\n\t\t\tcheckedWrite(t, w, []byte(\" - logs - Terraform run finished\\x03\"))\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tdoneReads := 0\n\tlr.done = func() (bool, error) {\n\t\tdoneReads++\n\t\tif logReads >= 3 {\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, nil\n\t}\n\n\tlogs, err := io.ReadAll(lr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := \"Terraform run started - logs - Terraform run finished\"\n\tif string(logs) != expected {\n\t\tt.Fatalf(\"expected %s, got: %s\", expected, string(logs))\n\t}\n\tif doneReads != 1 {\n\t\tt.Fatalf(\"expected 1 done reads, got %d reads\", doneReads)\n\t}\n\tif logReads != 4 {\n\t\tt.Fatalf(\"expected 4 log reads, got %d reads\", logReads)\n\t}\n}\n\nfunc TestLogReader_withMarkersMulti(t *testing.T) {\n\tt.Parallel()\n\n\tlogReads := 0\n\tts, lr := testLogReader(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tlogReads++\n\t\tswitch logReads {\n\t\tcase 2:\n\t\t\tcheckedWrite(t, w, []byte(\"\\x02\"))\n\t\tcase 3:\n\t\t\tcheckedWrite(t, w, []byte(\"Terraform run started\"))\n\t\tcase 16:\n\t\t\tcheckedWrite(t, w, []byte(\" - logs - \"))\n\t\tcase 30:\n\t\t\tcheckedWrite(t, w, []byte(\"Terraform run finished\"))\n\t\tcase 31:\n\t\t\tcheckedWrite(t, w, []byte(\"\\x03\"))\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tdoneReads := 0\n\tlr.done = func() (bool, error) {\n\t\tdoneReads++\n\t\tif logReads >= 31 {\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, nil\n\t}\n\n\tlogs, err := io.ReadAll(lr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := \"Terraform run started - logs - Terraform run finished\"\n\tif string(logs) != expected {\n\t\tt.Fatalf(\"expected %s, got: %s\", expected, string(logs))\n\t}\n\tif doneReads != 3 {\n\t\tt.Fatalf(\"expected 3 done reads, got %d reads\", doneReads)\n\t}\n\tif logReads != 31 {\n\t\tt.Fatalf(\"expected 31 log reads, got %d reads\", logReads)\n\t}\n}\n\nfunc TestLogReader_withoutMarkers(t *testing.T) {\n\tt.Parallel()\n\n\tlogReads := 0\n\tts, lr := testLogReader(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tlogReads++\n\t\tswitch logReads {\n\t\tcase 2:\n\t\t\tcheckedWrite(t, w, []byte(\"Terraform run started\"))\n\t\tcase 16:\n\t\t\tcheckedWrite(t, w, []byte(\" - logs - \"))\n\t\tcase 31:\n\t\t\tcheckedWrite(t, w, []byte(\"Terraform run finished\"))\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tdoneReads := 0\n\tlr.done = func() (bool, error) {\n\t\tdoneReads++\n\t\tif logReads >= 31 {\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, nil\n\t}\n\n\tlogs, err := io.ReadAll(lr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := \"Terraform run started - logs - Terraform run finished\"\n\tif string(logs) != expected {\n\t\tt.Fatalf(\"expected %s, got: %s\", expected, string(logs))\n\t}\n\tif doneReads != 25 {\n\t\tt.Fatalf(\"expected 14 done reads, got %d reads\", doneReads)\n\t}\n\tif logReads != 32 {\n\t\tt.Fatalf(\"expected 32 log reads, got %d reads\", logReads)\n\t}\n}\n\nfunc TestLogReader_withoutEndOfTextMarker(t *testing.T) {\n\tt.Parallel()\n\n\tlogReads := 0\n\tts, lr := testLogReader(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tlogReads++\n\t\tswitch logReads {\n\t\tcase 2:\n\t\t\tcheckedWrite(t, w, []byte(\"\\x02\"))\n\t\tcase 3:\n\t\t\tcheckedWrite(t, w, []byte(\"Terraform run started\"))\n\t\tcase 16:\n\t\t\tcheckedWrite(t, w, []byte(\" - logs - \"))\n\t\tcase 31:\n\t\t\tcheckedWrite(t, w, []byte(\"Terraform run finished\"))\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tdoneReads := 0\n\tlr.done = func() (bool, error) {\n\t\tdoneReads++\n\t\tif logReads >= 31 {\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, nil\n\t}\n\n\tlogs, err := io.ReadAll(lr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := \"Terraform run started - logs - Terraform run finished\"\n\tif string(logs) != expected {\n\t\tt.Fatalf(\"expected %s, got: %s\", expected, string(logs))\n\t}\n\tif doneReads != 3 {\n\t\tt.Fatalf(\"expected 3 done reads, got %d reads\", doneReads)\n\t}\n\tif logReads != 42 {\n\t\tt.Fatalf(\"expected 42 log reads, got %d reads\", logReads)\n\t}\n}\n"
  },
  {
    "path": "mocks/admin_opa_version_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_opa_version.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_opa_version.go -destination=mocks/admin_opa_version_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAdminOPAVersions is a mock of AdminOPAVersions interface.\ntype MockAdminOPAVersions struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAdminOPAVersionsMockRecorder\n}\n\n// MockAdminOPAVersionsMockRecorder is the mock recorder for MockAdminOPAVersions.\ntype MockAdminOPAVersionsMockRecorder struct {\n\tmock *MockAdminOPAVersions\n}\n\n// NewMockAdminOPAVersions creates a new mock instance.\nfunc NewMockAdminOPAVersions(ctrl *gomock.Controller) *MockAdminOPAVersions {\n\tmock := &MockAdminOPAVersions{ctrl: ctrl}\n\tmock.recorder = &MockAdminOPAVersionsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAdminOPAVersions) EXPECT() *MockAdminOPAVersionsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockAdminOPAVersions) Create(ctx context.Context, options tfe.AdminOPAVersionCreateOptions) (*tfe.AdminOPAVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminOPAVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockAdminOPAVersionsMockRecorder) Create(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockAdminOPAVersions)(nil).Create), ctx, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockAdminOPAVersions) Delete(ctx context.Context, id string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, id)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockAdminOPAVersionsMockRecorder) Delete(ctx, id any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockAdminOPAVersions)(nil).Delete), ctx, id)\n}\n\n// List mocks base method.\nfunc (m *MockAdminOPAVersions) List(ctx context.Context, options *tfe.AdminOPAVersionsListOptions) (*tfe.AdminOPAVersionsList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminOPAVersionsList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAdminOPAVersionsMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAdminOPAVersions)(nil).List), ctx, options)\n}\n\n// Read mocks base method.\nfunc (m *MockAdminOPAVersions) Read(ctx context.Context, id string) (*tfe.AdminOPAVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, id)\n\tret0, _ := ret[0].(*tfe.AdminOPAVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockAdminOPAVersionsMockRecorder) Read(ctx, id any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockAdminOPAVersions)(nil).Read), ctx, id)\n}\n\n// Update mocks base method.\nfunc (m *MockAdminOPAVersions) Update(ctx context.Context, id string, options tfe.AdminOPAVersionUpdateOptions) (*tfe.AdminOPAVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, id, options)\n\tret0, _ := ret[0].(*tfe.AdminOPAVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockAdminOPAVersionsMockRecorder) Update(ctx, id, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockAdminOPAVersions)(nil).Update), ctx, id, options)\n}\n"
  },
  {
    "path": "mocks/admin_organization_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_organization.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_organization.go -destination=mocks/admin_organization_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAdminOrganizations is a mock of AdminOrganizations interface.\ntype MockAdminOrganizations struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAdminOrganizationsMockRecorder\n}\n\n// MockAdminOrganizationsMockRecorder is the mock recorder for MockAdminOrganizations.\ntype MockAdminOrganizationsMockRecorder struct {\n\tmock *MockAdminOrganizations\n}\n\n// NewMockAdminOrganizations creates a new mock instance.\nfunc NewMockAdminOrganizations(ctrl *gomock.Controller) *MockAdminOrganizations {\n\tmock := &MockAdminOrganizations{ctrl: ctrl}\n\tmock.recorder = &MockAdminOrganizationsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAdminOrganizations) EXPECT() *MockAdminOrganizationsMockRecorder {\n\treturn m.recorder\n}\n\n// Delete mocks base method.\nfunc (m *MockAdminOrganizations) Delete(ctx context.Context, organization string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, organization)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockAdminOrganizationsMockRecorder) Delete(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockAdminOrganizations)(nil).Delete), ctx, organization)\n}\n\n// List mocks base method.\nfunc (m *MockAdminOrganizations) List(ctx context.Context, options *tfe.AdminOrganizationListOptions) (*tfe.AdminOrganizationList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminOrganizationList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAdminOrganizationsMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAdminOrganizations)(nil).List), ctx, options)\n}\n\n// ListModuleConsumers mocks base method.\nfunc (m *MockAdminOrganizations) ListModuleConsumers(ctx context.Context, organization string, options *tfe.AdminOrganizationListModuleConsumersOptions) (*tfe.AdminOrganizationList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListModuleConsumers\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.AdminOrganizationList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListModuleConsumers indicates an expected call of ListModuleConsumers.\nfunc (mr *MockAdminOrganizationsMockRecorder) ListModuleConsumers(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListModuleConsumers\", reflect.TypeOf((*MockAdminOrganizations)(nil).ListModuleConsumers), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockAdminOrganizations) Read(ctx context.Context, organization string) (*tfe.AdminOrganization, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, organization)\n\tret0, _ := ret[0].(*tfe.AdminOrganization)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockAdminOrganizationsMockRecorder) Read(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockAdminOrganizations)(nil).Read), ctx, organization)\n}\n\n// Update mocks base method.\nfunc (m *MockAdminOrganizations) Update(ctx context.Context, organization string, options tfe.AdminOrganizationUpdateOptions) (*tfe.AdminOrganization, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.AdminOrganization)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockAdminOrganizationsMockRecorder) Update(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockAdminOrganizations)(nil).Update), ctx, organization, options)\n}\n\n// UpdateModuleConsumers mocks base method.\nfunc (m *MockAdminOrganizations) UpdateModuleConsumers(ctx context.Context, organization string, consumerOrganizations []string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateModuleConsumers\", ctx, organization, consumerOrganizations)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpdateModuleConsumers indicates an expected call of UpdateModuleConsumers.\nfunc (mr *MockAdminOrganizationsMockRecorder) UpdateModuleConsumers(ctx, organization, consumerOrganizations any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateModuleConsumers\", reflect.TypeOf((*MockAdminOrganizations)(nil).UpdateModuleConsumers), ctx, organization, consumerOrganizations)\n}\n"
  },
  {
    "path": "mocks/admin_run_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_run.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_run.go -destination=mocks/admin_run_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAdminRuns is a mock of AdminRuns interface.\ntype MockAdminRuns struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAdminRunsMockRecorder\n}\n\n// MockAdminRunsMockRecorder is the mock recorder for MockAdminRuns.\ntype MockAdminRunsMockRecorder struct {\n\tmock *MockAdminRuns\n}\n\n// NewMockAdminRuns creates a new mock instance.\nfunc NewMockAdminRuns(ctrl *gomock.Controller) *MockAdminRuns {\n\tmock := &MockAdminRuns{ctrl: ctrl}\n\tmock.recorder = &MockAdminRunsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAdminRuns) EXPECT() *MockAdminRunsMockRecorder {\n\treturn m.recorder\n}\n\n// ForceCancel mocks base method.\nfunc (m *MockAdminRuns) ForceCancel(ctx context.Context, runID string, options tfe.AdminRunForceCancelOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ForceCancel\", ctx, runID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ForceCancel indicates an expected call of ForceCancel.\nfunc (mr *MockAdminRunsMockRecorder) ForceCancel(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ForceCancel\", reflect.TypeOf((*MockAdminRuns)(nil).ForceCancel), ctx, runID, options)\n}\n\n// List mocks base method.\nfunc (m *MockAdminRuns) List(ctx context.Context, options *tfe.AdminRunsListOptions) (*tfe.AdminRunsList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminRunsList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAdminRunsMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAdminRuns)(nil).List), ctx, options)\n}\n"
  },
  {
    "path": "mocks/admin_sentinel_version_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_sentinel_version.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_sentinel_version.go -destination=mocks/admin_sentinel_version_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAdminSentinelVersions is a mock of AdminSentinelVersions interface.\ntype MockAdminSentinelVersions struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAdminSentinelVersionsMockRecorder\n}\n\n// MockAdminSentinelVersionsMockRecorder is the mock recorder for MockAdminSentinelVersions.\ntype MockAdminSentinelVersionsMockRecorder struct {\n\tmock *MockAdminSentinelVersions\n}\n\n// NewMockAdminSentinelVersions creates a new mock instance.\nfunc NewMockAdminSentinelVersions(ctrl *gomock.Controller) *MockAdminSentinelVersions {\n\tmock := &MockAdminSentinelVersions{ctrl: ctrl}\n\tmock.recorder = &MockAdminSentinelVersionsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAdminSentinelVersions) EXPECT() *MockAdminSentinelVersionsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockAdminSentinelVersions) Create(ctx context.Context, options tfe.AdminSentinelVersionCreateOptions) (*tfe.AdminSentinelVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminSentinelVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockAdminSentinelVersionsMockRecorder) Create(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockAdminSentinelVersions)(nil).Create), ctx, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockAdminSentinelVersions) Delete(ctx context.Context, id string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, id)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockAdminSentinelVersionsMockRecorder) Delete(ctx, id any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockAdminSentinelVersions)(nil).Delete), ctx, id)\n}\n\n// List mocks base method.\nfunc (m *MockAdminSentinelVersions) List(ctx context.Context, options *tfe.AdminSentinelVersionsListOptions) (*tfe.AdminSentinelVersionsList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminSentinelVersionsList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAdminSentinelVersionsMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAdminSentinelVersions)(nil).List), ctx, options)\n}\n\n// Read mocks base method.\nfunc (m *MockAdminSentinelVersions) Read(ctx context.Context, id string) (*tfe.AdminSentinelVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, id)\n\tret0, _ := ret[0].(*tfe.AdminSentinelVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockAdminSentinelVersionsMockRecorder) Read(ctx, id any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockAdminSentinelVersions)(nil).Read), ctx, id)\n}\n\n// Update mocks base method.\nfunc (m *MockAdminSentinelVersions) Update(ctx context.Context, id string, options tfe.AdminSentinelVersionUpdateOptions) (*tfe.AdminSentinelVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, id, options)\n\tret0, _ := ret[0].(*tfe.AdminSentinelVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockAdminSentinelVersionsMockRecorder) Update(ctx, id, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockAdminSentinelVersions)(nil).Update), ctx, id, options)\n}\n"
  },
  {
    "path": "mocks/admin_setting_cost_estimation_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting_cost_estimation.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting_cost_estimation.go -destination=mocks/admin_setting_cost_estimation_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockCostEstimationSettings is a mock of CostEstimationSettings interface.\ntype MockCostEstimationSettings struct {\n\tctrl     *gomock.Controller\n\trecorder *MockCostEstimationSettingsMockRecorder\n}\n\n// MockCostEstimationSettingsMockRecorder is the mock recorder for MockCostEstimationSettings.\ntype MockCostEstimationSettingsMockRecorder struct {\n\tmock *MockCostEstimationSettings\n}\n\n// NewMockCostEstimationSettings creates a new mock instance.\nfunc NewMockCostEstimationSettings(ctrl *gomock.Controller) *MockCostEstimationSettings {\n\tmock := &MockCostEstimationSettings{ctrl: ctrl}\n\tmock.recorder = &MockCostEstimationSettingsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockCostEstimationSettings) EXPECT() *MockCostEstimationSettingsMockRecorder {\n\treturn m.recorder\n}\n\n// Read mocks base method.\nfunc (m *MockCostEstimationSettings) Read(ctx context.Context) (*tfe.AdminCostEstimationSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx)\n\tret0, _ := ret[0].(*tfe.AdminCostEstimationSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockCostEstimationSettingsMockRecorder) Read(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockCostEstimationSettings)(nil).Read), ctx)\n}\n\n// Update mocks base method.\nfunc (m *MockCostEstimationSettings) Update(ctx context.Context, options tfe.AdminCostEstimationSettingOptions) (*tfe.AdminCostEstimationSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminCostEstimationSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockCostEstimationSettingsMockRecorder) Update(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockCostEstimationSettings)(nil).Update), ctx, options)\n}\n"
  },
  {
    "path": "mocks/admin_setting_customization_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting_customization.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting_customization.go -destination=mocks/admin_setting_customization_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockCustomizationSettings is a mock of CustomizationSettings interface.\ntype MockCustomizationSettings struct {\n\tctrl     *gomock.Controller\n\trecorder *MockCustomizationSettingsMockRecorder\n}\n\n// MockCustomizationSettingsMockRecorder is the mock recorder for MockCustomizationSettings.\ntype MockCustomizationSettingsMockRecorder struct {\n\tmock *MockCustomizationSettings\n}\n\n// NewMockCustomizationSettings creates a new mock instance.\nfunc NewMockCustomizationSettings(ctrl *gomock.Controller) *MockCustomizationSettings {\n\tmock := &MockCustomizationSettings{ctrl: ctrl}\n\tmock.recorder = &MockCustomizationSettingsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockCustomizationSettings) EXPECT() *MockCustomizationSettingsMockRecorder {\n\treturn m.recorder\n}\n\n// Read mocks base method.\nfunc (m *MockCustomizationSettings) Read(ctx context.Context) (*tfe.AdminCustomizationSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx)\n\tret0, _ := ret[0].(*tfe.AdminCustomizationSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockCustomizationSettingsMockRecorder) Read(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockCustomizationSettings)(nil).Read), ctx)\n}\n\n// Update mocks base method.\nfunc (m *MockCustomizationSettings) Update(ctx context.Context, options tfe.AdminCustomizationSettingsUpdateOptions) (*tfe.AdminCustomizationSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminCustomizationSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockCustomizationSettingsMockRecorder) Update(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockCustomizationSettings)(nil).Update), ctx, options)\n}\n"
  },
  {
    "path": "mocks/admin_setting_general_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting_general.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting_general.go -destination=mocks/admin_setting_general_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockGeneralSettings is a mock of GeneralSettings interface.\ntype MockGeneralSettings struct {\n\tctrl     *gomock.Controller\n\trecorder *MockGeneralSettingsMockRecorder\n}\n\n// MockGeneralSettingsMockRecorder is the mock recorder for MockGeneralSettings.\ntype MockGeneralSettingsMockRecorder struct {\n\tmock *MockGeneralSettings\n}\n\n// NewMockGeneralSettings creates a new mock instance.\nfunc NewMockGeneralSettings(ctrl *gomock.Controller) *MockGeneralSettings {\n\tmock := &MockGeneralSettings{ctrl: ctrl}\n\tmock.recorder = &MockGeneralSettingsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockGeneralSettings) EXPECT() *MockGeneralSettingsMockRecorder {\n\treturn m.recorder\n}\n\n// Read mocks base method.\nfunc (m *MockGeneralSettings) Read(ctx context.Context) (*tfe.AdminGeneralSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx)\n\tret0, _ := ret[0].(*tfe.AdminGeneralSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockGeneralSettingsMockRecorder) Read(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockGeneralSettings)(nil).Read), ctx)\n}\n\n// Update mocks base method.\nfunc (m *MockGeneralSettings) Update(ctx context.Context, options tfe.AdminGeneralSettingsUpdateOptions) (*tfe.AdminGeneralSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminGeneralSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockGeneralSettingsMockRecorder) Update(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockGeneralSettings)(nil).Update), ctx, options)\n}\n"
  },
  {
    "path": "mocks/admin_setting_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting.go -destination=mocks/admin_setting_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n"
  },
  {
    "path": "mocks/admin_setting_oidc_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting_oidc.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting_oidc.go -destination=mocks/admin_setting_oidc_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockOIDCSettings is a mock of OIDCSettings interface.\ntype MockOIDCSettings struct {\n\tctrl     *gomock.Controller\n\trecorder *MockOIDCSettingsMockRecorder\n}\n\n// MockOIDCSettingsMockRecorder is the mock recorder for MockOIDCSettings.\ntype MockOIDCSettingsMockRecorder struct {\n\tmock *MockOIDCSettings\n}\n\n// NewMockOIDCSettings creates a new mock instance.\nfunc NewMockOIDCSettings(ctrl *gomock.Controller) *MockOIDCSettings {\n\tmock := &MockOIDCSettings{ctrl: ctrl}\n\tmock.recorder = &MockOIDCSettingsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockOIDCSettings) EXPECT() *MockOIDCSettingsMockRecorder {\n\treturn m.recorder\n}\n\n// RotateKey mocks base method.\nfunc (m *MockOIDCSettings) RotateKey(ctx context.Context) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RotateKey\", ctx)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RotateKey indicates an expected call of RotateKey.\nfunc (mr *MockOIDCSettingsMockRecorder) RotateKey(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RotateKey\", reflect.TypeOf((*MockOIDCSettings)(nil).RotateKey), ctx)\n}\n\n// TrimKey mocks base method.\nfunc (m *MockOIDCSettings) TrimKey(ctx context.Context) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"TrimKey\", ctx)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// TrimKey indicates an expected call of TrimKey.\nfunc (mr *MockOIDCSettingsMockRecorder) TrimKey(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"TrimKey\", reflect.TypeOf((*MockOIDCSettings)(nil).TrimKey), ctx)\n}\n"
  },
  {
    "path": "mocks/admin_setting_saml_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting_saml.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting_saml.go -destination=mocks/admin_setting_saml_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockSAMLSettings is a mock of SAMLSettings interface.\ntype MockSAMLSettings struct {\n\tctrl     *gomock.Controller\n\trecorder *MockSAMLSettingsMockRecorder\n}\n\n// MockSAMLSettingsMockRecorder is the mock recorder for MockSAMLSettings.\ntype MockSAMLSettingsMockRecorder struct {\n\tmock *MockSAMLSettings\n}\n\n// NewMockSAMLSettings creates a new mock instance.\nfunc NewMockSAMLSettings(ctrl *gomock.Controller) *MockSAMLSettings {\n\tmock := &MockSAMLSettings{ctrl: ctrl}\n\tmock.recorder = &MockSAMLSettingsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockSAMLSettings) EXPECT() *MockSAMLSettingsMockRecorder {\n\treturn m.recorder\n}\n\n// Read mocks base method.\nfunc (m *MockSAMLSettings) Read(ctx context.Context) (*tfe.AdminSAMLSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx)\n\tret0, _ := ret[0].(*tfe.AdminSAMLSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockSAMLSettingsMockRecorder) Read(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockSAMLSettings)(nil).Read), ctx)\n}\n\n// RevokeIdpCert mocks base method.\nfunc (m *MockSAMLSettings) RevokeIdpCert(ctx context.Context) (*tfe.AdminSAMLSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RevokeIdpCert\", ctx)\n\tret0, _ := ret[0].(*tfe.AdminSAMLSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// RevokeIdpCert indicates an expected call of RevokeIdpCert.\nfunc (mr *MockSAMLSettingsMockRecorder) RevokeIdpCert(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RevokeIdpCert\", reflect.TypeOf((*MockSAMLSettings)(nil).RevokeIdpCert), ctx)\n}\n\n// Update mocks base method.\nfunc (m *MockSAMLSettings) Update(ctx context.Context, options tfe.AdminSAMLSettingsUpdateOptions) (*tfe.AdminSAMLSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminSAMLSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockSAMLSettingsMockRecorder) Update(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockSAMLSettings)(nil).Update), ctx, options)\n}\n"
  },
  {
    "path": "mocks/admin_setting_scim_groups_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting_scim_groups.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting_scim_groups.go -destination=mocks/admin_setting_scim_groups_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAdminSCIMGroups is a mock of AdminSCIMGroups interface.\ntype MockAdminSCIMGroups struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAdminSCIMGroupsMockRecorder\n}\n\n// MockAdminSCIMGroupsMockRecorder is the mock recorder for MockAdminSCIMGroups.\ntype MockAdminSCIMGroupsMockRecorder struct {\n\tmock *MockAdminSCIMGroups\n}\n\n// NewMockAdminSCIMGroups creates a new mock instance.\nfunc NewMockAdminSCIMGroups(ctrl *gomock.Controller) *MockAdminSCIMGroups {\n\tmock := &MockAdminSCIMGroups{ctrl: ctrl}\n\tmock.recorder = &MockAdminSCIMGroupsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAdminSCIMGroups) EXPECT() *MockAdminSCIMGroupsMockRecorder {\n\treturn m.recorder\n}\n\n// List mocks base method.\nfunc (m *MockAdminSCIMGroups) List(ctx context.Context, options *tfe.AdminSCIMGroupListOptions) (*tfe.AdminSCIMGroupList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminSCIMGroupList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAdminSCIMGroupsMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAdminSCIMGroups)(nil).List), ctx, options)\n}\n"
  },
  {
    "path": "mocks/admin_setting_scim_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting_scim.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting_scim.go -destination=mocks/admin_setting_scim_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockSCIMSettings is a mock of SCIMSettings interface.\ntype MockSCIMSettings struct {\n\tctrl     *gomock.Controller\n\trecorder *MockSCIMSettingsMockRecorder\n}\n\n// MockSCIMSettingsMockRecorder is the mock recorder for MockSCIMSettings.\ntype MockSCIMSettingsMockRecorder struct {\n\tmock *MockSCIMSettings\n}\n\n// NewMockSCIMSettings creates a new mock instance.\nfunc NewMockSCIMSettings(ctrl *gomock.Controller) *MockSCIMSettings {\n\tmock := &MockSCIMSettings{ctrl: ctrl}\n\tmock.recorder = &MockSCIMSettingsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockSCIMSettings) EXPECT() *MockSCIMSettingsMockRecorder {\n\treturn m.recorder\n}\n\n// Delete mocks base method.\nfunc (m *MockSCIMSettings) Delete(ctx context.Context) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockSCIMSettingsMockRecorder) Delete(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockSCIMSettings)(nil).Delete), ctx)\n}\n\n// Read mocks base method.\nfunc (m *MockSCIMSettings) Read(ctx context.Context) (*tfe.AdminSCIMSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx)\n\tret0, _ := ret[0].(*tfe.AdminSCIMSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockSCIMSettingsMockRecorder) Read(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockSCIMSettings)(nil).Read), ctx)\n}\n\n// Update mocks base method.\nfunc (m *MockSCIMSettings) Update(ctx context.Context, options tfe.AdminSCIMSettingUpdateOptions) (*tfe.AdminSCIMSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminSCIMSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockSCIMSettingsMockRecorder) Update(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockSCIMSettings)(nil).Update), ctx, options)\n}\n"
  },
  {
    "path": "mocks/admin_setting_scim_token_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting_scim_token.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting_scim_token.go -destination=mocks/admin_setting_scim_token_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAdminSCIMTokens is a mock of AdminSCIMTokens interface.\ntype MockAdminSCIMTokens struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAdminSCIMTokensMockRecorder\n}\n\n// MockAdminSCIMTokensMockRecorder is the mock recorder for MockAdminSCIMTokens.\ntype MockAdminSCIMTokensMockRecorder struct {\n\tmock *MockAdminSCIMTokens\n}\n\n// NewMockAdminSCIMTokens creates a new mock instance.\nfunc NewMockAdminSCIMTokens(ctrl *gomock.Controller) *MockAdminSCIMTokens {\n\tmock := &MockAdminSCIMTokens{ctrl: ctrl}\n\tmock.recorder = &MockAdminSCIMTokensMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAdminSCIMTokens) EXPECT() *MockAdminSCIMTokensMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockAdminSCIMTokens) Create(ctx context.Context, description string) (*tfe.AdminSCIMToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, description)\n\tret0, _ := ret[0].(*tfe.AdminSCIMToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockAdminSCIMTokensMockRecorder) Create(ctx, description any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockAdminSCIMTokens)(nil).Create), ctx, description)\n}\n\n// CreateWithOptions mocks base method.\nfunc (m *MockAdminSCIMTokens) CreateWithOptions(ctx context.Context, options tfe.AdminSCIMTokenCreateOptions) (*tfe.AdminSCIMToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateWithOptions\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminSCIMToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateWithOptions indicates an expected call of CreateWithOptions.\nfunc (mr *MockAdminSCIMTokensMockRecorder) CreateWithOptions(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateWithOptions\", reflect.TypeOf((*MockAdminSCIMTokens)(nil).CreateWithOptions), ctx, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockAdminSCIMTokens) Delete(ctx context.Context, scimTokenID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, scimTokenID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockAdminSCIMTokensMockRecorder) Delete(ctx, scimTokenID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockAdminSCIMTokens)(nil).Delete), ctx, scimTokenID)\n}\n\n// List mocks base method.\nfunc (m *MockAdminSCIMTokens) List(ctx context.Context) (*tfe.AdminSCIMTokenList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx)\n\tret0, _ := ret[0].(*tfe.AdminSCIMTokenList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAdminSCIMTokensMockRecorder) List(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAdminSCIMTokens)(nil).List), ctx)\n}\n\n// Read mocks base method.\nfunc (m *MockAdminSCIMTokens) Read(ctx context.Context, scimTokenID string) (*tfe.AdminSCIMToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, scimTokenID)\n\tret0, _ := ret[0].(*tfe.AdminSCIMToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockAdminSCIMTokensMockRecorder) Read(ctx, scimTokenID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockAdminSCIMTokens)(nil).Read), ctx, scimTokenID)\n}\n"
  },
  {
    "path": "mocks/admin_setting_smtp_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting_smtp.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting_smtp.go -destination=mocks/admin_setting_smtp_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockSMTPSettings is a mock of SMTPSettings interface.\ntype MockSMTPSettings struct {\n\tctrl     *gomock.Controller\n\trecorder *MockSMTPSettingsMockRecorder\n}\n\n// MockSMTPSettingsMockRecorder is the mock recorder for MockSMTPSettings.\ntype MockSMTPSettingsMockRecorder struct {\n\tmock *MockSMTPSettings\n}\n\n// NewMockSMTPSettings creates a new mock instance.\nfunc NewMockSMTPSettings(ctrl *gomock.Controller) *MockSMTPSettings {\n\tmock := &MockSMTPSettings{ctrl: ctrl}\n\tmock.recorder = &MockSMTPSettingsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockSMTPSettings) EXPECT() *MockSMTPSettingsMockRecorder {\n\treturn m.recorder\n}\n\n// Read mocks base method.\nfunc (m *MockSMTPSettings) Read(ctx context.Context) (*tfe.AdminSMTPSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx)\n\tret0, _ := ret[0].(*tfe.AdminSMTPSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockSMTPSettingsMockRecorder) Read(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockSMTPSettings)(nil).Read), ctx)\n}\n\n// Update mocks base method.\nfunc (m *MockSMTPSettings) Update(ctx context.Context, options tfe.AdminSMTPSettingsUpdateOptions) (*tfe.AdminSMTPSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminSMTPSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockSMTPSettingsMockRecorder) Update(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockSMTPSettings)(nil).Update), ctx, options)\n}\n"
  },
  {
    "path": "mocks/admin_setting_twilio_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_setting_twilio.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_setting_twilio.go -destination=mocks/admin_setting_twilio_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockTwilioSettings is a mock of TwilioSettings interface.\ntype MockTwilioSettings struct {\n\tctrl     *gomock.Controller\n\trecorder *MockTwilioSettingsMockRecorder\n}\n\n// MockTwilioSettingsMockRecorder is the mock recorder for MockTwilioSettings.\ntype MockTwilioSettingsMockRecorder struct {\n\tmock *MockTwilioSettings\n}\n\n// NewMockTwilioSettings creates a new mock instance.\nfunc NewMockTwilioSettings(ctrl *gomock.Controller) *MockTwilioSettings {\n\tmock := &MockTwilioSettings{ctrl: ctrl}\n\tmock.recorder = &MockTwilioSettingsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockTwilioSettings) EXPECT() *MockTwilioSettingsMockRecorder {\n\treturn m.recorder\n}\n\n// Read mocks base method.\nfunc (m *MockTwilioSettings) Read(ctx context.Context) (*tfe.AdminTwilioSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx)\n\tret0, _ := ret[0].(*tfe.AdminTwilioSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockTwilioSettingsMockRecorder) Read(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockTwilioSettings)(nil).Read), ctx)\n}\n\n// Update mocks base method.\nfunc (m *MockTwilioSettings) Update(ctx context.Context, options tfe.AdminTwilioSettingsUpdateOptions) (*tfe.AdminTwilioSetting, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminTwilioSetting)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockTwilioSettingsMockRecorder) Update(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockTwilioSettings)(nil).Update), ctx, options)\n}\n\n// Verify mocks base method.\nfunc (m *MockTwilioSettings) Verify(ctx context.Context, options tfe.AdminTwilioSettingsVerifyOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Verify\", ctx, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Verify indicates an expected call of Verify.\nfunc (mr *MockTwilioSettingsMockRecorder) Verify(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Verify\", reflect.TypeOf((*MockTwilioSettings)(nil).Verify), ctx, options)\n}\n"
  },
  {
    "path": "mocks/admin_terraform_version_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_terraform_version.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_terraform_version.go -destination=mocks/admin_terraform_version_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAdminTerraformVersions is a mock of AdminTerraformVersions interface.\ntype MockAdminTerraformVersions struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAdminTerraformVersionsMockRecorder\n}\n\n// MockAdminTerraformVersionsMockRecorder is the mock recorder for MockAdminTerraformVersions.\ntype MockAdminTerraformVersionsMockRecorder struct {\n\tmock *MockAdminTerraformVersions\n}\n\n// NewMockAdminTerraformVersions creates a new mock instance.\nfunc NewMockAdminTerraformVersions(ctrl *gomock.Controller) *MockAdminTerraformVersions {\n\tmock := &MockAdminTerraformVersions{ctrl: ctrl}\n\tmock.recorder = &MockAdminTerraformVersionsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAdminTerraformVersions) EXPECT() *MockAdminTerraformVersionsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockAdminTerraformVersions) Create(ctx context.Context, options tfe.AdminTerraformVersionCreateOptions) (*tfe.AdminTerraformVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminTerraformVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockAdminTerraformVersionsMockRecorder) Create(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockAdminTerraformVersions)(nil).Create), ctx, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockAdminTerraformVersions) Delete(ctx context.Context, id string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, id)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockAdminTerraformVersionsMockRecorder) Delete(ctx, id any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockAdminTerraformVersions)(nil).Delete), ctx, id)\n}\n\n// List mocks base method.\nfunc (m *MockAdminTerraformVersions) List(ctx context.Context, options *tfe.AdminTerraformVersionsListOptions) (*tfe.AdminTerraformVersionsList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminTerraformVersionsList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAdminTerraformVersionsMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAdminTerraformVersions)(nil).List), ctx, options)\n}\n\n// Read mocks base method.\nfunc (m *MockAdminTerraformVersions) Read(ctx context.Context, id string) (*tfe.AdminTerraformVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, id)\n\tret0, _ := ret[0].(*tfe.AdminTerraformVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockAdminTerraformVersionsMockRecorder) Read(ctx, id any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockAdminTerraformVersions)(nil).Read), ctx, id)\n}\n\n// Update mocks base method.\nfunc (m *MockAdminTerraformVersions) Update(ctx context.Context, id string, options tfe.AdminTerraformVersionUpdateOptions) (*tfe.AdminTerraformVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, id, options)\n\tret0, _ := ret[0].(*tfe.AdminTerraformVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockAdminTerraformVersionsMockRecorder) Update(ctx, id, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockAdminTerraformVersions)(nil).Update), ctx, id, options)\n}\n"
  },
  {
    "path": "mocks/admin_user_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_user.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_user.go -destination=mocks/admin_user_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAdminUsers is a mock of AdminUsers interface.\ntype MockAdminUsers struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAdminUsersMockRecorder\n}\n\n// MockAdminUsersMockRecorder is the mock recorder for MockAdminUsers.\ntype MockAdminUsersMockRecorder struct {\n\tmock *MockAdminUsers\n}\n\n// NewMockAdminUsers creates a new mock instance.\nfunc NewMockAdminUsers(ctrl *gomock.Controller) *MockAdminUsers {\n\tmock := &MockAdminUsers{ctrl: ctrl}\n\tmock.recorder = &MockAdminUsersMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAdminUsers) EXPECT() *MockAdminUsersMockRecorder {\n\treturn m.recorder\n}\n\n// Delete mocks base method.\nfunc (m *MockAdminUsers) Delete(ctx context.Context, userID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, userID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockAdminUsersMockRecorder) Delete(ctx, userID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockAdminUsers)(nil).Delete), ctx, userID)\n}\n\n// Disable2FA mocks base method.\nfunc (m *MockAdminUsers) Disable2FA(ctx context.Context, userID string) (*tfe.AdminUser, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Disable2FA\", ctx, userID)\n\tret0, _ := ret[0].(*tfe.AdminUser)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Disable2FA indicates an expected call of Disable2FA.\nfunc (mr *MockAdminUsersMockRecorder) Disable2FA(ctx, userID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Disable2FA\", reflect.TypeOf((*MockAdminUsers)(nil).Disable2FA), ctx, userID)\n}\n\n// GrantAdmin mocks base method.\nfunc (m *MockAdminUsers) GrantAdmin(ctx context.Context, userID string) (*tfe.AdminUser, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GrantAdmin\", ctx, userID)\n\tret0, _ := ret[0].(*tfe.AdminUser)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GrantAdmin indicates an expected call of GrantAdmin.\nfunc (mr *MockAdminUsersMockRecorder) GrantAdmin(ctx, userID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GrantAdmin\", reflect.TypeOf((*MockAdminUsers)(nil).GrantAdmin), ctx, userID)\n}\n\n// List mocks base method.\nfunc (m *MockAdminUsers) List(ctx context.Context, options *tfe.AdminUserListOptions) (*tfe.AdminUserList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminUserList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAdminUsersMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAdminUsers)(nil).List), ctx, options)\n}\n\n// RevokeAdmin mocks base method.\nfunc (m *MockAdminUsers) RevokeAdmin(ctx context.Context, userID string) (*tfe.AdminUser, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RevokeAdmin\", ctx, userID)\n\tret0, _ := ret[0].(*tfe.AdminUser)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// RevokeAdmin indicates an expected call of RevokeAdmin.\nfunc (mr *MockAdminUsersMockRecorder) RevokeAdmin(ctx, userID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RevokeAdmin\", reflect.TypeOf((*MockAdminUsers)(nil).RevokeAdmin), ctx, userID)\n}\n\n// Suspend mocks base method.\nfunc (m *MockAdminUsers) Suspend(ctx context.Context, userID string) (*tfe.AdminUser, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Suspend\", ctx, userID)\n\tret0, _ := ret[0].(*tfe.AdminUser)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Suspend indicates an expected call of Suspend.\nfunc (mr *MockAdminUsersMockRecorder) Suspend(ctx, userID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Suspend\", reflect.TypeOf((*MockAdminUsers)(nil).Suspend), ctx, userID)\n}\n\n// Unsuspend mocks base method.\nfunc (m *MockAdminUsers) Unsuspend(ctx context.Context, userID string) (*tfe.AdminUser, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Unsuspend\", ctx, userID)\n\tret0, _ := ret[0].(*tfe.AdminUser)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Unsuspend indicates an expected call of Unsuspend.\nfunc (mr *MockAdminUsersMockRecorder) Unsuspend(ctx, userID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Unsuspend\", reflect.TypeOf((*MockAdminUsers)(nil).Unsuspend), ctx, userID)\n}\n"
  },
  {
    "path": "mocks/admin_workspace_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: admin_workspace.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=admin_workspace.go -destination=mocks/admin_workspace_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAdminWorkspaces is a mock of AdminWorkspaces interface.\ntype MockAdminWorkspaces struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAdminWorkspacesMockRecorder\n}\n\n// MockAdminWorkspacesMockRecorder is the mock recorder for MockAdminWorkspaces.\ntype MockAdminWorkspacesMockRecorder struct {\n\tmock *MockAdminWorkspaces\n}\n\n// NewMockAdminWorkspaces creates a new mock instance.\nfunc NewMockAdminWorkspaces(ctrl *gomock.Controller) *MockAdminWorkspaces {\n\tmock := &MockAdminWorkspaces{ctrl: ctrl}\n\tmock.recorder = &MockAdminWorkspacesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAdminWorkspaces) EXPECT() *MockAdminWorkspacesMockRecorder {\n\treturn m.recorder\n}\n\n// Delete mocks base method.\nfunc (m *MockAdminWorkspaces) Delete(ctx context.Context, workspaceID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, workspaceID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockAdminWorkspacesMockRecorder) Delete(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockAdminWorkspaces)(nil).Delete), ctx, workspaceID)\n}\n\n// List mocks base method.\nfunc (m *MockAdminWorkspaces) List(ctx context.Context, options *tfe.AdminWorkspaceListOptions) (*tfe.AdminWorkspaceList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AdminWorkspaceList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAdminWorkspacesMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAdminWorkspaces)(nil).List), ctx, options)\n}\n\n// Read mocks base method.\nfunc (m *MockAdminWorkspaces) Read(ctx context.Context, workspaceID string) (*tfe.AdminWorkspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, workspaceID)\n\tret0, _ := ret[0].(*tfe.AdminWorkspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockAdminWorkspacesMockRecorder) Read(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockAdminWorkspaces)(nil).Read), ctx, workspaceID)\n}\n"
  },
  {
    "path": "mocks/agent_pool_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: agent_pool.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=agent_pool.go -destination=mocks/agent_pool_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAgentPools is a mock of AgentPools interface.\ntype MockAgentPools struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAgentPoolsMockRecorder\n}\n\n// MockAgentPoolsMockRecorder is the mock recorder for MockAgentPools.\ntype MockAgentPoolsMockRecorder struct {\n\tmock *MockAgentPools\n}\n\n// NewMockAgentPools creates a new mock instance.\nfunc NewMockAgentPools(ctrl *gomock.Controller) *MockAgentPools {\n\tmock := &MockAgentPools{ctrl: ctrl}\n\tmock.recorder = &MockAgentPoolsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAgentPools) EXPECT() *MockAgentPoolsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockAgentPools) Create(ctx context.Context, organization string, options tfe.AgentPoolCreateOptions) (*tfe.AgentPool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.AgentPool)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockAgentPoolsMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockAgentPools)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockAgentPools) Delete(ctx context.Context, agentPoolID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, agentPoolID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockAgentPoolsMockRecorder) Delete(ctx, agentPoolID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockAgentPools)(nil).Delete), ctx, agentPoolID)\n}\n\n// List mocks base method.\nfunc (m *MockAgentPools) List(ctx context.Context, organization string, options *tfe.AgentPoolListOptions) (*tfe.AgentPoolList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.AgentPoolList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAgentPoolsMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAgentPools)(nil).List), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockAgentPools) Read(ctx context.Context, agentPoolID string) (*tfe.AgentPool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, agentPoolID)\n\tret0, _ := ret[0].(*tfe.AgentPool)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockAgentPoolsMockRecorder) Read(ctx, agentPoolID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockAgentPools)(nil).Read), ctx, agentPoolID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockAgentPools) ReadWithOptions(ctx context.Context, agentPoolID string, options *tfe.AgentPoolReadOptions) (*tfe.AgentPool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, agentPoolID, options)\n\tret0, _ := ret[0].(*tfe.AgentPool)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockAgentPoolsMockRecorder) ReadWithOptions(ctx, agentPoolID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockAgentPools)(nil).ReadWithOptions), ctx, agentPoolID, options)\n}\n\n// Update mocks base method.\nfunc (m *MockAgentPools) Update(ctx context.Context, agentPool string, options tfe.AgentPoolUpdateOptions) (*tfe.AgentPool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, agentPool, options)\n\tret0, _ := ret[0].(*tfe.AgentPool)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockAgentPoolsMockRecorder) Update(ctx, agentPool, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockAgentPools)(nil).Update), ctx, agentPool, options)\n}\n\n// UpdateAllowedProjects mocks base method.\nfunc (m *MockAgentPools) UpdateAllowedProjects(ctx context.Context, agentPool string, options tfe.AgentPoolAllowedProjectsUpdateOptions) (*tfe.AgentPool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateAllowedProjects\", ctx, agentPool, options)\n\tret0, _ := ret[0].(*tfe.AgentPool)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateAllowedProjects indicates an expected call of UpdateAllowedProjects.\nfunc (mr *MockAgentPoolsMockRecorder) UpdateAllowedProjects(ctx, agentPool, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateAllowedProjects\", reflect.TypeOf((*MockAgentPools)(nil).UpdateAllowedProjects), ctx, agentPool, options)\n}\n\n// UpdateAllowedWorkspaces mocks base method.\nfunc (m *MockAgentPools) UpdateAllowedWorkspaces(ctx context.Context, agentPool string, options tfe.AgentPoolAllowedWorkspacesUpdateOptions) (*tfe.AgentPool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateAllowedWorkspaces\", ctx, agentPool, options)\n\tret0, _ := ret[0].(*tfe.AgentPool)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateAllowedWorkspaces indicates an expected call of UpdateAllowedWorkspaces.\nfunc (mr *MockAgentPoolsMockRecorder) UpdateAllowedWorkspaces(ctx, agentPool, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateAllowedWorkspaces\", reflect.TypeOf((*MockAgentPools)(nil).UpdateAllowedWorkspaces), ctx, agentPool, options)\n}\n\n// UpdateExcludedWorkspaces mocks base method.\nfunc (m *MockAgentPools) UpdateExcludedWorkspaces(ctx context.Context, agentPool string, options tfe.AgentPoolExcludedWorkspacesUpdateOptions) (*tfe.AgentPool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateExcludedWorkspaces\", ctx, agentPool, options)\n\tret0, _ := ret[0].(*tfe.AgentPool)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateExcludedWorkspaces indicates an expected call of UpdateExcludedWorkspaces.\nfunc (mr *MockAgentPoolsMockRecorder) UpdateExcludedWorkspaces(ctx, agentPool, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateExcludedWorkspaces\", reflect.TypeOf((*MockAgentPools)(nil).UpdateExcludedWorkspaces), ctx, agentPool, options)\n}\n"
  },
  {
    "path": "mocks/agent_token_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: agent_token.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=agent_token.go -destination=mocks/agent_token_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAgentTokens is a mock of AgentTokens interface.\ntype MockAgentTokens struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAgentTokensMockRecorder\n}\n\n// MockAgentTokensMockRecorder is the mock recorder for MockAgentTokens.\ntype MockAgentTokensMockRecorder struct {\n\tmock *MockAgentTokens\n}\n\n// NewMockAgentTokens creates a new mock instance.\nfunc NewMockAgentTokens(ctrl *gomock.Controller) *MockAgentTokens {\n\tmock := &MockAgentTokens{ctrl: ctrl}\n\tmock.recorder = &MockAgentTokensMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAgentTokens) EXPECT() *MockAgentTokensMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockAgentTokens) Create(ctx context.Context, agentPoolID string, options tfe.AgentTokenCreateOptions) (*tfe.AgentToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, agentPoolID, options)\n\tret0, _ := ret[0].(*tfe.AgentToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockAgentTokensMockRecorder) Create(ctx, agentPoolID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockAgentTokens)(nil).Create), ctx, agentPoolID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockAgentTokens) Delete(ctx context.Context, agentTokenID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, agentTokenID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockAgentTokensMockRecorder) Delete(ctx, agentTokenID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockAgentTokens)(nil).Delete), ctx, agentTokenID)\n}\n\n// List mocks base method.\nfunc (m *MockAgentTokens) List(ctx context.Context, agentPoolID string) (*tfe.AgentTokenList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, agentPoolID)\n\tret0, _ := ret[0].(*tfe.AgentTokenList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAgentTokensMockRecorder) List(ctx, agentPoolID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAgentTokens)(nil).List), ctx, agentPoolID)\n}\n\n// Read mocks base method.\nfunc (m *MockAgentTokens) Read(ctx context.Context, agentTokenID string) (*tfe.AgentToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, agentTokenID)\n\tret0, _ := ret[0].(*tfe.AgentToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockAgentTokensMockRecorder) Read(ctx, agentTokenID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockAgentTokens)(nil).Read), ctx, agentTokenID)\n}\n"
  },
  {
    "path": "mocks/agents.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: agent.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=agent.go -destination=mocks/agents.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAgents is a mock of Agents interface.\ntype MockAgents struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAgentsMockRecorder\n}\n\n// MockAgentsMockRecorder is the mock recorder for MockAgents.\ntype MockAgentsMockRecorder struct {\n\tmock *MockAgents\n}\n\n// NewMockAgents creates a new mock instance.\nfunc NewMockAgents(ctrl *gomock.Controller) *MockAgents {\n\tmock := &MockAgents{ctrl: ctrl}\n\tmock.recorder = &MockAgentsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAgents) EXPECT() *MockAgentsMockRecorder {\n\treturn m.recorder\n}\n\n// List mocks base method.\nfunc (m *MockAgents) List(ctx context.Context, agentPoolID string, options *tfe.AgentListOptions) (*tfe.AgentList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, agentPoolID, options)\n\tret0, _ := ret[0].(*tfe.AgentList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAgentsMockRecorder) List(ctx, agentPoolID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAgents)(nil).List), ctx, agentPoolID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockAgents) Read(ctx context.Context, agentID string) (*tfe.Agent, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, agentID)\n\tret0, _ := ret[0].(*tfe.Agent)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockAgentsMockRecorder) Read(ctx, agentID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockAgents)(nil).Read), ctx, agentID)\n}\n"
  },
  {
    "path": "mocks/apply_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: apply.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=apply.go -destination=mocks/apply_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\tio \"io\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockApplies is a mock of Applies interface.\ntype MockApplies struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAppliesMockRecorder\n}\n\n// MockAppliesMockRecorder is the mock recorder for MockApplies.\ntype MockAppliesMockRecorder struct {\n\tmock *MockApplies\n}\n\n// NewMockApplies creates a new mock instance.\nfunc NewMockApplies(ctrl *gomock.Controller) *MockApplies {\n\tmock := &MockApplies{ctrl: ctrl}\n\tmock.recorder = &MockAppliesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockApplies) EXPECT() *MockAppliesMockRecorder {\n\treturn m.recorder\n}\n\n// Logs mocks base method.\nfunc (m *MockApplies) Logs(ctx context.Context, applyID string) (io.Reader, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Logs\", ctx, applyID)\n\tret0, _ := ret[0].(io.Reader)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Logs indicates an expected call of Logs.\nfunc (mr *MockAppliesMockRecorder) Logs(ctx, applyID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Logs\", reflect.TypeOf((*MockApplies)(nil).Logs), ctx, applyID)\n}\n\n// Read mocks base method.\nfunc (m *MockApplies) Read(ctx context.Context, applyID string) (*tfe.Apply, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, applyID)\n\tret0, _ := ret[0].(*tfe.Apply)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockAppliesMockRecorder) Read(ctx, applyID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockApplies)(nil).Read), ctx, applyID)\n}\n"
  },
  {
    "path": "mocks/audit_trail_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: audit_trail.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=audit_trail.go -destination=mocks/audit_trail_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockAuditTrails is a mock of AuditTrails interface.\ntype MockAuditTrails struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAuditTrailsMockRecorder\n}\n\n// MockAuditTrailsMockRecorder is the mock recorder for MockAuditTrails.\ntype MockAuditTrailsMockRecorder struct {\n\tmock *MockAuditTrails\n}\n\n// NewMockAuditTrails creates a new mock instance.\nfunc NewMockAuditTrails(ctrl *gomock.Controller) *MockAuditTrails {\n\tmock := &MockAuditTrails{ctrl: ctrl}\n\tmock.recorder = &MockAuditTrailsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAuditTrails) EXPECT() *MockAuditTrailsMockRecorder {\n\treturn m.recorder\n}\n\n// List mocks base method.\nfunc (m *MockAuditTrails) List(ctx context.Context, options *tfe.AuditTrailListOptions) (*tfe.AuditTrailList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.AuditTrailList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockAuditTrailsMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockAuditTrails)(nil).List), ctx, options)\n}\n"
  },
  {
    "path": "mocks/comment_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: comment.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=comment.go -destination=mocks/comment_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockComments is a mock of Comments interface.\ntype MockComments struct {\n\tctrl     *gomock.Controller\n\trecorder *MockCommentsMockRecorder\n}\n\n// MockCommentsMockRecorder is the mock recorder for MockComments.\ntype MockCommentsMockRecorder struct {\n\tmock *MockComments\n}\n\n// NewMockComments creates a new mock instance.\nfunc NewMockComments(ctrl *gomock.Controller) *MockComments {\n\tmock := &MockComments{ctrl: ctrl}\n\tmock.recorder = &MockCommentsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockComments) EXPECT() *MockCommentsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockComments) Create(ctx context.Context, runID string, options tfe.CommentCreateOptions) (*tfe.Comment, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, runID, options)\n\tret0, _ := ret[0].(*tfe.Comment)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockCommentsMockRecorder) Create(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockComments)(nil).Create), ctx, runID, options)\n}\n\n// List mocks base method.\nfunc (m *MockComments) List(ctx context.Context, runID string) (*tfe.CommentList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, runID)\n\tret0, _ := ret[0].(*tfe.CommentList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockCommentsMockRecorder) List(ctx, runID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockComments)(nil).List), ctx, runID)\n}\n\n// Read mocks base method.\nfunc (m *MockComments) Read(ctx context.Context, commentID string) (*tfe.Comment, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, commentID)\n\tret0, _ := ret[0].(*tfe.Comment)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockCommentsMockRecorder) Read(ctx, commentID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockComments)(nil).Read), ctx, commentID)\n}\n"
  },
  {
    "path": "mocks/configuration_version_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: configuration_version.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=configuration_version.go -destination=mocks/configuration_version_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\tio \"io\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockConfigurationVersions is a mock of ConfigurationVersions interface.\ntype MockConfigurationVersions struct {\n\tctrl     *gomock.Controller\n\trecorder *MockConfigurationVersionsMockRecorder\n}\n\n// MockConfigurationVersionsMockRecorder is the mock recorder for MockConfigurationVersions.\ntype MockConfigurationVersionsMockRecorder struct {\n\tmock *MockConfigurationVersions\n}\n\n// NewMockConfigurationVersions creates a new mock instance.\nfunc NewMockConfigurationVersions(ctrl *gomock.Controller) *MockConfigurationVersions {\n\tmock := &MockConfigurationVersions{ctrl: ctrl}\n\tmock.recorder = &MockConfigurationVersionsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockConfigurationVersions) EXPECT() *MockConfigurationVersionsMockRecorder {\n\treturn m.recorder\n}\n\n// Archive mocks base method.\nfunc (m *MockConfigurationVersions) Archive(ctx context.Context, cvID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Archive\", ctx, cvID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Archive indicates an expected call of Archive.\nfunc (mr *MockConfigurationVersionsMockRecorder) Archive(ctx, cvID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Archive\", reflect.TypeOf((*MockConfigurationVersions)(nil).Archive), ctx, cvID)\n}\n\n// Create mocks base method.\nfunc (m *MockConfigurationVersions) Create(ctx context.Context, workspaceID string, options tfe.ConfigurationVersionCreateOptions) (*tfe.ConfigurationVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.ConfigurationVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockConfigurationVersionsMockRecorder) Create(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockConfigurationVersions)(nil).Create), ctx, workspaceID, options)\n}\n\n// CreateForRegistryModule mocks base method.\nfunc (m *MockConfigurationVersions) CreateForRegistryModule(ctx context.Context, moduleID tfe.RegistryModuleID) (*tfe.ConfigurationVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateForRegistryModule\", ctx, moduleID)\n\tret0, _ := ret[0].(*tfe.ConfigurationVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateForRegistryModule indicates an expected call of CreateForRegistryModule.\nfunc (mr *MockConfigurationVersionsMockRecorder) CreateForRegistryModule(ctx, moduleID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateForRegistryModule\", reflect.TypeOf((*MockConfigurationVersions)(nil).CreateForRegistryModule), ctx, moduleID)\n}\n\n// Download mocks base method.\nfunc (m *MockConfigurationVersions) Download(ctx context.Context, cvID string) ([]byte, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Download\", ctx, cvID)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Download indicates an expected call of Download.\nfunc (mr *MockConfigurationVersionsMockRecorder) Download(ctx, cvID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Download\", reflect.TypeOf((*MockConfigurationVersions)(nil).Download), ctx, cvID)\n}\n\n// List mocks base method.\nfunc (m *MockConfigurationVersions) List(ctx context.Context, workspaceID string, options *tfe.ConfigurationVersionListOptions) (*tfe.ConfigurationVersionList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.ConfigurationVersionList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockConfigurationVersionsMockRecorder) List(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockConfigurationVersions)(nil).List), ctx, workspaceID, options)\n}\n\n// PermanentlyDeleteBackingData mocks base method.\nfunc (m *MockConfigurationVersions) PermanentlyDeleteBackingData(ctx context.Context, svID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PermanentlyDeleteBackingData\", ctx, svID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// PermanentlyDeleteBackingData indicates an expected call of PermanentlyDeleteBackingData.\nfunc (mr *MockConfigurationVersionsMockRecorder) PermanentlyDeleteBackingData(ctx, svID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PermanentlyDeleteBackingData\", reflect.TypeOf((*MockConfigurationVersions)(nil).PermanentlyDeleteBackingData), ctx, svID)\n}\n\n// Read mocks base method.\nfunc (m *MockConfigurationVersions) Read(ctx context.Context, cvID string) (*tfe.ConfigurationVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, cvID)\n\tret0, _ := ret[0].(*tfe.ConfigurationVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockConfigurationVersionsMockRecorder) Read(ctx, cvID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockConfigurationVersions)(nil).Read), ctx, cvID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockConfigurationVersions) ReadWithOptions(ctx context.Context, cvID string, options *tfe.ConfigurationVersionReadOptions) (*tfe.ConfigurationVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, cvID, options)\n\tret0, _ := ret[0].(*tfe.ConfigurationVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockConfigurationVersionsMockRecorder) ReadWithOptions(ctx, cvID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockConfigurationVersions)(nil).ReadWithOptions), ctx, cvID, options)\n}\n\n// RestoreBackingData mocks base method.\nfunc (m *MockConfigurationVersions) RestoreBackingData(ctx context.Context, svID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RestoreBackingData\", ctx, svID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RestoreBackingData indicates an expected call of RestoreBackingData.\nfunc (mr *MockConfigurationVersionsMockRecorder) RestoreBackingData(ctx, svID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RestoreBackingData\", reflect.TypeOf((*MockConfigurationVersions)(nil).RestoreBackingData), ctx, svID)\n}\n\n// SoftDeleteBackingData mocks base method.\nfunc (m *MockConfigurationVersions) SoftDeleteBackingData(ctx context.Context, svID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SoftDeleteBackingData\", ctx, svID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SoftDeleteBackingData indicates an expected call of SoftDeleteBackingData.\nfunc (mr *MockConfigurationVersionsMockRecorder) SoftDeleteBackingData(ctx, svID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SoftDeleteBackingData\", reflect.TypeOf((*MockConfigurationVersions)(nil).SoftDeleteBackingData), ctx, svID)\n}\n\n// Upload mocks base method.\nfunc (m *MockConfigurationVersions) Upload(ctx context.Context, url, path string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Upload\", ctx, url, path)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Upload indicates an expected call of Upload.\nfunc (mr *MockConfigurationVersionsMockRecorder) Upload(ctx, url, path any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Upload\", reflect.TypeOf((*MockConfigurationVersions)(nil).Upload), ctx, url, path)\n}\n\n// UploadTarGzip mocks base method.\nfunc (m *MockConfigurationVersions) UploadTarGzip(ctx context.Context, url string, archive io.Reader) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UploadTarGzip\", ctx, url, archive)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UploadTarGzip indicates an expected call of UploadTarGzip.\nfunc (mr *MockConfigurationVersionsMockRecorder) UploadTarGzip(ctx, url, archive any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UploadTarGzip\", reflect.TypeOf((*MockConfigurationVersions)(nil).UploadTarGzip), ctx, url, archive)\n}\n"
  },
  {
    "path": "mocks/cost_estimate_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: cost_estimate.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=cost_estimate.go -destination=mocks/cost_estimate_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\tio \"io\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockCostEstimates is a mock of CostEstimates interface.\ntype MockCostEstimates struct {\n\tctrl     *gomock.Controller\n\trecorder *MockCostEstimatesMockRecorder\n}\n\n// MockCostEstimatesMockRecorder is the mock recorder for MockCostEstimates.\ntype MockCostEstimatesMockRecorder struct {\n\tmock *MockCostEstimates\n}\n\n// NewMockCostEstimates creates a new mock instance.\nfunc NewMockCostEstimates(ctrl *gomock.Controller) *MockCostEstimates {\n\tmock := &MockCostEstimates{ctrl: ctrl}\n\tmock.recorder = &MockCostEstimatesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockCostEstimates) EXPECT() *MockCostEstimatesMockRecorder {\n\treturn m.recorder\n}\n\n// Logs mocks base method.\nfunc (m *MockCostEstimates) Logs(ctx context.Context, costEstimateID string) (io.Reader, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Logs\", ctx, costEstimateID)\n\tret0, _ := ret[0].(io.Reader)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Logs indicates an expected call of Logs.\nfunc (mr *MockCostEstimatesMockRecorder) Logs(ctx, costEstimateID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Logs\", reflect.TypeOf((*MockCostEstimates)(nil).Logs), ctx, costEstimateID)\n}\n\n// Read mocks base method.\nfunc (m *MockCostEstimates) Read(ctx context.Context, costEstimateID string) (*tfe.CostEstimate, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, costEstimateID)\n\tret0, _ := ret[0].(*tfe.CostEstimate)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockCostEstimatesMockRecorder) Read(ctx, costEstimateID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockCostEstimates)(nil).Read), ctx, costEstimateID)\n}\n"
  },
  {
    "path": "mocks/github_app_installation_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github_app_installation.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=github_app_installation.go -destination=mocks/github_app_installation_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockGHAInstallations is a mock of GHAInstallations interface.\ntype MockGHAInstallations struct {\n\tctrl     *gomock.Controller\n\trecorder *MockGHAInstallationsMockRecorder\n}\n\n// MockGHAInstallationsMockRecorder is the mock recorder for MockGHAInstallations.\ntype MockGHAInstallationsMockRecorder struct {\n\tmock *MockGHAInstallations\n}\n\n// NewMockGHAInstallations creates a new mock instance.\nfunc NewMockGHAInstallations(ctrl *gomock.Controller) *MockGHAInstallations {\n\tmock := &MockGHAInstallations{ctrl: ctrl}\n\tmock.recorder = &MockGHAInstallationsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockGHAInstallations) EXPECT() *MockGHAInstallationsMockRecorder {\n\treturn m.recorder\n}\n\n// List mocks base method.\nfunc (m *MockGHAInstallations) List(ctx context.Context, options *tfe.GHAInstallationListOptions) (*tfe.GHAInstallationList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.GHAInstallationList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockGHAInstallationsMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockGHAInstallations)(nil).List), ctx, options)\n}\n\n// Read mocks base method.\nfunc (m *MockGHAInstallations) Read(ctx context.Context, GHAInstallationID string) (*tfe.GHAInstallation, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, GHAInstallationID)\n\tret0, _ := ret[0].(*tfe.GHAInstallation)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockGHAInstallationsMockRecorder) Read(ctx, GHAInstallationID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockGHAInstallations)(nil).Read), ctx, GHAInstallationID)\n}\n"
  },
  {
    "path": "mocks/gpg_key_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: gpg_key.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=gpg_key.go -destination=mocks/gpg_key_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockGPGKeys is a mock of GPGKeys interface.\ntype MockGPGKeys struct {\n\tctrl     *gomock.Controller\n\trecorder *MockGPGKeysMockRecorder\n}\n\n// MockGPGKeysMockRecorder is the mock recorder for MockGPGKeys.\ntype MockGPGKeysMockRecorder struct {\n\tmock *MockGPGKeys\n}\n\n// NewMockGPGKeys creates a new mock instance.\nfunc NewMockGPGKeys(ctrl *gomock.Controller) *MockGPGKeys {\n\tmock := &MockGPGKeys{ctrl: ctrl}\n\tmock.recorder = &MockGPGKeysMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockGPGKeys) EXPECT() *MockGPGKeysMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockGPGKeys) Create(ctx context.Context, registryName tfe.RegistryName, options tfe.GPGKeyCreateOptions) (*tfe.GPGKey, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, registryName, options)\n\tret0, _ := ret[0].(*tfe.GPGKey)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockGPGKeysMockRecorder) Create(ctx, registryName, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockGPGKeys)(nil).Create), ctx, registryName, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockGPGKeys) Delete(ctx context.Context, keyID tfe.GPGKeyID) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, keyID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockGPGKeysMockRecorder) Delete(ctx, keyID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockGPGKeys)(nil).Delete), ctx, keyID)\n}\n\n// ListPrivate mocks base method.\nfunc (m *MockGPGKeys) ListPrivate(ctx context.Context, options tfe.GPGKeyListOptions) (*tfe.GPGKeyList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListPrivate\", ctx, options)\n\tret0, _ := ret[0].(*tfe.GPGKeyList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListPrivate indicates an expected call of ListPrivate.\nfunc (mr *MockGPGKeysMockRecorder) ListPrivate(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListPrivate\", reflect.TypeOf((*MockGPGKeys)(nil).ListPrivate), ctx, options)\n}\n\n// Read mocks base method.\nfunc (m *MockGPGKeys) Read(ctx context.Context, keyID tfe.GPGKeyID) (*tfe.GPGKey, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, keyID)\n\tret0, _ := ret[0].(*tfe.GPGKey)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockGPGKeysMockRecorder) Read(ctx, keyID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockGPGKeys)(nil).Read), ctx, keyID)\n}\n\n// Update mocks base method.\nfunc (m *MockGPGKeys) Update(ctx context.Context, keyID tfe.GPGKeyID, options tfe.GPGKeyUpdateOptions) (*tfe.GPGKey, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, keyID, options)\n\tret0, _ := ret[0].(*tfe.GPGKey)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockGPGKeysMockRecorder) Update(ctx, keyID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockGPGKeys)(nil).Update), ctx, keyID, options)\n}\n"
  },
  {
    "path": "mocks/ip_ranges_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: ip_ranges.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=ip_ranges.go -destination=mocks/ip_ranges_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockIPRanges is a mock of IPRanges interface.\ntype MockIPRanges struct {\n\tctrl     *gomock.Controller\n\trecorder *MockIPRangesMockRecorder\n}\n\n// MockIPRangesMockRecorder is the mock recorder for MockIPRanges.\ntype MockIPRangesMockRecorder struct {\n\tmock *MockIPRanges\n}\n\n// NewMockIPRanges creates a new mock instance.\nfunc NewMockIPRanges(ctrl *gomock.Controller) *MockIPRanges {\n\tmock := &MockIPRanges{ctrl: ctrl}\n\tmock.recorder = &MockIPRangesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockIPRanges) EXPECT() *MockIPRangesMockRecorder {\n\treturn m.recorder\n}\n\n// Read mocks base method.\nfunc (m *MockIPRanges) Read(ctx context.Context, modifiedSince string) (*tfe.IPRange, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, modifiedSince)\n\tret0, _ := ret[0].(*tfe.IPRange)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockIPRangesMockRecorder) Read(ctx, modifiedSince any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockIPRanges)(nil).Read), ctx, modifiedSince)\n}\n"
  },
  {
    "path": "mocks/logreader_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: logreader.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=logreader.go -destination=mocks/logreader_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n"
  },
  {
    "path": "mocks/notification_configuration_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: notification_configuration.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=notification_configuration.go -destination=mocks/notification_configuration_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockNotificationConfigurations is a mock of NotificationConfigurations interface.\ntype MockNotificationConfigurations struct {\n\tctrl     *gomock.Controller\n\trecorder *MockNotificationConfigurationsMockRecorder\n}\n\n// MockNotificationConfigurationsMockRecorder is the mock recorder for MockNotificationConfigurations.\ntype MockNotificationConfigurationsMockRecorder struct {\n\tmock *MockNotificationConfigurations\n}\n\n// NewMockNotificationConfigurations creates a new mock instance.\nfunc NewMockNotificationConfigurations(ctrl *gomock.Controller) *MockNotificationConfigurations {\n\tmock := &MockNotificationConfigurations{ctrl: ctrl}\n\tmock.recorder = &MockNotificationConfigurationsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockNotificationConfigurations) EXPECT() *MockNotificationConfigurationsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockNotificationConfigurations) Create(ctx context.Context, subscribableID string, options tfe.NotificationConfigurationCreateOptions) (*tfe.NotificationConfiguration, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, subscribableID, options)\n\tret0, _ := ret[0].(*tfe.NotificationConfiguration)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockNotificationConfigurationsMockRecorder) Create(ctx, subscribableID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockNotificationConfigurations)(nil).Create), ctx, subscribableID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockNotificationConfigurations) Delete(ctx context.Context, notificationConfigurationID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, notificationConfigurationID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockNotificationConfigurationsMockRecorder) Delete(ctx, notificationConfigurationID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockNotificationConfigurations)(nil).Delete), ctx, notificationConfigurationID)\n}\n\n// List mocks base method.\nfunc (m *MockNotificationConfigurations) List(ctx context.Context, subscribableID string, options *tfe.NotificationConfigurationListOptions) (*tfe.NotificationConfigurationList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, subscribableID, options)\n\tret0, _ := ret[0].(*tfe.NotificationConfigurationList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockNotificationConfigurationsMockRecorder) List(ctx, subscribableID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockNotificationConfigurations)(nil).List), ctx, subscribableID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockNotificationConfigurations) Read(ctx context.Context, notificationConfigurationID string) (*tfe.NotificationConfiguration, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, notificationConfigurationID)\n\tret0, _ := ret[0].(*tfe.NotificationConfiguration)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockNotificationConfigurationsMockRecorder) Read(ctx, notificationConfigurationID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockNotificationConfigurations)(nil).Read), ctx, notificationConfigurationID)\n}\n\n// Update mocks base method.\nfunc (m *MockNotificationConfigurations) Update(ctx context.Context, notificationConfigurationID string, options tfe.NotificationConfigurationUpdateOptions) (*tfe.NotificationConfiguration, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, notificationConfigurationID, options)\n\tret0, _ := ret[0].(*tfe.NotificationConfiguration)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockNotificationConfigurationsMockRecorder) Update(ctx, notificationConfigurationID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockNotificationConfigurations)(nil).Update), ctx, notificationConfigurationID, options)\n}\n\n// Verify mocks base method.\nfunc (m *MockNotificationConfigurations) Verify(ctx context.Context, notificationConfigurationID string) (*tfe.NotificationConfiguration, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Verify\", ctx, notificationConfigurationID)\n\tret0, _ := ret[0].(*tfe.NotificationConfiguration)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Verify indicates an expected call of Verify.\nfunc (mr *MockNotificationConfigurationsMockRecorder) Verify(ctx, notificationConfigurationID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Verify\", reflect.TypeOf((*MockNotificationConfigurations)(nil).Verify), ctx, notificationConfigurationID)\n}\n"
  },
  {
    "path": "mocks/oauth_client_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: oauth_client.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=oauth_client.go -destination=mocks/oauth_client_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockOAuthClients is a mock of OAuthClients interface.\ntype MockOAuthClients struct {\n\tctrl     *gomock.Controller\n\trecorder *MockOAuthClientsMockRecorder\n}\n\n// MockOAuthClientsMockRecorder is the mock recorder for MockOAuthClients.\ntype MockOAuthClientsMockRecorder struct {\n\tmock *MockOAuthClients\n}\n\n// NewMockOAuthClients creates a new mock instance.\nfunc NewMockOAuthClients(ctrl *gomock.Controller) *MockOAuthClients {\n\tmock := &MockOAuthClients{ctrl: ctrl}\n\tmock.recorder = &MockOAuthClientsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockOAuthClients) EXPECT() *MockOAuthClientsMockRecorder {\n\treturn m.recorder\n}\n\n// AddProjects mocks base method.\nfunc (m *MockOAuthClients) AddProjects(ctx context.Context, oAuthClientID string, options tfe.OAuthClientAddProjectsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddProjects\", ctx, oAuthClientID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddProjects indicates an expected call of AddProjects.\nfunc (mr *MockOAuthClientsMockRecorder) AddProjects(ctx, oAuthClientID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddProjects\", reflect.TypeOf((*MockOAuthClients)(nil).AddProjects), ctx, oAuthClientID, options)\n}\n\n// Create mocks base method.\nfunc (m *MockOAuthClients) Create(ctx context.Context, organization string, options tfe.OAuthClientCreateOptions) (*tfe.OAuthClient, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.OAuthClient)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockOAuthClientsMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockOAuthClients)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockOAuthClients) Delete(ctx context.Context, oAuthClientID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, oAuthClientID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockOAuthClientsMockRecorder) Delete(ctx, oAuthClientID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockOAuthClients)(nil).Delete), ctx, oAuthClientID)\n}\n\n// List mocks base method.\nfunc (m *MockOAuthClients) List(ctx context.Context, organization string, options *tfe.OAuthClientListOptions) (*tfe.OAuthClientList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.OAuthClientList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockOAuthClientsMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockOAuthClients)(nil).List), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockOAuthClients) Read(ctx context.Context, oAuthClientID string) (*tfe.OAuthClient, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, oAuthClientID)\n\tret0, _ := ret[0].(*tfe.OAuthClient)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockOAuthClientsMockRecorder) Read(ctx, oAuthClientID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockOAuthClients)(nil).Read), ctx, oAuthClientID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockOAuthClients) ReadWithOptions(ctx context.Context, oAuthClientID string, options *tfe.OAuthClientReadOptions) (*tfe.OAuthClient, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, oAuthClientID, options)\n\tret0, _ := ret[0].(*tfe.OAuthClient)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockOAuthClientsMockRecorder) ReadWithOptions(ctx, oAuthClientID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockOAuthClients)(nil).ReadWithOptions), ctx, oAuthClientID, options)\n}\n\n// RemoveProjects mocks base method.\nfunc (m *MockOAuthClients) RemoveProjects(ctx context.Context, oAuthClientID string, options tfe.OAuthClientRemoveProjectsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveProjects\", ctx, oAuthClientID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveProjects indicates an expected call of RemoveProjects.\nfunc (mr *MockOAuthClientsMockRecorder) RemoveProjects(ctx, oAuthClientID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveProjects\", reflect.TypeOf((*MockOAuthClients)(nil).RemoveProjects), ctx, oAuthClientID, options)\n}\n\n// Update mocks base method.\nfunc (m *MockOAuthClients) Update(ctx context.Context, oAuthClientID string, options tfe.OAuthClientUpdateOptions) (*tfe.OAuthClient, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, oAuthClientID, options)\n\tret0, _ := ret[0].(*tfe.OAuthClient)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockOAuthClientsMockRecorder) Update(ctx, oAuthClientID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockOAuthClients)(nil).Update), ctx, oAuthClientID, options)\n}\n"
  },
  {
    "path": "mocks/oauth_token_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: oauth_token.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=oauth_token.go -destination=mocks/oauth_token_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockOAuthTokens is a mock of OAuthTokens interface.\ntype MockOAuthTokens struct {\n\tctrl     *gomock.Controller\n\trecorder *MockOAuthTokensMockRecorder\n}\n\n// MockOAuthTokensMockRecorder is the mock recorder for MockOAuthTokens.\ntype MockOAuthTokensMockRecorder struct {\n\tmock *MockOAuthTokens\n}\n\n// NewMockOAuthTokens creates a new mock instance.\nfunc NewMockOAuthTokens(ctrl *gomock.Controller) *MockOAuthTokens {\n\tmock := &MockOAuthTokens{ctrl: ctrl}\n\tmock.recorder = &MockOAuthTokensMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockOAuthTokens) EXPECT() *MockOAuthTokensMockRecorder {\n\treturn m.recorder\n}\n\n// Delete mocks base method.\nfunc (m *MockOAuthTokens) Delete(ctx context.Context, oAuthTokenID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, oAuthTokenID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockOAuthTokensMockRecorder) Delete(ctx, oAuthTokenID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockOAuthTokens)(nil).Delete), ctx, oAuthTokenID)\n}\n\n// List mocks base method.\nfunc (m *MockOAuthTokens) List(ctx context.Context, organization string, options *tfe.OAuthTokenListOptions) (*tfe.OAuthTokenList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.OAuthTokenList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockOAuthTokensMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockOAuthTokens)(nil).List), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockOAuthTokens) Read(ctx context.Context, oAuthTokenID string) (*tfe.OAuthToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, oAuthTokenID)\n\tret0, _ := ret[0].(*tfe.OAuthToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockOAuthTokensMockRecorder) Read(ctx, oAuthTokenID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockOAuthTokens)(nil).Read), ctx, oAuthTokenID)\n}\n\n// Update mocks base method.\nfunc (m *MockOAuthTokens) Update(ctx context.Context, oAuthTokenID string, options tfe.OAuthTokenUpdateOptions) (*tfe.OAuthToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, oAuthTokenID, options)\n\tret0, _ := ret[0].(*tfe.OAuthToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockOAuthTokensMockRecorder) Update(ctx, oAuthTokenID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockOAuthTokens)(nil).Update), ctx, oAuthTokenID, options)\n}\n"
  },
  {
    "path": "mocks/organization_membership_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: organization_membership.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=organization_membership.go -destination=mocks/organization_membership_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockOrganizationMemberships is a mock of OrganizationMemberships interface.\ntype MockOrganizationMemberships struct {\n\tctrl     *gomock.Controller\n\trecorder *MockOrganizationMembershipsMockRecorder\n}\n\n// MockOrganizationMembershipsMockRecorder is the mock recorder for MockOrganizationMemberships.\ntype MockOrganizationMembershipsMockRecorder struct {\n\tmock *MockOrganizationMemberships\n}\n\n// NewMockOrganizationMemberships creates a new mock instance.\nfunc NewMockOrganizationMemberships(ctrl *gomock.Controller) *MockOrganizationMemberships {\n\tmock := &MockOrganizationMemberships{ctrl: ctrl}\n\tmock.recorder = &MockOrganizationMembershipsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockOrganizationMemberships) EXPECT() *MockOrganizationMembershipsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockOrganizationMemberships) Create(ctx context.Context, organization string, options tfe.OrganizationMembershipCreateOptions) (*tfe.OrganizationMembership, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.OrganizationMembership)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockOrganizationMembershipsMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockOrganizationMemberships)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockOrganizationMemberships) Delete(ctx context.Context, organizationMembershipID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, organizationMembershipID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockOrganizationMembershipsMockRecorder) Delete(ctx, organizationMembershipID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockOrganizationMemberships)(nil).Delete), ctx, organizationMembershipID)\n}\n\n// List mocks base method.\nfunc (m *MockOrganizationMemberships) List(ctx context.Context, organization string, options *tfe.OrganizationMembershipListOptions) (*tfe.OrganizationMembershipList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.OrganizationMembershipList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockOrganizationMembershipsMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockOrganizationMemberships)(nil).List), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockOrganizationMemberships) Read(ctx context.Context, organizationMembershipID string) (*tfe.OrganizationMembership, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, organizationMembershipID)\n\tret0, _ := ret[0].(*tfe.OrganizationMembership)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockOrganizationMembershipsMockRecorder) Read(ctx, organizationMembershipID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockOrganizationMemberships)(nil).Read), ctx, organizationMembershipID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockOrganizationMemberships) ReadWithOptions(ctx context.Context, organizationMembershipID string, options tfe.OrganizationMembershipReadOptions) (*tfe.OrganizationMembership, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, organizationMembershipID, options)\n\tret0, _ := ret[0].(*tfe.OrganizationMembership)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockOrganizationMembershipsMockRecorder) ReadWithOptions(ctx, organizationMembershipID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockOrganizationMemberships)(nil).ReadWithOptions), ctx, organizationMembershipID, options)\n}\n"
  },
  {
    "path": "mocks/organization_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: organization.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=organization.go -destination=mocks/organization_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockOrganizations is a mock of Organizations interface.\ntype MockOrganizations struct {\n\tctrl     *gomock.Controller\n\trecorder *MockOrganizationsMockRecorder\n}\n\n// MockOrganizationsMockRecorder is the mock recorder for MockOrganizations.\ntype MockOrganizationsMockRecorder struct {\n\tmock *MockOrganizations\n}\n\n// NewMockOrganizations creates a new mock instance.\nfunc NewMockOrganizations(ctrl *gomock.Controller) *MockOrganizations {\n\tmock := &MockOrganizations{ctrl: ctrl}\n\tmock.recorder = &MockOrganizationsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockOrganizations) EXPECT() *MockOrganizationsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockOrganizations) Create(ctx context.Context, options tfe.OrganizationCreateOptions) (*tfe.Organization, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, options)\n\tret0, _ := ret[0].(*tfe.Organization)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockOrganizationsMockRecorder) Create(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockOrganizations)(nil).Create), ctx, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockOrganizations) Delete(ctx context.Context, organization string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, organization)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockOrganizationsMockRecorder) Delete(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockOrganizations)(nil).Delete), ctx, organization)\n}\n\n// DeleteDataRetentionPolicy mocks base method.\nfunc (m *MockOrganizations) DeleteDataRetentionPolicy(ctx context.Context, organization string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteDataRetentionPolicy\", ctx, organization)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteDataRetentionPolicy indicates an expected call of DeleteDataRetentionPolicy.\nfunc (mr *MockOrganizationsMockRecorder) DeleteDataRetentionPolicy(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteDataRetentionPolicy\", reflect.TypeOf((*MockOrganizations)(nil).DeleteDataRetentionPolicy), ctx, organization)\n}\n\n// List mocks base method.\nfunc (m *MockOrganizations) List(ctx context.Context, options *tfe.OrganizationListOptions) (*tfe.OrganizationList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.OrganizationList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockOrganizationsMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockOrganizations)(nil).List), ctx, options)\n}\n\n// Read mocks base method.\nfunc (m *MockOrganizations) Read(ctx context.Context, organization string) (*tfe.Organization, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, organization)\n\tret0, _ := ret[0].(*tfe.Organization)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockOrganizationsMockRecorder) Read(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockOrganizations)(nil).Read), ctx, organization)\n}\n\n// ReadCapacity mocks base method.\nfunc (m *MockOrganizations) ReadCapacity(ctx context.Context, organization string) (*tfe.Capacity, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadCapacity\", ctx, organization)\n\tret0, _ := ret[0].(*tfe.Capacity)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadCapacity indicates an expected call of ReadCapacity.\nfunc (mr *MockOrganizationsMockRecorder) ReadCapacity(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadCapacity\", reflect.TypeOf((*MockOrganizations)(nil).ReadCapacity), ctx, organization)\n}\n\n// ReadDataRetentionPolicy mocks base method.\nfunc (m *MockOrganizations) ReadDataRetentionPolicy(ctx context.Context, organization string) (*tfe.DataRetentionPolicy, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadDataRetentionPolicy\", ctx, organization)\n\tret0, _ := ret[0].(*tfe.DataRetentionPolicy)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadDataRetentionPolicy indicates an expected call of ReadDataRetentionPolicy.\nfunc (mr *MockOrganizationsMockRecorder) ReadDataRetentionPolicy(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadDataRetentionPolicy\", reflect.TypeOf((*MockOrganizations)(nil).ReadDataRetentionPolicy), ctx, organization)\n}\n\n// ReadDataRetentionPolicyChoice mocks base method.\nfunc (m *MockOrganizations) ReadDataRetentionPolicyChoice(ctx context.Context, organization string) (*tfe.DataRetentionPolicyChoice, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadDataRetentionPolicyChoice\", ctx, organization)\n\tret0, _ := ret[0].(*tfe.DataRetentionPolicyChoice)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadDataRetentionPolicyChoice indicates an expected call of ReadDataRetentionPolicyChoice.\nfunc (mr *MockOrganizationsMockRecorder) ReadDataRetentionPolicyChoice(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadDataRetentionPolicyChoice\", reflect.TypeOf((*MockOrganizations)(nil).ReadDataRetentionPolicyChoice), ctx, organization)\n}\n\n// ReadEntitlements mocks base method.\nfunc (m *MockOrganizations) ReadEntitlements(ctx context.Context, organization string) (*tfe.Entitlements, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadEntitlements\", ctx, organization)\n\tret0, _ := ret[0].(*tfe.Entitlements)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadEntitlements indicates an expected call of ReadEntitlements.\nfunc (mr *MockOrganizationsMockRecorder) ReadEntitlements(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadEntitlements\", reflect.TypeOf((*MockOrganizations)(nil).ReadEntitlements), ctx, organization)\n}\n\n// ReadRunQueue mocks base method.\nfunc (m *MockOrganizations) ReadRunQueue(ctx context.Context, organization string, options tfe.ReadRunQueueOptions) (*tfe.RunQueue, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadRunQueue\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.RunQueue)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadRunQueue indicates an expected call of ReadRunQueue.\nfunc (mr *MockOrganizationsMockRecorder) ReadRunQueue(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadRunQueue\", reflect.TypeOf((*MockOrganizations)(nil).ReadRunQueue), ctx, organization, options)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockOrganizations) ReadWithOptions(ctx context.Context, organization string, options tfe.OrganizationReadOptions) (*tfe.Organization, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.Organization)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockOrganizationsMockRecorder) ReadWithOptions(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockOrganizations)(nil).ReadWithOptions), ctx, organization, options)\n}\n\n// SetDataRetentionPolicy mocks base method.\nfunc (m *MockOrganizations) SetDataRetentionPolicy(ctx context.Context, organization string, options tfe.DataRetentionPolicySetOptions) (*tfe.DataRetentionPolicy, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetDataRetentionPolicy\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.DataRetentionPolicy)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SetDataRetentionPolicy indicates an expected call of SetDataRetentionPolicy.\nfunc (mr *MockOrganizationsMockRecorder) SetDataRetentionPolicy(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetDataRetentionPolicy\", reflect.TypeOf((*MockOrganizations)(nil).SetDataRetentionPolicy), ctx, organization, options)\n}\n\n// SetDataRetentionPolicyDeleteOlder mocks base method.\nfunc (m *MockOrganizations) SetDataRetentionPolicyDeleteOlder(ctx context.Context, organization string, options tfe.DataRetentionPolicyDeleteOlderSetOptions) (*tfe.DataRetentionPolicyDeleteOlder, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetDataRetentionPolicyDeleteOlder\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.DataRetentionPolicyDeleteOlder)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SetDataRetentionPolicyDeleteOlder indicates an expected call of SetDataRetentionPolicyDeleteOlder.\nfunc (mr *MockOrganizationsMockRecorder) SetDataRetentionPolicyDeleteOlder(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetDataRetentionPolicyDeleteOlder\", reflect.TypeOf((*MockOrganizations)(nil).SetDataRetentionPolicyDeleteOlder), ctx, organization, options)\n}\n\n// SetDataRetentionPolicyDontDelete mocks base method.\nfunc (m *MockOrganizations) SetDataRetentionPolicyDontDelete(ctx context.Context, organization string, options tfe.DataRetentionPolicyDontDeleteSetOptions) (*tfe.DataRetentionPolicyDontDelete, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetDataRetentionPolicyDontDelete\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.DataRetentionPolicyDontDelete)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SetDataRetentionPolicyDontDelete indicates an expected call of SetDataRetentionPolicyDontDelete.\nfunc (mr *MockOrganizationsMockRecorder) SetDataRetentionPolicyDontDelete(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetDataRetentionPolicyDontDelete\", reflect.TypeOf((*MockOrganizations)(nil).SetDataRetentionPolicyDontDelete), ctx, organization, options)\n}\n\n// Update mocks base method.\nfunc (m *MockOrganizations) Update(ctx context.Context, organization string, options tfe.OrganizationUpdateOptions) (*tfe.Organization, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.Organization)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockOrganizationsMockRecorder) Update(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockOrganizations)(nil).Update), ctx, organization, options)\n}\n"
  },
  {
    "path": "mocks/organization_token_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: organization_token.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=organization_token.go -destination=mocks/organization_token_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockOrganizationTokens is a mock of OrganizationTokens interface.\ntype MockOrganizationTokens struct {\n\tctrl     *gomock.Controller\n\trecorder *MockOrganizationTokensMockRecorder\n}\n\n// MockOrganizationTokensMockRecorder is the mock recorder for MockOrganizationTokens.\ntype MockOrganizationTokensMockRecorder struct {\n\tmock *MockOrganizationTokens\n}\n\n// NewMockOrganizationTokens creates a new mock instance.\nfunc NewMockOrganizationTokens(ctrl *gomock.Controller) *MockOrganizationTokens {\n\tmock := &MockOrganizationTokens{ctrl: ctrl}\n\tmock.recorder = &MockOrganizationTokensMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockOrganizationTokens) EXPECT() *MockOrganizationTokensMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockOrganizationTokens) Create(ctx context.Context, organization string) (*tfe.OrganizationToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization)\n\tret0, _ := ret[0].(*tfe.OrganizationToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockOrganizationTokensMockRecorder) Create(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockOrganizationTokens)(nil).Create), ctx, organization)\n}\n\n// CreateWithOptions mocks base method.\nfunc (m *MockOrganizationTokens) CreateWithOptions(ctx context.Context, organization string, options tfe.OrganizationTokenCreateOptions) (*tfe.OrganizationToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateWithOptions\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.OrganizationToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateWithOptions indicates an expected call of CreateWithOptions.\nfunc (mr *MockOrganizationTokensMockRecorder) CreateWithOptions(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateWithOptions\", reflect.TypeOf((*MockOrganizationTokens)(nil).CreateWithOptions), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockOrganizationTokens) Delete(ctx context.Context, organization string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, organization)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockOrganizationTokensMockRecorder) Delete(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockOrganizationTokens)(nil).Delete), ctx, organization)\n}\n\n// DeleteWithOptions mocks base method.\nfunc (m *MockOrganizationTokens) DeleteWithOptions(ctx context.Context, organization string, options tfe.OrganizationTokenDeleteOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteWithOptions\", ctx, organization, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteWithOptions indicates an expected call of DeleteWithOptions.\nfunc (mr *MockOrganizationTokensMockRecorder) DeleteWithOptions(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteWithOptions\", reflect.TypeOf((*MockOrganizationTokens)(nil).DeleteWithOptions), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockOrganizationTokens) Read(ctx context.Context, organization string) (*tfe.OrganizationToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, organization)\n\tret0, _ := ret[0].(*tfe.OrganizationToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockOrganizationTokensMockRecorder) Read(ctx, organization any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockOrganizationTokens)(nil).Read), ctx, organization)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockOrganizationTokens) ReadWithOptions(ctx context.Context, organization string, options tfe.OrganizationTokenReadOptions) (*tfe.OrganizationToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.OrganizationToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockOrganizationTokensMockRecorder) ReadWithOptions(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockOrganizationTokens)(nil).ReadWithOptions), ctx, organization, options)\n}\n"
  },
  {
    "path": "mocks/plan_export_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: plan_export.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=plan_export.go -destination=mocks/plan_export_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockPlanExports is a mock of PlanExports interface.\ntype MockPlanExports struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPlanExportsMockRecorder\n}\n\n// MockPlanExportsMockRecorder is the mock recorder for MockPlanExports.\ntype MockPlanExportsMockRecorder struct {\n\tmock *MockPlanExports\n}\n\n// NewMockPlanExports creates a new mock instance.\nfunc NewMockPlanExports(ctrl *gomock.Controller) *MockPlanExports {\n\tmock := &MockPlanExports{ctrl: ctrl}\n\tmock.recorder = &MockPlanExportsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPlanExports) EXPECT() *MockPlanExportsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockPlanExports) Create(ctx context.Context, options tfe.PlanExportCreateOptions) (*tfe.PlanExport, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, options)\n\tret0, _ := ret[0].(*tfe.PlanExport)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockPlanExportsMockRecorder) Create(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockPlanExports)(nil).Create), ctx, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockPlanExports) Delete(ctx context.Context, planExportID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, planExportID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockPlanExportsMockRecorder) Delete(ctx, planExportID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockPlanExports)(nil).Delete), ctx, planExportID)\n}\n\n// Download mocks base method.\nfunc (m *MockPlanExports) Download(ctx context.Context, planExportID string) ([]byte, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Download\", ctx, planExportID)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Download indicates an expected call of Download.\nfunc (mr *MockPlanExportsMockRecorder) Download(ctx, planExportID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Download\", reflect.TypeOf((*MockPlanExports)(nil).Download), ctx, planExportID)\n}\n\n// Read mocks base method.\nfunc (m *MockPlanExports) Read(ctx context.Context, planExportID string) (*tfe.PlanExport, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, planExportID)\n\tret0, _ := ret[0].(*tfe.PlanExport)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockPlanExportsMockRecorder) Read(ctx, planExportID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockPlanExports)(nil).Read), ctx, planExportID)\n}\n"
  },
  {
    "path": "mocks/plan_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: plan.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=plan.go -destination=mocks/plan_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\tio \"io\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockPlans is a mock of Plans interface.\ntype MockPlans struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPlansMockRecorder\n}\n\n// MockPlansMockRecorder is the mock recorder for MockPlans.\ntype MockPlansMockRecorder struct {\n\tmock *MockPlans\n}\n\n// NewMockPlans creates a new mock instance.\nfunc NewMockPlans(ctrl *gomock.Controller) *MockPlans {\n\tmock := &MockPlans{ctrl: ctrl}\n\tmock.recorder = &MockPlansMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPlans) EXPECT() *MockPlansMockRecorder {\n\treturn m.recorder\n}\n\n// Logs mocks base method.\nfunc (m *MockPlans) Logs(ctx context.Context, planID string) (io.Reader, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Logs\", ctx, planID)\n\tret0, _ := ret[0].(io.Reader)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Logs indicates an expected call of Logs.\nfunc (mr *MockPlansMockRecorder) Logs(ctx, planID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Logs\", reflect.TypeOf((*MockPlans)(nil).Logs), ctx, planID)\n}\n\n// Read mocks base method.\nfunc (m *MockPlans) Read(ctx context.Context, planID string) (*tfe.Plan, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, planID)\n\tret0, _ := ret[0].(*tfe.Plan)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockPlansMockRecorder) Read(ctx, planID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockPlans)(nil).Read), ctx, planID)\n}\n\n// ReadJSONOutput mocks base method.\nfunc (m *MockPlans) ReadJSONOutput(ctx context.Context, planID string) ([]byte, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadJSONOutput\", ctx, planID)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadJSONOutput indicates an expected call of ReadJSONOutput.\nfunc (mr *MockPlansMockRecorder) ReadJSONOutput(ctx, planID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadJSONOutput\", reflect.TypeOf((*MockPlans)(nil).ReadJSONOutput), ctx, planID)\n}\n"
  },
  {
    "path": "mocks/policy_check_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: policy_check.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=policy_check.go -destination=mocks/policy_check_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\tio \"io\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockPolicyChecks is a mock of PolicyChecks interface.\ntype MockPolicyChecks struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPolicyChecksMockRecorder\n}\n\n// MockPolicyChecksMockRecorder is the mock recorder for MockPolicyChecks.\ntype MockPolicyChecksMockRecorder struct {\n\tmock *MockPolicyChecks\n}\n\n// NewMockPolicyChecks creates a new mock instance.\nfunc NewMockPolicyChecks(ctrl *gomock.Controller) *MockPolicyChecks {\n\tmock := &MockPolicyChecks{ctrl: ctrl}\n\tmock.recorder = &MockPolicyChecksMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPolicyChecks) EXPECT() *MockPolicyChecksMockRecorder {\n\treturn m.recorder\n}\n\n// List mocks base method.\nfunc (m *MockPolicyChecks) List(ctx context.Context, runID string, options *tfe.PolicyCheckListOptions) (*tfe.PolicyCheckList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, runID, options)\n\tret0, _ := ret[0].(*tfe.PolicyCheckList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockPolicyChecksMockRecorder) List(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockPolicyChecks)(nil).List), ctx, runID, options)\n}\n\n// Logs mocks base method.\nfunc (m *MockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Logs\", ctx, policyCheckID)\n\tret0, _ := ret[0].(io.Reader)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Logs indicates an expected call of Logs.\nfunc (mr *MockPolicyChecksMockRecorder) Logs(ctx, policyCheckID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Logs\", reflect.TypeOf((*MockPolicyChecks)(nil).Logs), ctx, policyCheckID)\n}\n\n// Override mocks base method.\nfunc (m *MockPolicyChecks) Override(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Override\", ctx, policyCheckID)\n\tret0, _ := ret[0].(*tfe.PolicyCheck)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Override indicates an expected call of Override.\nfunc (mr *MockPolicyChecksMockRecorder) Override(ctx, policyCheckID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Override\", reflect.TypeOf((*MockPolicyChecks)(nil).Override), ctx, policyCheckID)\n}\n\n// Read mocks base method.\nfunc (m *MockPolicyChecks) Read(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, policyCheckID)\n\tret0, _ := ret[0].(*tfe.PolicyCheck)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockPolicyChecksMockRecorder) Read(ctx, policyCheckID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockPolicyChecks)(nil).Read), ctx, policyCheckID)\n}\n"
  },
  {
    "path": "mocks/policy_evaluation.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: policy_evaluation.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=policy_evaluation.go -destination=mocks/policy_evaluation.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockPolicyEvaluations is a mock of PolicyEvaluations interface.\ntype MockPolicyEvaluations struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPolicyEvaluationsMockRecorder\n}\n\n// MockPolicyEvaluationsMockRecorder is the mock recorder for MockPolicyEvaluations.\ntype MockPolicyEvaluationsMockRecorder struct {\n\tmock *MockPolicyEvaluations\n}\n\n// NewMockPolicyEvaluations creates a new mock instance.\nfunc NewMockPolicyEvaluations(ctrl *gomock.Controller) *MockPolicyEvaluations {\n\tmock := &MockPolicyEvaluations{ctrl: ctrl}\n\tmock.recorder = &MockPolicyEvaluationsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPolicyEvaluations) EXPECT() *MockPolicyEvaluationsMockRecorder {\n\treturn m.recorder\n}\n\n// List mocks base method.\nfunc (m *MockPolicyEvaluations) List(ctx context.Context, taskStageID string, options *tfe.PolicyEvaluationListOptions) (*tfe.PolicyEvaluationList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, taskStageID, options)\n\tret0, _ := ret[0].(*tfe.PolicyEvaluationList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockPolicyEvaluationsMockRecorder) List(ctx, taskStageID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockPolicyEvaluations)(nil).List), ctx, taskStageID, options)\n}\n\n// MockPolicySetOutcomes is a mock of PolicySetOutcomes interface.\ntype MockPolicySetOutcomes struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPolicySetOutcomesMockRecorder\n}\n\n// MockPolicySetOutcomesMockRecorder is the mock recorder for MockPolicySetOutcomes.\ntype MockPolicySetOutcomesMockRecorder struct {\n\tmock *MockPolicySetOutcomes\n}\n\n// NewMockPolicySetOutcomes creates a new mock instance.\nfunc NewMockPolicySetOutcomes(ctrl *gomock.Controller) *MockPolicySetOutcomes {\n\tmock := &MockPolicySetOutcomes{ctrl: ctrl}\n\tmock.recorder = &MockPolicySetOutcomesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPolicySetOutcomes) EXPECT() *MockPolicySetOutcomesMockRecorder {\n\treturn m.recorder\n}\n\n// List mocks base method.\nfunc (m *MockPolicySetOutcomes) List(ctx context.Context, policyEvaluationID string, options *tfe.PolicySetOutcomeListOptions) (*tfe.PolicySetOutcomeList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, policyEvaluationID, options)\n\tret0, _ := ret[0].(*tfe.PolicySetOutcomeList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockPolicySetOutcomesMockRecorder) List(ctx, policyEvaluationID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockPolicySetOutcomes)(nil).List), ctx, policyEvaluationID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockPolicySetOutcomes) Read(ctx context.Context, policySetOutcomeID string) (*tfe.PolicySetOutcome, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, policySetOutcomeID)\n\tret0, _ := ret[0].(*tfe.PolicySetOutcome)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockPolicySetOutcomesMockRecorder) Read(ctx, policySetOutcomeID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockPolicySetOutcomes)(nil).Read), ctx, policySetOutcomeID)\n}\n"
  },
  {
    "path": "mocks/policy_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: policy.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=policy.go -destination=mocks/policy_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockPolicies is a mock of Policies interface.\ntype MockPolicies struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPoliciesMockRecorder\n}\n\n// MockPoliciesMockRecorder is the mock recorder for MockPolicies.\ntype MockPoliciesMockRecorder struct {\n\tmock *MockPolicies\n}\n\n// NewMockPolicies creates a new mock instance.\nfunc NewMockPolicies(ctrl *gomock.Controller) *MockPolicies {\n\tmock := &MockPolicies{ctrl: ctrl}\n\tmock.recorder = &MockPoliciesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPolicies) EXPECT() *MockPoliciesMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockPolicies) Create(ctx context.Context, organization string, options tfe.PolicyCreateOptions) (*tfe.Policy, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.Policy)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockPoliciesMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockPolicies)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockPolicies) Delete(ctx context.Context, policyID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, policyID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockPoliciesMockRecorder) Delete(ctx, policyID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockPolicies)(nil).Delete), ctx, policyID)\n}\n\n// Download mocks base method.\nfunc (m *MockPolicies) Download(ctx context.Context, policyID string) ([]byte, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Download\", ctx, policyID)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Download indicates an expected call of Download.\nfunc (mr *MockPoliciesMockRecorder) Download(ctx, policyID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Download\", reflect.TypeOf((*MockPolicies)(nil).Download), ctx, policyID)\n}\n\n// List mocks base method.\nfunc (m *MockPolicies) List(ctx context.Context, organization string, options *tfe.PolicyListOptions) (*tfe.PolicyList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.PolicyList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockPoliciesMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockPolicies)(nil).List), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockPolicies) Read(ctx context.Context, policyID string) (*tfe.Policy, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, policyID)\n\tret0, _ := ret[0].(*tfe.Policy)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockPoliciesMockRecorder) Read(ctx, policyID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockPolicies)(nil).Read), ctx, policyID)\n}\n\n// Update mocks base method.\nfunc (m *MockPolicies) Update(ctx context.Context, policyID string, options tfe.PolicyUpdateOptions) (*tfe.Policy, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, policyID, options)\n\tret0, _ := ret[0].(*tfe.Policy)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockPoliciesMockRecorder) Update(ctx, policyID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockPolicies)(nil).Update), ctx, policyID, options)\n}\n\n// Upload mocks base method.\nfunc (m *MockPolicies) Upload(ctx context.Context, policyID string, content []byte) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Upload\", ctx, policyID, content)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Upload indicates an expected call of Upload.\nfunc (mr *MockPoliciesMockRecorder) Upload(ctx, policyID, content any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Upload\", reflect.TypeOf((*MockPolicies)(nil).Upload), ctx, policyID, content)\n}\n"
  },
  {
    "path": "mocks/policy_set_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: policy_set.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=policy_set.go -destination=mocks/policy_set_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockPolicySets is a mock of PolicySets interface.\ntype MockPolicySets struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPolicySetsMockRecorder\n}\n\n// MockPolicySetsMockRecorder is the mock recorder for MockPolicySets.\ntype MockPolicySetsMockRecorder struct {\n\tmock *MockPolicySets\n}\n\n// NewMockPolicySets creates a new mock instance.\nfunc NewMockPolicySets(ctrl *gomock.Controller) *MockPolicySets {\n\tmock := &MockPolicySets{ctrl: ctrl}\n\tmock.recorder = &MockPolicySetsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPolicySets) EXPECT() *MockPolicySetsMockRecorder {\n\treturn m.recorder\n}\n\n// AddPolicies mocks base method.\nfunc (m *MockPolicySets) AddPolicies(ctx context.Context, policySetID string, options tfe.PolicySetAddPoliciesOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddPolicies\", ctx, policySetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddPolicies indicates an expected call of AddPolicies.\nfunc (mr *MockPolicySetsMockRecorder) AddPolicies(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddPolicies\", reflect.TypeOf((*MockPolicySets)(nil).AddPolicies), ctx, policySetID, options)\n}\n\n// AddProjectExclusions mocks base method.\nfunc (m *MockPolicySets) AddProjectExclusions(ctx context.Context, policySetID string, options tfe.PolicySetAddProjectExclusionsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddProjectExclusions\", ctx, policySetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddProjectExclusions indicates an expected call of AddProjectExclusions.\nfunc (mr *MockPolicySetsMockRecorder) AddProjectExclusions(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddProjectExclusions\", reflect.TypeOf((*MockPolicySets)(nil).AddProjectExclusions), ctx, policySetID, options)\n}\n\n// AddProjects mocks base method.\nfunc (m *MockPolicySets) AddProjects(ctx context.Context, policySetID string, options tfe.PolicySetAddProjectsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddProjects\", ctx, policySetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddProjects indicates an expected call of AddProjects.\nfunc (mr *MockPolicySetsMockRecorder) AddProjects(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddProjects\", reflect.TypeOf((*MockPolicySets)(nil).AddProjects), ctx, policySetID, options)\n}\n\n// AddWorkspaceExclusions mocks base method.\nfunc (m *MockPolicySets) AddWorkspaceExclusions(ctx context.Context, policySetID string, options tfe.PolicySetAddWorkspaceExclusionsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddWorkspaceExclusions\", ctx, policySetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddWorkspaceExclusions indicates an expected call of AddWorkspaceExclusions.\nfunc (mr *MockPolicySetsMockRecorder) AddWorkspaceExclusions(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddWorkspaceExclusions\", reflect.TypeOf((*MockPolicySets)(nil).AddWorkspaceExclusions), ctx, policySetID, options)\n}\n\n// AddWorkspaces mocks base method.\nfunc (m *MockPolicySets) AddWorkspaces(ctx context.Context, policySetID string, options tfe.PolicySetAddWorkspacesOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddWorkspaces\", ctx, policySetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddWorkspaces indicates an expected call of AddWorkspaces.\nfunc (mr *MockPolicySetsMockRecorder) AddWorkspaces(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddWorkspaces\", reflect.TypeOf((*MockPolicySets)(nil).AddWorkspaces), ctx, policySetID, options)\n}\n\n// Create mocks base method.\nfunc (m *MockPolicySets) Create(ctx context.Context, organization string, options tfe.PolicySetCreateOptions) (*tfe.PolicySet, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.PolicySet)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockPolicySetsMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockPolicySets)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockPolicySets) Delete(ctx context.Context, policyID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, policyID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockPolicySetsMockRecorder) Delete(ctx, policyID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockPolicySets)(nil).Delete), ctx, policyID)\n}\n\n// List mocks base method.\nfunc (m *MockPolicySets) List(ctx context.Context, organization string, options *tfe.PolicySetListOptions) (*tfe.PolicySetList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.PolicySetList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockPolicySetsMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockPolicySets)(nil).List), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockPolicySets) Read(ctx context.Context, policySetID string) (*tfe.PolicySet, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, policySetID)\n\tret0, _ := ret[0].(*tfe.PolicySet)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockPolicySetsMockRecorder) Read(ctx, policySetID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockPolicySets)(nil).Read), ctx, policySetID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockPolicySets) ReadWithOptions(ctx context.Context, policySetID string, options *tfe.PolicySetReadOptions) (*tfe.PolicySet, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, policySetID, options)\n\tret0, _ := ret[0].(*tfe.PolicySet)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockPolicySetsMockRecorder) ReadWithOptions(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockPolicySets)(nil).ReadWithOptions), ctx, policySetID, options)\n}\n\n// RemovePolicies mocks base method.\nfunc (m *MockPolicySets) RemovePolicies(ctx context.Context, policySetID string, options tfe.PolicySetRemovePoliciesOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemovePolicies\", ctx, policySetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemovePolicies indicates an expected call of RemovePolicies.\nfunc (mr *MockPolicySetsMockRecorder) RemovePolicies(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemovePolicies\", reflect.TypeOf((*MockPolicySets)(nil).RemovePolicies), ctx, policySetID, options)\n}\n\n// RemoveProjectExclusions mocks base method.\nfunc (m *MockPolicySets) RemoveProjectExclusions(ctx context.Context, policySetID string, options tfe.PolicySetRemoveProjectExclusionsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveProjectExclusions\", ctx, policySetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveProjectExclusions indicates an expected call of RemoveProjectExclusions.\nfunc (mr *MockPolicySetsMockRecorder) RemoveProjectExclusions(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveProjectExclusions\", reflect.TypeOf((*MockPolicySets)(nil).RemoveProjectExclusions), ctx, policySetID, options)\n}\n\n// RemoveProjects mocks base method.\nfunc (m *MockPolicySets) RemoveProjects(ctx context.Context, policySetID string, options tfe.PolicySetRemoveProjectsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveProjects\", ctx, policySetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveProjects indicates an expected call of RemoveProjects.\nfunc (mr *MockPolicySetsMockRecorder) RemoveProjects(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveProjects\", reflect.TypeOf((*MockPolicySets)(nil).RemoveProjects), ctx, policySetID, options)\n}\n\n// RemoveWorkspaceExclusions mocks base method.\nfunc (m *MockPolicySets) RemoveWorkspaceExclusions(ctx context.Context, policySetID string, options tfe.PolicySetRemoveWorkspaceExclusionsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveWorkspaceExclusions\", ctx, policySetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveWorkspaceExclusions indicates an expected call of RemoveWorkspaceExclusions.\nfunc (mr *MockPolicySetsMockRecorder) RemoveWorkspaceExclusions(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveWorkspaceExclusions\", reflect.TypeOf((*MockPolicySets)(nil).RemoveWorkspaceExclusions), ctx, policySetID, options)\n}\n\n// RemoveWorkspaces mocks base method.\nfunc (m *MockPolicySets) RemoveWorkspaces(ctx context.Context, policySetID string, options tfe.PolicySetRemoveWorkspacesOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveWorkspaces\", ctx, policySetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveWorkspaces indicates an expected call of RemoveWorkspaces.\nfunc (mr *MockPolicySetsMockRecorder) RemoveWorkspaces(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveWorkspaces\", reflect.TypeOf((*MockPolicySets)(nil).RemoveWorkspaces), ctx, policySetID, options)\n}\n\n// Update mocks base method.\nfunc (m *MockPolicySets) Update(ctx context.Context, policySetID string, options tfe.PolicySetUpdateOptions) (*tfe.PolicySet, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, policySetID, options)\n\tret0, _ := ret[0].(*tfe.PolicySet)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockPolicySetsMockRecorder) Update(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockPolicySets)(nil).Update), ctx, policySetID, options)\n}\n"
  },
  {
    "path": "mocks/policy_set_parameter_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: policy_set_parameter.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=policy_set_parameter.go -destination=mocks/policy_set_parameter_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockPolicySetParameters is a mock of PolicySetParameters interface.\ntype MockPolicySetParameters struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPolicySetParametersMockRecorder\n}\n\n// MockPolicySetParametersMockRecorder is the mock recorder for MockPolicySetParameters.\ntype MockPolicySetParametersMockRecorder struct {\n\tmock *MockPolicySetParameters\n}\n\n// NewMockPolicySetParameters creates a new mock instance.\nfunc NewMockPolicySetParameters(ctrl *gomock.Controller) *MockPolicySetParameters {\n\tmock := &MockPolicySetParameters{ctrl: ctrl}\n\tmock.recorder = &MockPolicySetParametersMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPolicySetParameters) EXPECT() *MockPolicySetParametersMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockPolicySetParameters) Create(ctx context.Context, policySetID string, options tfe.PolicySetParameterCreateOptions) (*tfe.PolicySetParameter, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, policySetID, options)\n\tret0, _ := ret[0].(*tfe.PolicySetParameter)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockPolicySetParametersMockRecorder) Create(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockPolicySetParameters)(nil).Create), ctx, policySetID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockPolicySetParameters) Delete(ctx context.Context, policySetID, parameterID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, policySetID, parameterID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockPolicySetParametersMockRecorder) Delete(ctx, policySetID, parameterID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockPolicySetParameters)(nil).Delete), ctx, policySetID, parameterID)\n}\n\n// List mocks base method.\nfunc (m *MockPolicySetParameters) List(ctx context.Context, policySetID string, options *tfe.PolicySetParameterListOptions) (*tfe.PolicySetParameterList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, policySetID, options)\n\tret0, _ := ret[0].(*tfe.PolicySetParameterList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockPolicySetParametersMockRecorder) List(ctx, policySetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockPolicySetParameters)(nil).List), ctx, policySetID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockPolicySetParameters) Read(ctx context.Context, policySetID, parameterID string) (*tfe.PolicySetParameter, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, policySetID, parameterID)\n\tret0, _ := ret[0].(*tfe.PolicySetParameter)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockPolicySetParametersMockRecorder) Read(ctx, policySetID, parameterID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockPolicySetParameters)(nil).Read), ctx, policySetID, parameterID)\n}\n\n// Update mocks base method.\nfunc (m *MockPolicySetParameters) Update(ctx context.Context, policySetID, parameterID string, options tfe.PolicySetParameterUpdateOptions) (*tfe.PolicySetParameter, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, policySetID, parameterID, options)\n\tret0, _ := ret[0].(*tfe.PolicySetParameter)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockPolicySetParametersMockRecorder) Update(ctx, policySetID, parameterID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockPolicySetParameters)(nil).Update), ctx, policySetID, parameterID, options)\n}\n"
  },
  {
    "path": "mocks/policy_set_version_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: policy_set_version.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=policy_set_version.go -destination=mocks/policy_set_version_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockPolicySetVersions is a mock of PolicySetVersions interface.\ntype MockPolicySetVersions struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPolicySetVersionsMockRecorder\n}\n\n// MockPolicySetVersionsMockRecorder is the mock recorder for MockPolicySetVersions.\ntype MockPolicySetVersionsMockRecorder struct {\n\tmock *MockPolicySetVersions\n}\n\n// NewMockPolicySetVersions creates a new mock instance.\nfunc NewMockPolicySetVersions(ctrl *gomock.Controller) *MockPolicySetVersions {\n\tmock := &MockPolicySetVersions{ctrl: ctrl}\n\tmock.recorder = &MockPolicySetVersionsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPolicySetVersions) EXPECT() *MockPolicySetVersionsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockPolicySetVersions) Create(ctx context.Context, policySetID string) (*tfe.PolicySetVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, policySetID)\n\tret0, _ := ret[0].(*tfe.PolicySetVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockPolicySetVersionsMockRecorder) Create(ctx, policySetID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockPolicySetVersions)(nil).Create), ctx, policySetID)\n}\n\n// Read mocks base method.\nfunc (m *MockPolicySetVersions) Read(ctx context.Context, policySetVersionID string) (*tfe.PolicySetVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, policySetVersionID)\n\tret0, _ := ret[0].(*tfe.PolicySetVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockPolicySetVersionsMockRecorder) Read(ctx, policySetVersionID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockPolicySetVersions)(nil).Read), ctx, policySetVersionID)\n}\n\n// Upload mocks base method.\nfunc (m *MockPolicySetVersions) Upload(ctx context.Context, psv tfe.PolicySetVersion, path string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Upload\", ctx, psv, path)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Upload indicates an expected call of Upload.\nfunc (mr *MockPolicySetVersionsMockRecorder) Upload(ctx, psv, path any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Upload\", reflect.TypeOf((*MockPolicySetVersions)(nil).Upload), ctx, psv, path)\n}\n"
  },
  {
    "path": "mocks/project_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: project.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=project.go -destination=mocks/project_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockProjects is a mock of Projects interface.\ntype MockProjects struct {\n\tctrl     *gomock.Controller\n\trecorder *MockProjectsMockRecorder\n}\n\n// MockProjectsMockRecorder is the mock recorder for MockProjects.\ntype MockProjectsMockRecorder struct {\n\tmock *MockProjects\n}\n\n// NewMockProjects creates a new mock instance.\nfunc NewMockProjects(ctrl *gomock.Controller) *MockProjects {\n\tmock := &MockProjects{ctrl: ctrl}\n\tmock.recorder = &MockProjectsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockProjects) EXPECT() *MockProjectsMockRecorder {\n\treturn m.recorder\n}\n\n// AddTagBindings mocks base method.\nfunc (m *MockProjects) AddTagBindings(ctx context.Context, projectID string, options tfe.ProjectAddTagBindingsOptions) ([]*tfe.TagBinding, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddTagBindings\", ctx, projectID, options)\n\tret0, _ := ret[0].([]*tfe.TagBinding)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// AddTagBindings indicates an expected call of AddTagBindings.\nfunc (mr *MockProjectsMockRecorder) AddTagBindings(ctx, projectID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddTagBindings\", reflect.TypeOf((*MockProjects)(nil).AddTagBindings), ctx, projectID, options)\n}\n\n// Create mocks base method.\nfunc (m *MockProjects) Create(ctx context.Context, organization string, options tfe.ProjectCreateOptions) (*tfe.Project, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.Project)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockProjectsMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockProjects)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockProjects) Delete(ctx context.Context, projectID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, projectID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockProjectsMockRecorder) Delete(ctx, projectID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockProjects)(nil).Delete), ctx, projectID)\n}\n\n// DeleteAllTagBindings mocks base method.\nfunc (m *MockProjects) DeleteAllTagBindings(ctx context.Context, projectID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteAllTagBindings\", ctx, projectID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteAllTagBindings indicates an expected call of DeleteAllTagBindings.\nfunc (mr *MockProjectsMockRecorder) DeleteAllTagBindings(ctx, projectID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteAllTagBindings\", reflect.TypeOf((*MockProjects)(nil).DeleteAllTagBindings), ctx, projectID)\n}\n\n// List mocks base method.\nfunc (m *MockProjects) List(ctx context.Context, organization string, options *tfe.ProjectListOptions) (*tfe.ProjectList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.ProjectList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockProjectsMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockProjects)(nil).List), ctx, organization, options)\n}\n\n// ListEffectiveTagBindings mocks base method.\nfunc (m *MockProjects) ListEffectiveTagBindings(ctx context.Context, workspaceID string) ([]*tfe.EffectiveTagBinding, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListEffectiveTagBindings\", ctx, workspaceID)\n\tret0, _ := ret[0].([]*tfe.EffectiveTagBinding)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListEffectiveTagBindings indicates an expected call of ListEffectiveTagBindings.\nfunc (mr *MockProjectsMockRecorder) ListEffectiveTagBindings(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListEffectiveTagBindings\", reflect.TypeOf((*MockProjects)(nil).ListEffectiveTagBindings), ctx, workspaceID)\n}\n\n// ListTagBindings mocks base method.\nfunc (m *MockProjects) ListTagBindings(ctx context.Context, projectID string) ([]*tfe.TagBinding, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListTagBindings\", ctx, projectID)\n\tret0, _ := ret[0].([]*tfe.TagBinding)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListTagBindings indicates an expected call of ListTagBindings.\nfunc (mr *MockProjectsMockRecorder) ListTagBindings(ctx, projectID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListTagBindings\", reflect.TypeOf((*MockProjects)(nil).ListTagBindings), ctx, projectID)\n}\n\n// Read mocks base method.\nfunc (m *MockProjects) Read(ctx context.Context, projectID string) (*tfe.Project, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, projectID)\n\tret0, _ := ret[0].(*tfe.Project)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockProjectsMockRecorder) Read(ctx, projectID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockProjects)(nil).Read), ctx, projectID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockProjects) ReadWithOptions(ctx context.Context, projectID string, options tfe.ProjectReadOptions) (*tfe.Project, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, projectID, options)\n\tret0, _ := ret[0].(*tfe.Project)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockProjectsMockRecorder) ReadWithOptions(ctx, projectID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockProjects)(nil).ReadWithOptions), ctx, projectID, options)\n}\n\n// Update mocks base method.\nfunc (m *MockProjects) Update(ctx context.Context, projectID string, options tfe.ProjectUpdateOptions) (*tfe.Project, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, projectID, options)\n\tret0, _ := ret[0].(*tfe.Project)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockProjectsMockRecorder) Update(ctx, projectID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockProjects)(nil).Update), ctx, projectID, options)\n}\n"
  },
  {
    "path": "mocks/query_runs_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: query_runs.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=query_runs.go -destination=mocks/query_runs_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\tio \"io\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockQueryRuns is a mock of QueryRuns interface.\ntype MockQueryRuns struct {\n\tctrl     *gomock.Controller\n\trecorder *MockQueryRunsMockRecorder\n}\n\n// MockQueryRunsMockRecorder is the mock recorder for MockQueryRuns.\ntype MockQueryRunsMockRecorder struct {\n\tmock *MockQueryRuns\n}\n\n// NewMockQueryRuns creates a new mock instance.\nfunc NewMockQueryRuns(ctrl *gomock.Controller) *MockQueryRuns {\n\tmock := &MockQueryRuns{ctrl: ctrl}\n\tmock.recorder = &MockQueryRunsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockQueryRuns) EXPECT() *MockQueryRunsMockRecorder {\n\treturn m.recorder\n}\n\n// Cancel mocks base method.\nfunc (m *MockQueryRuns) Cancel(ctx context.Context, runID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Cancel\", ctx, runID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Cancel indicates an expected call of Cancel.\nfunc (mr *MockQueryRunsMockRecorder) Cancel(ctx, runID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Cancel\", reflect.TypeOf((*MockQueryRuns)(nil).Cancel), ctx, runID)\n}\n\n// Create mocks base method.\nfunc (m *MockQueryRuns) Create(ctx context.Context, options tfe.QueryRunCreateOptions) (*tfe.QueryRun, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, options)\n\tret0, _ := ret[0].(*tfe.QueryRun)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockQueryRunsMockRecorder) Create(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockQueryRuns)(nil).Create), ctx, options)\n}\n\n// ForceCancel mocks base method.\nfunc (m *MockQueryRuns) ForceCancel(ctx context.Context, runID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ForceCancel\", ctx, runID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ForceCancel indicates an expected call of ForceCancel.\nfunc (mr *MockQueryRunsMockRecorder) ForceCancel(ctx, runID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ForceCancel\", reflect.TypeOf((*MockQueryRuns)(nil).ForceCancel), ctx, runID)\n}\n\n// List mocks base method.\nfunc (m *MockQueryRuns) List(ctx context.Context, workspaceID string, options *tfe.QueryRunListOptions) (*tfe.QueryRunList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.QueryRunList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockQueryRunsMockRecorder) List(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockQueryRuns)(nil).List), ctx, workspaceID, options)\n}\n\n// Logs mocks base method.\nfunc (m *MockQueryRuns) Logs(ctx context.Context, queryRunID string) (io.Reader, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Logs\", ctx, queryRunID)\n\tret0, _ := ret[0].(io.Reader)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Logs indicates an expected call of Logs.\nfunc (mr *MockQueryRunsMockRecorder) Logs(ctx, queryRunID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Logs\", reflect.TypeOf((*MockQueryRuns)(nil).Logs), ctx, queryRunID)\n}\n\n// Read mocks base method.\nfunc (m *MockQueryRuns) Read(ctx context.Context, queryRunID string) (*tfe.QueryRun, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, queryRunID)\n\tret0, _ := ret[0].(*tfe.QueryRun)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockQueryRunsMockRecorder) Read(ctx, queryRunID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockQueryRuns)(nil).Read), ctx, queryRunID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockQueryRuns) ReadWithOptions(ctx context.Context, queryRunID string, options *tfe.QueryRunReadOptions) (*tfe.QueryRun, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, queryRunID, options)\n\tret0, _ := ret[0].(*tfe.QueryRun)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockQueryRunsMockRecorder) ReadWithOptions(ctx, queryRunID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockQueryRuns)(nil).ReadWithOptions), ctx, queryRunID, options)\n}\n"
  },
  {
    "path": "mocks/registry_module_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: registry_module.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=registry_module.go -destination=mocks/registry_module_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\tio \"io\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockRegistryModules is a mock of RegistryModules interface.\ntype MockRegistryModules struct {\n\tctrl     *gomock.Controller\n\trecorder *MockRegistryModulesMockRecorder\n}\n\n// MockRegistryModulesMockRecorder is the mock recorder for MockRegistryModules.\ntype MockRegistryModulesMockRecorder struct {\n\tmock *MockRegistryModules\n}\n\n// NewMockRegistryModules creates a new mock instance.\nfunc NewMockRegistryModules(ctrl *gomock.Controller) *MockRegistryModules {\n\tmock := &MockRegistryModules{ctrl: ctrl}\n\tmock.recorder = &MockRegistryModulesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockRegistryModules) EXPECT() *MockRegistryModulesMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockRegistryModules) Create(ctx context.Context, organization string, options tfe.RegistryModuleCreateOptions) (*tfe.RegistryModule, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.RegistryModule)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockRegistryModulesMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockRegistryModules)(nil).Create), ctx, organization, options)\n}\n\n// CreateVersion mocks base method.\nfunc (m *MockRegistryModules) CreateVersion(ctx context.Context, moduleID tfe.RegistryModuleID, options tfe.RegistryModuleCreateVersionOptions) (*tfe.RegistryModuleVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateVersion\", ctx, moduleID, options)\n\tret0, _ := ret[0].(*tfe.RegistryModuleVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateVersion indicates an expected call of CreateVersion.\nfunc (mr *MockRegistryModulesMockRecorder) CreateVersion(ctx, moduleID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateVersion\", reflect.TypeOf((*MockRegistryModules)(nil).CreateVersion), ctx, moduleID, options)\n}\n\n// CreateWithVCSConnection mocks base method.\nfunc (m *MockRegistryModules) CreateWithVCSConnection(ctx context.Context, options tfe.RegistryModuleCreateWithVCSConnectionOptions) (*tfe.RegistryModule, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateWithVCSConnection\", ctx, options)\n\tret0, _ := ret[0].(*tfe.RegistryModule)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateWithVCSConnection indicates an expected call of CreateWithVCSConnection.\nfunc (mr *MockRegistryModulesMockRecorder) CreateWithVCSConnection(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateWithVCSConnection\", reflect.TypeOf((*MockRegistryModules)(nil).CreateWithVCSConnection), ctx, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockRegistryModules) Delete(ctx context.Context, organization, name string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, organization, name)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockRegistryModulesMockRecorder) Delete(ctx, organization, name any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockRegistryModules)(nil).Delete), ctx, organization, name)\n}\n\n// DeleteByName mocks base method.\nfunc (m *MockRegistryModules) DeleteByName(ctx context.Context, module tfe.RegistryModuleID) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteByName\", ctx, module)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteByName indicates an expected call of DeleteByName.\nfunc (mr *MockRegistryModulesMockRecorder) DeleteByName(ctx, module any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteByName\", reflect.TypeOf((*MockRegistryModules)(nil).DeleteByName), ctx, module)\n}\n\n// DeleteProvider mocks base method.\nfunc (m *MockRegistryModules) DeleteProvider(ctx context.Context, moduleID tfe.RegistryModuleID) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteProvider\", ctx, moduleID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteProvider indicates an expected call of DeleteProvider.\nfunc (mr *MockRegistryModulesMockRecorder) DeleteProvider(ctx, moduleID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteProvider\", reflect.TypeOf((*MockRegistryModules)(nil).DeleteProvider), ctx, moduleID)\n}\n\n// DeleteVersion mocks base method.\nfunc (m *MockRegistryModules) DeleteVersion(ctx context.Context, moduleID tfe.RegistryModuleID, version string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteVersion\", ctx, moduleID, version)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteVersion indicates an expected call of DeleteVersion.\nfunc (mr *MockRegistryModulesMockRecorder) DeleteVersion(ctx, moduleID, version any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteVersion\", reflect.TypeOf((*MockRegistryModules)(nil).DeleteVersion), ctx, moduleID, version)\n}\n\n// List mocks base method.\nfunc (m *MockRegistryModules) List(ctx context.Context, organization string, options *tfe.RegistryModuleListOptions) (*tfe.RegistryModuleList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.RegistryModuleList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockRegistryModulesMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockRegistryModules)(nil).List), ctx, organization, options)\n}\n\n// ListCommits mocks base method.\nfunc (m *MockRegistryModules) ListCommits(ctx context.Context, moduleID tfe.RegistryModuleID) (*tfe.CommitList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListCommits\", ctx, moduleID)\n\tret0, _ := ret[0].(*tfe.CommitList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListCommits indicates an expected call of ListCommits.\nfunc (mr *MockRegistryModulesMockRecorder) ListCommits(ctx, moduleID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListCommits\", reflect.TypeOf((*MockRegistryModules)(nil).ListCommits), ctx, moduleID)\n}\n\n// Read mocks base method.\nfunc (m *MockRegistryModules) Read(ctx context.Context, moduleID tfe.RegistryModuleID) (*tfe.RegistryModule, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, moduleID)\n\tret0, _ := ret[0].(*tfe.RegistryModule)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockRegistryModulesMockRecorder) Read(ctx, moduleID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockRegistryModules)(nil).Read), ctx, moduleID)\n}\n\n// ReadTerraformRegistryModule mocks base method.\nfunc (m *MockRegistryModules) ReadTerraformRegistryModule(ctx context.Context, moduleID tfe.RegistryModuleID, version string) (*tfe.TerraformRegistryModule, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadTerraformRegistryModule\", ctx, moduleID, version)\n\tret0, _ := ret[0].(*tfe.TerraformRegistryModule)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadTerraformRegistryModule indicates an expected call of ReadTerraformRegistryModule.\nfunc (mr *MockRegistryModulesMockRecorder) ReadTerraformRegistryModule(ctx, moduleID, version any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadTerraformRegistryModule\", reflect.TypeOf((*MockRegistryModules)(nil).ReadTerraformRegistryModule), ctx, moduleID, version)\n}\n\n// ReadVersion mocks base method.\nfunc (m *MockRegistryModules) ReadVersion(ctx context.Context, moduleID tfe.RegistryModuleID, version string) (*tfe.RegistryModuleVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadVersion\", ctx, moduleID, version)\n\tret0, _ := ret[0].(*tfe.RegistryModuleVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadVersion indicates an expected call of ReadVersion.\nfunc (mr *MockRegistryModulesMockRecorder) ReadVersion(ctx, moduleID, version any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadVersion\", reflect.TypeOf((*MockRegistryModules)(nil).ReadVersion), ctx, moduleID, version)\n}\n\n// Update mocks base method.\nfunc (m *MockRegistryModules) Update(ctx context.Context, moduleID tfe.RegistryModuleID, options tfe.RegistryModuleUpdateOptions) (*tfe.RegistryModule, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, moduleID, options)\n\tret0, _ := ret[0].(*tfe.RegistryModule)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockRegistryModulesMockRecorder) Update(ctx, moduleID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockRegistryModules)(nil).Update), ctx, moduleID, options)\n}\n\n// Upload mocks base method.\nfunc (m *MockRegistryModules) Upload(ctx context.Context, rmv tfe.RegistryModuleVersion, path string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Upload\", ctx, rmv, path)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Upload indicates an expected call of Upload.\nfunc (mr *MockRegistryModulesMockRecorder) Upload(ctx, rmv, path any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Upload\", reflect.TypeOf((*MockRegistryModules)(nil).Upload), ctx, rmv, path)\n}\n\n// UploadTarGzip mocks base method.\nfunc (m *MockRegistryModules) UploadTarGzip(ctx context.Context, url string, r io.Reader) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UploadTarGzip\", ctx, url, r)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UploadTarGzip indicates an expected call of UploadTarGzip.\nfunc (mr *MockRegistryModulesMockRecorder) UploadTarGzip(ctx, url, r any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UploadTarGzip\", reflect.TypeOf((*MockRegistryModules)(nil).UploadTarGzip), ctx, url, r)\n}\n"
  },
  {
    "path": "mocks/registry_no_code_module_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: registry_no_code_module.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=registry_no_code_module.go -destination=mocks/registry_no_code_module_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockRegistryNoCodeModules is a mock of RegistryNoCodeModules interface.\ntype MockRegistryNoCodeModules struct {\n\tctrl     *gomock.Controller\n\trecorder *MockRegistryNoCodeModulesMockRecorder\n}\n\n// MockRegistryNoCodeModulesMockRecorder is the mock recorder for MockRegistryNoCodeModules.\ntype MockRegistryNoCodeModulesMockRecorder struct {\n\tmock *MockRegistryNoCodeModules\n}\n\n// NewMockRegistryNoCodeModules creates a new mock instance.\nfunc NewMockRegistryNoCodeModules(ctrl *gomock.Controller) *MockRegistryNoCodeModules {\n\tmock := &MockRegistryNoCodeModules{ctrl: ctrl}\n\tmock.recorder = &MockRegistryNoCodeModulesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockRegistryNoCodeModules) EXPECT() *MockRegistryNoCodeModulesMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockRegistryNoCodeModules) Create(ctx context.Context, organization string, options tfe.RegistryNoCodeModuleCreateOptions) (*tfe.RegistryNoCodeModule, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.RegistryNoCodeModule)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockRegistryNoCodeModulesMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockRegistryNoCodeModules)(nil).Create), ctx, organization, options)\n}\n\n// CreateWorkspace mocks base method.\nfunc (m *MockRegistryNoCodeModules) CreateWorkspace(ctx context.Context, noCodeModuleID string, options *tfe.RegistryNoCodeModuleCreateWorkspaceOptions) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateWorkspace\", ctx, noCodeModuleID, options)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateWorkspace indicates an expected call of CreateWorkspace.\nfunc (mr *MockRegistryNoCodeModulesMockRecorder) CreateWorkspace(ctx, noCodeModuleID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateWorkspace\", reflect.TypeOf((*MockRegistryNoCodeModules)(nil).CreateWorkspace), ctx, noCodeModuleID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockRegistryNoCodeModules) Delete(ctx context.Context, ID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, ID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockRegistryNoCodeModulesMockRecorder) Delete(ctx, ID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockRegistryNoCodeModules)(nil).Delete), ctx, ID)\n}\n\n// Read mocks base method.\nfunc (m *MockRegistryNoCodeModules) Read(ctx context.Context, noCodeModuleID string, options *tfe.RegistryNoCodeModuleReadOptions) (*tfe.RegistryNoCodeModule, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, noCodeModuleID, options)\n\tret0, _ := ret[0].(*tfe.RegistryNoCodeModule)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockRegistryNoCodeModulesMockRecorder) Read(ctx, noCodeModuleID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockRegistryNoCodeModules)(nil).Read), ctx, noCodeModuleID, options)\n}\n\n// ReadVariables mocks base method.\nfunc (m *MockRegistryNoCodeModules) ReadVariables(ctx context.Context, noCodeModuleID, noCodeModuleVersion string, options *tfe.RegistryNoCodeModuleReadVariablesOptions) (*tfe.RegistryModuleVariableList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadVariables\", ctx, noCodeModuleID, noCodeModuleVersion, options)\n\tret0, _ := ret[0].(*tfe.RegistryModuleVariableList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadVariables indicates an expected call of ReadVariables.\nfunc (mr *MockRegistryNoCodeModulesMockRecorder) ReadVariables(ctx, noCodeModuleID, noCodeModuleVersion, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadVariables\", reflect.TypeOf((*MockRegistryNoCodeModules)(nil).ReadVariables), ctx, noCodeModuleID, noCodeModuleVersion, options)\n}\n\n// Update mocks base method.\nfunc (m *MockRegistryNoCodeModules) Update(ctx context.Context, noCodeModuleID string, options tfe.RegistryNoCodeModuleUpdateOptions) (*tfe.RegistryNoCodeModule, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, noCodeModuleID, options)\n\tret0, _ := ret[0].(*tfe.RegistryNoCodeModule)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockRegistryNoCodeModulesMockRecorder) Update(ctx, noCodeModuleID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockRegistryNoCodeModules)(nil).Update), ctx, noCodeModuleID, options)\n}\n\n// UpgradeWorkspace mocks base method.\nfunc (m *MockRegistryNoCodeModules) UpgradeWorkspace(ctx context.Context, noCodeModuleID, workspaceID string, options *tfe.RegistryNoCodeModuleUpgradeWorkspaceOptions) (*tfe.WorkspaceUpgrade, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpgradeWorkspace\", ctx, noCodeModuleID, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.WorkspaceUpgrade)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpgradeWorkspace indicates an expected call of UpgradeWorkspace.\nfunc (mr *MockRegistryNoCodeModulesMockRecorder) UpgradeWorkspace(ctx, noCodeModuleID, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpgradeWorkspace\", reflect.TypeOf((*MockRegistryNoCodeModules)(nil).UpgradeWorkspace), ctx, noCodeModuleID, workspaceID, options)\n}\n"
  },
  {
    "path": "mocks/registry_provider_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: registry_provider.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=registry_provider.go -destination=mocks/registry_provider_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockRegistryProviders is a mock of RegistryProviders interface.\ntype MockRegistryProviders struct {\n\tctrl     *gomock.Controller\n\trecorder *MockRegistryProvidersMockRecorder\n}\n\n// MockRegistryProvidersMockRecorder is the mock recorder for MockRegistryProviders.\ntype MockRegistryProvidersMockRecorder struct {\n\tmock *MockRegistryProviders\n}\n\n// NewMockRegistryProviders creates a new mock instance.\nfunc NewMockRegistryProviders(ctrl *gomock.Controller) *MockRegistryProviders {\n\tmock := &MockRegistryProviders{ctrl: ctrl}\n\tmock.recorder = &MockRegistryProvidersMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockRegistryProviders) EXPECT() *MockRegistryProvidersMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockRegistryProviders) Create(ctx context.Context, organization string, options tfe.RegistryProviderCreateOptions) (*tfe.RegistryProvider, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.RegistryProvider)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockRegistryProvidersMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockRegistryProviders)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockRegistryProviders) Delete(ctx context.Context, providerID tfe.RegistryProviderID) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, providerID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockRegistryProvidersMockRecorder) Delete(ctx, providerID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockRegistryProviders)(nil).Delete), ctx, providerID)\n}\n\n// List mocks base method.\nfunc (m *MockRegistryProviders) List(ctx context.Context, organization string, options *tfe.RegistryProviderListOptions) (*tfe.RegistryProviderList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.RegistryProviderList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockRegistryProvidersMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockRegistryProviders)(nil).List), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockRegistryProviders) Read(ctx context.Context, providerID tfe.RegistryProviderID, options *tfe.RegistryProviderReadOptions) (*tfe.RegistryProvider, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, providerID, options)\n\tret0, _ := ret[0].(*tfe.RegistryProvider)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockRegistryProvidersMockRecorder) Read(ctx, providerID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockRegistryProviders)(nil).Read), ctx, providerID, options)\n}\n"
  },
  {
    "path": "mocks/registry_provider_platform_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: registry_provider_platform.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=registry_provider_platform.go -destination=mocks/registry_provider_platform_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockRegistryProviderPlatforms is a mock of RegistryProviderPlatforms interface.\ntype MockRegistryProviderPlatforms struct {\n\tctrl     *gomock.Controller\n\trecorder *MockRegistryProviderPlatformsMockRecorder\n}\n\n// MockRegistryProviderPlatformsMockRecorder is the mock recorder for MockRegistryProviderPlatforms.\ntype MockRegistryProviderPlatformsMockRecorder struct {\n\tmock *MockRegistryProviderPlatforms\n}\n\n// NewMockRegistryProviderPlatforms creates a new mock instance.\nfunc NewMockRegistryProviderPlatforms(ctrl *gomock.Controller) *MockRegistryProviderPlatforms {\n\tmock := &MockRegistryProviderPlatforms{ctrl: ctrl}\n\tmock.recorder = &MockRegistryProviderPlatformsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockRegistryProviderPlatforms) EXPECT() *MockRegistryProviderPlatformsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockRegistryProviderPlatforms) Create(ctx context.Context, versionID tfe.RegistryProviderVersionID, options tfe.RegistryProviderPlatformCreateOptions) (*tfe.RegistryProviderPlatform, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, versionID, options)\n\tret0, _ := ret[0].(*tfe.RegistryProviderPlatform)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockRegistryProviderPlatformsMockRecorder) Create(ctx, versionID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockRegistryProviderPlatforms)(nil).Create), ctx, versionID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockRegistryProviderPlatforms) Delete(ctx context.Context, platformID tfe.RegistryProviderPlatformID) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, platformID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockRegistryProviderPlatformsMockRecorder) Delete(ctx, platformID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockRegistryProviderPlatforms)(nil).Delete), ctx, platformID)\n}\n\n// List mocks base method.\nfunc (m *MockRegistryProviderPlatforms) List(ctx context.Context, versionID tfe.RegistryProviderVersionID, options *tfe.RegistryProviderPlatformListOptions) (*tfe.RegistryProviderPlatformList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, versionID, options)\n\tret0, _ := ret[0].(*tfe.RegistryProviderPlatformList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockRegistryProviderPlatformsMockRecorder) List(ctx, versionID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockRegistryProviderPlatforms)(nil).List), ctx, versionID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockRegistryProviderPlatforms) Read(ctx context.Context, platformID tfe.RegistryProviderPlatformID) (*tfe.RegistryProviderPlatform, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, platformID)\n\tret0, _ := ret[0].(*tfe.RegistryProviderPlatform)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockRegistryProviderPlatformsMockRecorder) Read(ctx, platformID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockRegistryProviderPlatforms)(nil).Read), ctx, platformID)\n}\n"
  },
  {
    "path": "mocks/registry_provider_version_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: registry_provider_version.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=registry_provider_version.go -destination=mocks/registry_provider_version_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockRegistryProviderVersions is a mock of RegistryProviderVersions interface.\ntype MockRegistryProviderVersions struct {\n\tctrl     *gomock.Controller\n\trecorder *MockRegistryProviderVersionsMockRecorder\n}\n\n// MockRegistryProviderVersionsMockRecorder is the mock recorder for MockRegistryProviderVersions.\ntype MockRegistryProviderVersionsMockRecorder struct {\n\tmock *MockRegistryProviderVersions\n}\n\n// NewMockRegistryProviderVersions creates a new mock instance.\nfunc NewMockRegistryProviderVersions(ctrl *gomock.Controller) *MockRegistryProviderVersions {\n\tmock := &MockRegistryProviderVersions{ctrl: ctrl}\n\tmock.recorder = &MockRegistryProviderVersionsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockRegistryProviderVersions) EXPECT() *MockRegistryProviderVersionsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockRegistryProviderVersions) Create(ctx context.Context, providerID tfe.RegistryProviderID, options tfe.RegistryProviderVersionCreateOptions) (*tfe.RegistryProviderVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, providerID, options)\n\tret0, _ := ret[0].(*tfe.RegistryProviderVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockRegistryProviderVersionsMockRecorder) Create(ctx, providerID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockRegistryProviderVersions)(nil).Create), ctx, providerID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockRegistryProviderVersions) Delete(ctx context.Context, versionID tfe.RegistryProviderVersionID) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, versionID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockRegistryProviderVersionsMockRecorder) Delete(ctx, versionID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockRegistryProviderVersions)(nil).Delete), ctx, versionID)\n}\n\n// List mocks base method.\nfunc (m *MockRegistryProviderVersions) List(ctx context.Context, providerID tfe.RegistryProviderID, options *tfe.RegistryProviderVersionListOptions) (*tfe.RegistryProviderVersionList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, providerID, options)\n\tret0, _ := ret[0].(*tfe.RegistryProviderVersionList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockRegistryProviderVersionsMockRecorder) List(ctx, providerID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockRegistryProviderVersions)(nil).List), ctx, providerID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockRegistryProviderVersions) Read(ctx context.Context, versionID tfe.RegistryProviderVersionID) (*tfe.RegistryProviderVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, versionID)\n\tret0, _ := ret[0].(*tfe.RegistryProviderVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockRegistryProviderVersionsMockRecorder) Read(ctx, versionID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockRegistryProviderVersions)(nil).Read), ctx, versionID)\n}\n"
  },
  {
    "path": "mocks/run_events_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: run_event.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=run_event.go -destination=mocks/run_events_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockRunEvents is a mock of RunEvents interface.\ntype MockRunEvents struct {\n\tctrl     *gomock.Controller\n\trecorder *MockRunEventsMockRecorder\n}\n\n// MockRunEventsMockRecorder is the mock recorder for MockRunEvents.\ntype MockRunEventsMockRecorder struct {\n\tmock *MockRunEvents\n}\n\n// NewMockRunEvents creates a new mock instance.\nfunc NewMockRunEvents(ctrl *gomock.Controller) *MockRunEvents {\n\tmock := &MockRunEvents{ctrl: ctrl}\n\tmock.recorder = &MockRunEventsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockRunEvents) EXPECT() *MockRunEventsMockRecorder {\n\treturn m.recorder\n}\n\n// List mocks base method.\nfunc (m *MockRunEvents) List(ctx context.Context, runID string, options *tfe.RunEventListOptions) (*tfe.RunEventList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, runID, options)\n\tret0, _ := ret[0].(*tfe.RunEventList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockRunEventsMockRecorder) List(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockRunEvents)(nil).List), ctx, runID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockRunEvents) Read(ctx context.Context, runEventID string) (*tfe.RunEvent, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, runEventID)\n\tret0, _ := ret[0].(*tfe.RunEvent)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockRunEventsMockRecorder) Read(ctx, runEventID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockRunEvents)(nil).Read), ctx, runEventID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockRunEvents) ReadWithOptions(ctx context.Context, runEventID string, options *tfe.RunEventReadOptions) (*tfe.RunEvent, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, runEventID, options)\n\tret0, _ := ret[0].(*tfe.RunEvent)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockRunEventsMockRecorder) ReadWithOptions(ctx, runEventID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockRunEvents)(nil).ReadWithOptions), ctx, runEventID, options)\n}\n"
  },
  {
    "path": "mocks/run_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: run.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=run.go -destination=mocks/run_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockRuns is a mock of Runs interface.\ntype MockRuns struct {\n\tctrl     *gomock.Controller\n\trecorder *MockRunsMockRecorder\n}\n\n// MockRunsMockRecorder is the mock recorder for MockRuns.\ntype MockRunsMockRecorder struct {\n\tmock *MockRuns\n}\n\n// NewMockRuns creates a new mock instance.\nfunc NewMockRuns(ctrl *gomock.Controller) *MockRuns {\n\tmock := &MockRuns{ctrl: ctrl}\n\tmock.recorder = &MockRunsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockRuns) EXPECT() *MockRunsMockRecorder {\n\treturn m.recorder\n}\n\n// Apply mocks base method.\nfunc (m *MockRuns) Apply(ctx context.Context, runID string, options tfe.RunApplyOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Apply\", ctx, runID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Apply indicates an expected call of Apply.\nfunc (mr *MockRunsMockRecorder) Apply(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Apply\", reflect.TypeOf((*MockRuns)(nil).Apply), ctx, runID, options)\n}\n\n// Cancel mocks base method.\nfunc (m *MockRuns) Cancel(ctx context.Context, runID string, options tfe.RunCancelOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Cancel\", ctx, runID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Cancel indicates an expected call of Cancel.\nfunc (mr *MockRunsMockRecorder) Cancel(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Cancel\", reflect.TypeOf((*MockRuns)(nil).Cancel), ctx, runID, options)\n}\n\n// Create mocks base method.\nfunc (m *MockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*tfe.Run, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, options)\n\tret0, _ := ret[0].(*tfe.Run)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockRunsMockRecorder) Create(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockRuns)(nil).Create), ctx, options)\n}\n\n// Discard mocks base method.\nfunc (m *MockRuns) Discard(ctx context.Context, runID string, options tfe.RunDiscardOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Discard\", ctx, runID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Discard indicates an expected call of Discard.\nfunc (mr *MockRunsMockRecorder) Discard(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Discard\", reflect.TypeOf((*MockRuns)(nil).Discard), ctx, runID, options)\n}\n\n// ForceCancel mocks base method.\nfunc (m *MockRuns) ForceCancel(ctx context.Context, runID string, options tfe.RunForceCancelOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ForceCancel\", ctx, runID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ForceCancel indicates an expected call of ForceCancel.\nfunc (mr *MockRunsMockRecorder) ForceCancel(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ForceCancel\", reflect.TypeOf((*MockRuns)(nil).ForceCancel), ctx, runID, options)\n}\n\n// ForceExecute mocks base method.\nfunc (m *MockRuns) ForceExecute(ctx context.Context, runID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ForceExecute\", ctx, runID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ForceExecute indicates an expected call of ForceExecute.\nfunc (mr *MockRunsMockRecorder) ForceExecute(ctx, runID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ForceExecute\", reflect.TypeOf((*MockRuns)(nil).ForceExecute), ctx, runID)\n}\n\n// List mocks base method.\nfunc (m *MockRuns) List(ctx context.Context, workspaceID string, options *tfe.RunListOptions) (*tfe.RunList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.RunList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockRunsMockRecorder) List(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockRuns)(nil).List), ctx, workspaceID, options)\n}\n\n// ListForOrganization mocks base method.\nfunc (m *MockRuns) ListForOrganization(ctx context.Context, organization string, options *tfe.RunListForOrganizationOptions) (*tfe.OrganizationRunList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListForOrganization\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.OrganizationRunList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListForOrganization indicates an expected call of ListForOrganization.\nfunc (mr *MockRunsMockRecorder) ListForOrganization(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListForOrganization\", reflect.TypeOf((*MockRuns)(nil).ListForOrganization), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockRuns) Read(ctx context.Context, runID string) (*tfe.Run, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, runID)\n\tret0, _ := ret[0].(*tfe.Run)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockRunsMockRecorder) Read(ctx, runID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockRuns)(nil).Read), ctx, runID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockRuns) ReadWithOptions(ctx context.Context, runID string, options *tfe.RunReadOptions) (*tfe.Run, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, runID, options)\n\tret0, _ := ret[0].(*tfe.Run)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockRunsMockRecorder) ReadWithOptions(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockRuns)(nil).ReadWithOptions), ctx, runID, options)\n}\n"
  },
  {
    "path": "mocks/run_tasks_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: run_task.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=run_task.go -destination=mocks/run_tasks_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockRunTasks is a mock of RunTasks interface.\ntype MockRunTasks struct {\n\tctrl     *gomock.Controller\n\trecorder *MockRunTasksMockRecorder\n}\n\n// MockRunTasksMockRecorder is the mock recorder for MockRunTasks.\ntype MockRunTasksMockRecorder struct {\n\tmock *MockRunTasks\n}\n\n// NewMockRunTasks creates a new mock instance.\nfunc NewMockRunTasks(ctrl *gomock.Controller) *MockRunTasks {\n\tmock := &MockRunTasks{ctrl: ctrl}\n\tmock.recorder = &MockRunTasksMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockRunTasks) EXPECT() *MockRunTasksMockRecorder {\n\treturn m.recorder\n}\n\n// AttachToWorkspace mocks base method.\nfunc (m *MockRunTasks) AttachToWorkspace(ctx context.Context, workspaceID, runTaskID string, enforcementLevel tfe.TaskEnforcementLevel) (*tfe.WorkspaceRunTask, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AttachToWorkspace\", ctx, workspaceID, runTaskID, enforcementLevel)\n\tret0, _ := ret[0].(*tfe.WorkspaceRunTask)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// AttachToWorkspace indicates an expected call of AttachToWorkspace.\nfunc (mr *MockRunTasksMockRecorder) AttachToWorkspace(ctx, workspaceID, runTaskID, enforcementLevel any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AttachToWorkspace\", reflect.TypeOf((*MockRunTasks)(nil).AttachToWorkspace), ctx, workspaceID, runTaskID, enforcementLevel)\n}\n\n// Create mocks base method.\nfunc (m *MockRunTasks) Create(ctx context.Context, organization string, options tfe.RunTaskCreateOptions) (*tfe.RunTask, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.RunTask)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockRunTasksMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockRunTasks)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockRunTasks) Delete(ctx context.Context, runTaskID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, runTaskID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockRunTasksMockRecorder) Delete(ctx, runTaskID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockRunTasks)(nil).Delete), ctx, runTaskID)\n}\n\n// List mocks base method.\nfunc (m *MockRunTasks) List(ctx context.Context, organization string, options *tfe.RunTaskListOptions) (*tfe.RunTaskList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.RunTaskList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockRunTasksMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockRunTasks)(nil).List), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockRunTasks) Read(ctx context.Context, runTaskID string) (*tfe.RunTask, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, runTaskID)\n\tret0, _ := ret[0].(*tfe.RunTask)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockRunTasksMockRecorder) Read(ctx, runTaskID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockRunTasks)(nil).Read), ctx, runTaskID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockRunTasks) ReadWithOptions(ctx context.Context, runTaskID string, options *tfe.RunTaskReadOptions) (*tfe.RunTask, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, runTaskID, options)\n\tret0, _ := ret[0].(*tfe.RunTask)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockRunTasksMockRecorder) ReadWithOptions(ctx, runTaskID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockRunTasks)(nil).ReadWithOptions), ctx, runTaskID, options)\n}\n\n// Update mocks base method.\nfunc (m *MockRunTasks) Update(ctx context.Context, runTaskID string, options tfe.RunTaskUpdateOptions) (*tfe.RunTask, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, runTaskID, options)\n\tret0, _ := ret[0].(*tfe.RunTask)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockRunTasksMockRecorder) Update(ctx, runTaskID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockRunTasks)(nil).Update), ctx, runTaskID, options)\n}\n"
  },
  {
    "path": "mocks/run_trigger_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: run_trigger.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=run_trigger.go -destination=mocks/run_trigger_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockRunTriggers is a mock of RunTriggers interface.\ntype MockRunTriggers struct {\n\tctrl     *gomock.Controller\n\trecorder *MockRunTriggersMockRecorder\n}\n\n// MockRunTriggersMockRecorder is the mock recorder for MockRunTriggers.\ntype MockRunTriggersMockRecorder struct {\n\tmock *MockRunTriggers\n}\n\n// NewMockRunTriggers creates a new mock instance.\nfunc NewMockRunTriggers(ctrl *gomock.Controller) *MockRunTriggers {\n\tmock := &MockRunTriggers{ctrl: ctrl}\n\tmock.recorder = &MockRunTriggersMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockRunTriggers) EXPECT() *MockRunTriggersMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockRunTriggers) Create(ctx context.Context, workspaceID string, options tfe.RunTriggerCreateOptions) (*tfe.RunTrigger, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.RunTrigger)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockRunTriggersMockRecorder) Create(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockRunTriggers)(nil).Create), ctx, workspaceID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockRunTriggers) Delete(ctx context.Context, RunTriggerID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, RunTriggerID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockRunTriggersMockRecorder) Delete(ctx, RunTriggerID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockRunTriggers)(nil).Delete), ctx, RunTriggerID)\n}\n\n// List mocks base method.\nfunc (m *MockRunTriggers) List(ctx context.Context, workspaceID string, options *tfe.RunTriggerListOptions) (*tfe.RunTriggerList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.RunTriggerList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockRunTriggersMockRecorder) List(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockRunTriggers)(nil).List), ctx, workspaceID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockRunTriggers) Read(ctx context.Context, RunTriggerID string) (*tfe.RunTrigger, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, RunTriggerID)\n\tret0, _ := ret[0].(*tfe.RunTrigger)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockRunTriggersMockRecorder) Read(ctx, RunTriggerID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockRunTriggers)(nil).Read), ctx, RunTriggerID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockRunTriggers) ReadWithOptions(ctx context.Context, runID string, options *tfe.RunTriggerReadOptions) (*tfe.RunTrigger, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, runID, options)\n\tret0, _ := ret[0].(*tfe.RunTrigger)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockRunTriggersMockRecorder) ReadWithOptions(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockRunTriggers)(nil).ReadWithOptions), ctx, runID, options)\n}\n"
  },
  {
    "path": "mocks/ssh_key_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: ssh_key.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=ssh_key.go -destination=mocks/ssh_key_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockSSHKeys is a mock of SSHKeys interface.\ntype MockSSHKeys struct {\n\tctrl     *gomock.Controller\n\trecorder *MockSSHKeysMockRecorder\n}\n\n// MockSSHKeysMockRecorder is the mock recorder for MockSSHKeys.\ntype MockSSHKeysMockRecorder struct {\n\tmock *MockSSHKeys\n}\n\n// NewMockSSHKeys creates a new mock instance.\nfunc NewMockSSHKeys(ctrl *gomock.Controller) *MockSSHKeys {\n\tmock := &MockSSHKeys{ctrl: ctrl}\n\tmock.recorder = &MockSSHKeysMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockSSHKeys) EXPECT() *MockSSHKeysMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockSSHKeys) Create(ctx context.Context, organization string, options tfe.SSHKeyCreateOptions) (*tfe.SSHKey, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.SSHKey)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockSSHKeysMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockSSHKeys)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockSSHKeys) Delete(ctx context.Context, sshKeyID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, sshKeyID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockSSHKeysMockRecorder) Delete(ctx, sshKeyID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockSSHKeys)(nil).Delete), ctx, sshKeyID)\n}\n\n// List mocks base method.\nfunc (m *MockSSHKeys) List(ctx context.Context, organization string, options *tfe.SSHKeyListOptions) (*tfe.SSHKeyList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.SSHKeyList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockSSHKeysMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockSSHKeys)(nil).List), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockSSHKeys) Read(ctx context.Context, sshKeyID string) (*tfe.SSHKey, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, sshKeyID)\n\tret0, _ := ret[0].(*tfe.SSHKey)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockSSHKeysMockRecorder) Read(ctx, sshKeyID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockSSHKeys)(nil).Read), ctx, sshKeyID)\n}\n\n// Update mocks base method.\nfunc (m *MockSSHKeys) Update(ctx context.Context, sshKeyID string, options tfe.SSHKeyUpdateOptions) (*tfe.SSHKey, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, sshKeyID, options)\n\tret0, _ := ret[0].(*tfe.SSHKey)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockSSHKeysMockRecorder) Update(ctx, sshKeyID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockSSHKeys)(nil).Update), ctx, sshKeyID, options)\n}\n"
  },
  {
    "path": "mocks/state_version_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: state_version.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=state_version.go -destination=mocks/state_version_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockStateVersions is a mock of StateVersions interface.\ntype MockStateVersions struct {\n\tctrl     *gomock.Controller\n\trecorder *MockStateVersionsMockRecorder\n}\n\n// MockStateVersionsMockRecorder is the mock recorder for MockStateVersions.\ntype MockStateVersionsMockRecorder struct {\n\tmock *MockStateVersions\n}\n\n// NewMockStateVersions creates a new mock instance.\nfunc NewMockStateVersions(ctrl *gomock.Controller) *MockStateVersions {\n\tmock := &MockStateVersions{ctrl: ctrl}\n\tmock.recorder = &MockStateVersionsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockStateVersions) EXPECT() *MockStateVersionsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockStateVersions) Create(ctx context.Context, workspaceID string, options tfe.StateVersionCreateOptions) (*tfe.StateVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.StateVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockStateVersionsMockRecorder) Create(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockStateVersions)(nil).Create), ctx, workspaceID, options)\n}\n\n// Download mocks base method.\nfunc (m *MockStateVersions) Download(ctx context.Context, url string) ([]byte, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Download\", ctx, url)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Download indicates an expected call of Download.\nfunc (mr *MockStateVersionsMockRecorder) Download(ctx, url any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Download\", reflect.TypeOf((*MockStateVersions)(nil).Download), ctx, url)\n}\n\n// List mocks base method.\nfunc (m *MockStateVersions) List(ctx context.Context, options *tfe.StateVersionListOptions) (*tfe.StateVersionList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.StateVersionList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockStateVersionsMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockStateVersions)(nil).List), ctx, options)\n}\n\n// ListOutputs mocks base method.\nfunc (m *MockStateVersions) ListOutputs(ctx context.Context, svID string, options *tfe.StateVersionOutputsListOptions) (*tfe.StateVersionOutputsList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListOutputs\", ctx, svID, options)\n\tret0, _ := ret[0].(*tfe.StateVersionOutputsList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListOutputs indicates an expected call of ListOutputs.\nfunc (mr *MockStateVersionsMockRecorder) ListOutputs(ctx, svID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListOutputs\", reflect.TypeOf((*MockStateVersions)(nil).ListOutputs), ctx, svID, options)\n}\n\n// PermanentlyDeleteBackingData mocks base method.\nfunc (m *MockStateVersions) PermanentlyDeleteBackingData(ctx context.Context, svID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PermanentlyDeleteBackingData\", ctx, svID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// PermanentlyDeleteBackingData indicates an expected call of PermanentlyDeleteBackingData.\nfunc (mr *MockStateVersionsMockRecorder) PermanentlyDeleteBackingData(ctx, svID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PermanentlyDeleteBackingData\", reflect.TypeOf((*MockStateVersions)(nil).PermanentlyDeleteBackingData), ctx, svID)\n}\n\n// Read mocks base method.\nfunc (m *MockStateVersions) Read(ctx context.Context, svID string) (*tfe.StateVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, svID)\n\tret0, _ := ret[0].(*tfe.StateVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockStateVersionsMockRecorder) Read(ctx, svID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockStateVersions)(nil).Read), ctx, svID)\n}\n\n// ReadCurrent mocks base method.\nfunc (m *MockStateVersions) ReadCurrent(ctx context.Context, workspaceID string) (*tfe.StateVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadCurrent\", ctx, workspaceID)\n\tret0, _ := ret[0].(*tfe.StateVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadCurrent indicates an expected call of ReadCurrent.\nfunc (mr *MockStateVersionsMockRecorder) ReadCurrent(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadCurrent\", reflect.TypeOf((*MockStateVersions)(nil).ReadCurrent), ctx, workspaceID)\n}\n\n// ReadCurrentWithOptions mocks base method.\nfunc (m *MockStateVersions) ReadCurrentWithOptions(ctx context.Context, workspaceID string, options *tfe.StateVersionCurrentOptions) (*tfe.StateVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadCurrentWithOptions\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.StateVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadCurrentWithOptions indicates an expected call of ReadCurrentWithOptions.\nfunc (mr *MockStateVersionsMockRecorder) ReadCurrentWithOptions(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadCurrentWithOptions\", reflect.TypeOf((*MockStateVersions)(nil).ReadCurrentWithOptions), ctx, workspaceID, options)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockStateVersions) ReadWithOptions(ctx context.Context, svID string, options *tfe.StateVersionReadOptions) (*tfe.StateVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, svID, options)\n\tret0, _ := ret[0].(*tfe.StateVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockStateVersionsMockRecorder) ReadWithOptions(ctx, svID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockStateVersions)(nil).ReadWithOptions), ctx, svID, options)\n}\n\n// RestoreBackingData mocks base method.\nfunc (m *MockStateVersions) RestoreBackingData(ctx context.Context, svID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RestoreBackingData\", ctx, svID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RestoreBackingData indicates an expected call of RestoreBackingData.\nfunc (mr *MockStateVersionsMockRecorder) RestoreBackingData(ctx, svID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RestoreBackingData\", reflect.TypeOf((*MockStateVersions)(nil).RestoreBackingData), ctx, svID)\n}\n\n// SoftDeleteBackingData mocks base method.\nfunc (m *MockStateVersions) SoftDeleteBackingData(ctx context.Context, svID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SoftDeleteBackingData\", ctx, svID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SoftDeleteBackingData indicates an expected call of SoftDeleteBackingData.\nfunc (mr *MockStateVersionsMockRecorder) SoftDeleteBackingData(ctx, svID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SoftDeleteBackingData\", reflect.TypeOf((*MockStateVersions)(nil).SoftDeleteBackingData), ctx, svID)\n}\n\n// Upload mocks base method.\nfunc (m *MockStateVersions) Upload(ctx context.Context, workspaceID string, options tfe.StateVersionUploadOptions) (*tfe.StateVersion, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Upload\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.StateVersion)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Upload indicates an expected call of Upload.\nfunc (mr *MockStateVersionsMockRecorder) Upload(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Upload\", reflect.TypeOf((*MockStateVersions)(nil).Upload), ctx, workspaceID, options)\n}\n\n// UploadSanitizedState mocks base method.\nfunc (m *MockStateVersions) UploadSanitizedState(ctx context.Context, sanitizedStateUploadURL *string, sanitizedState []byte) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UploadSanitizedState\", ctx, sanitizedStateUploadURL, sanitizedState)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UploadSanitizedState indicates an expected call of UploadSanitizedState.\nfunc (mr *MockStateVersionsMockRecorder) UploadSanitizedState(ctx, sanitizedStateUploadURL, sanitizedState any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UploadSanitizedState\", reflect.TypeOf((*MockStateVersions)(nil).UploadSanitizedState), ctx, sanitizedStateUploadURL, sanitizedState)\n}\n"
  },
  {
    "path": "mocks/state_version_output_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: state_version_output.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=state_version_output.go -destination=mocks/state_version_output_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockStateVersionOutputs is a mock of StateVersionOutputs interface.\ntype MockStateVersionOutputs struct {\n\tctrl     *gomock.Controller\n\trecorder *MockStateVersionOutputsMockRecorder\n}\n\n// MockStateVersionOutputsMockRecorder is the mock recorder for MockStateVersionOutputs.\ntype MockStateVersionOutputsMockRecorder struct {\n\tmock *MockStateVersionOutputs\n}\n\n// NewMockStateVersionOutputs creates a new mock instance.\nfunc NewMockStateVersionOutputs(ctrl *gomock.Controller) *MockStateVersionOutputs {\n\tmock := &MockStateVersionOutputs{ctrl: ctrl}\n\tmock.recorder = &MockStateVersionOutputsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockStateVersionOutputs) EXPECT() *MockStateVersionOutputsMockRecorder {\n\treturn m.recorder\n}\n\n// Read mocks base method.\nfunc (m *MockStateVersionOutputs) Read(ctx context.Context, outputID string) (*tfe.StateVersionOutput, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, outputID)\n\tret0, _ := ret[0].(*tfe.StateVersionOutput)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockStateVersionOutputsMockRecorder) Read(ctx, outputID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockStateVersionOutputs)(nil).Read), ctx, outputID)\n}\n\n// ReadCurrent mocks base method.\nfunc (m *MockStateVersionOutputs) ReadCurrent(ctx context.Context, workspaceID string) (*tfe.StateVersionOutputsList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadCurrent\", ctx, workspaceID)\n\tret0, _ := ret[0].(*tfe.StateVersionOutputsList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadCurrent indicates an expected call of ReadCurrent.\nfunc (mr *MockStateVersionOutputsMockRecorder) ReadCurrent(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadCurrent\", reflect.TypeOf((*MockStateVersionOutputs)(nil).ReadCurrent), ctx, workspaceID)\n}\n"
  },
  {
    "path": "mocks/tag_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: tag.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=tag.go -destination=mocks/tag_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n"
  },
  {
    "path": "mocks/task_result_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: task_result.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=task_result.go -destination=mocks/task_result_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockTaskResults is a mock of TaskResults interface.\ntype MockTaskResults struct {\n\tctrl     *gomock.Controller\n\trecorder *MockTaskResultsMockRecorder\n}\n\n// MockTaskResultsMockRecorder is the mock recorder for MockTaskResults.\ntype MockTaskResultsMockRecorder struct {\n\tmock *MockTaskResults\n}\n\n// NewMockTaskResults creates a new mock instance.\nfunc NewMockTaskResults(ctrl *gomock.Controller) *MockTaskResults {\n\tmock := &MockTaskResults{ctrl: ctrl}\n\tmock.recorder = &MockTaskResultsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockTaskResults) EXPECT() *MockTaskResultsMockRecorder {\n\treturn m.recorder\n}\n\n// Read mocks base method.\nfunc (m *MockTaskResults) Read(ctx context.Context, taskResultID string) (*tfe.TaskResult, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, taskResultID)\n\tret0, _ := ret[0].(*tfe.TaskResult)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockTaskResultsMockRecorder) Read(ctx, taskResultID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockTaskResults)(nil).Read), ctx, taskResultID)\n}\n"
  },
  {
    "path": "mocks/task_stages_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: task_stages.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=task_stages.go -destination=mocks/task_stages_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockTaskStages is a mock of TaskStages interface.\ntype MockTaskStages struct {\n\tctrl     *gomock.Controller\n\trecorder *MockTaskStagesMockRecorder\n}\n\n// MockTaskStagesMockRecorder is the mock recorder for MockTaskStages.\ntype MockTaskStagesMockRecorder struct {\n\tmock *MockTaskStages\n}\n\n// NewMockTaskStages creates a new mock instance.\nfunc NewMockTaskStages(ctrl *gomock.Controller) *MockTaskStages {\n\tmock := &MockTaskStages{ctrl: ctrl}\n\tmock.recorder = &MockTaskStagesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockTaskStages) EXPECT() *MockTaskStagesMockRecorder {\n\treturn m.recorder\n}\n\n// List mocks base method.\nfunc (m *MockTaskStages) List(ctx context.Context, runID string, options *tfe.TaskStageListOptions) (*tfe.TaskStageList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, runID, options)\n\tret0, _ := ret[0].(*tfe.TaskStageList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockTaskStagesMockRecorder) List(ctx, runID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockTaskStages)(nil).List), ctx, runID, options)\n}\n\n// Override mocks base method.\nfunc (m *MockTaskStages) Override(ctx context.Context, taskStageID string, options tfe.TaskStageOverrideOptions) (*tfe.TaskStage, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Override\", ctx, taskStageID, options)\n\tret0, _ := ret[0].(*tfe.TaskStage)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Override indicates an expected call of Override.\nfunc (mr *MockTaskStagesMockRecorder) Override(ctx, taskStageID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Override\", reflect.TypeOf((*MockTaskStages)(nil).Override), ctx, taskStageID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockTaskStages) Read(ctx context.Context, taskStageID string, options *tfe.TaskStageReadOptions) (*tfe.TaskStage, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, taskStageID, options)\n\tret0, _ := ret[0].(*tfe.TaskStage)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockTaskStagesMockRecorder) Read(ctx, taskStageID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockTaskStages)(nil).Read), ctx, taskStageID, options)\n}\n"
  },
  {
    "path": "mocks/team_access_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: team_access.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=team_access.go -destination=mocks/team_access_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockTeamAccesses is a mock of TeamAccesses interface.\ntype MockTeamAccesses struct {\n\tctrl     *gomock.Controller\n\trecorder *MockTeamAccessesMockRecorder\n}\n\n// MockTeamAccessesMockRecorder is the mock recorder for MockTeamAccesses.\ntype MockTeamAccessesMockRecorder struct {\n\tmock *MockTeamAccesses\n}\n\n// NewMockTeamAccesses creates a new mock instance.\nfunc NewMockTeamAccesses(ctrl *gomock.Controller) *MockTeamAccesses {\n\tmock := &MockTeamAccesses{ctrl: ctrl}\n\tmock.recorder = &MockTeamAccessesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockTeamAccesses) EXPECT() *MockTeamAccessesMockRecorder {\n\treturn m.recorder\n}\n\n// Add mocks base method.\nfunc (m *MockTeamAccesses) Add(ctx context.Context, options tfe.TeamAccessAddOptions) (*tfe.TeamAccess, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Add\", ctx, options)\n\tret0, _ := ret[0].(*tfe.TeamAccess)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Add indicates an expected call of Add.\nfunc (mr *MockTeamAccessesMockRecorder) Add(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Add\", reflect.TypeOf((*MockTeamAccesses)(nil).Add), ctx, options)\n}\n\n// List mocks base method.\nfunc (m *MockTeamAccesses) List(ctx context.Context, options *tfe.TeamAccessListOptions) (*tfe.TeamAccessList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.TeamAccessList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockTeamAccessesMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockTeamAccesses)(nil).List), ctx, options)\n}\n\n// Read mocks base method.\nfunc (m *MockTeamAccesses) Read(ctx context.Context, teamAccessID string) (*tfe.TeamAccess, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, teamAccessID)\n\tret0, _ := ret[0].(*tfe.TeamAccess)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockTeamAccessesMockRecorder) Read(ctx, teamAccessID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockTeamAccesses)(nil).Read), ctx, teamAccessID)\n}\n\n// Remove mocks base method.\nfunc (m *MockTeamAccesses) Remove(ctx context.Context, teamAccessID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Remove\", ctx, teamAccessID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Remove indicates an expected call of Remove.\nfunc (mr *MockTeamAccessesMockRecorder) Remove(ctx, teamAccessID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Remove\", reflect.TypeOf((*MockTeamAccesses)(nil).Remove), ctx, teamAccessID)\n}\n\n// Update mocks base method.\nfunc (m *MockTeamAccesses) Update(ctx context.Context, teamAccessID string, options tfe.TeamAccessUpdateOptions) (*tfe.TeamAccess, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, teamAccessID, options)\n\tret0, _ := ret[0].(*tfe.TeamAccess)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockTeamAccessesMockRecorder) Update(ctx, teamAccessID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockTeamAccesses)(nil).Update), ctx, teamAccessID, options)\n}\n"
  },
  {
    "path": "mocks/team_member_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: team_member.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=team_member.go -destination=mocks/team_member_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockTeamMembers is a mock of TeamMembers interface.\ntype MockTeamMembers struct {\n\tctrl     *gomock.Controller\n\trecorder *MockTeamMembersMockRecorder\n}\n\n// MockTeamMembersMockRecorder is the mock recorder for MockTeamMembers.\ntype MockTeamMembersMockRecorder struct {\n\tmock *MockTeamMembers\n}\n\n// NewMockTeamMembers creates a new mock instance.\nfunc NewMockTeamMembers(ctrl *gomock.Controller) *MockTeamMembers {\n\tmock := &MockTeamMembers{ctrl: ctrl}\n\tmock.recorder = &MockTeamMembersMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockTeamMembers) EXPECT() *MockTeamMembersMockRecorder {\n\treturn m.recorder\n}\n\n// Add mocks base method.\nfunc (m *MockTeamMembers) Add(ctx context.Context, teamID string, options tfe.TeamMemberAddOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Add\", ctx, teamID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Add indicates an expected call of Add.\nfunc (mr *MockTeamMembersMockRecorder) Add(ctx, teamID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Add\", reflect.TypeOf((*MockTeamMembers)(nil).Add), ctx, teamID, options)\n}\n\n// List mocks base method.\nfunc (m *MockTeamMembers) List(ctx context.Context, teamID string) ([]*tfe.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, teamID)\n\tret0, _ := ret[0].([]*tfe.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockTeamMembersMockRecorder) List(ctx, teamID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockTeamMembers)(nil).List), ctx, teamID)\n}\n\n// ListOrganizationMemberships mocks base method.\nfunc (m *MockTeamMembers) ListOrganizationMemberships(ctx context.Context, teamID string) ([]*tfe.OrganizationMembership, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListOrganizationMemberships\", ctx, teamID)\n\tret0, _ := ret[0].([]*tfe.OrganizationMembership)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListOrganizationMemberships indicates an expected call of ListOrganizationMemberships.\nfunc (mr *MockTeamMembersMockRecorder) ListOrganizationMemberships(ctx, teamID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListOrganizationMemberships\", reflect.TypeOf((*MockTeamMembers)(nil).ListOrganizationMemberships), ctx, teamID)\n}\n\n// ListUsers mocks base method.\nfunc (m *MockTeamMembers) ListUsers(ctx context.Context, teamID string) ([]*tfe.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListUsers\", ctx, teamID)\n\tret0, _ := ret[0].([]*tfe.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListUsers indicates an expected call of ListUsers.\nfunc (mr *MockTeamMembersMockRecorder) ListUsers(ctx, teamID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListUsers\", reflect.TypeOf((*MockTeamMembers)(nil).ListUsers), ctx, teamID)\n}\n\n// Remove mocks base method.\nfunc (m *MockTeamMembers) Remove(ctx context.Context, teamID string, options tfe.TeamMemberRemoveOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Remove\", ctx, teamID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Remove indicates an expected call of Remove.\nfunc (mr *MockTeamMembersMockRecorder) Remove(ctx, teamID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Remove\", reflect.TypeOf((*MockTeamMembers)(nil).Remove), ctx, teamID, options)\n}\n"
  },
  {
    "path": "mocks/team_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: team.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=team.go -destination=mocks/team_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockTeams is a mock of Teams interface.\ntype MockTeams struct {\n\tctrl     *gomock.Controller\n\trecorder *MockTeamsMockRecorder\n}\n\n// MockTeamsMockRecorder is the mock recorder for MockTeams.\ntype MockTeamsMockRecorder struct {\n\tmock *MockTeams\n}\n\n// NewMockTeams creates a new mock instance.\nfunc NewMockTeams(ctrl *gomock.Controller) *MockTeams {\n\tmock := &MockTeams{ctrl: ctrl}\n\tmock.recorder = &MockTeamsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockTeams) EXPECT() *MockTeamsMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockTeams) Create(ctx context.Context, organization string, options tfe.TeamCreateOptions) (*tfe.Team, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.Team)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockTeamsMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockTeams)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockTeams) Delete(ctx context.Context, teamID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, teamID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockTeamsMockRecorder) Delete(ctx, teamID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockTeams)(nil).Delete), ctx, teamID)\n}\n\n// List mocks base method.\nfunc (m *MockTeams) List(ctx context.Context, organization string, options *tfe.TeamListOptions) (*tfe.TeamList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.TeamList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockTeamsMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockTeams)(nil).List), ctx, organization, options)\n}\n\n// Read mocks base method.\nfunc (m *MockTeams) Read(ctx context.Context, teamID string) (*tfe.Team, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, teamID)\n\tret0, _ := ret[0].(*tfe.Team)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockTeamsMockRecorder) Read(ctx, teamID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockTeams)(nil).Read), ctx, teamID)\n}\n\n// Update mocks base method.\nfunc (m *MockTeams) Update(ctx context.Context, teamID string, options tfe.TeamUpdateOptions) (*tfe.Team, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, teamID, options)\n\tret0, _ := ret[0].(*tfe.Team)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockTeamsMockRecorder) Update(ctx, teamID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockTeams)(nil).Update), ctx, teamID, options)\n}\n"
  },
  {
    "path": "mocks/team_project_access_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: team_project_access.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=team_project_access.go -destination=mocks/team_project_access_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockTeamProjectAccesses is a mock of TeamProjectAccesses interface.\ntype MockTeamProjectAccesses struct {\n\tctrl     *gomock.Controller\n\trecorder *MockTeamProjectAccessesMockRecorder\n}\n\n// MockTeamProjectAccessesMockRecorder is the mock recorder for MockTeamProjectAccesses.\ntype MockTeamProjectAccessesMockRecorder struct {\n\tmock *MockTeamProjectAccesses\n}\n\n// NewMockTeamProjectAccesses creates a new mock instance.\nfunc NewMockTeamProjectAccesses(ctrl *gomock.Controller) *MockTeamProjectAccesses {\n\tmock := &MockTeamProjectAccesses{ctrl: ctrl}\n\tmock.recorder = &MockTeamProjectAccessesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockTeamProjectAccesses) EXPECT() *MockTeamProjectAccessesMockRecorder {\n\treturn m.recorder\n}\n\n// Add mocks base method.\nfunc (m *MockTeamProjectAccesses) Add(ctx context.Context, options tfe.TeamProjectAccessAddOptions) (*tfe.TeamProjectAccess, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Add\", ctx, options)\n\tret0, _ := ret[0].(*tfe.TeamProjectAccess)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Add indicates an expected call of Add.\nfunc (mr *MockTeamProjectAccessesMockRecorder) Add(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Add\", reflect.TypeOf((*MockTeamProjectAccesses)(nil).Add), ctx, options)\n}\n\n// List mocks base method.\nfunc (m *MockTeamProjectAccesses) List(ctx context.Context, options tfe.TeamProjectAccessListOptions) (*tfe.TeamProjectAccessList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, options)\n\tret0, _ := ret[0].(*tfe.TeamProjectAccessList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockTeamProjectAccessesMockRecorder) List(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockTeamProjectAccesses)(nil).List), ctx, options)\n}\n\n// Read mocks base method.\nfunc (m *MockTeamProjectAccesses) Read(ctx context.Context, teamProjectAccessID string) (*tfe.TeamProjectAccess, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, teamProjectAccessID)\n\tret0, _ := ret[0].(*tfe.TeamProjectAccess)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockTeamProjectAccessesMockRecorder) Read(ctx, teamProjectAccessID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockTeamProjectAccesses)(nil).Read), ctx, teamProjectAccessID)\n}\n\n// Remove mocks base method.\nfunc (m *MockTeamProjectAccesses) Remove(ctx context.Context, teamProjectAccessID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Remove\", ctx, teamProjectAccessID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Remove indicates an expected call of Remove.\nfunc (mr *MockTeamProjectAccessesMockRecorder) Remove(ctx, teamProjectAccessID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Remove\", reflect.TypeOf((*MockTeamProjectAccesses)(nil).Remove), ctx, teamProjectAccessID)\n}\n\n// Update mocks base method.\nfunc (m *MockTeamProjectAccesses) Update(ctx context.Context, teamProjectAccessID string, options tfe.TeamProjectAccessUpdateOptions) (*tfe.TeamProjectAccess, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, teamProjectAccessID, options)\n\tret0, _ := ret[0].(*tfe.TeamProjectAccess)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockTeamProjectAccessesMockRecorder) Update(ctx, teamProjectAccessID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockTeamProjectAccesses)(nil).Update), ctx, teamProjectAccessID, options)\n}\n"
  },
  {
    "path": "mocks/team_token_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: team_token.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=team_token.go -destination=mocks/team_token_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockTeamTokens is a mock of TeamTokens interface.\ntype MockTeamTokens struct {\n\tctrl     *gomock.Controller\n\trecorder *MockTeamTokensMockRecorder\n}\n\n// MockTeamTokensMockRecorder is the mock recorder for MockTeamTokens.\ntype MockTeamTokensMockRecorder struct {\n\tmock *MockTeamTokens\n}\n\n// NewMockTeamTokens creates a new mock instance.\nfunc NewMockTeamTokens(ctrl *gomock.Controller) *MockTeamTokens {\n\tmock := &MockTeamTokens{ctrl: ctrl}\n\tmock.recorder = &MockTeamTokensMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockTeamTokens) EXPECT() *MockTeamTokensMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockTeamTokens) Create(ctx context.Context, teamID string) (*tfe.TeamToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, teamID)\n\tret0, _ := ret[0].(*tfe.TeamToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockTeamTokensMockRecorder) Create(ctx, teamID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockTeamTokens)(nil).Create), ctx, teamID)\n}\n\n// CreateWithOptions mocks base method.\nfunc (m *MockTeamTokens) CreateWithOptions(ctx context.Context, teamID string, options tfe.TeamTokenCreateOptions) (*tfe.TeamToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateWithOptions\", ctx, teamID, options)\n\tret0, _ := ret[0].(*tfe.TeamToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateWithOptions indicates an expected call of CreateWithOptions.\nfunc (mr *MockTeamTokensMockRecorder) CreateWithOptions(ctx, teamID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateWithOptions\", reflect.TypeOf((*MockTeamTokens)(nil).CreateWithOptions), ctx, teamID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockTeamTokens) Delete(ctx context.Context, teamID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, teamID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockTeamTokensMockRecorder) Delete(ctx, teamID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockTeamTokens)(nil).Delete), ctx, teamID)\n}\n\n// DeleteByID mocks base method.\nfunc (m *MockTeamTokens) DeleteByID(ctx context.Context, tokenID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteByID\", ctx, tokenID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteByID indicates an expected call of DeleteByID.\nfunc (mr *MockTeamTokensMockRecorder) DeleteByID(ctx, tokenID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteByID\", reflect.TypeOf((*MockTeamTokens)(nil).DeleteByID), ctx, tokenID)\n}\n\n// List mocks base method.\nfunc (m *MockTeamTokens) List(ctx context.Context, organizationID string, options *tfe.TeamTokenListOptions) (*tfe.TeamTokenList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organizationID, options)\n\tret0, _ := ret[0].(*tfe.TeamTokenList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockTeamTokensMockRecorder) List(ctx, organizationID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockTeamTokens)(nil).List), ctx, organizationID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockTeamTokens) Read(ctx context.Context, teamID string) (*tfe.TeamToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, teamID)\n\tret0, _ := ret[0].(*tfe.TeamToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockTeamTokensMockRecorder) Read(ctx, teamID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockTeamTokens)(nil).Read), ctx, teamID)\n}\n\n// ReadByID mocks base method.\nfunc (m *MockTeamTokens) ReadByID(ctx context.Context, teamID string) (*tfe.TeamToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadByID\", ctx, teamID)\n\tret0, _ := ret[0].(*tfe.TeamToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadByID indicates an expected call of ReadByID.\nfunc (mr *MockTeamTokensMockRecorder) ReadByID(ctx, teamID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadByID\", reflect.TypeOf((*MockTeamTokens)(nil).ReadByID), ctx, teamID)\n}\n"
  },
  {
    "path": "mocks/test_run_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: test_run.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=test_run.go -destination=mocks/test_run_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\tio \"io\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockTestRuns is a mock of TestRuns interface.\ntype MockTestRuns struct {\n\tctrl     *gomock.Controller\n\trecorder *MockTestRunsMockRecorder\n}\n\n// MockTestRunsMockRecorder is the mock recorder for MockTestRuns.\ntype MockTestRunsMockRecorder struct {\n\tmock *MockTestRuns\n}\n\n// NewMockTestRuns creates a new mock instance.\nfunc NewMockTestRuns(ctrl *gomock.Controller) *MockTestRuns {\n\tmock := &MockTestRuns{ctrl: ctrl}\n\tmock.recorder = &MockTestRunsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockTestRuns) EXPECT() *MockTestRunsMockRecorder {\n\treturn m.recorder\n}\n\n// Cancel mocks base method.\nfunc (m *MockTestRuns) Cancel(ctx context.Context, moduleID tfe.RegistryModuleID, testRunID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Cancel\", ctx, moduleID, testRunID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Cancel indicates an expected call of Cancel.\nfunc (mr *MockTestRunsMockRecorder) Cancel(ctx, moduleID, testRunID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Cancel\", reflect.TypeOf((*MockTestRuns)(nil).Cancel), ctx, moduleID, testRunID)\n}\n\n// Create mocks base method.\nfunc (m *MockTestRuns) Create(ctx context.Context, options tfe.TestRunCreateOptions) (*tfe.TestRun, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, options)\n\tret0, _ := ret[0].(*tfe.TestRun)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockTestRunsMockRecorder) Create(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockTestRuns)(nil).Create), ctx, options)\n}\n\n// ForceCancel mocks base method.\nfunc (m *MockTestRuns) ForceCancel(ctx context.Context, moduleID tfe.RegistryModuleID, testRunID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ForceCancel\", ctx, moduleID, testRunID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ForceCancel indicates an expected call of ForceCancel.\nfunc (mr *MockTestRunsMockRecorder) ForceCancel(ctx, moduleID, testRunID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ForceCancel\", reflect.TypeOf((*MockTestRuns)(nil).ForceCancel), ctx, moduleID, testRunID)\n}\n\n// List mocks base method.\nfunc (m *MockTestRuns) List(ctx context.Context, moduleID tfe.RegistryModuleID, options *tfe.TestRunListOptions) (*tfe.TestRunList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, moduleID, options)\n\tret0, _ := ret[0].(*tfe.TestRunList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockTestRunsMockRecorder) List(ctx, moduleID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockTestRuns)(nil).List), ctx, moduleID, options)\n}\n\n// Logs mocks base method.\nfunc (m *MockTestRuns) Logs(ctx context.Context, moduleID tfe.RegistryModuleID, testRunID string) (io.Reader, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Logs\", ctx, moduleID, testRunID)\n\tret0, _ := ret[0].(io.Reader)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Logs indicates an expected call of Logs.\nfunc (mr *MockTestRunsMockRecorder) Logs(ctx, moduleID, testRunID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Logs\", reflect.TypeOf((*MockTestRuns)(nil).Logs), ctx, moduleID, testRunID)\n}\n\n// Read mocks base method.\nfunc (m *MockTestRuns) Read(ctx context.Context, moduleID tfe.RegistryModuleID, testRunID string) (*tfe.TestRun, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, moduleID, testRunID)\n\tret0, _ := ret[0].(*tfe.TestRun)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockTestRunsMockRecorder) Read(ctx, moduleID, testRunID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockTestRuns)(nil).Read), ctx, moduleID, testRunID)\n}\n"
  },
  {
    "path": "mocks/test_variables_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: test_variables.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=test_variables.go -destination=mocks/test_variables_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockTestVariables is a mock of TestVariables interface.\ntype MockTestVariables struct {\n\tctrl     *gomock.Controller\n\trecorder *MockTestVariablesMockRecorder\n}\n\n// MockTestVariablesMockRecorder is the mock recorder for MockTestVariables.\ntype MockTestVariablesMockRecorder struct {\n\tmock *MockTestVariables\n}\n\n// NewMockTestVariables creates a new mock instance.\nfunc NewMockTestVariables(ctrl *gomock.Controller) *MockTestVariables {\n\tmock := &MockTestVariables{ctrl: ctrl}\n\tmock.recorder = &MockTestVariablesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockTestVariables) EXPECT() *MockTestVariablesMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockTestVariables) Create(ctx context.Context, moduleID tfe.RegistryModuleID, options tfe.VariableCreateOptions) (*tfe.Variable, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, moduleID, options)\n\tret0, _ := ret[0].(*tfe.Variable)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockTestVariablesMockRecorder) Create(ctx, moduleID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockTestVariables)(nil).Create), ctx, moduleID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockTestVariables) Delete(ctx context.Context, moduleID tfe.RegistryModuleID, variableID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, moduleID, variableID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockTestVariablesMockRecorder) Delete(ctx, moduleID, variableID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockTestVariables)(nil).Delete), ctx, moduleID, variableID)\n}\n\n// List mocks base method.\nfunc (m *MockTestVariables) List(ctx context.Context, moduleID tfe.RegistryModuleID, options *tfe.VariableListOptions) (*tfe.VariableList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, moduleID, options)\n\tret0, _ := ret[0].(*tfe.VariableList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockTestVariablesMockRecorder) List(ctx, moduleID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockTestVariables)(nil).List), ctx, moduleID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockTestVariables) Read(ctx context.Context, moduleID tfe.RegistryModuleID, variableID string) (*tfe.Variable, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, moduleID, variableID)\n\tret0, _ := ret[0].(*tfe.Variable)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockTestVariablesMockRecorder) Read(ctx, moduleID, variableID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockTestVariables)(nil).Read), ctx, moduleID, variableID)\n}\n\n// Update mocks base method.\nfunc (m *MockTestVariables) Update(ctx context.Context, moduleID tfe.RegistryModuleID, variableID string, options tfe.VariableUpdateOptions) (*tfe.Variable, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, moduleID, variableID, options)\n\tret0, _ := ret[0].(*tfe.Variable)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockTestVariablesMockRecorder) Update(ctx, moduleID, variableID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockTestVariables)(nil).Update), ctx, moduleID, variableID, options)\n}\n"
  },
  {
    "path": "mocks/user_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: user.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=user.go -destination=mocks/user_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockUsers is a mock of Users interface.\ntype MockUsers struct {\n\tctrl     *gomock.Controller\n\trecorder *MockUsersMockRecorder\n}\n\n// MockUsersMockRecorder is the mock recorder for MockUsers.\ntype MockUsersMockRecorder struct {\n\tmock *MockUsers\n}\n\n// NewMockUsers creates a new mock instance.\nfunc NewMockUsers(ctrl *gomock.Controller) *MockUsers {\n\tmock := &MockUsers{ctrl: ctrl}\n\tmock.recorder = &MockUsersMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockUsers) EXPECT() *MockUsersMockRecorder {\n\treturn m.recorder\n}\n\n// ReadCurrent mocks base method.\nfunc (m *MockUsers) ReadCurrent(ctx context.Context) (*tfe.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadCurrent\", ctx)\n\tret0, _ := ret[0].(*tfe.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadCurrent indicates an expected call of ReadCurrent.\nfunc (mr *MockUsersMockRecorder) ReadCurrent(ctx any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadCurrent\", reflect.TypeOf((*MockUsers)(nil).ReadCurrent), ctx)\n}\n\n// UpdateCurrent mocks base method.\nfunc (m *MockUsers) UpdateCurrent(ctx context.Context, options tfe.UserUpdateOptions) (*tfe.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateCurrent\", ctx, options)\n\tret0, _ := ret[0].(*tfe.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateCurrent indicates an expected call of UpdateCurrent.\nfunc (mr *MockUsersMockRecorder) UpdateCurrent(ctx, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateCurrent\", reflect.TypeOf((*MockUsers)(nil).UpdateCurrent), ctx, options)\n}\n"
  },
  {
    "path": "mocks/user_token_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: user_token.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=user_token.go -destination=mocks/user_token_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockUserTokens is a mock of UserTokens interface.\ntype MockUserTokens struct {\n\tctrl     *gomock.Controller\n\trecorder *MockUserTokensMockRecorder\n}\n\n// MockUserTokensMockRecorder is the mock recorder for MockUserTokens.\ntype MockUserTokensMockRecorder struct {\n\tmock *MockUserTokens\n}\n\n// NewMockUserTokens creates a new mock instance.\nfunc NewMockUserTokens(ctrl *gomock.Controller) *MockUserTokens {\n\tmock := &MockUserTokens{ctrl: ctrl}\n\tmock.recorder = &MockUserTokensMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockUserTokens) EXPECT() *MockUserTokensMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockUserTokens) Create(ctx context.Context, userID string, options tfe.UserTokenCreateOptions) (*tfe.UserToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, userID, options)\n\tret0, _ := ret[0].(*tfe.UserToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockUserTokensMockRecorder) Create(ctx, userID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockUserTokens)(nil).Create), ctx, userID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockUserTokens) Delete(ctx context.Context, tokenID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, tokenID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockUserTokensMockRecorder) Delete(ctx, tokenID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockUserTokens)(nil).Delete), ctx, tokenID)\n}\n\n// List mocks base method.\nfunc (m *MockUserTokens) List(ctx context.Context, userID string) (*tfe.UserTokenList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, userID)\n\tret0, _ := ret[0].(*tfe.UserTokenList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockUserTokensMockRecorder) List(ctx, userID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockUserTokens)(nil).List), ctx, userID)\n}\n\n// Read mocks base method.\nfunc (m *MockUserTokens) Read(ctx context.Context, tokenID string) (*tfe.UserToken, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, tokenID)\n\tret0, _ := ret[0].(*tfe.UserToken)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockUserTokensMockRecorder) Read(ctx, tokenID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockUserTokens)(nil).Read), ctx, tokenID)\n}\n"
  },
  {
    "path": "mocks/variable_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: variable.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=variable.go -destination=mocks/variable_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockVariables is a mock of Variables interface.\ntype MockVariables struct {\n\tctrl     *gomock.Controller\n\trecorder *MockVariablesMockRecorder\n}\n\n// MockVariablesMockRecorder is the mock recorder for MockVariables.\ntype MockVariablesMockRecorder struct {\n\tmock *MockVariables\n}\n\n// NewMockVariables creates a new mock instance.\nfunc NewMockVariables(ctrl *gomock.Controller) *MockVariables {\n\tmock := &MockVariables{ctrl: ctrl}\n\tmock.recorder = &MockVariablesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockVariables) EXPECT() *MockVariablesMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockVariables) Create(ctx context.Context, workspaceID string, options tfe.VariableCreateOptions) (*tfe.Variable, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.Variable)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockVariablesMockRecorder) Create(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockVariables)(nil).Create), ctx, workspaceID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockVariables) Delete(ctx context.Context, workspaceID, variableID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, workspaceID, variableID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockVariablesMockRecorder) Delete(ctx, workspaceID, variableID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockVariables)(nil).Delete), ctx, workspaceID, variableID)\n}\n\n// List mocks base method.\nfunc (m *MockVariables) List(ctx context.Context, workspaceID string, options *tfe.VariableListOptions) (*tfe.VariableList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.VariableList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockVariablesMockRecorder) List(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockVariables)(nil).List), ctx, workspaceID, options)\n}\n\n// ListAll mocks base method.\nfunc (m *MockVariables) ListAll(ctx context.Context, workspaceID string, options *tfe.VariableListOptions) (*tfe.VariableList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListAll\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.VariableList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListAll indicates an expected call of ListAll.\nfunc (mr *MockVariablesMockRecorder) ListAll(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListAll\", reflect.TypeOf((*MockVariables)(nil).ListAll), ctx, workspaceID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockVariables) Read(ctx context.Context, workspaceID, variableID string) (*tfe.Variable, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, workspaceID, variableID)\n\tret0, _ := ret[0].(*tfe.Variable)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockVariablesMockRecorder) Read(ctx, workspaceID, variableID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockVariables)(nil).Read), ctx, workspaceID, variableID)\n}\n\n// Update mocks base method.\nfunc (m *MockVariables) Update(ctx context.Context, workspaceID, variableID string, options tfe.VariableUpdateOptions) (*tfe.Variable, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, workspaceID, variableID, options)\n\tret0, _ := ret[0].(*tfe.Variable)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockVariablesMockRecorder) Update(ctx, workspaceID, variableID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockVariables)(nil).Update), ctx, workspaceID, variableID, options)\n}\n"
  },
  {
    "path": "mocks/variable_set_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: variable_set.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=variable_set.go -destination=mocks/variable_set_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockVariableSets is a mock of VariableSets interface.\ntype MockVariableSets struct {\n\tctrl     *gomock.Controller\n\trecorder *MockVariableSetsMockRecorder\n}\n\n// MockVariableSetsMockRecorder is the mock recorder for MockVariableSets.\ntype MockVariableSetsMockRecorder struct {\n\tmock *MockVariableSets\n}\n\n// NewMockVariableSets creates a new mock instance.\nfunc NewMockVariableSets(ctrl *gomock.Controller) *MockVariableSets {\n\tmock := &MockVariableSets{ctrl: ctrl}\n\tmock.recorder = &MockVariableSetsMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockVariableSets) EXPECT() *MockVariableSetsMockRecorder {\n\treturn m.recorder\n}\n\n// ApplyToProjects mocks base method.\nfunc (m *MockVariableSets) ApplyToProjects(ctx context.Context, variableSetID string, options tfe.VariableSetApplyToProjectsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ApplyToProjects\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ApplyToProjects indicates an expected call of ApplyToProjects.\nfunc (mr *MockVariableSetsMockRecorder) ApplyToProjects(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ApplyToProjects\", reflect.TypeOf((*MockVariableSets)(nil).ApplyToProjects), ctx, variableSetID, options)\n}\n\n// ApplyToStacks mocks base method.\nfunc (m *MockVariableSets) ApplyToStacks(ctx context.Context, variableSetID string, options *tfe.VariableSetApplyToStacksOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ApplyToStacks\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ApplyToStacks indicates an expected call of ApplyToStacks.\nfunc (mr *MockVariableSetsMockRecorder) ApplyToStacks(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ApplyToStacks\", reflect.TypeOf((*MockVariableSets)(nil).ApplyToStacks), ctx, variableSetID, options)\n}\n\n// ApplyToWorkspaces mocks base method.\nfunc (m *MockVariableSets) ApplyToWorkspaces(ctx context.Context, variableSetID string, options *tfe.VariableSetApplyToWorkspacesOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ApplyToWorkspaces\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// ApplyToWorkspaces indicates an expected call of ApplyToWorkspaces.\nfunc (mr *MockVariableSetsMockRecorder) ApplyToWorkspaces(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ApplyToWorkspaces\", reflect.TypeOf((*MockVariableSets)(nil).ApplyToWorkspaces), ctx, variableSetID, options)\n}\n\n// Create mocks base method.\nfunc (m *MockVariableSets) Create(ctx context.Context, organization string, options *tfe.VariableSetCreateOptions) (*tfe.VariableSet, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.VariableSet)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockVariableSetsMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockVariableSets)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockVariableSets) Delete(ctx context.Context, variableSetID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, variableSetID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockVariableSetsMockRecorder) Delete(ctx, variableSetID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockVariableSets)(nil).Delete), ctx, variableSetID)\n}\n\n// List mocks base method.\nfunc (m *MockVariableSets) List(ctx context.Context, organization string, options *tfe.VariableSetListOptions) (*tfe.VariableSetList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.VariableSetList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockVariableSetsMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockVariableSets)(nil).List), ctx, organization, options)\n}\n\n// ListForProject mocks base method.\nfunc (m *MockVariableSets) ListForProject(ctx context.Context, projectID string, options *tfe.VariableSetListOptions) (*tfe.VariableSetList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListForProject\", ctx, projectID, options)\n\tret0, _ := ret[0].(*tfe.VariableSetList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListForProject indicates an expected call of ListForProject.\nfunc (mr *MockVariableSetsMockRecorder) ListForProject(ctx, projectID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListForProject\", reflect.TypeOf((*MockVariableSets)(nil).ListForProject), ctx, projectID, options)\n}\n\n// ListForWorkspace mocks base method.\nfunc (m *MockVariableSets) ListForWorkspace(ctx context.Context, workspaceID string, options *tfe.VariableSetListOptions) (*tfe.VariableSetList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListForWorkspace\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.VariableSetList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListForWorkspace indicates an expected call of ListForWorkspace.\nfunc (mr *MockVariableSetsMockRecorder) ListForWorkspace(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListForWorkspace\", reflect.TypeOf((*MockVariableSets)(nil).ListForWorkspace), ctx, workspaceID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockVariableSets) Read(ctx context.Context, variableSetID string, options *tfe.VariableSetReadOptions) (*tfe.VariableSet, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(*tfe.VariableSet)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockVariableSetsMockRecorder) Read(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockVariableSets)(nil).Read), ctx, variableSetID, options)\n}\n\n// RemoveFromProjects mocks base method.\nfunc (m *MockVariableSets) RemoveFromProjects(ctx context.Context, variableSetID string, options tfe.VariableSetRemoveFromProjectsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveFromProjects\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveFromProjects indicates an expected call of RemoveFromProjects.\nfunc (mr *MockVariableSetsMockRecorder) RemoveFromProjects(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveFromProjects\", reflect.TypeOf((*MockVariableSets)(nil).RemoveFromProjects), ctx, variableSetID, options)\n}\n\n// RemoveFromStacks mocks base method.\nfunc (m *MockVariableSets) RemoveFromStacks(ctx context.Context, variableSetID string, options *tfe.VariableSetRemoveFromStacksOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveFromStacks\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveFromStacks indicates an expected call of RemoveFromStacks.\nfunc (mr *MockVariableSetsMockRecorder) RemoveFromStacks(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveFromStacks\", reflect.TypeOf((*MockVariableSets)(nil).RemoveFromStacks), ctx, variableSetID, options)\n}\n\n// RemoveFromWorkspaces mocks base method.\nfunc (m *MockVariableSets) RemoveFromWorkspaces(ctx context.Context, variableSetID string, options *tfe.VariableSetRemoveFromWorkspacesOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveFromWorkspaces\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveFromWorkspaces indicates an expected call of RemoveFromWorkspaces.\nfunc (mr *MockVariableSetsMockRecorder) RemoveFromWorkspaces(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveFromWorkspaces\", reflect.TypeOf((*MockVariableSets)(nil).RemoveFromWorkspaces), ctx, variableSetID, options)\n}\n\n// Update mocks base method.\nfunc (m *MockVariableSets) Update(ctx context.Context, variableSetID string, options *tfe.VariableSetUpdateOptions) (*tfe.VariableSet, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(*tfe.VariableSet)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockVariableSetsMockRecorder) Update(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockVariableSets)(nil).Update), ctx, variableSetID, options)\n}\n\n// UpdateStacks mocks base method.\nfunc (m *MockVariableSets) UpdateStacks(ctx context.Context, variableSetID string, options *tfe.VariableSetUpdateStacksOptions) (*tfe.VariableSet, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateStacks\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(*tfe.VariableSet)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateStacks indicates an expected call of UpdateStacks.\nfunc (mr *MockVariableSetsMockRecorder) UpdateStacks(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateStacks\", reflect.TypeOf((*MockVariableSets)(nil).UpdateStacks), ctx, variableSetID, options)\n}\n\n// UpdateWorkspaces mocks base method.\nfunc (m *MockVariableSets) UpdateWorkspaces(ctx context.Context, variableSetID string, options *tfe.VariableSetUpdateWorkspacesOptions) (*tfe.VariableSet, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateWorkspaces\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(*tfe.VariableSet)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateWorkspaces indicates an expected call of UpdateWorkspaces.\nfunc (mr *MockVariableSetsMockRecorder) UpdateWorkspaces(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateWorkspaces\", reflect.TypeOf((*MockVariableSets)(nil).UpdateWorkspaces), ctx, variableSetID, options)\n}\n"
  },
  {
    "path": "mocks/variable_set_variable_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: variable_set_variable.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=variable_set_variable.go -destination=mocks/variable_set_variable_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockVariableSetVariables is a mock of VariableSetVariables interface.\ntype MockVariableSetVariables struct {\n\tctrl     *gomock.Controller\n\trecorder *MockVariableSetVariablesMockRecorder\n}\n\n// MockVariableSetVariablesMockRecorder is the mock recorder for MockVariableSetVariables.\ntype MockVariableSetVariablesMockRecorder struct {\n\tmock *MockVariableSetVariables\n}\n\n// NewMockVariableSetVariables creates a new mock instance.\nfunc NewMockVariableSetVariables(ctrl *gomock.Controller) *MockVariableSetVariables {\n\tmock := &MockVariableSetVariables{ctrl: ctrl}\n\tmock.recorder = &MockVariableSetVariablesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockVariableSetVariables) EXPECT() *MockVariableSetVariablesMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockVariableSetVariables) Create(ctx context.Context, variableSetID string, options *tfe.VariableSetVariableCreateOptions) (*tfe.VariableSetVariable, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(*tfe.VariableSetVariable)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockVariableSetVariablesMockRecorder) Create(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockVariableSetVariables)(nil).Create), ctx, variableSetID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockVariableSetVariables) Delete(ctx context.Context, variableSetID, variableID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, variableSetID, variableID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockVariableSetVariablesMockRecorder) Delete(ctx, variableSetID, variableID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockVariableSetVariables)(nil).Delete), ctx, variableSetID, variableID)\n}\n\n// List mocks base method.\nfunc (m *MockVariableSetVariables) List(ctx context.Context, variableSetID string, options *tfe.VariableSetVariableListOptions) (*tfe.VariableSetVariableList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, variableSetID, options)\n\tret0, _ := ret[0].(*tfe.VariableSetVariableList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockVariableSetVariablesMockRecorder) List(ctx, variableSetID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockVariableSetVariables)(nil).List), ctx, variableSetID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockVariableSetVariables) Read(ctx context.Context, variableSetID, variableID string) (*tfe.VariableSetVariable, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, variableSetID, variableID)\n\tret0, _ := ret[0].(*tfe.VariableSetVariable)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockVariableSetVariablesMockRecorder) Read(ctx, variableSetID, variableID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockVariableSetVariables)(nil).Read), ctx, variableSetID, variableID)\n}\n\n// Update mocks base method.\nfunc (m *MockVariableSetVariables) Update(ctx context.Context, variableSetID, variableID string, options *tfe.VariableSetVariableUpdateOptions) (*tfe.VariableSetVariable, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, variableSetID, variableID, options)\n\tret0, _ := ret[0].(*tfe.VariableSetVariable)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockVariableSetVariablesMockRecorder) Update(ctx, variableSetID, variableID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockVariableSetVariables)(nil).Update), ctx, variableSetID, variableID, options)\n}\n"
  },
  {
    "path": "mocks/workspace_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: workspace.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=workspace.go -destination=mocks/workspace_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\tio \"io\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockWorkspaces is a mock of Workspaces interface.\ntype MockWorkspaces struct {\n\tctrl     *gomock.Controller\n\trecorder *MockWorkspacesMockRecorder\n}\n\n// MockWorkspacesMockRecorder is the mock recorder for MockWorkspaces.\ntype MockWorkspacesMockRecorder struct {\n\tmock *MockWorkspaces\n}\n\n// NewMockWorkspaces creates a new mock instance.\nfunc NewMockWorkspaces(ctrl *gomock.Controller) *MockWorkspaces {\n\tmock := &MockWorkspaces{ctrl: ctrl}\n\tmock.recorder = &MockWorkspacesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockWorkspaces) EXPECT() *MockWorkspacesMockRecorder {\n\treturn m.recorder\n}\n\n// AddRemoteStateConsumers mocks base method.\nfunc (m *MockWorkspaces) AddRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceAddRemoteStateConsumersOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddRemoteStateConsumers\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddRemoteStateConsumers indicates an expected call of AddRemoteStateConsumers.\nfunc (mr *MockWorkspacesMockRecorder) AddRemoteStateConsumers(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddRemoteStateConsumers\", reflect.TypeOf((*MockWorkspaces)(nil).AddRemoteStateConsumers), ctx, workspaceID, options)\n}\n\n// AddTagBindings mocks base method.\nfunc (m *MockWorkspaces) AddTagBindings(ctx context.Context, workspaceID string, options tfe.WorkspaceAddTagBindingsOptions) ([]*tfe.TagBinding, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddTagBindings\", ctx, workspaceID, options)\n\tret0, _ := ret[0].([]*tfe.TagBinding)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// AddTagBindings indicates an expected call of AddTagBindings.\nfunc (mr *MockWorkspacesMockRecorder) AddTagBindings(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddTagBindings\", reflect.TypeOf((*MockWorkspaces)(nil).AddTagBindings), ctx, workspaceID, options)\n}\n\n// AddTags mocks base method.\nfunc (m *MockWorkspaces) AddTags(ctx context.Context, workspaceID string, options tfe.WorkspaceAddTagsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddTags\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddTags indicates an expected call of AddTags.\nfunc (mr *MockWorkspacesMockRecorder) AddTags(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddTags\", reflect.TypeOf((*MockWorkspaces)(nil).AddTags), ctx, workspaceID, options)\n}\n\n// AssignSSHKey mocks base method.\nfunc (m *MockWorkspaces) AssignSSHKey(ctx context.Context, workspaceID string, options tfe.WorkspaceAssignSSHKeyOptions) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AssignSSHKey\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// AssignSSHKey indicates an expected call of AssignSSHKey.\nfunc (mr *MockWorkspacesMockRecorder) AssignSSHKey(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AssignSSHKey\", reflect.TypeOf((*MockWorkspaces)(nil).AssignSSHKey), ctx, workspaceID, options)\n}\n\n// Create mocks base method.\nfunc (m *MockWorkspaces) Create(ctx context.Context, organization string, options tfe.WorkspaceCreateOptions) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockWorkspacesMockRecorder) Create(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockWorkspaces)(nil).Create), ctx, organization, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockWorkspaces) Delete(ctx context.Context, organization, workspace string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, organization, workspace)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockWorkspacesMockRecorder) Delete(ctx, organization, workspace any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockWorkspaces)(nil).Delete), ctx, organization, workspace)\n}\n\n// DeleteAllTagBindings mocks base method.\nfunc (m *MockWorkspaces) DeleteAllTagBindings(ctx context.Context, workspaceID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteAllTagBindings\", ctx, workspaceID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteAllTagBindings indicates an expected call of DeleteAllTagBindings.\nfunc (mr *MockWorkspacesMockRecorder) DeleteAllTagBindings(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteAllTagBindings\", reflect.TypeOf((*MockWorkspaces)(nil).DeleteAllTagBindings), ctx, workspaceID)\n}\n\n// DeleteByID mocks base method.\nfunc (m *MockWorkspaces) DeleteByID(ctx context.Context, workspaceID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteByID\", ctx, workspaceID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteByID indicates an expected call of DeleteByID.\nfunc (mr *MockWorkspacesMockRecorder) DeleteByID(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteByID\", reflect.TypeOf((*MockWorkspaces)(nil).DeleteByID), ctx, workspaceID)\n}\n\n// DeleteDataRetentionPolicy mocks base method.\nfunc (m *MockWorkspaces) DeleteDataRetentionPolicy(ctx context.Context, workspaceID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteDataRetentionPolicy\", ctx, workspaceID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteDataRetentionPolicy indicates an expected call of DeleteDataRetentionPolicy.\nfunc (mr *MockWorkspacesMockRecorder) DeleteDataRetentionPolicy(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteDataRetentionPolicy\", reflect.TypeOf((*MockWorkspaces)(nil).DeleteDataRetentionPolicy), ctx, workspaceID)\n}\n\n// ForceUnlock mocks base method.\nfunc (m *MockWorkspaces) ForceUnlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ForceUnlock\", ctx, workspaceID)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ForceUnlock indicates an expected call of ForceUnlock.\nfunc (mr *MockWorkspacesMockRecorder) ForceUnlock(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ForceUnlock\", reflect.TypeOf((*MockWorkspaces)(nil).ForceUnlock), ctx, workspaceID)\n}\n\n// List mocks base method.\nfunc (m *MockWorkspaces) List(ctx context.Context, organization string, options *tfe.WorkspaceListOptions) (*tfe.WorkspaceList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, organization, options)\n\tret0, _ := ret[0].(*tfe.WorkspaceList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockWorkspacesMockRecorder) List(ctx, organization, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockWorkspaces)(nil).List), ctx, organization, options)\n}\n\n// ListEffectiveTagBindings mocks base method.\nfunc (m *MockWorkspaces) ListEffectiveTagBindings(ctx context.Context, workspaceID string) ([]*tfe.EffectiveTagBinding, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListEffectiveTagBindings\", ctx, workspaceID)\n\tret0, _ := ret[0].([]*tfe.EffectiveTagBinding)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListEffectiveTagBindings indicates an expected call of ListEffectiveTagBindings.\nfunc (mr *MockWorkspacesMockRecorder) ListEffectiveTagBindings(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListEffectiveTagBindings\", reflect.TypeOf((*MockWorkspaces)(nil).ListEffectiveTagBindings), ctx, workspaceID)\n}\n\n// ListRemoteStateConsumers mocks base method.\nfunc (m *MockWorkspaces) ListRemoteStateConsumers(ctx context.Context, workspaceID string, options *tfe.RemoteStateConsumersListOptions) (*tfe.WorkspaceList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListRemoteStateConsumers\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.WorkspaceList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListRemoteStateConsumers indicates an expected call of ListRemoteStateConsumers.\nfunc (mr *MockWorkspacesMockRecorder) ListRemoteStateConsumers(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListRemoteStateConsumers\", reflect.TypeOf((*MockWorkspaces)(nil).ListRemoteStateConsumers), ctx, workspaceID, options)\n}\n\n// ListTagBindings mocks base method.\nfunc (m *MockWorkspaces) ListTagBindings(ctx context.Context, workspaceID string) ([]*tfe.TagBinding, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListTagBindings\", ctx, workspaceID)\n\tret0, _ := ret[0].([]*tfe.TagBinding)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListTagBindings indicates an expected call of ListTagBindings.\nfunc (mr *MockWorkspacesMockRecorder) ListTagBindings(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListTagBindings\", reflect.TypeOf((*MockWorkspaces)(nil).ListTagBindings), ctx, workspaceID)\n}\n\n// ListTags mocks base method.\nfunc (m *MockWorkspaces) ListTags(ctx context.Context, workspaceID string, options *tfe.WorkspaceTagListOptions) (*tfe.TagList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListTags\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.TagList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListTags indicates an expected call of ListTags.\nfunc (mr *MockWorkspacesMockRecorder) ListTags(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListTags\", reflect.TypeOf((*MockWorkspaces)(nil).ListTags), ctx, workspaceID, options)\n}\n\n// Lock mocks base method.\nfunc (m *MockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Lock\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Lock indicates an expected call of Lock.\nfunc (mr *MockWorkspacesMockRecorder) Lock(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Lock\", reflect.TypeOf((*MockWorkspaces)(nil).Lock), ctx, workspaceID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockWorkspaces) Read(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, organization, workspace)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockWorkspacesMockRecorder) Read(ctx, organization, workspace any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockWorkspaces)(nil).Read), ctx, organization, workspace)\n}\n\n// ReadByID mocks base method.\nfunc (m *MockWorkspaces) ReadByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadByID\", ctx, workspaceID)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadByID indicates an expected call of ReadByID.\nfunc (mr *MockWorkspacesMockRecorder) ReadByID(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadByID\", reflect.TypeOf((*MockWorkspaces)(nil).ReadByID), ctx, workspaceID)\n}\n\n// ReadByIDWithOptions mocks base method.\nfunc (m *MockWorkspaces) ReadByIDWithOptions(ctx context.Context, workspaceID string, options *tfe.WorkspaceReadOptions) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadByIDWithOptions\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadByIDWithOptions indicates an expected call of ReadByIDWithOptions.\nfunc (mr *MockWorkspacesMockRecorder) ReadByIDWithOptions(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadByIDWithOptions\", reflect.TypeOf((*MockWorkspaces)(nil).ReadByIDWithOptions), ctx, workspaceID, options)\n}\n\n// ReadDataRetentionPolicy mocks base method.\nfunc (m *MockWorkspaces) ReadDataRetentionPolicy(ctx context.Context, workspaceID string) (*tfe.DataRetentionPolicy, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadDataRetentionPolicy\", ctx, workspaceID)\n\tret0, _ := ret[0].(*tfe.DataRetentionPolicy)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadDataRetentionPolicy indicates an expected call of ReadDataRetentionPolicy.\nfunc (mr *MockWorkspacesMockRecorder) ReadDataRetentionPolicy(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadDataRetentionPolicy\", reflect.TypeOf((*MockWorkspaces)(nil).ReadDataRetentionPolicy), ctx, workspaceID)\n}\n\n// ReadDataRetentionPolicyChoice mocks base method.\nfunc (m *MockWorkspaces) ReadDataRetentionPolicyChoice(ctx context.Context, workspaceID string) (*tfe.DataRetentionPolicyChoice, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadDataRetentionPolicyChoice\", ctx, workspaceID)\n\tret0, _ := ret[0].(*tfe.DataRetentionPolicyChoice)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadDataRetentionPolicyChoice indicates an expected call of ReadDataRetentionPolicyChoice.\nfunc (mr *MockWorkspacesMockRecorder) ReadDataRetentionPolicyChoice(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadDataRetentionPolicyChoice\", reflect.TypeOf((*MockWorkspaces)(nil).ReadDataRetentionPolicyChoice), ctx, workspaceID)\n}\n\n// ReadWithOptions mocks base method.\nfunc (m *MockWorkspaces) ReadWithOptions(ctx context.Context, organization, workspace string, options *tfe.WorkspaceReadOptions) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadWithOptions\", ctx, organization, workspace, options)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReadWithOptions indicates an expected call of ReadWithOptions.\nfunc (mr *MockWorkspacesMockRecorder) ReadWithOptions(ctx, organization, workspace, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadWithOptions\", reflect.TypeOf((*MockWorkspaces)(nil).ReadWithOptions), ctx, organization, workspace, options)\n}\n\n// Readme mocks base method.\nfunc (m *MockWorkspaces) Readme(ctx context.Context, workspaceID string) (io.Reader, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Readme\", ctx, workspaceID)\n\tret0, _ := ret[0].(io.Reader)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Readme indicates an expected call of Readme.\nfunc (mr *MockWorkspacesMockRecorder) Readme(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Readme\", reflect.TypeOf((*MockWorkspaces)(nil).Readme), ctx, workspaceID)\n}\n\n// RemoveRemoteStateConsumers mocks base method.\nfunc (m *MockWorkspaces) RemoveRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceRemoveRemoteStateConsumersOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveRemoteStateConsumers\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveRemoteStateConsumers indicates an expected call of RemoveRemoteStateConsumers.\nfunc (mr *MockWorkspacesMockRecorder) RemoveRemoteStateConsumers(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveRemoteStateConsumers\", reflect.TypeOf((*MockWorkspaces)(nil).RemoveRemoteStateConsumers), ctx, workspaceID, options)\n}\n\n// RemoveTags mocks base method.\nfunc (m *MockWorkspaces) RemoveTags(ctx context.Context, workspaceID string, options tfe.WorkspaceRemoveTagsOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveTags\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveTags indicates an expected call of RemoveTags.\nfunc (mr *MockWorkspacesMockRecorder) RemoveTags(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveTags\", reflect.TypeOf((*MockWorkspaces)(nil).RemoveTags), ctx, workspaceID, options)\n}\n\n// RemoveVCSConnection mocks base method.\nfunc (m *MockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveVCSConnection\", ctx, organization, workspace)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// RemoveVCSConnection indicates an expected call of RemoveVCSConnection.\nfunc (mr *MockWorkspacesMockRecorder) RemoveVCSConnection(ctx, organization, workspace any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveVCSConnection\", reflect.TypeOf((*MockWorkspaces)(nil).RemoveVCSConnection), ctx, organization, workspace)\n}\n\n// RemoveVCSConnectionByID mocks base method.\nfunc (m *MockWorkspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveVCSConnectionByID\", ctx, workspaceID)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// RemoveVCSConnectionByID indicates an expected call of RemoveVCSConnectionByID.\nfunc (mr *MockWorkspacesMockRecorder) RemoveVCSConnectionByID(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveVCSConnectionByID\", reflect.TypeOf((*MockWorkspaces)(nil).RemoveVCSConnectionByID), ctx, workspaceID)\n}\n\n// SafeDelete mocks base method.\nfunc (m *MockWorkspaces) SafeDelete(ctx context.Context, organization, workspace string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SafeDelete\", ctx, organization, workspace)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SafeDelete indicates an expected call of SafeDelete.\nfunc (mr *MockWorkspacesMockRecorder) SafeDelete(ctx, organization, workspace any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SafeDelete\", reflect.TypeOf((*MockWorkspaces)(nil).SafeDelete), ctx, organization, workspace)\n}\n\n// SafeDeleteByID mocks base method.\nfunc (m *MockWorkspaces) SafeDeleteByID(ctx context.Context, workspaceID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SafeDeleteByID\", ctx, workspaceID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SafeDeleteByID indicates an expected call of SafeDeleteByID.\nfunc (mr *MockWorkspacesMockRecorder) SafeDeleteByID(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SafeDeleteByID\", reflect.TypeOf((*MockWorkspaces)(nil).SafeDeleteByID), ctx, workspaceID)\n}\n\n// SetDataRetentionPolicy mocks base method.\nfunc (m *MockWorkspaces) SetDataRetentionPolicy(ctx context.Context, workspaceID string, options tfe.DataRetentionPolicySetOptions) (*tfe.DataRetentionPolicy, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetDataRetentionPolicy\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.DataRetentionPolicy)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SetDataRetentionPolicy indicates an expected call of SetDataRetentionPolicy.\nfunc (mr *MockWorkspacesMockRecorder) SetDataRetentionPolicy(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetDataRetentionPolicy\", reflect.TypeOf((*MockWorkspaces)(nil).SetDataRetentionPolicy), ctx, workspaceID, options)\n}\n\n// SetDataRetentionPolicyDeleteOlder mocks base method.\nfunc (m *MockWorkspaces) SetDataRetentionPolicyDeleteOlder(ctx context.Context, workspaceID string, options tfe.DataRetentionPolicyDeleteOlderSetOptions) (*tfe.DataRetentionPolicyDeleteOlder, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetDataRetentionPolicyDeleteOlder\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.DataRetentionPolicyDeleteOlder)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SetDataRetentionPolicyDeleteOlder indicates an expected call of SetDataRetentionPolicyDeleteOlder.\nfunc (mr *MockWorkspacesMockRecorder) SetDataRetentionPolicyDeleteOlder(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetDataRetentionPolicyDeleteOlder\", reflect.TypeOf((*MockWorkspaces)(nil).SetDataRetentionPolicyDeleteOlder), ctx, workspaceID, options)\n}\n\n// SetDataRetentionPolicyDontDelete mocks base method.\nfunc (m *MockWorkspaces) SetDataRetentionPolicyDontDelete(ctx context.Context, workspaceID string, options tfe.DataRetentionPolicyDontDeleteSetOptions) (*tfe.DataRetentionPolicyDontDelete, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetDataRetentionPolicyDontDelete\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.DataRetentionPolicyDontDelete)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SetDataRetentionPolicyDontDelete indicates an expected call of SetDataRetentionPolicyDontDelete.\nfunc (mr *MockWorkspacesMockRecorder) SetDataRetentionPolicyDontDelete(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetDataRetentionPolicyDontDelete\", reflect.TypeOf((*MockWorkspaces)(nil).SetDataRetentionPolicyDontDelete), ctx, workspaceID, options)\n}\n\n// UnassignSSHKey mocks base method.\nfunc (m *MockWorkspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UnassignSSHKey\", ctx, workspaceID)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UnassignSSHKey indicates an expected call of UnassignSSHKey.\nfunc (mr *MockWorkspacesMockRecorder) UnassignSSHKey(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UnassignSSHKey\", reflect.TypeOf((*MockWorkspaces)(nil).UnassignSSHKey), ctx, workspaceID)\n}\n\n// Unlock mocks base method.\nfunc (m *MockWorkspaces) Unlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Unlock\", ctx, workspaceID)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Unlock indicates an expected call of Unlock.\nfunc (mr *MockWorkspacesMockRecorder) Unlock(ctx, workspaceID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Unlock\", reflect.TypeOf((*MockWorkspaces)(nil).Unlock), ctx, workspaceID)\n}\n\n// Update mocks base method.\nfunc (m *MockWorkspaces) Update(ctx context.Context, organization, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, organization, workspace, options)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockWorkspacesMockRecorder) Update(ctx, organization, workspace, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockWorkspaces)(nil).Update), ctx, organization, workspace, options)\n}\n\n// UpdateByID mocks base method.\nfunc (m *MockWorkspaces) UpdateByID(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateByID\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.Workspace)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateByID indicates an expected call of UpdateByID.\nfunc (mr *MockWorkspacesMockRecorder) UpdateByID(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateByID\", reflect.TypeOf((*MockWorkspaces)(nil).UpdateByID), ctx, workspaceID, options)\n}\n\n// UpdateRemoteStateConsumers mocks base method.\nfunc (m *MockWorkspaces) UpdateRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateRemoteStateConsumersOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateRemoteStateConsumers\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpdateRemoteStateConsumers indicates an expected call of UpdateRemoteStateConsumers.\nfunc (mr *MockWorkspacesMockRecorder) UpdateRemoteStateConsumers(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateRemoteStateConsumers\", reflect.TypeOf((*MockWorkspaces)(nil).UpdateRemoteStateConsumers), ctx, workspaceID, options)\n}\n"
  },
  {
    "path": "mocks/workspace_resources.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: workspace_resources.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=workspace_resources.go -destination=mocks/workspace_resources.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockWorkspaceResources is a mock of WorkspaceResources interface.\ntype MockWorkspaceResources struct {\n\tctrl     *gomock.Controller\n\trecorder *MockWorkspaceResourcesMockRecorder\n}\n\n// MockWorkspaceResourcesMockRecorder is the mock recorder for MockWorkspaceResources.\ntype MockWorkspaceResourcesMockRecorder struct {\n\tmock *MockWorkspaceResources\n}\n\n// NewMockWorkspaceResources creates a new mock instance.\nfunc NewMockWorkspaceResources(ctrl *gomock.Controller) *MockWorkspaceResources {\n\tmock := &MockWorkspaceResources{ctrl: ctrl}\n\tmock.recorder = &MockWorkspaceResourcesMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockWorkspaceResources) EXPECT() *MockWorkspaceResourcesMockRecorder {\n\treturn m.recorder\n}\n\n// List mocks base method.\nfunc (m *MockWorkspaceResources) List(ctx context.Context, workspaceID string, options *tfe.WorkspaceResourceListOptions) (*tfe.WorkspaceResourcesList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.WorkspaceResourcesList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockWorkspaceResourcesMockRecorder) List(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockWorkspaceResources)(nil).List), ctx, workspaceID, options)\n}\n"
  },
  {
    "path": "mocks/workspace_run_tasks_mocks.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: workspace_run_task.go\n//\n// Generated by this command:\n//\n//\tmockgen -source=workspace_run_task.go -destination=mocks/workspace_run_tasks_mocks.go -package=mocks\n//\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\ttfe \"github.com/hashicorp/go-tfe\"\n\tgomock \"go.uber.org/mock/gomock\"\n)\n\n// MockWorkspaceRunTasks is a mock of WorkspaceRunTasks interface.\ntype MockWorkspaceRunTasks struct {\n\tctrl     *gomock.Controller\n\trecorder *MockWorkspaceRunTasksMockRecorder\n}\n\n// MockWorkspaceRunTasksMockRecorder is the mock recorder for MockWorkspaceRunTasks.\ntype MockWorkspaceRunTasksMockRecorder struct {\n\tmock *MockWorkspaceRunTasks\n}\n\n// NewMockWorkspaceRunTasks creates a new mock instance.\nfunc NewMockWorkspaceRunTasks(ctrl *gomock.Controller) *MockWorkspaceRunTasks {\n\tmock := &MockWorkspaceRunTasks{ctrl: ctrl}\n\tmock.recorder = &MockWorkspaceRunTasksMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockWorkspaceRunTasks) EXPECT() *MockWorkspaceRunTasksMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method.\nfunc (m *MockWorkspaceRunTasks) Create(ctx context.Context, workspaceID string, options tfe.WorkspaceRunTaskCreateOptions) (*tfe.WorkspaceRunTask, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.WorkspaceRunTask)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create.\nfunc (mr *MockWorkspaceRunTasksMockRecorder) Create(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MockWorkspaceRunTasks)(nil).Create), ctx, workspaceID, options)\n}\n\n// Delete mocks base method.\nfunc (m *MockWorkspaceRunTasks) Delete(ctx context.Context, workspaceID, workspaceTaskID string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Delete\", ctx, workspaceID, workspaceTaskID)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Delete indicates an expected call of Delete.\nfunc (mr *MockWorkspaceRunTasksMockRecorder) Delete(ctx, workspaceID, workspaceTaskID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Delete\", reflect.TypeOf((*MockWorkspaceRunTasks)(nil).Delete), ctx, workspaceID, workspaceTaskID)\n}\n\n// List mocks base method.\nfunc (m *MockWorkspaceRunTasks) List(ctx context.Context, workspaceID string, options *tfe.WorkspaceRunTaskListOptions) (*tfe.WorkspaceRunTaskList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"List\", ctx, workspaceID, options)\n\tret0, _ := ret[0].(*tfe.WorkspaceRunTaskList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// List indicates an expected call of List.\nfunc (mr *MockWorkspaceRunTasksMockRecorder) List(ctx, workspaceID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"List\", reflect.TypeOf((*MockWorkspaceRunTasks)(nil).List), ctx, workspaceID, options)\n}\n\n// Read mocks base method.\nfunc (m *MockWorkspaceRunTasks) Read(ctx context.Context, workspaceID, workspaceTaskID string) (*tfe.WorkspaceRunTask, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", ctx, workspaceID, workspaceTaskID)\n\tret0, _ := ret[0].(*tfe.WorkspaceRunTask)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read.\nfunc (mr *MockWorkspaceRunTasksMockRecorder) Read(ctx, workspaceID, workspaceTaskID any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*MockWorkspaceRunTasks)(nil).Read), ctx, workspaceID, workspaceTaskID)\n}\n\n// Update mocks base method.\nfunc (m *MockWorkspaceRunTasks) Update(ctx context.Context, workspaceID, workspaceTaskID string, options tfe.WorkspaceRunTaskUpdateOptions) (*tfe.WorkspaceRunTask, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Update\", ctx, workspaceID, workspaceTaskID, options)\n\tret0, _ := ret[0].(*tfe.WorkspaceRunTask)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Update indicates an expected call of Update.\nfunc (mr *MockWorkspaceRunTasksMockRecorder) Update(ctx, workspaceID, workspaceTaskID, options any) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Update\", reflect.TypeOf((*MockWorkspaceRunTasks)(nil).Update), ctx, workspaceID, workspaceTaskID, options)\n}\n"
  },
  {
    "path": "notification_configuration.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ NotificationConfigurations = (*notificationConfigurations)(nil)\n\n// NotificationConfigurations describes all the Notification Configuration\n// related methods that the Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/notification-configurations\ntype NotificationConfigurations interface {\n\t// List all the notification configurations within a workspace.\n\tList(ctx context.Context, subscribableID string, options *NotificationConfigurationListOptions) (*NotificationConfigurationList, error)\n\n\t// Create a new notification configuration with the given options.\n\tCreate(ctx context.Context, subscribableID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error)\n\n\t// Read a notification configuration by its ID.\n\tRead(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error)\n\n\t// Update an existing notification configuration.\n\tUpdate(ctx context.Context, notificationConfigurationID string, options NotificationConfigurationUpdateOptions) (*NotificationConfiguration, error)\n\n\t// Delete a notification configuration by its ID.\n\tDelete(ctx context.Context, notificationConfigurationID string) error\n\n\t// Verify a notification configuration by its ID.\n\tVerify(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error)\n}\n\n// notificationConfigurations implements NotificationConfigurations.\ntype notificationConfigurations struct {\n\tclient *Client\n}\n\n// NotificationTriggerType represents the different TFE notifications that can be sent\n// as a run's progress transitions between different states\ntype NotificationTriggerType string\n\nconst (\n\tNotificationTriggerCreated                        NotificationTriggerType = \"run:created\"\n\tNotificationTriggerPlanning                       NotificationTriggerType = \"run:planning\"\n\tNotificationTriggerNeedsAttention                 NotificationTriggerType = \"run:needs_attention\"\n\tNotificationTriggerApplying                       NotificationTriggerType = \"run:applying\"\n\tNotificationTriggerCompleted                      NotificationTriggerType = \"run:completed\"\n\tNotificationTriggerErrored                        NotificationTriggerType = \"run:errored\"\n\tNotificationTriggerAssessmentDrifted              NotificationTriggerType = \"assessment:drifted\"\n\tNotificationTriggerAssessmentFailed               NotificationTriggerType = \"assessment:failed\"\n\tNotificationTriggerAssessmentCheckFailed          NotificationTriggerType = \"assessment:check_failure\"\n\tNotificationTriggerWorkspaceAutoDestroyReminder   NotificationTriggerType = \"workspace:auto_destroy_reminder\"\n\tNotificationTriggerWorkspaceAutoDestroyRunResults NotificationTriggerType = \"workspace:auto_destroy_run_results\"\n\tNotificationTriggerChangeRequestCreated           NotificationTriggerType = \"change_request:created\"\n)\n\n// NotificationDestinationType represents the destination type of the\n// notification configuration.\ntype NotificationDestinationType string\n\n// List of available notification destination types.\nconst (\n\tNotificationDestinationTypeEmail          NotificationDestinationType = \"email\"\n\tNotificationDestinationTypeGeneric        NotificationDestinationType = \"generic\"\n\tNotificationDestinationTypeSlack          NotificationDestinationType = \"slack\"\n\tNotificationDestinationTypeMicrosoftTeams NotificationDestinationType = \"microsoft-teams\"\n)\n\n// NotificationConfigurationList represents a list of Notification\n// Configurations.\ntype NotificationConfigurationList struct {\n\t*Pagination\n\tItems []*NotificationConfiguration\n}\n\n// NotificationConfigurationSubscribableChoice is a choice type struct that represents the possible values\n// within a polymorphic relation. If a value is available, exactly one field\n// will be non-nil.\ntype NotificationConfigurationSubscribableChoice struct {\n\tTeam      *Team\n\tWorkspace *Workspace\n}\n\n// NotificationConfiguration represents a Notification Configuration.\ntype NotificationConfiguration struct {\n\tID                string                      `jsonapi:\"primary,notification-configurations\"`\n\tCreatedAt         time.Time                   `jsonapi:\"attr,created-at,iso8601\"`\n\tDeliveryResponses []*DeliveryResponse         `jsonapi:\"attr,delivery-responses\"`\n\tDestinationType   NotificationDestinationType `jsonapi:\"attr,destination-type\"`\n\tEnabled           bool                        `jsonapi:\"attr,enabled\"`\n\tName              string                      `jsonapi:\"attr,name\"`\n\tToken             string                      `jsonapi:\"attr,token\"`\n\tTriggers          []string                    `jsonapi:\"attr,triggers\"`\n\tUpdatedAt         time.Time                   `jsonapi:\"attr,updated-at,iso8601\"`\n\tURL               string                      `jsonapi:\"attr,url\"`\n\n\t// EmailAddresses is only available for TFE users. It is not available in HCP Terraform.\n\tEmailAddresses []string `jsonapi:\"attr,email-addresses\"`\n\n\t// Relations\n\t// DEPRECATED. The subscribable field is polymorphic. Use NotificationConfigurationSubscribableChoice instead.\n\tSubscribable       *Workspace                                   `jsonapi:\"relation,subscribable,omitempty\"`\n\tSubscribableChoice *NotificationConfigurationSubscribableChoice `jsonapi:\"polyrelation,subscribable\"`\n\n\tEmailUsers []*User `jsonapi:\"relation,users\"`\n}\n\n// DeliveryResponse represents a notification configuration delivery response.\ntype DeliveryResponse struct {\n\tBody       string              `jsonapi:\"attr,body\"`\n\tCode       string              `jsonapi:\"attr,code\"`\n\tHeaders    map[string][]string `jsonapi:\"attr,headers\"`\n\tSentAt     time.Time           `jsonapi:\"attr,sent-at,rfc3339\"`\n\tSuccessful string              `jsonapi:\"attr,successful\"`\n\tURL        string              `jsonapi:\"attr,url\"`\n}\n\n// NotificationConfigurationListOptions represents the options for listing\n// notification configurations.\ntype NotificationConfigurationListOptions struct {\n\tListOptions\n\n\tSubscribableChoice *NotificationConfigurationSubscribableChoice\n}\n\n// NotificationConfigurationCreateOptions represents the options for\n// creating a new notification configuration.\ntype NotificationConfigurationCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,notification-configurations\"`\n\n\t// Required: The destination type of the notification configuration\n\tDestinationType *NotificationDestinationType `jsonapi:\"attr,destination-type\"`\n\n\t// Required: Whether the notification configuration should be enabled or not\n\tEnabled *bool `jsonapi:\"attr,enabled\"`\n\n\t// Required: The name of the notification configuration\n\tName *string `jsonapi:\"attr,name\"`\n\n\t// Optional: The token of the notification configuration\n\tToken *string `jsonapi:\"attr,token,omitempty\"`\n\n\t// Optional: The list of run events that will trigger notifications.\n\tTriggers []NotificationTriggerType `jsonapi:\"attr,triggers,omitempty\"`\n\n\t// Optional: The url of the notification configuration\n\tURL *string `jsonapi:\"attr,url,omitempty\"`\n\n\t// Optional: The list of email addresses that will receive notification emails.\n\t// EmailAddresses is only available for TFE users. It is not available in HCP Terraform.\n\tEmailAddresses []string `jsonapi:\"attr,email-addresses,omitempty\"`\n\n\t// Optional: The list of users belonging to the organization that will receive notification emails.\n\tEmailUsers []*User `jsonapi:\"relation,users,omitempty\"`\n\n\t// Required: The workspace or team that the notification configuration is associated with.\n\tSubscribableChoice *NotificationConfigurationSubscribableChoice `jsonapi:\"polyrelation,subscribable,omitempty\"`\n}\n\n// NotificationConfigurationUpdateOptions represents the options for\n// updating a existing notification configuration.\ntype NotificationConfigurationUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,notification-configurations\"`\n\n\t// Optional: Whether the notification configuration should be enabled or not\n\tEnabled *bool `jsonapi:\"attr,enabled,omitempty\"`\n\n\t// Optional: The name of the notification configuration\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: The token of the notification configuration\n\tToken *string `jsonapi:\"attr,token,omitempty\"`\n\n\t// Optional: The list of run events that will trigger notifications.\n\tTriggers []NotificationTriggerType `jsonapi:\"attr,triggers,omitempty\"`\n\n\t// Optional: The url of the notification configuration\n\tURL *string `jsonapi:\"attr,url,omitempty\"`\n\n\t// Optional: The list of email addresses that will receive notification emails.\n\t// EmailAddresses is only available for TFE users. It is not available in HCP Terraform.\n\tEmailAddresses []string `jsonapi:\"attr,email-addresses,omitempty\"`\n\n\t// Optional: The list of users belonging to the organization that will receive notification emails.\n\tEmailUsers []*User `jsonapi:\"relation,users,omitempty\"`\n}\n\n// List all the notification configurations associated with a workspace.\nfunc (s *notificationConfigurations) List(ctx context.Context, subscribableID string, options *NotificationConfigurationListOptions) (*NotificationConfigurationList, error) {\n\tvar u string\n\tif options == nil {\n\t\toptions = &NotificationConfigurationListOptions{\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{\n\t\t\t\tWorkspace: &Workspace{ID: subscribableID},\n\t\t\t},\n\t\t}\n\t} else if options.SubscribableChoice == nil {\n\t\toptions.SubscribableChoice = &NotificationConfigurationSubscribableChoice{\n\t\t\tWorkspace: &Workspace{ID: subscribableID},\n\t\t}\n\t}\n\n\tif options.SubscribableChoice.Team != nil {\n\t\tif !validStringID(&subscribableID) {\n\t\t\treturn nil, ErrInvalidTeamID\n\t\t}\n\t\tu = fmt.Sprintf(\"teams/%s/notification-configurations\", url.PathEscape(subscribableID))\n\t} else {\n\t\tif !validStringID(&subscribableID) {\n\t\t\treturn nil, ErrInvalidWorkspaceID\n\t\t}\n\t\tu = fmt.Sprintf(\"workspaces/%s/notification-configurations\", url.PathEscape(subscribableID))\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tncl := &NotificationConfigurationList{}\n\terr = req.Do(ctx, ncl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i := range ncl.Items {\n\t\tbackfillDeprecatedSubscribable(ncl.Items[i])\n\t}\n\n\treturn ncl, nil\n}\n\n// Create a notification configuration with the given options.\nfunc (s *notificationConfigurations) Create(ctx context.Context, subscribableID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error) {\n\tvar u string\n\tvar subscribableChoice *NotificationConfigurationSubscribableChoice\n\tif options.SubscribableChoice == nil || options.SubscribableChoice.Team == nil {\n\t\tu = fmt.Sprintf(\"workspaces/%s/notification-configurations\", url.PathEscape(subscribableID))\n\t\toptions.SubscribableChoice = &NotificationConfigurationSubscribableChoice{Workspace: &Workspace{ID: subscribableID}}\n\t} else {\n\t\tu = fmt.Sprintf(\"teams/%s/notification-configurations\", url.PathEscape(subscribableID))\n\t\toptions.SubscribableChoice = &NotificationConfigurationSubscribableChoice{Team: &Team{ID: subscribableID}}\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnc := &NotificationConfiguration{SubscribableChoice: subscribableChoice}\n\terr = req.Do(ctx, nc)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbackfillDeprecatedSubscribable(nc)\n\n\treturn nc, nil\n}\n\n// Read a notification configuration by its ID.\nfunc (s *notificationConfigurations) Read(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error) {\n\tif !validStringID(&notificationConfigurationID) {\n\t\treturn nil, ErrInvalidNotificationConfigID\n\t}\n\n\tu := fmt.Sprintf(\"notification-configurations/%s\", url.PathEscape(notificationConfigurationID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnc := &NotificationConfiguration{}\n\terr = req.Do(ctx, nc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbackfillDeprecatedSubscribable(nc)\n\n\treturn nc, nil\n}\n\n// Updates a notification configuration with the given options.\nfunc (s *notificationConfigurations) Update(ctx context.Context, notificationConfigurationID string, options NotificationConfigurationUpdateOptions) (*NotificationConfiguration, error) {\n\tif !validStringID(&notificationConfigurationID) {\n\t\treturn nil, ErrInvalidNotificationConfigID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"notification-configurations/%s\", url.PathEscape(notificationConfigurationID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnc := &NotificationConfiguration{}\n\terr = req.Do(ctx, nc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbackfillDeprecatedSubscribable(nc)\n\n\treturn nc, nil\n}\n\n// Delete a notifications configuration by its ID.\nfunc (s *notificationConfigurations) Delete(ctx context.Context, notificationConfigurationID string) error {\n\tif !validStringID(&notificationConfigurationID) {\n\t\treturn ErrInvalidNotificationConfigID\n\t}\n\n\tu := fmt.Sprintf(\"notification-configurations/%s\", url.PathEscape(notificationConfigurationID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Verify a notification configuration by delivering a verification\n// payload to the configured url.\nfunc (s *notificationConfigurations) Verify(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error) {\n\tif !validStringID(&notificationConfigurationID) {\n\t\treturn nil, ErrInvalidNotificationConfigID\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"notification-configurations/%s/actions/verify\", url.PathEscape(notificationConfigurationID))\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnc := &NotificationConfiguration{}\n\terr = req.Do(ctx, nc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn nc, nil\n}\n\nfunc (o NotificationConfigurationCreateOptions) valid() error {\n\tif o.SubscribableChoice == nil || o.SubscribableChoice.Workspace != nil {\n\t\tif !validStringID(&o.SubscribableChoice.Workspace.ID) {\n\t\t\treturn ErrInvalidWorkspaceID\n\t\t}\n\t} else {\n\t\tif !validStringID(&o.SubscribableChoice.Team.ID) {\n\t\t\treturn ErrInvalidTeamID\n\t\t}\n\t}\n\n\tif o.DestinationType == nil {\n\t\treturn ErrRequiredDestinationType\n\t}\n\tif o.Enabled == nil {\n\t\treturn ErrRequiredEnabled\n\t}\n\tif !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\n\tif !validNotificationTriggerType(o.Triggers) {\n\t\treturn ErrInvalidNotificationTrigger\n\t}\n\n\tif *o.DestinationType == NotificationDestinationTypeGeneric ||\n\t\t*o.DestinationType == NotificationDestinationTypeSlack ||\n\t\t*o.DestinationType == NotificationDestinationTypeMicrosoftTeams {\n\t\tif o.URL == nil {\n\t\t\treturn ErrRequiredURL\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o NotificationConfigurationUpdateOptions) valid() error {\n\tif o.Name != nil && !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\n\tif !validNotificationTriggerType(o.Triggers) {\n\t\treturn ErrInvalidNotificationTrigger\n\t}\n\n\treturn nil\n}\n\nfunc backfillDeprecatedSubscribable(notification *NotificationConfiguration) {\n\tif notification.Subscribable != nil || notification.SubscribableChoice == nil {\n\t\treturn\n\t}\n\n\tif notification.SubscribableChoice.Workspace != nil {\n\t\tnotification.Subscribable = notification.SubscribableChoice.Workspace\n\t}\n}\n\nfunc validNotificationTriggerType(triggers []NotificationTriggerType) bool {\n\tfor _, t := range triggers {\n\t\tswitch t {\n\t\tcase NotificationTriggerApplying,\n\t\t\tNotificationTriggerNeedsAttention,\n\t\t\tNotificationTriggerCompleted,\n\t\t\tNotificationTriggerCreated,\n\t\t\tNotificationTriggerErrored,\n\t\t\tNotificationTriggerPlanning,\n\t\t\tNotificationTriggerAssessmentDrifted,\n\t\t\tNotificationTriggerAssessmentFailed,\n\t\t\tNotificationTriggerWorkspaceAutoDestroyReminder,\n\t\t\tNotificationTriggerWorkspaceAutoDestroyRunResults,\n\t\t\tNotificationTriggerChangeRequestCreated,\n\t\t\tNotificationTriggerAssessmentCheckFailed:\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "notification_configuration_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNotificationConfigurationList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\tncTest1, ncTestCleanup1 := createNotificationConfiguration(t, client, wTest, nil)\n\tdefer ncTestCleanup1()\n\tncTest2, ncTestCleanup2 := createNotificationConfiguration(t, client, wTest, nil)\n\tdefer ncTestCleanup2()\n\n\tt.Run(\"with a valid workspace\", func(t *testing.T) {\n\t\tncl, err := client.NotificationConfigurations.List(\n\t\t\tctx,\n\t\t\twTest.ID,\n\t\t\tnil,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, ncl.Items, ncTest1)\n\t\tassert.Contains(t, ncl.Items, ncTest2)\n\n\t\tassert.Equal(t, 0, ncl.CurrentPage)\n\t\tassert.Equal(t, 0, ncl.TotalCount)\n\n\t\tassert.NotNil(t, ncl.Items[0].Subscribable)\n\t\tassert.NotEmpty(t, ncl.Items[0].Subscribable)\n\t\tassert.NotNil(t, ncl.Items[0].SubscribableChoice.Workspace)\n\t\tassert.NotEmpty(t, ncl.Items[0].SubscribableChoice.Workspace)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tncl, err := client.NotificationConfigurations.List(\n\t\t\tctx,\n\t\t\twTest.ID,\n\t\t\t&NotificationConfigurationListOptions{\n\t\t\t\tListOptions: ListOptions{\n\t\t\t\t\tPageNumber: 999,\n\t\t\t\t\tPageSize:   100,\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, ncl.Items)\n\t\tassert.Equal(t, 999, ncl.CurrentPage)\n\t\tassert.Equal(t, 2, ncl.TotalCount)\n\t})\n\n\tt.Run(\"without a valid workspace\", func(t *testing.T) {\n\t\tncl, err := client.NotificationConfigurations.List(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tnil,\n\t\t)\n\t\tassert.Nil(t, ncl)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestNotificationConfigurationList_forTeams(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithStandardEntitlementPlan().Update(t)\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tt.Cleanup(tmTestCleanup)\n\trequire.NotNil(t, tmTest)\n\n\tncTest1, ncTestCleanup1 := createTeamNotificationConfiguration(t, client, tmTest, nil)\n\tt.Cleanup(ncTestCleanup1)\n\tncTest2, ncTestCleanup2 := createTeamNotificationConfiguration(t, client, tmTest, nil)\n\tt.Cleanup(ncTestCleanup2)\n\n\tt.Run(\"with a valid team\", func(t *testing.T) {\n\t\tncl, err := client.NotificationConfigurations.List(\n\t\t\tctx,\n\t\t\ttmTest.ID,\n\t\t\t&NotificationConfigurationListOptions{\n\t\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{\n\t\t\t\t\tTeam: tmTest,\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, ncl.Items, ncTest1)\n\t\tassert.Contains(t, ncl.Items, ncTest2)\n\t})\n\n\tt.Run(\"without a valid team\", func(t *testing.T) {\n\t\tncl, err := client.NotificationConfigurations.List(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\t&NotificationConfigurationListOptions{\n\t\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{\n\t\t\t\t\tTeam: tmTest,\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\t\tassert.Nil(t, ncl)\n\t\tassert.EqualError(t, err, ErrInvalidTeamID.Error())\n\t})\n}\n\nfunc TestNotificationConfigurationCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\t// Create user to use when testing email destination type\n\torgMemberTest, orgMemberTestCleanup := createOrganizationMembership(t, client, orgTest)\n\tdefer orgMemberTestCleanup()\n\n\torgMemberTest.User = &User{ID: orgMemberTest.User.ID}\n\n\tt.Run(\"with all required values\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType: NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\tEnabled:         Bool(false),\n\t\t\tName:            String(randomString(t)),\n\t\t\tToken:           String(randomString(t)),\n\t\t\tURL:             String(\"http://example.com\"),\n\t\t\tTriggers:        []NotificationTriggerType{NotificationTriggerCreated},\n\t\t}\n\n\t\t_, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without a required value\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType: NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\tEnabled:         Bool(false),\n\t\t\tToken:           String(randomString(t)),\n\t\t\tURL:             String(\"http://example.com\"),\n\t\t\tTriggers:        []NotificationTriggerType{NotificationTriggerCreated},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"without a required value URL when destination type is generic\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType: NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\tEnabled:         Bool(false),\n\t\t\tName:            String(randomString(t)),\n\t\t\tToken:           String(randomString(t)),\n\t\t\tTriggers:        []NotificationTriggerType{NotificationTriggerCreated},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.Equal(t, err, ErrRequiredURL)\n\t})\n\n\tt.Run(\"without a required value URL when destination type is slack\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType: NotificationDestination(NotificationDestinationTypeSlack),\n\t\t\tEnabled:         Bool(false),\n\t\t\tName:            String(randomString(t)),\n\t\t\tTriggers:        []NotificationTriggerType{NotificationTriggerCreated},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.Equal(t, err, ErrRequiredURL)\n\t})\n\n\tt.Run(\"without a required value URL when destination type is MS Teams\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType: NotificationDestination(NotificationDestinationTypeMicrosoftTeams),\n\t\t\tEnabled:         Bool(false),\n\t\t\tName:            String(randomString(t)),\n\t\t\tTriggers:        []NotificationTriggerType{NotificationTriggerCreated},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.Equal(t, err, ErrRequiredURL)\n\t})\n\n\tt.Run(\"without a valid workspace\", func(t *testing.T) {\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, badIdentifier, NotificationConfigurationCreateOptions{})\n\t\tassert.Nil(t, nc)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n\n\tt.Run(\"with an invalid notification trigger\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType: NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\tEnabled:         Bool(false),\n\t\t\tName:            String(randomString(t)),\n\t\t\tToken:           String(randomString(t)),\n\t\t\tURL:             String(\"http://example.com\"),\n\t\t\tTriggers:        []NotificationTriggerType{\"the beacons of gondor are lit\"},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.EqualError(t, err, ErrInvalidNotificationTrigger.Error())\n\t})\n\n\tt.Run(\"with email users when destination type is email\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType: NotificationDestination(NotificationDestinationTypeEmail),\n\t\t\tEnabled:         Bool(false),\n\t\t\tName:            String(randomString(t)),\n\t\t\tEmailUsers:      []*User{orgMemberTest.User},\n\t\t}\n\n\t\t_, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without email users when destination type is email\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType: NotificationDestination(NotificationDestinationTypeEmail),\n\t\t\tEnabled:         Bool(false),\n\t\t\tName:            String(randomString(t)),\n\t\t}\n\n\t\t_, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestNotificationConfigurationsCreate_byType(t *testing.T) {\n\tt.Parallel()\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\t// Create user to use when testing email destination type\n\torgMemberTest, orgMemberTestCleanup := createOrganizationMembership(t, client, orgTest)\n\tt.Cleanup(orgMemberTestCleanup)\n\n\torgMemberTest.User = &User{ID: orgMemberTest.User.ID}\n\n\ttestCases := []NotificationTriggerType{\n\t\tNotificationTriggerCreated,\n\t\tNotificationTriggerPlanning,\n\t\tNotificationTriggerNeedsAttention,\n\t\tNotificationTriggerApplying,\n\t\tNotificationTriggerCompleted,\n\t\tNotificationTriggerErrored,\n\t\tNotificationTriggerAssessmentDrifted,\n\t\tNotificationTriggerAssessmentFailed,\n\t\tNotificationTriggerAssessmentCheckFailed,\n\t\tNotificationTriggerWorkspaceAutoDestroyReminder,\n\t\tNotificationTriggerWorkspaceAutoDestroyRunResults,\n\t}\n\n\tfor _, trigger := range testCases {\n\t\tmessage := fmt.Sprintf(\"with trigger %s and all required values\", trigger)\n\n\t\tt.Run(message, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\t\tDestinationType: NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\t\tEnabled:         Bool(false),\n\t\t\t\tName:            String(randomString(t)),\n\t\t\t\tToken:           String(randomString(t)),\n\t\t\t\tURL:             String(\"http://example.com\"),\n\t\t\t\tTriggers:        []NotificationTriggerType{trigger},\n\t\t\t}\n\n\t\t\t_, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)\n\t\t\trequire.NoError(t, err)\n\t\t})\n\t}\n}\n\nfunc TestNotificationConfigurationCreate_forTeams(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithStandardEntitlementPlan().Update(t)\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tt.Cleanup(tmTestCleanup)\n\n\t// Create user to use when testing email destination type\n\torgMemberTest, orgMemberTestCleanup := createOrganizationMembership(t, client, orgTest)\n\tt.Cleanup(orgMemberTestCleanup)\n\n\t// Add user to team\n\toptions := TeamMemberAddOptions{\n\t\tOrganizationMembershipIDs: []string{orgMemberTest.ID},\n\t}\n\terr := client.TeamMembers.Add(ctx, tmTest.ID, options)\n\trequire.NoError(t, err)\n\n\tt.Run(\"with all required values\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType:    NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\tEnabled:            Bool(false),\n\t\t\tName:               String(randomString(t)),\n\t\t\tToken:              String(randomString(t)),\n\t\t\tURL:                String(\"http://example.com\"),\n\t\t\tTriggers:           []NotificationTriggerType{NotificationTriggerChangeRequestCreated},\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: tmTest},\n\t\t}\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, tmTest.ID, options)\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, nc)\n\t})\n\n\tt.Run(\"without a required value\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType:    NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\tEnabled:            Bool(false),\n\t\t\tToken:              String(randomString(t)),\n\t\t\tURL:                String(\"http://example.com\"),\n\t\t\tTriggers:           []NotificationTriggerType{NotificationTriggerChangeRequestCreated},\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: tmTest},\n\t\t}\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, tmTest.ID, options)\n\n\t\tassert.Nil(t, nc)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"without a required value URL when destination type is generic\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType:    NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\tEnabled:            Bool(false),\n\t\t\tName:               String(randomString(t)),\n\t\t\tToken:              String(randomString(t)),\n\t\t\tTriggers:           []NotificationTriggerType{NotificationTriggerChangeRequestCreated},\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: tmTest},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, tmTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.Equal(t, err, ErrRequiredURL)\n\t})\n\n\tt.Run(\"without a required value URL when destination type is slack\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType:    NotificationDestination(NotificationDestinationTypeSlack),\n\t\t\tEnabled:            Bool(false),\n\t\t\tName:               String(randomString(t)),\n\t\t\tTriggers:           []NotificationTriggerType{NotificationTriggerChangeRequestCreated},\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: tmTest},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, tmTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.Equal(t, err, ErrRequiredURL)\n\t})\n\n\tt.Run(\"without a required value URL when destination type is MS Teams\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType:    NotificationDestination(NotificationDestinationTypeMicrosoftTeams),\n\t\t\tEnabled:            Bool(false),\n\t\t\tName:               String(randomString(t)),\n\t\t\tTriggers:           []NotificationTriggerType{NotificationTriggerChangeRequestCreated},\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: tmTest},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, tmTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.Equal(t, err, ErrRequiredURL)\n\t})\n\n\tt.Run(\"without a valid team\", func(t *testing.T) {\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, badIdentifier, NotificationConfigurationCreateOptions{\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{\n\t\t\t\tTeam: tmTest,\n\t\t\t},\n\t\t})\n\t\tassert.Nil(t, nc)\n\t\tassert.EqualError(t, err, ErrInvalidTeamID.Error())\n\t})\n\n\tt.Run(\"with an invalid notification trigger\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType:    NotificationDestination(NotificationDestinationTypeGeneric),\n\t\t\tEnabled:            Bool(false),\n\t\t\tName:               String(randomString(t)),\n\t\t\tToken:              String(randomString(t)),\n\t\t\tURL:                String(\"http://example.com\"),\n\t\t\tTriggers:           []NotificationTriggerType{\"the beacons of gondor are lit\"},\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: tmTest},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Create(ctx, tmTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.EqualError(t, err, ErrInvalidNotificationTrigger.Error())\n\t})\n\n\tt.Run(\"with email users when destination type is email\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType:    NotificationDestination(NotificationDestinationTypeEmail),\n\t\t\tEnabled:            Bool(false),\n\t\t\tName:               String(randomString(t)),\n\t\t\tEmailUsers:         []*User{orgMemberTest.User},\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: tmTest},\n\t\t}\n\n\t\t_, err := client.NotificationConfigurations.Create(ctx, tmTest.ID, options)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without email users when destination type is email\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationCreateOptions{\n\t\t\tDestinationType:    NotificationDestination(NotificationDestinationTypeEmail),\n\t\t\tEnabled:            Bool(false),\n\t\t\tName:               String(randomString(t)),\n\t\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: tmTest},\n\t\t}\n\n\t\t_, err := client.NotificationConfigurations.Create(ctx, tmTest.ID, options)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestNotificationConfigurationRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tncTest, ncTestCleanup := createNotificationConfiguration(t, client, nil, nil)\n\tdefer ncTestCleanup()\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\tnc, err := client.NotificationConfigurations.Read(ctx, ncTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, ncTest.ID, nc.ID)\n\t})\n\n\tt.Run(\"when the notification configuration does not exist\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Read(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the notification configuration ID is invalid\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Read(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidNotificationConfigID)\n\t})\n}\n\nfunc TestNotificationConfigurationRead_forTeams(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithStandardEntitlementPlan().Update(t)\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tt.Cleanup(tmTestCleanup)\n\n\tncTest, ncTestCleanup := createTeamNotificationConfiguration(t, client, tmTest, nil)\n\tt.Cleanup(ncTestCleanup)\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\tnc, err := client.NotificationConfigurations.Read(ctx, ncTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, ncTest.ID, nc.ID)\n\t})\n\n\tt.Run(\"when the notification configuration does not exist\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Read(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the notification configuration ID is invalid\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Read(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidNotificationConfigID)\n\t})\n}\n\nfunc TestNotificationConfigurationUpdate_forTeams(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithStandardEntitlementPlan().Update(t)\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tt.Cleanup(tmTestCleanup)\n\n\tncTest, ncTestCleanup := createTeamNotificationConfiguration(t, client, tmTest, nil)\n\tt.Cleanup(ncTestCleanup)\n\n\t// Create users to use when testing email destination type\n\torgMemberTest1, orgMemberTest1Cleanup := createOrganizationMembership(t, client, orgTest)\n\tdefer orgMemberTest1Cleanup()\n\torgMemberTest2, orgMemberTest2Cleanup := createOrganizationMembership(t, client, orgTest)\n\tdefer orgMemberTest2Cleanup()\n\n\torgMemberTest1.User = &User{ID: orgMemberTest1.User.ID}\n\torgMemberTest2.User = &User{ID: orgMemberTest2.User.ID}\n\n\t// Add users to team\n\tfor _, orgMember := range []*OrganizationMembership{orgMemberTest1, orgMemberTest2} {\n\t\toptions := TeamMemberAddOptions{\n\t\t\tOrganizationMembershipIDs: []string{orgMember.ID},\n\t\t}\n\t\terr := client.TeamMembers.Add(ctx, tmTest.ID, options)\n\t\trequire.NoError(t, err)\n\t}\n\n\toptions := &NotificationConfigurationCreateOptions{\n\t\tDestinationType:    NotificationDestination(NotificationDestinationTypeEmail),\n\t\tEnabled:            Bool(false),\n\t\tName:               String(randomString(t)),\n\t\tEmailUsers:         []*User{orgMemberTest1.User},\n\t\tSubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: tmTest},\n\t}\n\tncEmailTest, ncEmailTestCleanup := createTeamNotificationConfiguration(t, client, tmTest, options)\n\tt.Cleanup(ncEmailTestCleanup)\n\n\tt.Run(\"with options\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationUpdateOptions{\n\t\t\tEnabled: Bool(true),\n\t\t\tName:    String(\"newName\"),\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Update(ctx, ncTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, nc.Enabled, true)\n\t\tassert.Equal(t, nc.Name, \"newName\")\n\t})\n\n\tt.Run(\"with invalid notification trigger\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationUpdateOptions{\n\t\t\tTriggers: []NotificationTriggerType{\"fly you fools!\"},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Update(ctx, ncTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.EqualError(t, err, ErrInvalidNotificationTrigger.Error())\n\t})\n\n\tt.Run(\"with email users when destination type is email\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationUpdateOptions{\n\t\t\tEnabled:    Bool(true),\n\t\t\tName:       String(\"newName\"),\n\t\t\tEmailUsers: []*User{orgMemberTest1.User, orgMemberTest2.User},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Update(ctx, ncEmailTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, nc.Enabled, true)\n\t\tassert.Equal(t, nc.Name, \"newName\")\n\t\tassert.Contains(t, nc.EmailUsers, orgMemberTest1.User)\n\t\tassert.Contains(t, nc.EmailUsers, orgMemberTest2.User)\n\t})\n\n\tt.Run(\"without email users when destination type is email\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationUpdateOptions{\n\t\t\tEnabled: Bool(true),\n\t\t\tName:    String(\"newName\"),\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Update(ctx, ncEmailTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, nc.Enabled, true)\n\t\tassert.Equal(t, nc.Name, \"newName\")\n\t\tassert.Empty(t, nc.EmailUsers)\n\t})\n\n\tt.Run(\"without options\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Update(ctx, ncTest.ID, NotificationConfigurationUpdateOptions{})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the notification configuration does not exist\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Update(ctx, \"nonexisting\", NotificationConfigurationUpdateOptions{})\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the notification configuration ID is invalid\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Update(ctx, badIdentifier, NotificationConfigurationUpdateOptions{})\n\t\tassert.Equal(t, err, ErrInvalidNotificationConfigID)\n\t})\n}\n\nfunc TestNotificationConfigurationUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\tncTest, ncTestCleanup := createNotificationConfiguration(t, client, wTest, nil)\n\tdefer ncTestCleanup()\n\n\t// Create users to use when testing email destination type\n\torgMemberTest1, orgMemberTest1Cleanup := createOrganizationMembership(t, client, orgTest)\n\tdefer orgMemberTest1Cleanup()\n\torgMemberTest2, orgMemberTest2Cleanup := createOrganizationMembership(t, client, orgTest)\n\tdefer orgMemberTest2Cleanup()\n\n\torgMemberTest1.User = &User{ID: orgMemberTest1.User.ID}\n\torgMemberTest2.User = &User{ID: orgMemberTest2.User.ID}\n\n\toptions := &NotificationConfigurationCreateOptions{\n\t\tDestinationType: NotificationDestination(NotificationDestinationTypeEmail),\n\t\tEnabled:         Bool(false),\n\t\tName:            String(randomString(t)),\n\t\tEmailUsers:      []*User{orgMemberTest1.User},\n\t}\n\tncEmailTest, ncEmailTestCleanup := createNotificationConfiguration(t, client, wTest, options)\n\tdefer ncEmailTestCleanup()\n\n\tt.Run(\"with options\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationUpdateOptions{\n\t\t\tEnabled: Bool(true),\n\t\t\tName:    String(\"newName\"),\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Update(ctx, ncTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, nc.Enabled, true)\n\t\tassert.Equal(t, nc.Name, \"newName\")\n\t})\n\n\tt.Run(\"with invalid notification trigger\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationUpdateOptions{\n\t\t\tTriggers: []NotificationTriggerType{\"fly you fools!\"},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Update(ctx, ncTest.ID, options)\n\t\tassert.Nil(t, nc)\n\t\tassert.EqualError(t, err, ErrInvalidNotificationTrigger.Error())\n\t})\n\n\tt.Run(\"with email users when destination type is email\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationUpdateOptions{\n\t\t\tEnabled:    Bool(true),\n\t\t\tName:       String(\"newName\"),\n\t\t\tEmailUsers: []*User{orgMemberTest1.User, orgMemberTest2.User},\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Update(ctx, ncEmailTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, nc.Enabled, true)\n\t\tassert.Equal(t, nc.Name, \"newName\")\n\t\tassert.Contains(t, nc.EmailUsers, orgMemberTest1.User)\n\t\tassert.Contains(t, nc.EmailUsers, orgMemberTest2.User)\n\t})\n\n\tt.Run(\"without email users when destination type is email\", func(t *testing.T) {\n\t\toptions := NotificationConfigurationUpdateOptions{\n\t\t\tEnabled: Bool(true),\n\t\t\tName:    String(\"newName\"),\n\t\t}\n\n\t\tnc, err := client.NotificationConfigurations.Update(ctx, ncEmailTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, nc.Enabled, true)\n\t\tassert.Equal(t, nc.Name, \"newName\")\n\t\tassert.Empty(t, nc.EmailUsers)\n\t})\n\n\tt.Run(\"without options\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Update(ctx, ncTest.ID, NotificationConfigurationUpdateOptions{})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the notification configuration does not exist\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Update(ctx, \"nonexisting\", NotificationConfigurationUpdateOptions{})\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the notification configuration ID is invalid\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Update(ctx, badIdentifier, NotificationConfigurationUpdateOptions{})\n\t\tassert.Equal(t, err, ErrInvalidNotificationConfigID)\n\t})\n}\n\nfunc TestNotificationConfigurationDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\tncTest, _ := createNotificationConfiguration(t, client, wTest, nil)\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\terr := client.NotificationConfigurations.Delete(ctx, ncTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.NotificationConfigurations.Read(ctx, ncTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the notification configuration does not exist\", func(t *testing.T) {\n\t\terr := client.NotificationConfigurations.Delete(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the notification configuration ID is invalid\", func(t *testing.T) {\n\t\terr := client.NotificationConfigurations.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidNotificationConfigID)\n\t})\n}\n\nfunc TestNotificationConfigurationDelete_forTeams(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithStandardEntitlementPlan().Update(t)\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tt.Cleanup(tmTestCleanup)\n\n\tncTest, _ := createTeamNotificationConfiguration(t, client, tmTest, nil)\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\terr := client.NotificationConfigurations.Delete(ctx, ncTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.NotificationConfigurations.Read(ctx, ncTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the notification configuration does not exist\", func(t *testing.T) {\n\t\terr := client.NotificationConfigurations.Delete(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the notification configuration ID is invalid\", func(t *testing.T) {\n\t\terr := client.NotificationConfigurations.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidNotificationConfigID)\n\t})\n}\n\nfunc TestNotificationConfigurationVerify(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tncTest, ncTestCleanup := createNotificationConfiguration(t, client, nil, nil)\n\tdefer ncTestCleanup()\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Verify(ctx, ncTest.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the notification configuration does not exists\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Verify(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the notification configuration ID is invalid\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Verify(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidNotificationConfigID)\n\t})\n}\n\nfunc TestNotificationConfigurationVerify_forTeams(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithStandardEntitlementPlan().Update(t)\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tt.Cleanup(tmTestCleanup)\n\n\tncTest, ncTestCleanup := createTeamNotificationConfiguration(t, client, tmTest, nil)\n\tt.Cleanup(ncTestCleanup)\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Verify(ctx, ncTest.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the notification configuration does not exists\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Verify(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the notification configuration ID is invalid\", func(t *testing.T) {\n\t\t_, err := client.NotificationConfigurations.Verify(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidNotificationConfigID)\n\t})\n}\n"
  },
  {
    "path": "oauth_client.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ OAuthClients = (*oAuthClients)(nil)\n\n// OAuthClients describes all the OAuth client related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/oauth-clients\ntype OAuthClients interface {\n\t// List all the OAuth clients for a given organization.\n\tList(ctx context.Context, organization string, options *OAuthClientListOptions) (*OAuthClientList, error)\n\n\t// Create an OAuth client to connect an organization and a VCS provider.\n\tCreate(ctx context.Context, organization string, options OAuthClientCreateOptions) (*OAuthClient, error)\n\n\t// Read an OAuth client by its ID.\n\tRead(ctx context.Context, oAuthClientID string) (*OAuthClient, error)\n\n\t// ReadWithOptions reads an oauth client by its ID using the options supplied.\n\tReadWithOptions(ctx context.Context, oAuthClientID string, options *OAuthClientReadOptions) (*OAuthClient, error)\n\n\t// Update an existing OAuth client by its ID.\n\tUpdate(ctx context.Context, oAuthClientID string, options OAuthClientUpdateOptions) (*OAuthClient, error)\n\n\t// Delete an OAuth client by its ID.\n\tDelete(ctx context.Context, oAuthClientID string) error\n\n\t// AddProjects add projects to an oauth client.\n\tAddProjects(ctx context.Context, oAuthClientID string, options OAuthClientAddProjectsOptions) error\n\n\t// RemoveProjects remove projects from an oauth client.\n\tRemoveProjects(ctx context.Context, oAuthClientID string, options OAuthClientRemoveProjectsOptions) error\n}\n\n// oAuthClients implements OAuthClients.\ntype oAuthClients struct {\n\tclient *Client\n}\n\n// ServiceProviderType represents a VCS type.\ntype ServiceProviderType string\n\n// List of available VCS types.\nconst (\n\tServiceProviderAzureDevOpsServer   ServiceProviderType = \"ado_server\"\n\tServiceProviderAzureDevOpsServices ServiceProviderType = \"ado_services\"\n\tServiceProviderBitbucketDataCenter ServiceProviderType = \"bitbucket_data_center\"\n\tServiceProviderBitbucket           ServiceProviderType = \"bitbucket_hosted\"\n\t// Bitbucket Server v5.4.0 and above\n\tServiceProviderBitbucketServer ServiceProviderType = \"bitbucket_server\"\n\t// Bitbucket Server v5.3.0 and below\n\tServiceProviderBitbucketServerLegacy ServiceProviderType = \"bitbucket_server_legacy\"\n\tServiceProviderGithub                ServiceProviderType = \"github\"\n\tServiceProviderGithubEE              ServiceProviderType = \"github_enterprise\"\n\tServiceProviderGitlab                ServiceProviderType = \"gitlab_hosted\"\n\tServiceProviderGitlabCE              ServiceProviderType = \"gitlab_community_edition\"\n\tServiceProviderGitlabEE              ServiceProviderType = \"gitlab_enterprise_edition\"\n)\n\n// OAuthClientList represents a list of OAuth clients.\ntype OAuthClientList struct {\n\t*Pagination\n\tItems []*OAuthClient\n}\n\n// OAuthClient represents a connection between an organization and a VCS\n// provider.\ntype OAuthClient struct {\n\tID                  string              `jsonapi:\"primary,oauth-clients\"`\n\tAPIURL              string              `jsonapi:\"attr,api-url\"`\n\tCallbackURL         string              `jsonapi:\"attr,callback-url\"`\n\tConnectPath         string              `jsonapi:\"attr,connect-path\"`\n\tCreatedAt           time.Time           `jsonapi:\"attr,created-at,iso8601\"`\n\tHTTPURL             string              `jsonapi:\"attr,http-url\"`\n\tKey                 string              `jsonapi:\"attr,key\"`\n\tRSAPublicKey        string              `jsonapi:\"attr,rsa-public-key\"`\n\tName                *string             `jsonapi:\"attr,name\"`\n\tSecret              string              `jsonapi:\"attr,secret\"`\n\tServiceProvider     ServiceProviderType `jsonapi:\"attr,service-provider\"`\n\tServiceProviderName string              `jsonapi:\"attr,service-provider-display-name\"`\n\tOrganizationScoped  *bool               `jsonapi:\"attr,organization-scoped\"`\n\n\t// Relations\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n\tOAuthTokens  []*OAuthToken `jsonapi:\"relation,oauth-tokens\"`\n\tAgentPool    *AgentPool    `jsonapi:\"relation,agent-pool\"`\n\t// The projects to which the oauth client applies.\n\tProjects []*Project `jsonapi:\"relation,projects\"`\n}\n\n// A list of relations to include\ntype OAuthClientIncludeOpt string\n\nconst (\n\tOauthClientOauthTokens OAuthClientIncludeOpt = \"oauth_tokens\"\n\tOauthClientProjects    OAuthClientIncludeOpt = \"projects\"\n)\n\n// OAuthClientListOptions represents the options for listing\n// OAuth clients.\ntype OAuthClientListOptions struct {\n\tListOptions\n\n\tInclude []OAuthClientIncludeOpt `url:\"include,omitempty\"`\n}\n\n// OAuthClientReadOptions are read options.\n// For a full list of relations, please see:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/oauth-clients#relationships\ntype OAuthClientReadOptions struct {\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/oauth-clients#available-related-resources\n\tInclude []OAuthClientIncludeOpt `url:\"include,omitempty\"`\n}\n\n// OAuthClientCreateOptions represents the options for creating an OAuth client.\ntype OAuthClientCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,oauth-clients\"`\n\n\t// A display name for the OAuth Client.\n\tName *string `jsonapi:\"attr,name\"`\n\n\t// Required: The base URL of your VCS provider's API.\n\tAPIURL *string `jsonapi:\"attr,api-url\"`\n\n\t// Required: The homepage of your VCS provider.\n\tHTTPURL *string `jsonapi:\"attr,http-url\"`\n\n\t// Optional: The OAuth Client key.\n\tKey *string `jsonapi:\"attr,key,omitempty\"`\n\n\t// Optional: The token string you were given by your VCS provider.\n\tOAuthToken *string `jsonapi:\"attr,oauth-token-string,omitempty\"`\n\n\t// Optional: The initial list of projects for which the oauth client should be associated with.\n\tProjects []*Project `jsonapi:\"relation,projects,omitempty\"`\n\n\t// Optional: Private key associated with this vcs provider - only available for ado_server\n\tPrivateKey *string `jsonapi:\"attr,private-key,omitempty\"`\n\n\t// Optional: Secret key associated with this vcs provider - only available for ado_server\n\tSecret *string `jsonapi:\"attr,secret,omitempty\"`\n\n\t// Optional: RSAPublicKey the text of the SSH public key associated with your\n\t// BitBucket Data Center Application Link.\n\tRSAPublicKey *string `jsonapi:\"attr,rsa-public-key,omitempty\"`\n\n\t// Required: The VCS provider being connected with.\n\tServiceProvider *ServiceProviderType `jsonapi:\"attr,service-provider\"`\n\n\t// Optional: AgentPool to associate the VCS Provider with, for PrivateVCS support\n\tAgentPool *AgentPool `jsonapi:\"relation,agent-pool,omitempty\"`\n\n\t// Optional: Whether the OAuthClient is available to all workspaces in the organization.\n\t// True if the oauth client is organization scoped, false otherwise.\n\tOrganizationScoped *bool `jsonapi:\"attr,organization-scoped,omitempty\"`\n}\n\n// OAuthClientUpdateOptions represents the options for updating an OAuth client.\ntype OAuthClientUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,oauth-clients\"`\n\n\t// Optional: A display name for the OAuth Client.\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: The OAuth Client key.\n\tKey *string `jsonapi:\"attr,key,omitempty\"`\n\n\t// Optional: Secret key associated with this vcs provider - only available for ado_server\n\tSecret *string `jsonapi:\"attr,secret,omitempty\"`\n\n\t// Optional: RSAPublicKey the text of the SSH public key associated with your BitBucket\n\t// Server Application Link.\n\tRSAPublicKey *string `jsonapi:\"attr,rsa-public-key,omitempty\"`\n\n\t// Optional: The token string you were given by your VCS provider.\n\tOAuthToken *string `jsonapi:\"attr,oauth-token-string,omitempty\"`\n\n\t// Optional: AgentPool to associate the VCS Provider with, for PrivateVCS support\n\tAgentPool *AgentPool `jsonapi:\"relation,agent-pool,omitempty\"`\n\n\t// Optional: Whether the OAuthClient is available to all workspaces in the organization.\n\t// True if the oauth client is organization scoped, false otherwise.\n\tOrganizationScoped *bool `jsonapi:\"attr,organization-scoped,omitempty\"`\n}\n\n// OAuthClientAddProjectsOptions represents the options for adding projects\n// to an oauth client.\ntype OAuthClientAddProjectsOptions struct {\n\t// The projects to add to an oauth client.\n\tProjects []*Project\n}\n\n// OAuthClientRemoveProjectsOptions represents the options for removing\n// projects from an oauth client.\ntype OAuthClientRemoveProjectsOptions struct {\n\t// The projects to remove from an oauth client.\n\tProjects []*Project\n}\n\n// List all the OAuth clients for a given organization.\nfunc (s *oAuthClients) List(ctx context.Context, organization string, options *OAuthClientListOptions) (*OAuthClientList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/oauth-clients\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tocl := &OAuthClientList{}\n\terr = req.Do(ctx, ocl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ocl, nil\n}\n\n// Create an OAuth client to connect an organization and a VCS provider.\nfunc (s *oAuthClients) Create(ctx context.Context, organization string, options OAuthClientCreateOptions) (*OAuthClient, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/oauth-clients\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\toc := &OAuthClient{}\n\terr = req.Do(ctx, oc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn oc, nil\n}\n\n// Read an OAuth client by its ID.\nfunc (s *oAuthClients) Read(ctx context.Context, oAuthClientID string) (*OAuthClient, error) {\n\treturn s.ReadWithOptions(ctx, oAuthClientID, nil)\n}\n\nfunc (s *oAuthClients) ReadWithOptions(ctx context.Context, oAuthClientID string, options *OAuthClientReadOptions) (*OAuthClient, error) {\n\tif !validStringID(&oAuthClientID) {\n\t\treturn nil, ErrInvalidOauthClientID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"oauth-clients/%s\", url.PathEscape(oAuthClientID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\toc := &OAuthClient{}\n\terr = req.Do(ctx, oc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn oc, err\n}\n\n// Update an OAuth client by its ID.\nfunc (s *oAuthClients) Update(ctx context.Context, oAuthClientID string, options OAuthClientUpdateOptions) (*OAuthClient, error) {\n\tif !validStringID(&oAuthClientID) {\n\t\treturn nil, ErrInvalidOauthClientID\n\t}\n\n\tu := fmt.Sprintf(\"oauth-clients/%s\", url.PathEscape(oAuthClientID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\toc := &OAuthClient{}\n\terr = req.Do(ctx, oc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn oc, err\n}\n\n// Delete an OAuth client by its ID.\nfunc (s *oAuthClients) Delete(ctx context.Context, oAuthClientID string) error {\n\tif !validStringID(&oAuthClientID) {\n\t\treturn ErrInvalidOauthClientID\n\t}\n\n\tu := fmt.Sprintf(\"oauth-clients/%s\", url.PathEscape(oAuthClientID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o OAuthClientCreateOptions) valid() error {\n\tif !validString(o.APIURL) {\n\t\treturn ErrRequiredAPIURL\n\t}\n\tif !validString(o.HTTPURL) {\n\t\treturn ErrRequiredHTTPURL\n\t}\n\tif o.ServiceProvider == nil {\n\t\treturn ErrRequiredServiceProvider\n\t}\n\tif !validString(o.OAuthToken) &&\n\t\t*o.ServiceProvider != *ServiceProvider(ServiceProviderBitbucketServer) &&\n\t\t*o.ServiceProvider != *ServiceProvider(ServiceProviderBitbucketDataCenter) {\n\t\treturn ErrRequiredOauthToken\n\t}\n\tif validString(o.PrivateKey) && *o.ServiceProvider != *ServiceProvider(ServiceProviderAzureDevOpsServer) {\n\t\treturn ErrUnsupportedPrivateKey\n\t}\n\treturn nil\n}\n\nfunc (o *OAuthClientListOptions) valid() error {\n\treturn nil\n}\n\n// AddProjects adds projects to a given oauth client.\nfunc (s *oAuthClients) AddProjects(ctx context.Context, oAuthClientID string, options OAuthClientAddProjectsOptions) error {\n\tif !validStringID(&oAuthClientID) {\n\t\treturn ErrInvalidOauthClientID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"oauth-clients/%s/relationships/projects\", url.PathEscape(oAuthClientID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.Projects)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// RemoveProjects removes projects from an oauth client.\nfunc (s *oAuthClients) RemoveProjects(ctx context.Context, oAuthClientID string, options OAuthClientRemoveProjectsOptions) error {\n\tif !validStringID(&oAuthClientID) {\n\t\treturn ErrInvalidOauthClientID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"oauth-clients/%s/relationships/projects\", url.PathEscape(oAuthClientID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.Projects)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o OAuthClientAddProjectsOptions) valid() error {\n\tif o.Projects == nil {\n\t\treturn ErrRequiredProject\n\t}\n\tif len(o.Projects) == 0 {\n\t\treturn ErrProjectMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o OAuthClientRemoveProjectsOptions) valid() error {\n\tif o.Projects == nil {\n\t\treturn ErrRequiredProject\n\t}\n\tif len(o.Projects) == 0 {\n\t\treturn ErrProjectMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o *OAuthClientReadOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "oauth_client_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOAuthClientsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tocTest1, ocTestCleanup1 := createOAuthClient(t, client, orgTest, nil)\n\tdefer ocTestCleanup1()\n\tocTest2, ocTestCleanup2 := createOAuthClient(t, client, orgTest, nil)\n\tdefer ocTestCleanup2()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tocl, err := client.OAuthClients.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tt.Run(\"the OAuth tokens relationship is decoded correcly\", func(t *testing.T) {\n\t\t\tfor _, oc := range ocl.Items {\n\t\t\t\tassert.Equal(t, 1, len(oc.OAuthTokens))\n\t\t\t}\n\t\t})\n\n\t\t// We need to strip some fields before the next test.\n\t\tfor _, oc := range append(ocl.Items, ocTest1, ocTest2) {\n\t\t\toc.OAuthTokens = nil\n\t\t\toc.Organization = nil\n\t\t}\n\n\t\tassert.Contains(t, ocl.Items, ocTest1)\n\t\tassert.Contains(t, ocl.Items, ocTest2)\n\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\tassert.Equal(t, 1, ocl.CurrentPage)\n\t\tassert.Equal(t, 2, ocl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\toptions := &OAuthClientListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t}\n\n\t\tocl, err := client.OAuthClients.List(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, ocl.Items)\n\t\tassert.Equal(t, 999, ocl.CurrentPage)\n\t\tassert.Equal(t, 2, ocl.TotalCount)\n\t})\n\n\tt.Run(\"with Include options\", func(t *testing.T) {\n\t\tocl, err := client.OAuthClients.List(ctx, orgTest.Name, &OAuthClientListOptions{\n\t\t\tInclude: []OAuthClientIncludeOpt{OauthClientOauthTokens},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, ocl.Items)\n\t\trequire.NotNil(t, ocl.Items[0])\n\t\trequire.NotEmpty(t, ocl.Items[0].OAuthTokens)\n\t\tassert.NotEmpty(t, ocl.Items[0].OAuthTokens[0].ID)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tocl, err := client.OAuthClients.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, ocl)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestOAuthClientsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tgithubToken := os.Getenv(\"OAUTH_CLIENT_GITHUB_TOKEN\")\n\tif githubToken == \"\" {\n\t\tt.Skip(\"Export a valid OAUTH_CLIENT_GITHUB_TOKEN before running this test!\")\n\t}\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\t\tHTTPURL:         String(\"https://github.com\"),\n\t\t\tOAuthToken:      String(githubToken),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\t}\n\n\t\toc, err := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, oc.ID)\n\t\tassert.Nil(t, oc.Name)\n\t\tassert.Equal(t, \"https://api.github.com\", oc.APIURL)\n\t\tassert.Equal(t, \"https://github.com\", oc.HTTPURL)\n\t\tassert.Equal(t, 1, len(oc.OAuthTokens))\n\t\tassert.Equal(t, ServiceProviderGithub, oc.ServiceProvider)\n\n\t\tt.Run(\"the organization relationship is decoded correctly\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, oc.Organization)\n\t\t})\n\t})\n\n\tt.Run(\"without an valid organization\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\t\tHTTPURL:         String(\"https://github.com\"),\n\t\t\tOAuthToken:      String(githubToken),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\t}\n\n\t\t_, err := client.OAuthClients.Create(ctx, badIdentifier, options)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"without an API URL\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tHTTPURL:         String(\"https://github.com\"),\n\t\t\tOAuthToken:      String(githubToken),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\t}\n\n\t\t_, err := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\tassert.Equal(t, err, ErrRequiredAPIURL)\n\t})\n\n\tt.Run(\"without a HTTP URL\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\t\tOAuthToken:      String(githubToken),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\t}\n\n\t\t_, err := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\tassert.Equal(t, err, ErrRequiredHTTPURL)\n\t})\n\n\tt.Run(\"without an OAuth token\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\t\tHTTPURL:         String(\"https://github.com\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\t}\n\n\t\t_, err := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\tassert.Equal(t, err, ErrRequiredOauthToken)\n\t})\n\n\tt.Run(\"without a service provider\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:     String(\"https://api.github.com\"),\n\t\t\tHTTPURL:    String(\"https://github.com\"),\n\t\t\tOAuthToken: String(githubToken),\n\t\t}\n\n\t\t_, err := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\tassert.Equal(t, err, ErrRequiredServiceProvider)\n\t})\n}\n\nfunc TestOAuthClientsCreate_rsaKeyPair(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with key, rsa public/private key options\", func(t *testing.T) {\n\t\tkey := randomString(t)\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://bbdc.com\"),\n\t\t\tHTTPURL:         String(\"https://bbdc.com\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderBitbucketDataCenter),\n\t\t\tKey:             String(key),\n\t\t\tSecret:          String(privateKey),\n\t\t\tRSAPublicKey:    String(publicKey),\n\t\t}\n\n\t\toc, err := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, oc.ID)\n\t\tassert.Equal(t, \"https://bbdc.com\", oc.APIURL)\n\t\tassert.Equal(t, \"https://bbdc.com\", oc.HTTPURL)\n\t\tassert.Equal(t, ServiceProviderBitbucketDataCenter, oc.ServiceProvider)\n\t\tassert.Equal(t, publicKey, oc.RSAPublicKey)\n\t\tassert.Equal(t, key, oc.Key)\n\t})\n}\n\nfunc TestOAuthClientsCreate_agentPool(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tgithubToken := os.Getenv(\"OAUTH_CLIENT_GITHUB_TOKEN\")\n\tif githubToken == \"\" {\n\t\tt.Skip(\"Export a valid OAUTH_CLIENT_GITHUB_TOKEN before running this test!\")\n\t}\n\n\tt.Run(\"with valid agent pool external id\", func(t *testing.T) {\n\t\t// This requires access to Private VCS feature and tfc-agent running locally\n\t\tt.Skip()\n\t\torgTestRead, errOrg := client.Organizations.Read(ctx, \"xxxxx\")\n\t\trequire.NoError(t, errOrg)\n\t\tagentPoolTestRead, errAgentPool := client.AgentPools.Read(ctx, \"xxxxx\")\n\t\trequire.NoError(t, errAgentPool)\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://githubenterprise.xxxxx\"),\n\t\t\tHTTPURL:         String(\"https://githubenterprise.xxxxx\"),\n\t\t\tOAuthToken:      String(githubToken),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithubEE),\n\t\t\tAgentPool:       agentPoolTestRead,\n\t\t}\n\t\toc, errCreate := client.OAuthClients.Create(ctx, orgTestRead.Name, options)\n\t\trequire.NoError(t, errCreate)\n\t\tassert.NotEmpty(t, oc.ID)\n\t\tassert.Equal(t, \"https://githubenterprise.xxxxx\", oc.APIURL)\n\t\tassert.Equal(t, \"https://githubenterprise.xxxxx\", oc.HTTPURL)\n\t\tassert.Equal(t, 1, len(oc.OAuthTokens))\n\t\tassert.Equal(t, ServiceProviderGithubEE, oc.ServiceProvider)\n\t\tassert.Equal(t, agentPoolTestRead.ID, oc.AgentPool.ID)\n\t})\n\n\tt.Run(\"with an invalid agent pool\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\t\tagentPoolTest, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\t\tdefer agentPoolCleanup()\n\t\tagentPoolID := agentPoolTest.ID\n\t\tagentPoolTest.ID = badIdentifier\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://githubenterprise.xxxxx\"),\n\t\t\tHTTPURL:         String(\"https://githubenterprise.xxxxx\"),\n\t\t\tOAuthToken:      String(githubToken),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithubEE),\n\t\t\tAgentPool:       agentPoolTest,\n\t\t}\n\t\t_, errCreate := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\trequire.Error(t, errCreate)\n\t\tassert.Contains(t, errCreate.Error(), \"the provided agent pool does not exist or you are not authorized to use it\")\n\t\tagentPoolTest.ID = agentPoolID\n\t})\n\n\tt.Run(\"with no agents connected\", func(t *testing.T) {\n\t\tt.Skip(\"Skipping due to persistent failures - see TF-31172\")\n\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\t\tagentPoolTest, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\t\tdefer agentPoolCleanup()\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://githubenterprise.xxxxx\"),\n\t\t\tHTTPURL:         String(\"https://githubenterprise.xxxxx\"),\n\t\t\tOAuthToken:      String(githubToken),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithubEE),\n\t\t\tAgentPool:       agentPoolTest,\n\t\t}\n\t\t_, errCreate := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\tassert.Contains(t, errCreate.Error(), \"the organization does not have private VCS enabled\")\n\t\trequire.Error(t, errCreate)\n\t})\n}\n\nfunc TestOAuthClientsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tocTest, ocTestCleanup := createOAuthClient(t, client, nil, nil)\n\tdefer ocTestCleanup()\n\n\tt.Run(\"when the OAuth client exists\", func(t *testing.T) {\n\t\toc, err := client.OAuthClients.Read(ctx, ocTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, ocTest.ID, oc.ID)\n\t\tassert.Equal(t, ocTest.APIURL, oc.APIURL)\n\t\tassert.Equal(t, ocTest.CallbackURL, oc.CallbackURL)\n\t\tassert.Equal(t, ocTest.ConnectPath, oc.ConnectPath)\n\t\tassert.Equal(t, ocTest.HTTPURL, oc.HTTPURL)\n\t\tassert.Equal(t, ocTest.ServiceProvider, oc.ServiceProvider)\n\t\tassert.Equal(t, ocTest.ServiceProviderName, oc.ServiceProviderName)\n\t\tassert.Equal(t, ocTest.OAuthTokens, oc.OAuthTokens)\n\t\tassert.Equal(t, ocTest.OrganizationScoped, oc.OrganizationScoped)\n\t})\n\n\tt.Run(\"when the OAuth client does not exist\", func(t *testing.T) {\n\t\toc, err := client.OAuthClients.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, oc)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"without a valid OAuth client ID\", func(t *testing.T) {\n\t\toc, err := client.OAuthClients.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, oc)\n\t\tassert.Equal(t, err, ErrInvalidOauthClientID)\n\t})\n}\n\nfunc TestOAuthClientsReadWithOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpj, pjCleanup := createProject(t, client, orgTest)\n\tdefer pjCleanup()\n\n\tocTest, ocTestCleanup := createOAuthClient(t, client, nil, []*Project{pj})\n\tdefer ocTestCleanup()\n\n\topts := &OAuthClientReadOptions{\n\t\tInclude: []OAuthClientIncludeOpt{OauthClientProjects},\n\t}\n\tt.Run(\"when the OAuth client exists\", func(t *testing.T) {\n\t\tocWithOptions, err := client.OAuthClients.ReadWithOptions(ctx, ocTest.ID, opts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ocTest.Projects, ocWithOptions.Projects)\n\t})\n}\n\nfunc TestOAuthClientsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tocTest, _ := createOAuthClient(t, client, orgTest, nil)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.OAuthClients.Delete(ctx, ocTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = retry(func() (interface{}, error) {\n\t\t\tc, err := client.OAuthClients.Read(ctx, ocTest.ID)\n\t\t\tif err != ErrResourceNotFound {\n\t\t\t\treturn nil, fmt.Errorf(\"expected %s, but err was %s\", ErrResourceNotFound, err)\n\t\t\t}\n\t\t\treturn c, err\n\t\t})\n\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the OAuth client does not exist\", func(t *testing.T) {\n\t\terr := client.OAuthClients.Delete(ctx, ocTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the OAuth client ID is invalid\", func(t *testing.T) {\n\t\terr := client.OAuthClients.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidOauthClientID)\n\t})\n}\n\nfunc TestOAuthClientsCreateOptionsValid(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\t\tHTTPURL:         String(\"https://github.com\"),\n\t\t\tOAuthToken:      String(\"NOTHING\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\t}\n\n\t\terr := options.valid()\n\t\tassert.Nil(t, err)\n\t})\n\n\tt.Run(\"without an API URL\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tHTTPURL:         String(\"https://github.com\"),\n\t\t\tOAuthToken:      String(\"NOTHING\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\t}\n\n\t\terr := options.valid()\n\t\tassert.Equal(t, err, ErrRequiredAPIURL)\n\t})\n\n\tt.Run(\"without a HTTP URL\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\t\tOAuthToken:      String(\"NOTHING\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\t}\n\n\t\terr := options.valid()\n\t\tassert.Equal(t, err, ErrRequiredHTTPURL)\n\t})\n\n\tt.Run(\"without an OAuth token\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\t\tHTTPURL:         String(\"https://github.com\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\t}\n\n\t\terr := options.valid()\n\t\tassert.Equal(t, err, ErrRequiredOauthToken)\n\t})\n\n\tt.Run(\"without a service provider\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:     String(\"https://api.github.com\"),\n\t\t\tHTTPURL:    String(\"https://github.com\"),\n\t\t\tOAuthToken: String(\"NOTHING\"),\n\t\t}\n\n\t\terr := options.valid()\n\t\tassert.Equal(t, err, ErrRequiredServiceProvider)\n\t})\n\n\tt.Run(\"without private key and not ado_server options\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\t\tHTTPURL:         String(\"https://github.com\"),\n\t\t\tOAuthToken:      String(\"NOTHING\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGitlabEE),\n\t\t}\n\n\t\terr := options.valid()\n\t\tassert.Nil(t, err)\n\t})\n\n\tt.Run(\"with empty private key and not ado_server options\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\t\tHTTPURL:         String(\"https://github.com\"),\n\t\t\tOAuthToken:      String(\"NOTHING\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGitlabEE),\n\t\t\tPrivateKey:      String(\"\"),\n\t\t}\n\n\t\terr := options.valid()\n\t\tassert.Nil(t, err)\n\t})\n\n\tt.Run(\"with private key and not ado_server options\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://api.github.com\"),\n\t\t\tHTTPURL:         String(\"https://github.com\"),\n\t\t\tOAuthToken:      String(\"NOTHING\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderGithub),\n\t\t\tPrivateKey:      String(\"NOTHING\"),\n\t\t}\n\n\t\terr := options.valid()\n\t\tassert.Equal(t, err, ErrUnsupportedPrivateKey)\n\t})\n\n\tt.Run(\"with valid options including private key\", func(t *testing.T) {\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://ado.example.com\"),\n\t\t\tHTTPURL:         String(\"https://ado.example.com\"),\n\t\t\tOAuthToken:      String(\"NOTHING\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderAzureDevOpsServer),\n\t\t\tPrivateKey:      String(\"NOTHING\"),\n\t\t}\n\n\t\terr := options.valid()\n\t\tassert.Nil(t, err)\n\t})\n}\n\nfunc TestOAuthClientsAddProjects(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpTest1, pTestCleanup1 := createProject(t, client, orgTest)\n\tdefer pTestCleanup1()\n\tpTest2, pTestCleanup2 := createProject(t, client, orgTest)\n\tdefer pTestCleanup2()\n\tpsTest, psTestCleanup := createOAuthClient(t, client, orgTest, nil)\n\tdefer psTestCleanup()\n\n\tt.Run(\"with projects provided\", func(t *testing.T) {\n\t\terr := client.OAuthClients.AddProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tOAuthClientAddProjectsOptions{\n\t\t\t\tProjects: []*Project{pTest1, pTest2},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.OAuthClients.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 2, len(ps.Projects))\n\n\t\tvar ids []string\n\t\tfor _, pj := range ps.Projects {\n\t\t\tids = append(ids, pj.ID)\n\t\t}\n\n\t\tassert.Contains(t, ids, pTest1.ID)\n\t\tassert.Contains(t, ids, pTest2.ID)\n\t})\n\n\tt.Run(\"without projects provided\", func(t *testing.T) {\n\t\terr := client.OAuthClients.AddProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tOAuthClientAddProjectsOptions{},\n\t\t)\n\t\tassert.Equal(t, err, ErrRequiredProject)\n\t})\n\n\tt.Run(\"with empty projects slice\", func(t *testing.T) {\n\t\terr := client.OAuthClients.AddProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tOAuthClientAddProjectsOptions{Projects: []*Project{}},\n\t\t)\n\t\tassert.Equal(t, err, ErrProjectMinLimit)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.OAuthClients.AddProjects(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tOAuthClientAddProjectsOptions{\n\t\t\t\tProjects: []*Project{pTest1, pTest2},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrInvalidOauthClientID)\n\t})\n}\n\nfunc TestOAuthClientsRemoveProjects(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpTest1, pTestCleanup1 := createProject(t, client, orgTest)\n\tdefer pTestCleanup1()\n\tpTest2, pTestCleanup2 := createProject(t, client, orgTest)\n\tdefer pTestCleanup2()\n\tpsTest, psTestCleanup := createOAuthClient(t, client, orgTest, []*Project{pTest1, pTest2})\n\tdefer psTestCleanup()\n\n\tt.Run(\"with projects provided\", func(t *testing.T) {\n\t\terr := client.OAuthClients.RemoveProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tOAuthClientRemoveProjectsOptions{\n\t\t\t\tProjects: []*Project{pTest1, pTest2},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.OAuthClients.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 0, len(ps.Projects))\n\t\tassert.Empty(t, ps.Projects)\n\t})\n\n\tt.Run(\"without projects provided\", func(t *testing.T) {\n\t\terr := client.OAuthClients.RemoveProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tOAuthClientRemoveProjectsOptions{},\n\t\t)\n\t\tassert.Equal(t, err, ErrRequiredProject)\n\t})\n\n\tt.Run(\"with empty projects slice\", func(t *testing.T) {\n\t\terr := client.OAuthClients.RemoveProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tOAuthClientRemoveProjectsOptions{Projects: []*Project{}},\n\t\t)\n\t\tassert.Equal(t, err, ErrProjectMinLimit)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.OAuthClients.RemoveProjects(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tOAuthClientRemoveProjectsOptions{\n\t\t\t\tProjects: []*Project{pTest1, pTest2},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrInvalidOauthClientID)\n\t})\n}\n\nfunc TestOAuthClientsUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"updates organization scoped\", func(t *testing.T) {\n\t\torganizationScoped := false\n\t\torganizationScopedTrue := true\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:             String(\"https://bbdc.com\"),\n\t\t\tHTTPURL:            String(\"https://bbdc.com\"),\n\t\t\tServiceProvider:    ServiceProvider(ServiceProviderBitbucketDataCenter),\n\t\t\tOrganizationScoped: &organizationScopedTrue,\n\t\t}\n\n\t\torigOC, err := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, origOC.ID)\n\n\t\tupdateOpts := OAuthClientUpdateOptions{\n\t\t\tOrganizationScoped: &organizationScoped,\n\t\t}\n\t\toc, err := client.OAuthClients.Update(ctx, origOC.ID, updateOpts)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, oc.ID)\n\t\tassert.NotEqual(t, origOC.OrganizationScoped, oc.OrganizationScoped)\n\t})\n\n\tt.Run(\"updates agent pool\", func(t *testing.T) {\n\t\tt.Skip(\"Skipping due to persistent failures - see TF-31172\")\n\t\ttestAgentPool1, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\t\tdefer agentPoolCleanup()\n\t\ttestAgentPool2, agentPoolCleanup2 := createAgentPool(t, client, orgTest)\n\t\tdefer agentPoolCleanup2()\n\n\t\torganizationScopedTrue := true\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:             String(\"https://bbdc.com\"),\n\t\t\tHTTPURL:            String(\"https://bbdc.com\"),\n\t\t\tServiceProvider:    ServiceProvider(ServiceProviderBitbucketDataCenter),\n\t\t\tOrganizationScoped: &organizationScopedTrue,\n\t\t\tAgentPool:          testAgentPool1,\n\t\t}\n\n\t\torigOC, err := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, origOC.ID)\n\n\t\tupdateOpts := OAuthClientUpdateOptions{\n\t\t\tAgentPool: testAgentPool2,\n\t\t}\n\t\toc, err := client.OAuthClients.Update(ctx, origOC.ID, updateOpts)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, oc.ID)\n\t\tassert.Equal(t, oc.AgentPool.ID, testAgentPool2.ID)\n\t\tassert.NotEqual(t, origOC.AgentPool.ID, oc.AgentPool.ID)\n\t})\n}\n\nconst publicKey = `\n-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoKizy4xbN6qZFAwIJV24\n-----END PUBLIC KEY-----\n`\n\nconst privateKey = `\n-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAoKizy4xbN6qZFAwIJV24liz/vYBSvR3SjEiUzhpp0uMAmICN\n-----END RSA PRIVATE KEY-----\n`\n\nfunc TestOAuthClientsUpdate_rsaKeyPair(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"updates a new key\", func(t *testing.T) {\n\t\toriginalKey := randomString(t)\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://bbdc.com\"),\n\t\t\tHTTPURL:         String(\"https://bbdc.com\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderBitbucketDataCenter),\n\t\t\tKey:             String(originalKey),\n\t\t\tSecret:          String(privateKey),\n\t\t\tRSAPublicKey:    String(publicKey),\n\t\t}\n\n\t\torigOC, err := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, origOC.ID)\n\n\t\tnewKey := randomString(t)\n\t\tupdateOpts := OAuthClientUpdateOptions{\n\t\t\tKey: String(newKey),\n\t\t}\n\t\toc, err := client.OAuthClients.Update(ctx, origOC.ID, updateOpts)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, oc.ID)\n\t\tassert.Equal(t, ServiceProviderBitbucketDataCenter, oc.ServiceProvider)\n\t\tassert.Equal(t, oc.RSAPublicKey, origOC.RSAPublicKey)\n\t\tassert.Equal(t, newKey, oc.Key)\n\t})\n\n\tt.Run(\"errors when missing key\", func(t *testing.T) {\n\t\toriginalKey := randomString(t)\n\t\toptions := OAuthClientCreateOptions{\n\t\t\tAPIURL:          String(\"https://bbdc.com\"),\n\t\t\tHTTPURL:         String(\"https://bbdc.com\"),\n\t\t\tServiceProvider: ServiceProvider(ServiceProviderBitbucketDataCenter),\n\t\t\tKey:             String(originalKey),\n\t\t\tSecret:          String(privateKey),\n\t\t\tRSAPublicKey:    String(publicKey),\n\t\t}\n\n\t\torigOC, err := client.OAuthClients.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, origOC.ID)\n\n\t\tupdateOpts := OAuthClientUpdateOptions{\n\t\t\tKey: String(\"\"),\n\t\t}\n\t\t_, err = client.OAuthClients.Update(ctx, origOC.ID, updateOpts)\n\t\tassert.Error(t, err, \"The Consumer Key for Bitbucket Data Center must be present. Please add a value for `key`.\")\n\t})\n}\n"
  },
  {
    "path": "oauth_token.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ OAuthTokens = (*oAuthTokens)(nil)\n\n// OAuthTokens describes all the OAuth token related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/oauth-tokens\ntype OAuthTokens interface {\n\t// List all the OAuth tokens for a given organization.\n\tList(ctx context.Context, organization string, options *OAuthTokenListOptions) (*OAuthTokenList, error)\n\t// Read a OAuth token by its ID.\n\tRead(ctx context.Context, oAuthTokenID string) (*OAuthToken, error)\n\n\t// Update an existing OAuth token.\n\tUpdate(ctx context.Context, oAuthTokenID string, options OAuthTokenUpdateOptions) (*OAuthToken, error)\n\n\t// Delete a OAuth token by its ID.\n\tDelete(ctx context.Context, oAuthTokenID string) error\n}\n\n// oAuthTokens implements OAuthTokens.\ntype oAuthTokens struct {\n\tclient *Client\n}\n\n// OAuthTokenList represents a list of OAuth tokens.\ntype OAuthTokenList struct {\n\t*Pagination\n\tItems []*OAuthToken\n}\n\n// OAuthToken represents a VCS configuration including the associated\n// OAuth token\ntype OAuthToken struct {\n\tID                  string    `jsonapi:\"primary,oauth-tokens\"`\n\tUID                 string    `jsonapi:\"attr,uid\"`\n\tCreatedAt           time.Time `jsonapi:\"attr,created-at,iso8601\"`\n\tHasSSHKey           bool      `jsonapi:\"attr,has-ssh-key\"`\n\tServiceProviderUser string    `jsonapi:\"attr,service-provider-user\"`\n\n\t// Relations\n\tOAuthClient *OAuthClient `jsonapi:\"relation,oauth-client\"`\n}\n\n// OAuthTokenListOptions represents the options for listing\n// OAuth tokens.\ntype OAuthTokenListOptions struct {\n\tListOptions\n}\n\n// OAuthTokenUpdateOptions represents the options for updating an OAuth token.\ntype OAuthTokenUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,oauth-tokens\"`\n\n\t// Optional: A private SSH key to be used for git clone operations.\n\tPrivateSSHKey *string `jsonapi:\"attr,ssh-key,omitempty\"`\n}\n\n// List all the OAuth tokens for a given organization.\nfunc (s *oAuthTokens) List(ctx context.Context, organization string, options *OAuthTokenListOptions) (*OAuthTokenList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/oauth-tokens\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\totl := &OAuthTokenList{}\n\terr = req.Do(ctx, otl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn otl, nil\n}\n\n// Read an OAuth token by its ID.\nfunc (s *oAuthTokens) Read(ctx context.Context, oAuthTokenID string) (*OAuthToken, error) {\n\tif !validStringID(&oAuthTokenID) {\n\t\treturn nil, ErrInvalidOauthTokenID\n\t}\n\n\tu := fmt.Sprintf(\"oauth-tokens/%s\", url.PathEscape(oAuthTokenID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tot := &OAuthToken{}\n\terr = req.Do(ctx, ot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ot, err\n}\n\n// Update an existing OAuth token.\nfunc (s *oAuthTokens) Update(ctx context.Context, oAuthTokenID string, options OAuthTokenUpdateOptions) (*OAuthToken, error) {\n\tif !validStringID(&oAuthTokenID) {\n\t\treturn nil, ErrInvalidOauthTokenID\n\t}\n\n\tu := fmt.Sprintf(\"oauth-tokens/%s\", url.PathEscape(oAuthTokenID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tot := &OAuthToken{}\n\terr = req.Do(ctx, ot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ot, err\n}\n\n// Delete an OAuth token by its ID.\nfunc (s *oAuthTokens) Delete(ctx context.Context, oAuthTokenID string) error {\n\tif !validStringID(&oAuthTokenID) {\n\t\treturn ErrInvalidOauthTokenID\n\t}\n\n\tu := fmt.Sprintf(\"oauth-tokens/%s\", url.PathEscape(oAuthTokenID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "oauth_token_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOAuthTokensList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\totTest1, otTest1Cleanup := createOAuthToken(t, client, orgTest)\n\tdefer otTest1Cleanup()\n\totTest2, otTest2Cleanup := createOAuthToken(t, client, orgTest)\n\tdefer otTest2Cleanup()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\totl, err := client.OAuthTokens.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tt.Run(\"the OAuth client relationship is decoded correcly\", func(t *testing.T) {\n\t\t\tfor _, ot := range otl.Items {\n\t\t\t\tassert.NotEmpty(t, ot.OAuthClient)\n\t\t\t}\n\t\t})\n\n\t\t// We need to strip some fields before the next test.\n\t\tfor _, ot := range otl.Items {\n\t\t\tot.CreatedAt = time.Time{}\n\t\t\tot.ServiceProviderUser = \"\"\n\t\t\tot.OAuthClient = nil\n\t\t}\n\n\t\tassert.Contains(t, otl.Items, otTest1)\n\t\tassert.Contains(t, otl.Items, otTest2)\n\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\tassert.Equal(t, 1, otl.CurrentPage)\n\t\tassert.Equal(t, 2, otl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\toptions := &OAuthTokenListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t}\n\n\t\totl, err := client.OAuthTokens.List(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, otl.Items)\n\t\tassert.Equal(t, 999, otl.CurrentPage)\n\t\tassert.Equal(t, 2, otl.TotalCount)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\totl, err := client.OAuthTokens.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, otl)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestOAuthTokensRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\totTest, otTestCleanup := createOAuthToken(t, client, nil)\n\tdefer otTestCleanup()\n\n\tt.Run(\"when the OAuth token exists\", func(t *testing.T) {\n\t\tot, err := client.OAuthTokens.Read(ctx, otTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, otTest.ID, ot.ID)\n\t\tassert.NotEmpty(t, ot.OAuthClient)\n\t})\n\n\tt.Run(\"when the OAuth token does not exist\", func(t *testing.T) {\n\t\tot, err := client.OAuthTokens.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, ot)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"without a valid OAuth token ID\", func(t *testing.T) {\n\t\tot, err := client.OAuthTokens.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, ot)\n\t\tassert.Equal(t, err, ErrInvalidOauthTokenID)\n\t})\n}\n\nfunc TestOAuthTokensUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\totTest, otTestCleanup := createOAuthToken(t, client, nil)\n\tdefer otTestCleanup()\n\n\tt.Run(\"before updating with an SSH key\", func(t *testing.T) {\n\t\tassert.False(t, otTest.HasSSHKey)\n\t})\n\n\tt.Run(\"without options\", func(t *testing.T) {\n\t\tot, err := client.OAuthTokens.Update(ctx, otTest.ID, OAuthTokenUpdateOptions{})\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, ot.HasSSHKey)\n\t})\n\n\tt.Run(\"when updating with a valid SSH key\", func(t *testing.T) {\n\t\tdummyPrivateSSHKey := `-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDIF0s2yX7dSQQL1grdTbai1Mb7sEco6RIOz8iqrHTGqmESpu5n\nd8imMkV5KadgVBJ/UvHsWpg446O3DAMYn0Y6f8dDlK7pmCEtiGVKTR1PaVRMpF8R\n5Guvrmlru8Kex5ozh0pPMB15aGsIzSezCKgSs74Od9YL4smdgKyYwqsu3wIDAQAB\nAoGBAKCs6+4j4icqYgBrMjBCHp4lRWCJTqtQdfrE6jv73o5F9Uu4FwupScwD5HwG\ncezNtkjeP3zvxvsv+aCdGcNk60vSz4n9Nt6gEJveWFSpePYXKZ9cz/IjFLI7nSzc\n1msLyE3DfUqB91s/A/aT5p0LiVDc8i4mCGDOga2OINIwqDGZAkEA/Vz8dkcqsAVW\nCL1F000hWTrM6tu0V+x8Nm8CRx7wM/Gy/19PbV0t26wCVG0GXyLWsV2//huY7w5b\n3AcSl5pfJQJBAMosYQXk5L4S+qivz2zmZdtyz+Ik6IZ3PwZoED32PxGSdW5rG8iP\nV+iSJek5ESkS1zeXwDMnF4LeoBY9H07DiLMCQQCrHm1o2SIMpl34IxWQ4+wdHuid\nyuuf4pn2Db2lGVE0VA8ICXBUtfUuA5vDN6tw/8+vFVmBn1QISVNjZOd6uwl9AkA+\njIRoAm0SsWSDlAEkvBN/VYIjgS+/il0haki8ItdYZGuYgeLSpiaYeb7o7RL2FjIn\nrPd12/5WKvJ0buykvbIpAkEA5Uy3T8xQJkDGbp0+xA0yThoOYiB09lAok8I7Sv/5\ndpIe8YOINN27XaojJvVpT5uBVCcZLF+G7kaMjSwCTlDx3Q==\n-----END RSA PRIVATE KEY-----`\n\n\t\tot, err := client.OAuthTokens.Update(ctx, otTest.ID, OAuthTokenUpdateOptions{\n\t\t\tPrivateSSHKey: String(dummyPrivateSSHKey),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, otTest.ID, ot.ID)\n\t\tassert.True(t, ot.HasSSHKey)\n\t})\n\n\tt.Run(\"when updating with an invalid SSH key\", func(t *testing.T) {\n\t\tot, err := client.OAuthTokens.Update(ctx, otTest.ID, OAuthTokenUpdateOptions{\n\t\t\tPrivateSSHKey: String(randomString(t)),\n\t\t})\n\t\tassert.Nil(t, ot)\n\t\tassert.Contains(t, err.Error(), \"Ssh key is invalid\")\n\t})\n\n\tt.Run(\"without a valid policy ID\", func(t *testing.T) {\n\t\tot, err := client.OAuthTokens.Update(ctx, badIdentifier, OAuthTokenUpdateOptions{})\n\t\tassert.Nil(t, ot)\n\t\tassert.Equal(t, err, ErrInvalidOauthTokenID)\n\t})\n}\n\nfunc TestOAuthTokensDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\totTest, otCleanup := createOAuthToken(t, client, orgTest)\n\tdefer otCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.OAuthTokens.Delete(ctx, otTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the OAuth token - it should fail.\n\t\t_, err = client.OAuthTokens.Read(ctx, otTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the OAuth token does not exist\", func(t *testing.T) {\n\t\terr := client.OAuthTokens.Delete(ctx, otTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the OAuth token ID is invalid\", func(t *testing.T) {\n\t\terr := client.OAuthTokens.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidOauthTokenID)\n\t})\n}\n"
  },
  {
    "path": "organization.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Organizations = (*organizations)(nil)\n\n// Organizations describes all the organization related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organizations\ntype Organizations interface {\n\t// List all the organizations visible to the current user.\n\tList(ctx context.Context, options *OrganizationListOptions) (*OrganizationList, error)\n\n\t// Create a new organization with the given options.\n\tCreate(ctx context.Context, options OrganizationCreateOptions) (*Organization, error)\n\n\t// Read an organization by its name.\n\tRead(ctx context.Context, organization string) (*Organization, error)\n\n\t// Read an organization by its name with options\n\tReadWithOptions(ctx context.Context, organization string, options OrganizationReadOptions) (*Organization, error)\n\n\t// Update attributes of an existing organization.\n\tUpdate(ctx context.Context, organization string, options OrganizationUpdateOptions) (*Organization, error)\n\n\t// Delete an organization by its name.\n\tDelete(ctx context.Context, organization string) error\n\n\t// ReadCapacity shows the current run capacity of an organization.\n\tReadCapacity(ctx context.Context, organization string) (*Capacity, error)\n\n\t// ReadEntitlements shows the entitlements of an organization.\n\tReadEntitlements(ctx context.Context, organization string) (*Entitlements, error)\n\n\t// ReadRunQueue shows the current run queue of an organization.\n\tReadRunQueue(ctx context.Context, organization string, options ReadRunQueueOptions) (*RunQueue, error)\n\n\t// ReadDataRetentionPolicy reads an organization's data retention policy\n\t// **Note: This functionality is only available in Terraform Enterprise versions v202311-1 and v202312-1.**\n\t//\n\t// Deprecated: Use ReadDataRetentionPolicyChoice instead.\n\tReadDataRetentionPolicy(ctx context.Context, organization string) (*DataRetentionPolicy, error)\n\n\t// ReadDataRetentionPolicyChoice reads an organization's data retention policy\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tReadDataRetentionPolicyChoice(ctx context.Context, organization string) (*DataRetentionPolicyChoice, error)\n\n\t// SetDataRetentionPolicy sets an organization's data retention policy\n\t// **Note: This functionality is only available in Terraform Enterprise versions v202311-1 and v202312-1.**\n\t//\n\t// Deprecated: Use SetDataRetentionPolicyDeleteOlder instead\n\tSetDataRetentionPolicy(ctx context.Context, organization string, options DataRetentionPolicySetOptions) (*DataRetentionPolicy, error)\n\n\t// SetDataRetentionPolicyDeleteOlder sets an organization's data retention policy to delete data older than a certain number of days\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tSetDataRetentionPolicyDeleteOlder(ctx context.Context, organization string, options DataRetentionPolicyDeleteOlderSetOptions) (*DataRetentionPolicyDeleteOlder, error)\n\n\t// SetDataRetentionPolicyDontDelete sets an organization's data retention policy to explicitly not delete data\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tSetDataRetentionPolicyDontDelete(ctx context.Context, organization string, options DataRetentionPolicyDontDeleteSetOptions) (*DataRetentionPolicyDontDelete, error)\n\n\t// DeleteDataRetentionPolicy deletes an organization's data retention policy\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tDeleteDataRetentionPolicy(ctx context.Context, organization string) error\n}\n\n// organizations implements Organizations.\ntype organizations struct {\n\tclient *Client\n}\n\n// AuthPolicyType represents an authentication policy type.\ntype AuthPolicyType string\n\n// List of available authentication policies.\nconst (\n\tAuthPolicyPassword  AuthPolicyType = \"password\"\n\tAuthPolicyTwoFactor AuthPolicyType = \"two_factor_mandatory\"\n)\n\n// OrganizationList represents a list of organizations.\ntype OrganizationList struct {\n\t*Pagination\n\tItems []*Organization\n}\n\n// Organization represents a Terraform Enterprise organization.\ntype Organization struct {\n\tName                                              string                   `jsonapi:\"primary,organizations\"`\n\tAssessmentsEnforced                               bool                     `jsonapi:\"attr,assessments-enforced\"`\n\tCollaboratorAuthPolicy                            AuthPolicyType           `jsonapi:\"attr,collaborator-auth-policy\"`\n\tCostEstimationEnabled                             bool                     `jsonapi:\"attr,cost-estimation-enabled\"`\n\tCreatedAt                                         time.Time                `jsonapi:\"attr,created-at,iso8601\"`\n\tDefaultExecutionMode                              string                   `jsonapi:\"attr,default-execution-mode\"`\n\tEmail                                             string                   `jsonapi:\"attr,email\"`\n\tExternalID                                        string                   `jsonapi:\"attr,external-id\"`\n\tIsUnified                                         bool                     `jsonapi:\"attr,is-unified\"`\n\tOwnersTeamSAMLRoleID                              string                   `jsonapi:\"attr,owners-team-saml-role-id\"`\n\tPermissions                                       *OrganizationPermissions `jsonapi:\"attr,permissions\"`\n\tSAMLEnabled                                       bool                     `jsonapi:\"attr,saml-enabled\"`\n\tStacksEnabled                                     bool                     `jsonapi:\"attr,stacks-enabled\"`\n\tSessionRemember                                   int                      `jsonapi:\"attr,session-remember\"`\n\tSessionTimeout                                    int                      `jsonapi:\"attr,session-timeout\"`\n\tTrialExpiresAt                                    time.Time                `jsonapi:\"attr,trial-expires-at,iso8601\"`\n\tTwoFactorConformant                               bool                     `jsonapi:\"attr,two-factor-conformant\"`\n\tSendPassingStatusesForUntriggeredSpeculativePlans bool                     `jsonapi:\"attr,send-passing-statuses-for-untriggered-speculative-plans\"`\n\tRemainingTestableCount                            int                      `jsonapi:\"attr,remaining-testable-count\"`\n\tSpeculativePlanManagementEnabled                  bool                     `jsonapi:\"attr,speculative-plan-management-enabled\"`\n\tEnforceHYOK                                       bool                     `jsonapi:\"attr,enforce-hyok\"`\n\tUserTokensEnabled                                 *bool                    `jsonapi:\"attr,user-tokens-enabled\"`\n\t// Optional: If enabled, SendPassingStatusesForUntriggeredSpeculativePlans needs to be false.\n\tAggregatedCommitStatusEnabled bool `jsonapi:\"attr,aggregated-commit-status-enabled,omitempty\"`\n\t// Note: This will be false for TFE versions older than v202211, where the setting was introduced.\n\t// On those TFE versions, safe delete does not exist, so ALL deletes will be force deletes.\n\tAllowForceDeleteWorkspaces bool `jsonapi:\"attr,allow-force-delete-workspaces\"`\n\n\t// Relations\n\tDefaultProject           *Project           `jsonapi:\"relation,default-project\"`\n\tDefaultAgentPool         *AgentPool         `jsonapi:\"relation,default-agent-pool\"`\n\tPrimaryHYOKConfiguration *HYOKConfiguration `jsonapi:\"relation,primary-hyok-configuration,omitempty\"`\n\n\t// Deprecated: Use DataRetentionPolicyChoice instead.\n\tDataRetentionPolicy *DataRetentionPolicy\n\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tDataRetentionPolicyChoice *DataRetentionPolicyChoice `jsonapi:\"polyrelation,data-retention-policy\"`\n}\n\n// OrganizationIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organizations#available-related-resources\ntype OrganizationIncludeOpt string\n\nconst (\n\t// **Note: This include option is still in BETA and subject to change.**\n\tOrganizationDefaultProject OrganizationIncludeOpt = \"default-project\"\n)\n\n// OrganizationReadOptions represents the options for reading organizations.\ntype OrganizationReadOptions struct {\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organizations#available-related-resources\n\tInclude []OrganizationIncludeOpt `url:\"include,omitempty\"`\n}\n\n// Capacity represents the current run capacity of an organization.\ntype Capacity struct {\n\tOrganization string `jsonapi:\"primary,organization-capacity\"`\n\tPending      int    `jsonapi:\"attr,pending\"`\n\tRunning      int    `jsonapi:\"attr,running\"`\n}\n\n// Entitlements represents the entitlements of an organization.\ntype Entitlements struct {\n\tID                         string `jsonapi:\"primary,entitlement-sets\"`\n\tAgents                     bool   `jsonapi:\"attr,agents\"`\n\tAuditLogging               bool   `jsonapi:\"attr,audit-logging\"`\n\tCostEstimation             bool   `jsonapi:\"attr,cost-estimation\"`\n\tGlobalRunTasks             bool   `jsonapi:\"attr,global-run-tasks\"`\n\tOperations                 bool   `jsonapi:\"attr,operations\"`\n\tPrivateModuleRegistry      bool   `jsonapi:\"attr,private-module-registry\"`\n\tPrivateRunTasks            bool   `jsonapi:\"attr,private-run-tasks\"`\n\tRunTasks                   bool   `jsonapi:\"attr,run-tasks\"`\n\tSSO                        bool   `jsonapi:\"attr,sso\"`\n\tSentinel                   bool   `jsonapi:\"attr,sentinel\"`\n\tStateStorage               bool   `jsonapi:\"attr,state-storage\"`\n\tTeams                      bool   `jsonapi:\"attr,teams\"`\n\tVCSIntegrations            bool   `jsonapi:\"attr,vcs-integrations\"`\n\tWaypointActions            bool   `jsonapi:\"attr,waypoint-actions\"`\n\tWaypointTemplatesAndAddons bool   `jsonapi:\"attr,waypoint-templates-and-addons\"`\n}\n\n// RunQueue represents the current run queue of an organization.\ntype RunQueue struct {\n\t*Pagination\n\tItems []*Run\n}\n\n// OrganizationPermissions represents the organization permissions.\ntype OrganizationPermissions struct {\n\tCanCreateTeam               bool `jsonapi:\"attr,can-create-team\"`\n\tCanCreateWorkspace          bool `jsonapi:\"attr,can-create-workspace\"`\n\tCanCreateWorkspaceMigration bool `jsonapi:\"attr,can-create-workspace-migration\"`\n\tCanDeployNoCodeModules      bool `jsonapi:\"attr,can-deploy-no-code-modules\"`\n\tCanDestroy                  bool `jsonapi:\"attr,can-destroy\"`\n\tCanManageAuditing           bool `jsonapi:\"attr,can-manage-auditing\"`\n\tCanManageNoCodeModules      bool `jsonapi:\"attr,can-manage-no-code-modules\"`\n\tCanManageRunTasks           bool `jsonapi:\"attr,can-manage-run-tasks\"`\n\tCanTraverse                 bool `jsonapi:\"attr,can-traverse\"`\n\tCanUpdate                   bool `jsonapi:\"attr,can-update\"`\n\tCanUpdateAPIToken           bool `jsonapi:\"attr,can-update-api-token\"`\n\tCanUpdateOAuth              bool `jsonapi:\"attr,can-update-oauth\"`\n\tCanUpdateSentinel           bool `jsonapi:\"attr,can-update-sentinel\"`\n\tCanUpdateHYOKConfiguration  bool `jsonapi:\"attr,can-update-hyok-configuration\"`\n\tCanViewHYOKFeatureInfo      bool `jsonapi:\"attr,can-view-hyok-feature-info\"`\n\tCanEnableStacks             bool `jsonapi:\"attr,can-enable-stacks\"`\n\tCanCreateProject            bool `jsonapi:\"attr,can-create-project\"`\n}\n\n// OrganizationListOptions represents the options for listing organizations.\ntype OrganizationListOptions struct {\n\tListOptions\n\n\t// Optional: A query string used to filter organizations.\n\t// Organizations with a name or email partially matching this value will be returned.\n\tQuery string `url:\"q,omitempty\"`\n}\n\n// OrganizationCreateOptions represents the options for creating an organization.\ntype OrganizationCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,organizations\"`\n\n\t// Required: Name of the organization.\n\tName *string `jsonapi:\"attr,name\"`\n\n\t// Optional: AssessmentsEnforced toggles whether health assessment enablement is enforced across all assessable workspaces (those with a minimum terraform version of 0.15.4 and not running in local execution mode) or if the decision to enabled health assessments is delegated to the workspace setting AssessmentsEnabled.\n\tAssessmentsEnforced *bool `jsonapi:\"attr,assessments-enforced,omitempty\"`\n\n\t// Required: Admin email address.\n\tEmail *string `jsonapi:\"attr,email\"`\n\n\t// Optional: Session expiration (minutes).\n\tSessionRemember *int `jsonapi:\"attr,session-remember,omitempty\"`\n\n\t// Optional: Session timeout after inactivity (minutes).\n\tSessionTimeout *int `jsonapi:\"attr,session-timeout,omitempty\"`\n\n\t// Optional: Authentication policy.\n\tCollaboratorAuthPolicy *AuthPolicyType `jsonapi:\"attr,collaborator-auth-policy,omitempty\"`\n\n\t// Optional: Enable Cost Estimation\n\tCostEstimationEnabled *bool `jsonapi:\"attr,cost-estimation-enabled,omitempty\"`\n\n\t// Optional: The name of the \"owners\" team\n\tOwnersTeamSAMLRoleID *string `jsonapi:\"attr,owners-team-saml-role-id,omitempty\"`\n\n\t// Optional: SendPassingStatusesForUntriggeredSpeculativePlans toggles behavior of untriggered speculative plans to send status updates to version control systems like GitHub.\n\tSendPassingStatusesForUntriggeredSpeculativePlans *bool `jsonapi:\"attr,send-passing-statuses-for-untriggered-speculative-plans,omitempty\"`\n\n\t// Optional: If enabled, SendPassingStatusesForUntriggeredSpeculativePlans needs to be false.\n\tAggregatedCommitStatusEnabled *bool `jsonapi:\"attr,aggregated-commit-status-enabled,omitempty\"`\n\n\t// Optional: SpeculativePlanManagementEnabled toggles whether pending speculative plans from outdated commits will be cancelled if a newer commit is pushed to the same branch.\n\tSpeculativePlanManagementEnabled *bool `jsonapi:\"attr,speculative-plan-management-enabled,omitempty\"`\n\n\t// Optional: AllowForceDeleteWorkspaces toggles behavior of allowing workspace admins to delete workspaces with resources under management.\n\tAllowForceDeleteWorkspaces *bool `jsonapi:\"attr,allow-force-delete-workspaces,omitempty\"`\n\n\t// Optional: DefaultExecutionMode the default execution mode for workspaces\n\tDefaultExecutionMode *string `jsonapi:\"attr,default-execution-mode,omitempty\"`\n\n\t// Optional: EnforceHYOK if HYOK is enforced for the organization.\n\tEnforceHYOK *bool `jsonapi:\"attr,enforce-hyok,omitempty\"`\n\n\t// Optional: StacksEnabled toggles whether stacks are enabled for the organization. This setting\n\t// is considered BETA, SUBJECT TO CHANGE, and likely unavailable to most users.\n\tStacksEnabled *bool `jsonapi:\"attr,stacks-enabled,omitempty\"`\n\n\t// Optional: RegistryMonorepoSupportEnabled toggles whether monorepo support is enabled for the organization\n\tRegistryMonorepoSupportEnabled *bool `jsonapi:\"attr,registry-monorepo-support-enabled,omitempty\"`\n\n\t// Optional: UserTokensEnabled toggles whether user tokens may be used to access resources in this organization.\n\tUserTokensEnabled *bool `jsonapi:\"attr,user-tokens-enabled,omitempty\"`\n}\n\n// OrganizationUpdateOptions represents the options for updating an organization.\ntype OrganizationUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,organizations\"`\n\n\t// New name for the organization.\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: AssessmentsEnforced toggles whether health assessment enablement is enforced across all assessable workspaces (those with a minimum terraform version of 0.15.4 and not running in local execution mode) or if the decision to enabled health assessments is delegated to the workspace setting AssessmentsEnabled.\n\tAssessmentsEnforced *bool `jsonapi:\"attr,assessments-enforced,omitempty\"`\n\n\t// New admin email address.\n\tEmail *string `jsonapi:\"attr,email,omitempty\"`\n\n\t// Session expiration (minutes).\n\tSessionRemember *int `jsonapi:\"attr,session-remember,omitempty\"`\n\n\t// Session timeout after inactivity (minutes).\n\tSessionTimeout *int `jsonapi:\"attr,session-timeout,omitempty\"`\n\n\t// Authentication policy.\n\tCollaboratorAuthPolicy *AuthPolicyType `jsonapi:\"attr,collaborator-auth-policy,omitempty\"`\n\n\t// Enable Cost Estimation\n\tCostEstimationEnabled *bool `jsonapi:\"attr,cost-estimation-enabled,omitempty\"`\n\n\t// The name of the \"owners\" team\n\tOwnersTeamSAMLRoleID *string `jsonapi:\"attr,owners-team-saml-role-id,omitempty\"`\n\n\t// SendPassingStatusesForUntriggeredSpeculativePlans toggles behavior of untriggered speculative plans to send status updates to version control systems like GitHub.\n\tSendPassingStatusesForUntriggeredSpeculativePlans *bool `jsonapi:\"attr,send-passing-statuses-for-untriggered-speculative-plans,omitempty\"`\n\n\t// Optional: If enabled, SendPassingStatusesForUntriggeredSpeculativePlans needs to be false.\n\tAggregatedCommitStatusEnabled *bool `jsonapi:\"attr,aggregated-commit-status-enabled,omitempty\"`\n\n\t// Optional: SpeculativePlanManagementEnabled toggles whether pending speculative plans from outdated commits will be cancelled if a newer commit is pushed to the same branch.\n\tSpeculativePlanManagementEnabled *bool `jsonapi:\"attr,speculative-plan-management-enabled,omitempty\"`\n\n\t// Optional: AllowForceDeleteWorkspaces toggles behavior of allowing workspace admins to delete workspaces with resources under management.\n\tAllowForceDeleteWorkspaces *bool `jsonapi:\"attr,allow-force-delete-workspaces,omitempty\"`\n\n\t// Optional: DefaultExecutionMode the default execution mode for workspaces\n\tDefaultExecutionMode *string `jsonapi:\"attr,default-execution-mode,omitempty\"`\n\n\t// Optional: DefaultAgentPoolId default agent pool for workspaces, requires DefaultExecutionMode to be set to `agent`\n\tDefaultAgentPool *AgentPool `jsonapi:\"relation,default-agent-pool,omitempty\"`\n\n\t// Optional: EnforceHYOK if HYOK is enforced for the organization.\n\tEnforceHYOK *bool `jsonapi:\"attr,enforce-hyok,omitempty\"`\n\n\t// Optional: StacksEnabled toggles whether stacks are enabled for the organization. This setting\n\t// is considered BETA, SUBJECT TO CHANGE, and likely unavailable to most users.\n\tStacksEnabled *bool `jsonapi:\"attr,stacks-enabled,omitempty\"`\n\n\t// Optional: RegistryMonorepoSupportEnabled toggles whether monorepo support is enabled for the organization\n\tRegistryMonorepoSupportEnabled *bool `jsonapi:\"attr,registry-monorepo-support-enabled,omitempty\"`\n\n\t// Optional: UserTokensEnabled toggles whether user tokens may be used to access resources in this organization.\n\tUserTokensEnabled *bool `jsonapi:\"attr,user-tokens-enabled,omitempty\"`\n}\n\n// ReadRunQueueOptions represents the options for showing the queue.\ntype ReadRunQueueOptions struct {\n\tListOptions\n}\n\n// List all the organizations visible to the current user.\nfunc (s *organizations) List(ctx context.Context, options *OrganizationListOptions) (*OrganizationList, error) {\n\treq, err := s.client.NewRequest(\"GET\", \"organizations\", options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\torgl := &OrganizationList{}\n\terr = req.Do(ctx, orgl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn orgl, nil\n}\n\n// Create a new organization with the given options.\nfunc (s *organizations) Create(ctx context.Context, options OrganizationCreateOptions) (*Organization, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", \"organizations\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\torg := &Organization{}\n\terr = req.Do(ctx, org)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn org, nil\n}\n\n// Read an organization by its name.\nfunc (s *organizations) Read(ctx context.Context, organization string) (*Organization, error) {\n\treturn s.ReadWithOptions(ctx, organization, OrganizationReadOptions{})\n}\n\n// Read an organization by its name with options\nfunc (s *organizations) ReadWithOptions(ctx context.Context, organization string, options OrganizationReadOptions) (*Organization, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\torg := &Organization{}\n\terr = req.Do(ctx, org)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Manually populate the deprecated DataRetentionPolicy field\n\torg.DataRetentionPolicy = org.DataRetentionPolicyChoice.ConvertToLegacyStruct()\n\n\treturn org, nil\n}\n\n// Update attributes of an existing organization.\nfunc (s *organizations) Update(ctx context.Context, organization string, options OrganizationUpdateOptions) (*Organization, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\torg := &Organization{}\n\terr = req.Do(ctx, org)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn org, nil\n}\n\n// Delete an organization by its name.\nfunc (s *organizations) Delete(ctx context.Context, organization string) error {\n\tif !validStringID(&organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// ReadCapacity shows the currently used capacity of an organization.\nfunc (s *organizations) ReadCapacity(ctx context.Context, organization string) (*Capacity, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/capacity\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc := &Capacity{}\n\terr = req.Do(ctx, c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c, nil\n}\n\n// ReadEntitlements shows the entitlements of an organization.\nfunc (s *organizations) ReadEntitlements(ctx context.Context, organization string) (*Entitlements, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/entitlement-set\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te := &Entitlements{}\n\terr = req.Do(ctx, e)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn e, nil\n}\n\n// ReadRunQueue shows the current run queue of an organization.\nfunc (s *organizations) ReadRunQueue(ctx context.Context, organization string, options ReadRunQueueOptions) (*RunQueue, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/runs/queue\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trq := &RunQueue{}\n\terr = req.Do(ctx, rq)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rq, nil\n}\n\nfunc (s *organizations) ReadDataRetentionPolicy(ctx context.Context, organization string) (*DataRetentionPolicy, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/relationships/data-retention-policy\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdataRetentionPolicy := &DataRetentionPolicy{}\n\terr = req.Do(ctx, dataRetentionPolicy)\n\n\tif err != nil {\n\t\t// try to detect known issue where this function is used with TFE >= 202401,\n\t\t// and direct user towards the V2 function\n\t\tif drpUnmarshalEr.MatchString(err.Error()) {\n\t\t\treturn nil, fmt.Errorf(\"error reading deprecated DataRetentionPolicy, use ReadDataRetentionPolicyChoice instead\")\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn dataRetentionPolicy, nil\n}\n\nfunc (s *organizations) ReadDataRetentionPolicyChoice(ctx context.Context, organization string) (*DataRetentionPolicyChoice, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\t// The API to read the drp is org/<name>/relationships/data-retention-policy\n\t// However, this API can return multiple \"types\" (e.g. data-retention-policy-delete-olders, or data-retention-policy-dont-deletes)\n\t// Ideally we would deserialize this directly into the choice type (DataRetentionPolicyChoice)...however, there isn't a way to\n\t// tell the current jsonapi implementation that the direct result of an endpoint could be different types. Relationships can be polymorphic,\n\t// but the direct result of an endpoint can't be (as far as the jsonapi implementation is concerned)\n\n\t// Instead, we need to figure out the type of the data retention policy first, and deserialize it into the matching model. We\n\t// can then create a choice type manually\n\torg, err := s.Read(ctx, organization)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// there is no drp (of a known type)\n\tif org.DataRetentionPolicyChoice == nil || !org.DataRetentionPolicyChoice.IsPopulated() {\n\t\treturn org.DataRetentionPolicyChoice, nil\n\t}\n\n\tu := s.dataRetentionPolicyLink(organization)\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdataRetentionPolicy := &DataRetentionPolicyChoice{}\n\t// if reading the org told us it was a \"delete older policy\" deserialize into the DeleteOlder portion of the choice model\n\tif org.DataRetentionPolicyChoice.DataRetentionPolicyDeleteOlder != nil {\n\t\tdeleteOlder := &DataRetentionPolicyDeleteOlder{}\n\t\terr = req.Do(ctx, deleteOlder)\n\t\tdataRetentionPolicy.DataRetentionPolicyDeleteOlder = deleteOlder\n\n\t\t// if reading the org told us it was a \"delete older policy\" deserialize into the DeleteOlder portion of the choice model\n\t} else if org.DataRetentionPolicyChoice.DataRetentionPolicyDontDelete != nil {\n\t\tdontDelete := &DataRetentionPolicyDontDelete{}\n\t\terr = req.Do(ctx, dontDelete)\n\t\tdataRetentionPolicy.DataRetentionPolicyDontDelete = dontDelete\n\t} else if org.DataRetentionPolicyChoice.DataRetentionPolicy != nil {\n\t\tlegacyDrp := &DataRetentionPolicy{}\n\t\terr = req.Do(ctx, legacyDrp)\n\t\tdataRetentionPolicy.DataRetentionPolicy = legacyDrp\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dataRetentionPolicy, nil\n}\n\n// Deprecated: Use SetDataRetentionPolicyDeleteOlder instead\n// **Note: This functionality is only available in Terraform Enterprise versions v202311-1 and v202312-1.**\nfunc (s *organizations) SetDataRetentionPolicy(ctx context.Context, organization string, options DataRetentionPolicySetOptions) (*DataRetentionPolicy, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := s.dataRetentionPolicyLink(organization)\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdataRetentionPolicy := &DataRetentionPolicy{}\n\terr = req.Do(ctx, dataRetentionPolicy)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dataRetentionPolicy, nil\n}\n\nfunc (s *organizations) SetDataRetentionPolicyDeleteOlder(ctx context.Context, organization string, options DataRetentionPolicyDeleteOlderSetOptions) (*DataRetentionPolicyDeleteOlder, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := s.dataRetentionPolicyLink(organization)\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdataRetentionPolicy := &DataRetentionPolicyDeleteOlder{}\n\terr = req.Do(ctx, dataRetentionPolicy)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dataRetentionPolicy, nil\n}\n\nfunc (s *organizations) SetDataRetentionPolicyDontDelete(ctx context.Context, organization string, options DataRetentionPolicyDontDeleteSetOptions) (*DataRetentionPolicyDontDelete, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := s.dataRetentionPolicyLink(organization)\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdataRetentionPolicy := &DataRetentionPolicyDontDelete{}\n\terr = req.Do(ctx, dataRetentionPolicy)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dataRetentionPolicy, nil\n}\n\nfunc (s *organizations) DeleteDataRetentionPolicy(ctx context.Context, organization string) error {\n\tif !validStringID(&organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\n\tu := s.dataRetentionPolicyLink(organization)\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o OrganizationCreateOptions) valid() error {\n\tif !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\tif !validStringID(o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\tif !validString(o.Email) {\n\t\treturn ErrRequiredEmail\n\t}\n\treturn nil\n}\n\nfunc (s *organizations) dataRetentionPolicyLink(name string) string {\n\treturn fmt.Sprintf(\"organizations/%s/relationships/data-retention-policy\", url.PathEscape(name))\n}\n"
  },
  {
    "path": "organization_audit_configuration.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\nvar _ OrganizationAuditConfigurations = (*organizationAuditConfigurations)(nil)\n\n// OrganizationAuditConfigurations describes the configuration for auditing events for the organization.\ntype OrganizationAuditConfigurations interface {\n\t// Read the audit configuration of an organization by its name.\n\tRead(ctx context.Context, organization string) (*OrganizationAuditConfiguration, error)\n\n\t// Send a test audit event for an organization by its name.\n\tTest(ctx context.Context, organization string) (*OrganizationAuditConfigurationTest, error)\n\n\t// Update the audit configuration of an organization by its name.\n\tUpdate(ctx context.Context, organization string, options OrganizationAuditConfigurationOptions) (*OrganizationAuditConfiguration, error)\n}\n\n// OrganizationAuditConfiguration represents the auditing configuration for a HCP Terraform Organization.\ntype OrganizationAuditConfiguration struct {\n\tAuditTrails          *OrganizationAuditConfigAuditTrails    `jsonapi:\"attr,audit-trails,omitempty\"`\n\tHCPAuditLogStreaming *OrganizationAuditConfigAuditStreaming `jsonapi:\"attr,hcp-audit-log-streaming,omitempty\"`\n\tID                   string                                 `jsonapi:\"primary,audit-configurations\"`\n\tPermissions          *OrganizationAuditConfigPermissions    `jsonapi:\"attr,permissions,omitempty\"`\n\tTimestamps           *OrganizationAuditConfigTimestamps     `jsonapi:\"attr,timestamps,omitempty\"`\n\tUpdatedAt            time.Time                              `jsonapi:\"attr,updated-at,iso8601\"`\n\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n}\n\ntype OrganizationAuditConfigAuditTrails struct {\n\tEnabled bool `jsonapi:\"attr,enabled\"`\n}\n\ntype OrganizationAuditConfigAuditStreaming struct {\n\tEnabled                bool   `jsonapi:\"attr,enabled\"`\n\tOrganizationID         string `jsonapi:\"attr,organization-id\"`\n\tUseDefaultOrganization bool   `jsonapi:\"attr,use-default-organization\"`\n}\n\ntype OrganizationAuditConfigPermissions struct {\n\tCanEnableHCPAuditLogStreaming              bool `jsonapi:\"attr,can-enable-hcp-audit-log-streaming\"`\n\tCanSetHCPAuditLogStreamingOrganization     bool `jsonapi:\"attr,can-set-hcp-audit-log-streaming-organization-id\"`\n\tCanUseDefaultAuditLogStreamingOrganization bool `jsonapi:\"attr,can-use-default-audit-log-streaming-organization\"`\n}\n\ntype OrganizationAuditConfigTimestamps struct {\n\tAuditTrailsDisabledAt           *time.Time `jsonapi:\"attr,audit-trails-disabled-at,iso8601,omitempty\"`\n\tAuditTrailsEnabledAt            *time.Time `jsonapi:\"attr,audit-trails-enabled-at,iso8601,omitempty\"`\n\tAuditTrailsLastFailure          *time.Time `jsonapi:\"attr,audit-trails-last-failure,iso8601,omitempty\"`\n\tAuditTrailsLastSuccess          *time.Time `jsonapi:\"attr,audit-trails-last-success,iso8601,omitempty\"`\n\tHCPAuditLogStreamingDisabledAt  *time.Time `jsonapi:\"attr,hcp-audit-log-streaming-disabled-at,iso8601,omitempty\"`\n\tHCPAuditLogStreamingEnabledAt   *time.Time `jsonapi:\"attr,hcp-audit-log-streaming-enabled-at,iso8601,omitempty\"`\n\tHCPAuditLogStreamingLastFailure *time.Time `jsonapi:\"attr,hcp-audit-log-streaming-last-failure,iso8601,omitempty\"`\n\tHCPAuditLogStreamingLastSuccess *time.Time `jsonapi:\"attr,hcp-audit-log-streaming-last-success,iso8601,omitempty\"`\n}\n\ntype OrganizationAuditConfigurationTest struct {\n\tRequestID *string `json:\"request-id,omitempty\"`\n}\n\ntype OrganizationAuditConfigurationOptions struct {\n\tAuditTrails          *OrganizationAuditConfigAuditTrails    `jsonapi:\"attr,audit-trails,omitempty\"`\n\tHCPAuditLogStreaming *OrganizationAuditConfigAuditStreaming `jsonapi:\"attr,hcp-audit-log-streaming,omitempty\"`\n}\n\ntype organizationAuditConfigurations struct {\n\tclient *Client\n}\n\n// Read the audit configuration of an organization by its name.\nfunc (s *organizationAuditConfigurations) Read(ctx context.Context, organization string) (*OrganizationAuditConfiguration, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/audit-configuration\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tac := &OrganizationAuditConfiguration{}\n\terr = req.Do(ctx, ac)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ac, err\n}\n\n// Send a test audit event for an organization by its name.\nfunc (s *organizationAuditConfigurations) Test(ctx context.Context, organization string) (*OrganizationAuditConfigurationTest, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/audit-configuration/test\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresult := &OrganizationAuditConfigurationTest{}\n\terr = req.DoJSON(ctx, result)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, err\n}\n\n// Update the audit configuration of an organization by its name.\nfunc (s *organizationAuditConfigurations) Update(ctx context.Context, organization string, options OrganizationAuditConfigurationOptions) (*OrganizationAuditConfiguration, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/audit-configuration\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tac := &OrganizationAuditConfiguration{}\n\terr = req.Do(ctx, ac)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ac, err\n}\n"
  },
  {
    "path": "organization_audit_configuration_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOrganizationAuditConfigurationRead(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tif v, err := hasAuditLogging(client, orgTest.Name); err != nil {\n\t\tt.Fatalf(\"Could not retrieve the entitlements for the test organization.: %s\", err)\n\t} else if !v {\n\t\tt.Fatal(\"The test organization requires the audit-logging entitlement but is not entitled.\")\n\t\treturn\n\t}\n\n\tac, err := client.OrganizationAuditConfigurations.Read(ctx, orgTest.Name)\n\trequire.NoError(t, err)\n\n\t// By default audit trails is enabled\n\tassert.Equal(t, ac.AuditTrails.Enabled, true)\n\tassert.NotNil(t, ac.Organization)\n\tassert.Equal(t, orgTest.Name, ac.Organization.Name)\n}\n\nfunc TestOrganizationAuditConfigurationTest(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tif v, err := hasAuditLogging(client, orgTest.Name); err != nil {\n\t\tt.Fatalf(\"Could not retrieve the entitlements for the test organization.: %s\", err)\n\t} else if !v {\n\t\tt.Fatal(\"The test organization requires the audit-logging entitlement but is not entitled.\")\n\t\treturn\n\t}\n\n\tresult, err := client.OrganizationAuditConfigurations.Test(ctx, orgTest.Name)\n\trequire.NoError(t, err)\n\n\t// Expect a Request ID is returned\n\tassert.NotNil(t, result.RequestID)\n}\n\nfunc TestOrganizationAuditConfigurationUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tif v, err := hasAuditLogging(client, orgTest.Name); err != nil {\n\t\tt.Fatalf(\"Could not retrieve the entitlements for the test organization.: %s\", err)\n\t} else if !v {\n\t\tt.Fatal(\"The test organization requires the audit-logging entitlement but is not entitled.\")\n\t\treturn\n\t}\n\n\tac, err := client.OrganizationAuditConfigurations.Read(ctx, orgTest.Name)\n\trequire.NoError(t, err)\n\n\t// Unfortunately we can't really test the HCP Log Streaming because it requires either an integrated HCP organization,\n\t// or a valid HCP login session. Neither of which are setup for the test organization. Instead we just \"update\" the settings\n\t// with the existing ones. This doesn't prove that the endpoint behaves properly, but just tests that we can at least send\n\t// a payload to the expected API route.\n\tnewCfg, err := client.OrganizationAuditConfigurations.Update(ctx, orgTest.Name, OrganizationAuditConfigurationOptions{\n\t\tAuditTrails: &OrganizationAuditConfigAuditTrails{\n\t\t\tEnabled: ac.AuditTrails.Enabled,\n\t\t},\n\t\tHCPAuditLogStreaming: &OrganizationAuditConfigAuditStreaming{\n\t\t\tEnabled: ac.HCPAuditLogStreaming.Enabled,\n\t\t},\n\t})\n\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, ac.AuditTrails.Enabled, newCfg.AuditTrails.Enabled)\n\tassert.Equal(t, ac.HCPAuditLogStreaming.Enabled, newCfg.HCPAuditLogStreaming.Enabled)\n}\n"
  },
  {
    "path": "organization_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOrganizationsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest1, orgTest1Cleanup := createOrganization(t, client)\n\tt.Cleanup(orgTest1Cleanup)\n\torgTest2, orgTest2Cleanup := createOrganization(t, client)\n\tt.Cleanup(orgTest2Cleanup)\n\n\tt.Run(\"with no list options\", func(t *testing.T) {\n\t\torgl, err := client.Organizations.List(ctx, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, orgl.Items, orgTest1)\n\t\tassert.Contains(t, orgl.Items, orgTest2)\n\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\tassert.Equal(t, 1, orgl.CurrentPage)\n\t\tassert.Equal(t, 2, orgl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\torgl, err := client.Organizations.List(ctx, &OrganizationListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, orgl)\n\t\tassert.Equal(t, 999, orgl.CurrentPage)\n\t\tassert.Equal(t, 2, orgl.TotalCount)\n\t})\n\n\tt.Run(\"when querying on a valid org name\", func(t *testing.T) {\n\t\torg, orgTestCleanup := createOrganization(t, client)\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\torgList, err := client.Organizations.List(ctx, &OrganizationListOptions{\n\t\t\tQuery: org.Name,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, true, orgItemsContainsName(orgList.Items, org.Name))\n\t})\n\n\tt.Run(\"when querying on a valid email\", func(t *testing.T) {\n\t\torg, orgTestCleanup := createOrganization(t, client)\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\torgList, err := client.Organizations.List(ctx, &OrganizationListOptions{\n\t\t\tQuery: org.Email,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, true, orgItemsContainsEmail(orgList.Items, org.Email))\n\t})\n\n\tt.Run(\"with invalid query name\", func(t *testing.T) {\n\t\torg, orgTestCleanup := createOrganization(t, client)\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\torgList, err := client.Organizations.List(ctx, &OrganizationListOptions{\n\t\t\tQuery: org.Name,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.NotEqual(t, orgList.Items, orgTest1.Name)\n\t})\n\n\tt.Run(\"with invalid query email\", func(t *testing.T) {\n\t\torg, orgTestCleanup := createOrganization(t, client)\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\torgList, err := client.Organizations.List(ctx, &OrganizationListOptions{\n\t\t\tQuery: org.Email,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.NotEqual(t, orgList.Items, orgTest1.Email)\n\t})\n}\n\nfunc TestOrganizationsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := OrganizationCreateOptions{\n\t\t\tName:  String(randomString(t)),\n\t\t\tEmail: String(randomString(t) + \"@tfe.local\"),\n\t\t}\n\n\t\torg, err := client.Organizations.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\n\t\tt.Cleanup(func() {\n\t\t\terr := client.Organizations.Delete(ctx, org.Name)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error deleting organization (%s): %s\", org.Name, err)\n\t\t\t}\n\t\t})\n\n\t\tassert.Equal(t, *options.Name, org.Name)\n\t\tassert.Equal(t, *options.Email, org.Email)\n\t\tassert.Equal(t, \"remote\", org.DefaultExecutionMode)\n\t\tassert.Nil(t, org.DefaultAgentPool)\n\t})\n\n\tt.Run(\"when no email is provided\", func(t *testing.T) {\n\t\torg, err := client.Organizations.Create(ctx, OrganizationCreateOptions{\n\t\t\tName: String(\"foo\"),\n\t\t})\n\t\tassert.Nil(t, org)\n\t\tassert.Equal(t, err, ErrRequiredEmail)\n\t})\n\n\tt.Run(\"when no name is provided\", func(t *testing.T) {\n\t\t_, err := client.Organizations.Create(ctx, OrganizationCreateOptions{\n\t\t\tEmail: String(\"foo@bar.com\"),\n\t\t})\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"with invalid name\", func(t *testing.T) {\n\t\torg, err := client.Organizations.Create(ctx, OrganizationCreateOptions{\n\t\t\tName:  String(badIdentifier),\n\t\t\tEmail: String(\"foo@bar.com\"),\n\t\t})\n\t\tassert.Nil(t, org)\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n}\n\nfunc TestOrganizationsReadWithBusiness(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\t// With Business\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tt.Run(\"when the org exists\", func(t *testing.T) {\n\t\torg, err := client.Organizations.Read(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, orgTest.Name, org.Name)\n\t\tassert.Equal(t, orgTest.ExternalID, org.ExternalID)\n\t\tassert.NotEmpty(t, org.Permissions)\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\tassert.True(t, org.Permissions.CanDestroy)\n\t\t\tassert.True(t, org.Permissions.CanDeployNoCodeModules)\n\t\t\tassert.True(t, org.Permissions.CanManageNoCodeModules)\n\t\t})\n\t})\n}\n\nfunc TestOrganizationsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tt.Run(\"when the org exists\", func(t *testing.T) {\n\t\torg, err := client.Organizations.Read(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, orgTest, org)\n\t\tassert.NotEmpty(t, org.Permissions)\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\tassert.True(t, org.Permissions.CanDestroy)\n\t\t})\n\n\t\tt.Run(\"timestamps are populated\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, org.CreatedAt)\n\t\t\t// By default accounts are in the free tier and are not in a trial\n\t\t\tassert.Empty(t, org.TrialExpiresAt)\n\t\t\tassert.Greater(t, org.RemainingTestableCount, 1)\n\t\t})\n\t})\n\n\tt.Run(\"with invalid name\", func(t *testing.T) {\n\t\torg, err := client.Organizations.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, org)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when the org does not exist\", func(t *testing.T) {\n\t\t_, err := client.Organizations.Read(ctx, randomString(t))\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"reads default project\", func(t *testing.T) {\n\t\torg, err := client.Organizations.ReadWithOptions(ctx, orgTest.Name, OrganizationReadOptions{Include: []OrganizationIncludeOpt{OrganizationDefaultProject}})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, orgTest.Name, org.Name)\n\n\t\trequire.NotNil(t, org.DefaultProject)\n\t\tassert.NotNil(t, org.DefaultProject.Name)\n\t})\n\n\tt.Run(\"with default execution mode of 'agent'\", func(t *testing.T) {\n\t\torgAgentTest, orgAgentTestCleanup := createOrganizationWithDefaultAgentPool(t, client)\n\t\torg, err := client.Organizations.Read(ctx, orgAgentTest.Name)\n\n\t\tt.Cleanup(orgAgentTestCleanup)\n\t\trequire.NoError(t, err)\n\n\t\tt.Run(\"execution mode and agent pool are properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, \"agent\", org.DefaultExecutionMode)\n\t\t\tassert.NotNil(t, org.DefaultAgentPool)\n\t\t\tassert.Equal(t, org.DefaultAgentPool.ID, orgAgentTest.DefaultAgentPool.ID)\n\t\t})\n\t})\n\n\tt.Run(\"read primary hyok configuration of an organization\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name that has primary hyok configuration\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\torg, err := client.Organizations.Read(ctx, hyokOrganizationName)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, org.PrimaryHYOKConfiguration)\n\t})\n\n\tt.Run(\"read enforce hyok of an organization\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name that has enforce hyok set to true or false\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\torg, err := client.Organizations.Read(ctx, hyokOrganizationName)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, org.EnforceHYOK || !org.EnforceHYOK)\n\t})\n}\n\nfunc TestOrganizationsUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"with HCP Terraform-only options\", func(t *testing.T) {\n\t\tskipIfEnterprise(t)\n\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\toptions := OrganizationUpdateOptions{\n\t\t\tSendPassingStatusesForUntriggeredSpeculativePlans: Bool(false),\n\t\t}\n\n\t\torg, err := client.Organizations.Update(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, false, org.SendPassingStatusesForUntriggeredSpeculativePlans)\n\t})\n\n\tt.Run(\"with new AggregatedCommitStatusEnabled option\", func(t *testing.T) {\n\t\tskipIfEnterprise(t)\n\n\t\tfor _, testCase := range []bool{true, false} {\n\t\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\t\tt.Cleanup(orgTestCleanup)\n\n\t\t\toptions := OrganizationUpdateOptions{\n\t\t\t\tAggregatedCommitStatusEnabled: Bool(testCase),\n\t\t\t}\n\n\t\t\torg, err := client.Organizations.Update(ctx, orgTest.Name, options)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.Equal(t, testCase, org.AggregatedCommitStatusEnabled)\n\t\t}\n\t})\n\n\tt.Run(\"with new SpeculativePlanManagementEnabled option\", func(t *testing.T) {\n\t\tskipIfEnterprise(t)\n\n\t\tfor _, testCase := range []bool{true, false} {\n\t\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\t\tt.Cleanup(orgTestCleanup)\n\n\t\t\toptions := OrganizationUpdateOptions{\n\t\t\t\tSpeculativePlanManagementEnabled: Bool(testCase),\n\t\t\t}\n\n\t\t\torg, err := client.Organizations.Update(ctx, orgTest.Name, options)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.Equal(t, testCase, org.SpeculativePlanManagementEnabled)\n\t\t}\n\t})\n\n\tt.Run(\"with new UserTokensEnabled option\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\tassert.True(t, *orgTest.UserTokensEnabled, \"user tokens enabled by default\")\n\n\t\t// we need to switch to an owner's team token, otherwise the client (which auths with a user token)\n\t\t// wont be able to delete the org after we disable user tokens\n\t\tteamList, err := client.Teams.List(ctx, orgTest.Name, &TeamListOptions{\n\t\t\tNames: []string{\"owners\"},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// it should be the only team, we just created the org...\n\t\trequire.Len(t, teamList.Items, 1)\n\t\townersTeam := teamList.Items[0]\n\n\t\townerToken, ownerTokenCleanup := createTeamToken(t, client, ownersTeam)\n\t\tt.Cleanup(ownerTokenCleanup)\n\n\t\townerClient := testClient(t)\n\t\townerClient.token = ownerToken.Token\n\n\t\t// disable user tokens for the organization\n\t\toptions := OrganizationUpdateOptions{\n\t\t\tUserTokensEnabled: Bool(false),\n\t\t}\n\n\t\torg, err := ownerClient.Organizations.Update(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, *org.UserTokensEnabled, \"user tokens disabled\")\n\n\t\t// try reading something with the user token client and verify that it fails, where the team token client\n\t\t// succeeds\n\t\t_, err = client.Organizations.Read(ctx, orgTest.Name)\n\t\tassert.Error(t, err)\n\t\tassert.ErrorContains(t, err, \"unauthorized\")\n\n\t\torg, err = ownerClient.Organizations.Read(ctx, orgTest.Name)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, orgTest.Name, org.Name)\n\t\tassert.False(t, *org.UserTokensEnabled, \"user tokens disabled\")\n\n\t\t// re-enable user tokens\n\t\toptions = OrganizationUpdateOptions{\n\t\t\tUserTokensEnabled: Bool(true),\n\t\t}\n\t\torg, err = ownerClient.Organizations.Update(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, *org.UserTokensEnabled, \"user tokens re-enabled\")\n\n\t\t// try reading with the user token again and verify that it works\n\t\torg, err = client.Organizations.Read(ctx, orgTest.Name)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, orgTest.Name, org.Name)\n\t\tassert.True(t, *org.UserTokensEnabled, \"user tokens re-enabled\")\n\t})\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\n\t\toptions := OrganizationUpdateOptions{\n\t\t\tName:                 String(randomString(t)),\n\t\t\tEmail:                String(randomString(t) + \"@tfe.local\"),\n\t\t\tSessionTimeout:       Int(3600),\n\t\t\tSessionRemember:      Int(3600),\n\t\t\tDefaultExecutionMode: String(\"local\"),\n\t\t}\n\n\t\torg, err := client.Organizations.Update(ctx, orgTest.Name, options)\n\t\tif err != nil {\n\t\t\torgTestCleanup()\n\t\t}\n\t\trequire.NoError(t, err)\n\n\t\t// Make sure we clean up the renamed org.\n\t\tdefer func() {\n\t\t\terr := client.Organizations.Delete(ctx, org.Name)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"Error deleting organization (%s): %s\", org.Name, err)\n\t\t\t}\n\t\t}()\n\n\t\t// Also get a fresh result from the API to ensure we get the\n\t\t// expected values back.\n\t\trefreshed, err := client.Organizations.Read(ctx, *options.Name)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Organization{\n\t\t\torg,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, *options.Email, item.Email)\n\t\t\tassert.Equal(t, *options.SessionTimeout, item.SessionTimeout)\n\t\t\tassert.Equal(t, *options.SessionRemember, item.SessionRemember)\n\t\t\tassert.Equal(t, *options.DefaultExecutionMode, item.DefaultExecutionMode)\n\t\t}\n\t})\n\n\tt.Run(\"with invalid name\", func(t *testing.T) {\n\t\torg, err := client.Organizations.Update(ctx, badIdentifier, OrganizationUpdateOptions{})\n\t\tassert.Nil(t, org)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"with agent pool provided, but remote execution mode\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\tpool, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(agentPoolCleanup)\n\n\t\torg, err := client.Organizations.Update(ctx, orgTest.Name, OrganizationUpdateOptions{\n\t\t\tDefaultAgentPool: pool,\n\t\t})\n\t\tassert.Nil(t, org)\n\t\tassert.ErrorContains(t, err, \"must not be specified unless using 'agent' execution mode\")\n\t})\n\n\tt.Run(\"when only updating a subset of fields\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\torg, err := client.Organizations.Update(ctx, orgTest.Name, OrganizationUpdateOptions{})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, orgTest.Name, org.Name)\n\t\tassert.Equal(t, orgTest.Email, org.Email)\n\t})\n\n\tt.Run(\"with different default execution modes\", func(t *testing.T) {\n\t\t// this helper creates an organization and then updates it to use a default agent pool, so it implicitly asserts\n\t\t// that the organization's execution mode can be updated from 'remote' -> 'agent'\n\t\torg, orgAgentTestCleanup := createOrganizationWithDefaultAgentPool(t, client)\n\t\tassert.Equal(t, \"agent\", org.DefaultExecutionMode)\n\t\tassert.NotNil(t, org.DefaultAgentPool)\n\n\t\t// assert that organization's execution mode can be updated from 'agent' -> 'remote'\n\t\torg, err := client.Organizations.Update(ctx, org.Name, OrganizationUpdateOptions{\n\t\t\tDefaultExecutionMode: String(\"remote\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"remote\", org.DefaultExecutionMode)\n\t\tassert.Nil(t, org.DefaultAgentPool)\n\n\t\t// assert that organization's execution mode can be updated from 'remote' -> 'local'\n\t\torg, err = client.Organizations.Update(ctx, org.Name, OrganizationUpdateOptions{\n\t\t\tDefaultExecutionMode: String(\"local\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"local\", org.DefaultExecutionMode)\n\t\tassert.Nil(t, org.DefaultAgentPool)\n\n\t\tt.Cleanup(orgAgentTestCleanup)\n\t})\n\n\tt.Run(\"update enforce hyok of an organization to true\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name with hyok permissions\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\torg, err := client.Organizations.Update(ctx, hyokOrganizationName, OrganizationUpdateOptions{\n\t\t\tEnforceHYOK: Bool(true),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, org.EnforceHYOK)\n\t})\n\n\tt.Run(\"update enforce hyok of an organization to false\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name with hyok permissions\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\torg, err := client.Organizations.Update(ctx, hyokOrganizationName, OrganizationUpdateOptions{\n\t\t\tEnforceHYOK: Bool(false),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, org.EnforceHYOK)\n\t})\n}\n\nfunc TestOrganizationsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\torgTest, _ := createOrganization(t, client)\n\n\t\terr := client.Organizations.Delete(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\t// Try fetching the org again - it should error.\n\t\t_, err = client.Organizations.Read(ctx, orgTest.Name)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid name\", func(t *testing.T) {\n\t\terr := client.Organizations.Delete(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestOrganizationsReadCapacity_RunDependent(t *testing.T) {\n\tt.Skip(\"Capacity queues are not available in the API\")\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest1, wTestCleanup1 := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup1)\n\twTest2, wTestCleanup2 := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup2)\n\twTest3, wTestCleanup3 := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup3)\n\twTest4, wTestCleanup4 := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup4)\n\n\tt.Run(\"without queued runs\", func(t *testing.T) {\n\t\tc, err := client.Organizations.ReadCapacity(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 0, c.Pending)\n\t\tassert.Equal(t, 0, c.Running)\n\t})\n\n\t// For this test FRQ should be enabled and have a\n\t// limit of 2 concurrent runs per organization.\n\tt.Run(\"with queued runs\", func(t *testing.T) {\n\t\t_, runCleanup1 := createRun(t, client, wTest1)\n\t\tt.Cleanup(runCleanup1)\n\t\t_, runCleanup2 := createRun(t, client, wTest2)\n\t\tt.Cleanup(runCleanup2)\n\t\t_, runCleanup3 := createRun(t, client, wTest3)\n\t\tt.Cleanup(runCleanup3)\n\t\t_, runCleanup4 := createRun(t, client, wTest4)\n\t\tt.Cleanup(runCleanup4)\n\n\t\tc, err := client.Organizations.ReadCapacity(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 2, c.Pending)\n\t\tassert.Equal(t, 2, c.Running)\n\t})\n\n\tt.Run(\"with invalid name\", func(t *testing.T) {\n\t\torg, err := client.Organizations.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, org)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when the org does not exist\", func(t *testing.T) {\n\t\t_, err := client.Organizations.Read(ctx, randomString(t))\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestOrganizationsReadEntitlements(t *testing.T) {\n\tt.Parallel()\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithStandardEntitlementPlan().Update(t)\n\n\tt.Run(\"when the org exists\", func(t *testing.T) {\n\t\tentitlements, err := client.Organizations.ReadEntitlements(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, entitlements.ID)\n\t\tassert.True(t, entitlements.Agents)\n\t\tassert.True(t, entitlements.AuditLogging)\n\t\tassert.True(t, entitlements.CostEstimation)\n\t\tassert.True(t, entitlements.Operations)\n\t\tassert.True(t, entitlements.PrivateModuleRegistry)\n\t\tassert.True(t, entitlements.SSO)\n\t\tassert.True(t, entitlements.Sentinel)\n\t\tassert.True(t, entitlements.StateStorage)\n\t\tassert.True(t, entitlements.Teams)\n\t\tassert.True(t, entitlements.VCSIntegrations)\n\t\tassert.False(t, entitlements.WaypointActions)\n\t\tassert.True(t, entitlements.WaypointTemplatesAndAddons)\n\t})\n\n\tt.Run(\"with invalid name\", func(t *testing.T) {\n\t\tentitlements, err := client.Organizations.ReadEntitlements(ctx, badIdentifier)\n\t\tassert.Nil(t, entitlements)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when the org does not exist\", func(t *testing.T) {\n\t\t_, err := client.Organizations.ReadEntitlements(ctx, randomString(t))\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n}\n\nfunc TestOrganizationsReadRunQueue_RunDependent(t *testing.T) {\n\tt.Skip(\"Capacity queues are not available in the API\")\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest1, wTestCleanup1 := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup1)\n\twTest2, wTestCleanup2 := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup2)\n\twTest3, wTestCleanup3 := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup3)\n\twTest4, wTestCleanup4 := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup4)\n\n\tt.Run(\"without queued runs\", func(t *testing.T) {\n\t\trq, err := client.Organizations.ReadRunQueue(ctx, orgTest.Name, ReadRunQueueOptions{})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 0, len(rq.Items))\n\t})\n\n\t// Create a couple or runs to fill the queue.\n\trTest1, rTestCleanup1 := createRun(t, client, wTest1)\n\tt.Cleanup(rTestCleanup1)\n\trTest2, rTestCleanup2 := createRun(t, client, wTest2)\n\tt.Cleanup(rTestCleanup2)\n\trTest3, rTestCleanup3 := createRun(t, client, wTest3)\n\tt.Cleanup(rTestCleanup3)\n\trTest4, rTestCleanup4 := createRun(t, client, wTest4)\n\tt.Cleanup(rTestCleanup4)\n\n\t// For this test FRQ should be enabled and have a\n\t// limit of 2 concurrent runs per organization.\n\tt.Run(\"with queued runs\", func(t *testing.T) {\n\t\trq, err := client.Organizations.ReadRunQueue(ctx, orgTest.Name, ReadRunQueueOptions{})\n\t\trequire.NoError(t, err)\n\n\t\tfound := []string{}\n\t\tfor _, r := range rq.Items {\n\t\t\tfound = append(found, r.ID)\n\t\t}\n\n\t\tassert.Contains(t, found, rTest1.ID)\n\t\tassert.Contains(t, found, rTest2.ID)\n\t\tassert.Contains(t, found, rTest3.ID)\n\t\tassert.Contains(t, found, rTest4.ID)\n\t})\n\n\tt.Run(\"without queue options\", func(t *testing.T) {\n\t\trq, err := client.Organizations.ReadRunQueue(ctx, orgTest.Name, ReadRunQueueOptions{})\n\t\trequire.NoError(t, err)\n\n\t\tfound := []string{}\n\t\tfor _, r := range rq.Items {\n\t\t\tfound = append(found, r.ID)\n\t\t}\n\n\t\tassert.Contains(t, found, rTest1.ID)\n\t\tassert.Contains(t, found, rTest2.ID)\n\t\tassert.Contains(t, found, rTest3.ID)\n\t\tassert.Contains(t, found, rTest4.ID)\n\t\tassert.Equal(t, 1, rq.CurrentPage)\n\t\tassert.Equal(t, 4, rq.TotalCount)\n\t})\n\n\tt.Run(\"with queue options\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\trq, err := client.Organizations.ReadRunQueue(ctx, orgTest.Name, ReadRunQueueOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Empty(t, rq.Items)\n\t\tassert.Equal(t, 999, rq.CurrentPage)\n\t\tassert.Equal(t, 4, rq.TotalCount)\n\t})\n\n\tt.Run(\"with invalid name\", func(t *testing.T) {\n\t\torg, err := client.Organizations.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, org)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when the org does not exist\", func(t *testing.T) {\n\t\t_, err := client.Organizations.Read(ctx, randomString(t))\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestOrganization_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"organizations\",\n\t\t\t\"id\":   \"org-name\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"assessments-enforced\":     true,\n\t\t\t\t\"collaborator-auth-policy\": AuthPolicyPassword,\n\t\t\t\t\"cost-estimation-enabled\":  true,\n\t\t\t\t\"created-at\":               \"2018-03-02T23:42:06.651Z\",\n\t\t\t\t\"email\":                    \"test@hashicorp.com\",\n\t\t\t\t\"permissions\": map[string]interface{}{\n\t\t\t\t\t\"can-create-team\": true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\torg := &Organization{}\n\terr = unmarshalResponse(responseBody, org)\n\trequire.NoError(t, err)\n\n\tiso8601TimeFormat := \"2006-01-02T15:04:05Z\"\n\tparsedTime, err := time.Parse(iso8601TimeFormat, \"2018-03-02T23:42:06.651Z\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, org.Name, \"org-name\")\n\tassert.Equal(t, org.AssessmentsEnforced, true)\n\tassert.Equal(t, org.CreatedAt, parsedTime)\n\tassert.Equal(t, org.CollaboratorAuthPolicy, AuthPolicyPassword)\n\tassert.Equal(t, org.CostEstimationEnabled, true)\n\tassert.Equal(t, org.Email, \"test@hashicorp.com\")\n\tassert.NotEmpty(t, org.Permissions)\n\tassert.Equal(t, org.Permissions.CanCreateTeam, true)\n}\n\nfunc TestOrganizationsReadRunTasksPermission(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tt.Run(\"when the org exists\", func(t *testing.T) {\n\t\torg, err := client.Organizations.Read(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, orgTest, org)\n\t\tassert.NotEmpty(t, org.Permissions)\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\tassert.True(t, org.Permissions.CanManageRunTasks)\n\t\t})\n\t})\n}\n\nfunc TestOrganizationsReadRunTasksEntitlement(t *testing.T) {\n\tt.Parallel()\n\tskipIfEnterprise(t)\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tt.Run(\"when the org exists\", func(t *testing.T) {\n\t\tentitlements, err := client.Organizations.ReadEntitlements(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, entitlements.ID)\n\t\tassert.True(t, entitlements.RunTasks)\n\t})\n}\n\nfunc TestOrganizationsAllowForceDeleteSetting(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"creates and updates allow force delete\", func(t *testing.T) {\n\t\toptions := OrganizationCreateOptions{\n\t\t\tName:                       String(randomString(t)),\n\t\t\tEmail:                      String(randomString(t) + \"@tfe.local\"),\n\t\t\tAllowForceDeleteWorkspaces: Bool(true),\n\t\t}\n\n\t\torg, err := client.Organizations.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\n\t\tt.Cleanup(func() {\n\t\t\terr := client.Organizations.Delete(ctx, org.Name)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error deleting organization (%s): %s\", org.Name, err)\n\t\t\t}\n\t\t})\n\n\t\tassert.Equal(t, *options.Name, org.Name)\n\t\tassert.Equal(t, *options.Email, org.Email)\n\t\tassert.True(t, org.AllowForceDeleteWorkspaces)\n\n\t\torg, err = client.Organizations.Update(ctx, org.Name, OrganizationUpdateOptions{AllowForceDeleteWorkspaces: Bool(false)})\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, org.AllowForceDeleteWorkspaces)\n\n\t\torg, err = client.Organizations.Read(ctx, org.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, org.AllowForceDeleteWorkspaces)\n\t})\n}\n\nfunc TestOrganization_DataRetentionPolicy(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\torganization, err := client.Organizations.Read(ctx, orgTest.Name)\n\trequire.NoError(t, err)\n\trequire.Nil(t, organization.DataRetentionPolicy)\n\trequire.Nil(t, organization.DataRetentionPolicyChoice)\n\n\tdataRetentionPolicy, err := client.Organizations.ReadDataRetentionPolicyChoice(ctx, orgTest.Name)\n\trequire.NoError(t, err)\n\trequire.Nil(t, dataRetentionPolicy)\n\n\tt.Run(\"set and update data retention policy to delete older\", func(t *testing.T) {\n\t\tcreatedDataRetentionPolicy, err := client.Organizations.SetDataRetentionPolicyDeleteOlder(ctx, orgTest.Name, DataRetentionPolicyDeleteOlderSetOptions{DeleteOlderThanNDays: 33})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 33, createdDataRetentionPolicy.DeleteOlderThanNDays)\n\t\trequire.Contains(t, createdDataRetentionPolicy.ID, \"drp-\")\n\n\t\tdataRetentionPolicy, err = client.Organizations.ReadDataRetentionPolicyChoice(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder)\n\n\t\trequire.Equal(t, 33, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.DeleteOlderThanNDays)\n\t\trequire.Equal(t, createdDataRetentionPolicy.ID, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.ID)\n\t\trequire.Contains(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.ID, \"drp-\")\n\n\t\torganization, err := client.Organizations.Read(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.ID, organization.DataRetentionPolicyChoice.DataRetentionPolicyDeleteOlder.ID)\n\n\t\t// deprecated DataRetentionPolicy field should also have been populated\n\t\trequire.NotNil(t, organization.DataRetentionPolicy)\n\t\trequire.Equal(t, organization.DataRetentionPolicy.ID, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.ID)\n\n\t\t// try updating the number of days\n\t\tcreatedDataRetentionPolicy, err = client.Organizations.SetDataRetentionPolicyDeleteOlder(ctx, orgTest.Name, DataRetentionPolicyDeleteOlderSetOptions{DeleteOlderThanNDays: 1})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, createdDataRetentionPolicy.DeleteOlderThanNDays)\n\n\t\tdataRetentionPolicy, err = client.Organizations.ReadDataRetentionPolicyChoice(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder)\n\t\trequire.Equal(t, 1, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.DeleteOlderThanNDays)\n\t\trequire.Equal(t, createdDataRetentionPolicy.ID, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.ID)\n\t})\n\n\tt.Run(\"set data retention policy to not delete\", func(t *testing.T) {\n\t\tcreatedDataRetentionPolicy, err := client.Organizations.SetDataRetentionPolicyDontDelete(ctx, orgTest.Name, DataRetentionPolicyDontDeleteSetOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, createdDataRetentionPolicy.ID, \"drp-\")\n\n\t\tdataRetentionPolicy, err = client.Organizations.ReadDataRetentionPolicyChoice(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDontDelete)\n\t\trequire.Equal(t, createdDataRetentionPolicy.ID, dataRetentionPolicy.DataRetentionPolicyDontDelete.ID)\n\n\t\t// dont delete policies should leave the legacy DataRetentionPolicy field on organizations empty\n\t\torganization, err := client.Organizations.Read(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, organization.DataRetentionPolicy)\n\t})\n\n\tt.Run(\"change data retention policy type\", func(t *testing.T) {\n\t\t_, err = client.Organizations.SetDataRetentionPolicyDeleteOlder(ctx, orgTest.Name, DataRetentionPolicyDeleteOlderSetOptions{DeleteOlderThanNDays: 45})\n\t\trequire.NoError(t, err)\n\n\t\tdataRetentionPolicy, err = client.Organizations.ReadDataRetentionPolicyChoice(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder)\n\t\trequire.Equal(t, 45, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.DeleteOlderThanNDays)\n\t\trequire.Nil(t, dataRetentionPolicy.DataRetentionPolicyDontDelete)\n\n\t\t_, err = client.Organizations.SetDataRetentionPolicyDontDelete(ctx, orgTest.Name, DataRetentionPolicyDontDeleteSetOptions{})\n\t\trequire.NoError(t, err)\n\t\tdataRetentionPolicy, err = client.Organizations.ReadDataRetentionPolicyChoice(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDontDelete)\n\n\t\t_, err = client.Organizations.SetDataRetentionPolicyDeleteOlder(ctx, orgTest.Name, DataRetentionPolicyDeleteOlderSetOptions{DeleteOlderThanNDays: 20})\n\t\trequire.NoError(t, err)\n\n\t\tdataRetentionPolicy, err = client.Organizations.ReadDataRetentionPolicyChoice(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder)\n\t\trequire.Equal(t, 20, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.DeleteOlderThanNDays)\n\t\trequire.Nil(t, dataRetentionPolicy.DataRetentionPolicyDontDelete)\n\t})\n\n\tt.Run(\"delete data retention policy\", func(t *testing.T) {\n\t\terr = client.Organizations.DeleteDataRetentionPolicy(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\tdataRetentionPolicy, err = client.Organizations.ReadDataRetentionPolicyChoice(ctx, orgTest.Name)\n\t\tassert.Nil(t, err)\n\t\trequire.Nil(t, dataRetentionPolicy)\n\t})\n}\n\nfunc orgItemsContainsName(items []*Organization, name string) bool {\n\thasName := false\n\tfor _, item := range items {\n\t\tif item.Name == name {\n\t\t\thasName = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn hasName\n}\n\nfunc orgItemsContainsEmail(items []*Organization, email string) bool {\n\thasEmail := false\n\tfor _, item := range items {\n\t\tif item.Email == email {\n\t\t\thasEmail = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn hasEmail\n}\n"
  },
  {
    "path": "organization_membership.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ OrganizationMemberships = (*organizationMemberships)(nil)\n\n// OrganizationMemberships describes all the organization membership related methods that\n// the Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organization-memberships\ntype OrganizationMemberships interface {\n\t// List all the organization memberships of the given organization.\n\tList(ctx context.Context, organization string, options *OrganizationMembershipListOptions) (*OrganizationMembershipList, error)\n\n\t// Create a new organization membership with the given options.\n\tCreate(ctx context.Context, organization string, options OrganizationMembershipCreateOptions) (*OrganizationMembership, error)\n\n\t// Read an organization membership by ID\n\tRead(ctx context.Context, organizationMembershipID string) (*OrganizationMembership, error)\n\n\t// Read an organization membership by ID with options\n\tReadWithOptions(ctx context.Context, organizationMembershipID string, options OrganizationMembershipReadOptions) (*OrganizationMembership, error)\n\n\t// Delete an organization membership by its ID.\n\tDelete(ctx context.Context, organizationMembershipID string) error\n}\n\n// organizationMemberships implements OrganizationMemberships.\ntype organizationMemberships struct {\n\tclient *Client\n}\n\n// OrganizationMembershipStatus represents an organization membership status.\ntype OrganizationMembershipStatus string\n\nconst (\n\tOrganizationMembershipActive  OrganizationMembershipStatus = \"active\"\n\tOrganizationMembershipInvited OrganizationMembershipStatus = \"invited\"\n)\n\n// OrganizationMembershipList represents a list of organization memberships.\ntype OrganizationMembershipList struct {\n\t*Pagination\n\tItems []*OrganizationMembership\n}\n\n// OrganizationMembership represents a Terraform Enterprise organization membership.\ntype OrganizationMembership struct {\n\tID     string                       `jsonapi:\"primary,organization-memberships\"`\n\tStatus OrganizationMembershipStatus `jsonapi:\"attr,status\"`\n\tEmail  string                       `jsonapi:\"attr,email\"`\n\n\t// Relations\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n\tUser         *User         `jsonapi:\"relation,user\"`\n\tTeams        []*Team       `jsonapi:\"relation,teams\"`\n}\n\n// OrgMembershipIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organization-memberships#available-related-resources\ntype OrgMembershipIncludeOpt string\n\nconst (\n\tOrgMembershipUser OrgMembershipIncludeOpt = \"user\"\n\tOrgMembershipTeam OrgMembershipIncludeOpt = \"teams\"\n)\n\n// OrganizationMembershipListOptions represents the options for listing organization memberships.\ntype OrganizationMembershipListOptions struct {\n\tListOptions\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organization-memberships#available-related-resources\n\tInclude []OrgMembershipIncludeOpt `url:\"include,omitempty\"`\n\n\t// Optional: A list of organization member emails to filter by.\n\tEmails []string `url:\"filter[email],omitempty\"`\n\n\t// Optional: If specified, restricts results to those matching status value.\n\tStatus OrganizationMembershipStatus `url:\"filter[status],omitempty\"`\n\n\t// Optional: A query string to search organization memberships by user name\n\t// and email.\n\tQuery string `url:\"q,omitempty\"`\n}\n\n// OrganizationMembershipCreateOptions represents the options for creating an organization membership.\ntype OrganizationMembershipCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,organization-memberships\"`\n\n\t// Required: User's email address.\n\tEmail *string `jsonapi:\"attr,email\"`\n\n\t// Optional: A list of teams in the organization to add the user to\n\tTeams []*Team `jsonapi:\"relation,teams,omitempty\"`\n}\n\n// OrganizationMembershipReadOptions represents the options for reading organization memberships.\ntype OrganizationMembershipReadOptions struct {\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organization-memberships#available-related-resources\n\tInclude []OrgMembershipIncludeOpt `url:\"include,omitempty\"`\n}\n\n// List all the organization memberships of the given organization.\nfunc (s *organizationMemberships) List(ctx context.Context, organization string, options *OrganizationMembershipListOptions) (*OrganizationMembershipList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/organization-memberships\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tml := &OrganizationMembershipList{}\n\terr = req.Do(ctx, ml)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ml, nil\n}\n\n// Create an organization membership with the given options.\nfunc (s *organizationMemberships) Create(ctx context.Context, organization string, options OrganizationMembershipCreateOptions) (*OrganizationMembership, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/organization-memberships\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tm := &OrganizationMembership{}\n\terr = req.Do(ctx, m)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn m, nil\n}\n\n// Read an organization membership by its ID.\nfunc (s *organizationMemberships) Read(ctx context.Context, organizationMembershipID string) (*OrganizationMembership, error) {\n\treturn s.ReadWithOptions(ctx, organizationMembershipID, OrganizationMembershipReadOptions{})\n}\n\n// Read an organization membership by ID with options\nfunc (s *organizationMemberships) ReadWithOptions(ctx context.Context, organizationMembershipID string, options OrganizationMembershipReadOptions) (*OrganizationMembership, error) {\n\tif !validStringID(&organizationMembershipID) {\n\t\treturn nil, ErrInvalidMembership\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organization-memberships/%s\", url.PathEscape(organizationMembershipID))\n\treq, err := s.client.NewRequest(\"GET\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmem := &OrganizationMembership{}\n\terr = req.Do(ctx, mem)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn mem, nil\n}\n\n// Delete an organization membership by its ID.\nfunc (s *organizationMemberships) Delete(ctx context.Context, organizationMembershipID string) error {\n\tif !validStringID(&organizationMembershipID) {\n\t\treturn ErrInvalidMembership\n\t}\n\n\tu := fmt.Sprintf(\"organization-memberships/%s\", url.PathEscape(organizationMembershipID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o OrganizationMembershipCreateOptions) valid() error {\n\tif o.Email == nil {\n\t\treturn ErrRequiredEmail\n\t}\n\treturn nil\n}\n\nfunc (o *OrganizationMembershipListOptions) valid() error {\n\tif o == nil {\n\t\treturn nil\n\t}\n\n\tif err := validateOrgMembershipEmailParams(o.Emails); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (o OrganizationMembershipReadOptions) valid() error {\n\treturn nil\n}\n\nfunc validateOrgMembershipEmailParams(emails []string) error {\n\tfor _, email := range emails {\n\t\tif !validEmail(email) {\n\t\t\treturn ErrInvalidEmail\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "organization_membership_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOrganizationMembershipsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tmemTest1, memTest1Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tdefer memTest1Cleanup()\n\t\tmemTest2, memTest2Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tdefer memTest2Cleanup()\n\n\t\t// The create helper includes the related user, so we should remove it for our equality test\n\t\tmemTest1.User = &User{ID: memTest1.User.ID}\n\t\tmemTest2.User = &User{ID: memTest2.User.ID}\n\n\t\tml, err := client.OrganizationMemberships.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Contains(t, ml.Items, memTest1)\n\t\tassert.Contains(t, ml.Items, memTest2)\n\t})\n\n\tt.Run(\"with pagination options\", func(t *testing.T) {\n\t\t_, memTest1Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tdefer memTest1Cleanup()\n\t\t_, memTest2Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tdefer memTest2Cleanup()\n\n\t\tml, err := client.OrganizationMemberships.List(ctx, orgTest.Name, &OrganizationMembershipListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, ml.Items)\n\t\tassert.Equal(t, 999, ml.CurrentPage)\n\n\t\t// Three because the creator of the organization is a member, in addition to the two we added to setup the test.\n\t\tassert.Equal(t, 3, ml.TotalCount)\n\t})\n\n\tt.Run(\"with include options\", func(t *testing.T) {\n\t\tmemTest1, memTest1Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tdefer memTest1Cleanup()\n\t\tmemTest2, memTest2Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tdefer memTest2Cleanup()\n\n\t\tml, err := client.OrganizationMemberships.List(ctx, orgTest.Name, &OrganizationMembershipListOptions{\n\t\t\tInclude: []OrgMembershipIncludeOpt{OrgMembershipUser},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Contains(t, ml.Items, memTest1)\n\t\tassert.Contains(t, ml.Items, memTest2)\n\t})\n\n\tt.Run(\"with email filter option\", func(t *testing.T) {\n\t\t_, memTest1Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tdefer memTest1Cleanup()\n\t\tmemTest2, memTest2Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tdefer memTest2Cleanup()\n\n\t\tmemTest3, memTest3Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tdefer memTest3Cleanup()\n\n\t\tmemTest2.User = &User{ID: memTest2.User.ID}\n\t\tmemTest3.User = &User{ID: memTest3.User.ID}\n\n\t\tml, err := client.OrganizationMemberships.List(ctx, orgTest.Name, &OrganizationMembershipListOptions{\n\t\t\tEmails: []string{memTest2.Email, memTest3.Email},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, ml.Items, 2)\n\t\tassert.Contains(t, ml.Items, memTest2)\n\t\tassert.Contains(t, ml.Items, memTest3)\n\n\t\tt.Run(\"with invalid email\", func(t *testing.T) {\n\t\t\tml, err = client.OrganizationMemberships.List(ctx, orgTest.Name, &OrganizationMembershipListOptions{\n\t\t\t\tEmails: []string{\"foobar\"},\n\t\t\t})\n\t\t\tassert.Equal(t, err, ErrInvalidEmail)\n\t\t})\n\t})\n\n\tt.Run(\"with status filter option\", func(t *testing.T) {\n\t\t_, memTest1Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tt.Cleanup(memTest1Cleanup)\n\t\t_, memTest2Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tt.Cleanup(memTest2Cleanup)\n\n\t\tml, err := client.OrganizationMemberships.List(ctx, orgTest.Name, &OrganizationMembershipListOptions{\n\t\t\tStatus: OrganizationMembershipInvited,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.Len(t, ml.Items, 2)\n\t\tfor _, member := range ml.Items {\n\t\t\tassert.Equal(t, member.Status, OrganizationMembershipInvited)\n\t\t}\n\t})\n\n\tt.Run(\"with search query string\", func(t *testing.T) {\n\t\tmemTest1, memTest1Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tt.Cleanup(memTest1Cleanup)\n\t\t_, memTest2Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tt.Cleanup(memTest2Cleanup)\n\t\t_, memTest3Cleanup := createOrganizationMembership(t, client, orgTest)\n\t\tt.Cleanup(memTest3Cleanup)\n\n\t\tt.Run(\"using an email\", func(t *testing.T) {\n\t\t\tml, err := client.OrganizationMemberships.List(ctx, orgTest.Name, &OrganizationMembershipListOptions{\n\t\t\t\tQuery: memTest1.Email,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Len(t, ml.Items, 1)\n\t\t\tassert.Equal(t, ml.Items[0].Email, memTest1.Email)\n\t\t})\n\n\t\tt.Run(\"using a user name\", func(t *testing.T) {\n\t\t\tt.Skip(\"Skipping, missing Account API support in order to set usernames\")\n\t\t})\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tml, err := client.OrganizationMemberships.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, ml)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestOrganizationMembershipsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := OrganizationMembershipCreateOptions{\n\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t}\n\n\t\tmem, err := client.OrganizationMemberships.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.OrganizationMemberships.ReadWithOptions(ctx, mem.ID, OrganizationMembershipReadOptions{\n\t\t\tInclude: []OrgMembershipIncludeOpt{OrgMembershipUser},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, refreshed, mem)\n\t})\n\n\tt.Run(\"when options is missing email\", func(t *testing.T) {\n\t\tmem, err := client.OrganizationMemberships.Create(ctx, orgTest.Name, OrganizationMembershipCreateOptions{})\n\n\t\tassert.Nil(t, mem)\n\t\tassert.Equal(t, err, ErrRequiredEmail)\n\t})\n\n\tt.Run(\"with an invalid organization\", func(t *testing.T) {\n\t\tmem, err := client.OrganizationMemberships.Create(ctx, badIdentifier, OrganizationMembershipCreateOptions{\n\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t})\n\n\t\tassert.Nil(t, mem)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when an error is returned from the api\", func(t *testing.T) {\n\t\tmem, err := client.OrganizationMemberships.Create(ctx, orgTest.Name, OrganizationMembershipCreateOptions{\n\t\t\tEmail: String(\"not-an-email-address\"),\n\t\t})\n\n\t\tassert.Nil(t, mem)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"with initial teams\", func(t *testing.T) {\n\t\tteamTest1, teamTestCleanup1 := createTeam(t, client, orgTest)\n\t\tdefer teamTestCleanup1()\n\t\tteamTest2, teamTestCleanup2 := createTeam(t, client, orgTest)\n\t\tdefer teamTestCleanup2()\n\n\t\toptions := OrganizationMembershipCreateOptions{\n\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t\tTeams: []*Team{teamTest1, teamTest2},\n\t\t}\n\n\t\tmem, err := client.OrganizationMemberships.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify that the user is now in the org\n\t\trefreshed, err := client.OrganizationMemberships.ReadWithOptions(ctx, mem.ID, OrganizationMembershipReadOptions{\n\t\t\tInclude: []OrgMembershipIncludeOpt{OrgMembershipUser},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, refreshed, mem)\n\n\t\t// Verify that the user is in the teams\n\t\t// Cant read from the teams, b/c the user is invited to them not a full member yet\n\t\trequire.Equal(t, len(refreshed.Teams), 2)\n\t\trefreshedTeamIds := make([]string, 2)\n\t\trefreshedTeamIds[0] = refreshed.Teams[0].ID\n\t\trefreshedTeamIds[1] = refreshed.Teams[1].ID\n\n\t\tassert.Contains(t, refreshedTeamIds, teamTest1.ID)\n\t\tassert.Contains(t, refreshedTeamIds, teamTest2.ID)\n\t})\n}\n\nfunc TestOrganizationMembershipsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tmemTest, memTestCleanup := createOrganizationMembership(t, client, nil)\n\tdefer memTestCleanup()\n\n\t// The create API endpoint automatically includes the related user, so we should drop\n\t// the additional parts of the user which get deserialized.\n\tmemTest.User = &User{\n\t\tID: memTest.User.ID,\n\t}\n\n\tt.Run(\"when the membership exists\", func(t *testing.T) {\n\t\tmem, err := client.OrganizationMemberships.Read(ctx, memTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, memTest, mem)\n\t})\n\n\tt.Run(\"when the membership does not exist\", func(t *testing.T) {\n\t\tmem, err := client.OrganizationMemberships.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, mem)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid membership id\", func(t *testing.T) {\n\t\tmem, err := client.OrganizationMemberships.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, mem)\n\t\tassert.Equal(t, err, ErrInvalidMembership)\n\t})\n}\n\nfunc TestOrganizationMembershipsReadWithOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tmemTest, memTestCleanup := createOrganizationMembership(t, client, nil)\n\tdefer memTestCleanup()\n\n\toptions := OrganizationMembershipReadOptions{\n\t\tInclude: []OrgMembershipIncludeOpt{OrgMembershipUser},\n\t}\n\n\tt.Run(\"when the membership exists\", func(t *testing.T) {\n\t\tmem, err := client.OrganizationMemberships.ReadWithOptions(ctx, memTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, memTest, mem)\n\t})\n\n\tt.Run(\"without options\", func(t *testing.T) {\n\t\t_, err := client.OrganizationMemberships.ReadWithOptions(ctx, memTest.ID, OrganizationMembershipReadOptions{})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with invalid include option\", func(t *testing.T) {\n\t\t_, err := client.OrganizationMemberships.ReadWithOptions(ctx, memTest.ID, OrganizationMembershipReadOptions{\n\t\t\tInclude: []OrgMembershipIncludeOpt{\"users\"},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidIncludeValue)\n\t})\n\n\tt.Run(\"when the membership does not exist\", func(t *testing.T) {\n\t\tmem, err := client.OrganizationMemberships.ReadWithOptions(ctx, \"nonexisting\", options)\n\t\tassert.Nil(t, mem)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid membership id\", func(t *testing.T) {\n\t\tmem, err := client.OrganizationMemberships.ReadWithOptions(ctx, badIdentifier, options)\n\t\tassert.Nil(t, mem)\n\t\tassert.Equal(t, err, ErrInvalidMembership)\n\t})\n}\n\nfunc TestOrganizationMembershipsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tmem, _ := createOrganizationMembership(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.OrganizationMemberships.Delete(ctx, mem.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.OrganizationMemberships.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.NotContains(t, refreshed.Items, mem)\n\t})\n\n\tt.Run(\"when membership is invalid\", func(t *testing.T) {\n\t\terr := client.OrganizationMemberships.Delete(ctx, badIdentifier)\n\n\t\tassert.Equal(t, err, ErrInvalidMembership)\n\t})\n\n\tt.Run(\"when an error is returned from the api\", func(t *testing.T) {\n\t\terr := client.OrganizationMemberships.Delete(ctx, \"not-an-identifier\")\n\n\t\tassert.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "organization_tags.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\nvar _ OrganizationTags = (*organizationTags)(nil)\n\n// OrganizationMemberships describes all the list of tags used with all resources across the organization.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organization-tags\ntype OrganizationTags interface {\n\t// List all tags within an organization\n\tList(ctx context.Context, organization string, options *OrganizationTagsListOptions) (*OrganizationTagsList, error)\n\n\t// Delete tags from an organization\n\tDelete(ctx context.Context, organization string, options OrganizationTagsDeleteOptions) error\n\n\t// Associate an organization's workspace with a tag\n\tAddWorkspaces(ctx context.Context, tag string, options AddWorkspacesToTagOptions) error\n}\n\n// organizationTags implements OrganizationTags.\ntype organizationTags struct {\n\tclient *Client\n}\n\n// OrganizationTagsList represents a list of organization tags\ntype OrganizationTagsList struct {\n\t*Pagination\n\tItems []*OrganizationTag\n}\n\n// OrganizationTag represents a Terraform Enterprise Organization tag\ntype OrganizationTag struct {\n\tID string `jsonapi:\"primary,tags\"`\n\t// Optional:\n\tName string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: Number of workspaces that have this tag\n\tInstanceCount int `jsonapi:\"attr,instance-count,omitempty\"`\n\n\t// The org this tag belongs to\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n}\n\n// OrganizationTagsListOptions represents the options for listing organization tags\ntype OrganizationTagsListOptions struct {\n\tListOptions\n\t// Optional:\n\tFilter string `url:\"filter[exclude][taggable][id],omitempty\"`\n\n\t// Optional: A search query string. Organization tags are searchable by name likeness.\n\tQuery string `url:\"q,omitempty\"`\n}\n\n// OrganizationTagsDeleteOptions represents the request body for deleting a tag in an organization\ntype OrganizationTagsDeleteOptions struct {\n\tIDs []string // Required\n}\n\n// AddWorkspacesToTagOptions represents the request body to add a workspace to a tag\ntype AddWorkspacesToTagOptions struct {\n\tWorkspaceIDs []string // Required\n}\n\n// this represents a single tag ID\ntype tagID struct {\n\tID string `jsonapi:\"primary,tags\"`\n}\n\n// this represents a single workspace ID\ntype workspaceID struct {\n\tID string `jsonapi:\"primary,workspaces\"`\n}\n\n// List all the tags in an organization. You can provide query params through OrganizationTagsListOptions\nfunc (s *organizationTags) List(ctx context.Context, organization string, options *OrganizationTagsListOptions) (*OrganizationTagsList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/tags\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttags := &OrganizationTagsList{}\n\terr = req.Do(ctx, tags)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tags, nil\n}\n\n// Delete tags from a Terraform Enterprise organization\nfunc (s *organizationTags) Delete(ctx context.Context, organization string, options OrganizationTagsDeleteOptions) error {\n\tif !validStringID(&organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/tags\", url.PathEscape(organization))\n\tvar tagsToRemove []*tagID\n\tfor _, id := range options.IDs {\n\t\ttagsToRemove = append(tagsToRemove, &tagID{ID: id})\n\t}\n\n\treq, err := s.client.NewRequest(\"DELETE\", u, tagsToRemove)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Add workspaces to a tag\nfunc (s *organizationTags) AddWorkspaces(ctx context.Context, tag string, options AddWorkspacesToTagOptions) error {\n\tif !validStringID(&tag) {\n\t\treturn ErrInvalidTag\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tvar workspaces []*workspaceID\n\tfor _, id := range options.WorkspaceIDs {\n\t\tworkspaces = append(workspaces, &workspaceID{ID: id})\n\t}\n\n\tu := fmt.Sprintf(\"tags/%s/relationships/workspaces\", url.PathEscape(tag))\n\treq, err := s.client.NewRequest(\"POST\", u, workspaces)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (opts *OrganizationTagsDeleteOptions) valid() error {\n\tif len(opts.IDs) == 0 {\n\t\treturn ErrRequiredTagID\n\t}\n\n\tfor _, id := range opts.IDs {\n\t\tif !validStringID(&id) {\n\t\t\terrorMsg := fmt.Sprintf(\"%s is not a valid id value\", id)\n\t\t\treturn errors.New(errorMsg)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (w *AddWorkspacesToTagOptions) valid() error {\n\tif len(w.WorkspaceIDs) == 0 {\n\t\treturn ErrRequiredTagWorkspaceID\n\t}\n\n\tfor _, id := range w.WorkspaceIDs {\n\t\tif !validStringID(&id) {\n\t\t\terrorMsg := fmt.Sprintf(\"%s is not a valid id value\", id)\n\t\t\treturn errors.New(errorMsg)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "organization_tags_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOrganizationTagsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tassert.NotNil(t, orgTest)\n\n\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer workspaceTestCleanup()\n\n\tassert.NotNil(t, workspaceTest)\n\n\tvar tags []*Tag\n\tfor i := 0; i < 10; i++ {\n\t\ttags = append(tags, &Tag{\n\t\t\tName: fmt.Sprintf(\"tag%d\", i),\n\t\t})\n\t}\n\n\terr := client.Workspaces.AddTags(ctx, workspaceTest.ID, WorkspaceAddTagsOptions{\n\t\tTags: tags,\n\t})\n\trequire.NoError(t, err)\n\n\t// this is a tag id we'll use in the filter param of the second test\n\tvar testTagID string\n\n\t// this is the tag Name we'll use with the query parameter in the third test\n\tvar testTagName string\n\n\tt.Run(\"with no query params\", func(t *testing.T) {\n\t\ttags, err := client.OrganizationTags.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 10, len(tags.Items))\n\n\t\ttestTagID = tags.Items[0].ID\n\t\ttestTagName = tags.Items[0].Name\n\n\t\tfor _, tag := range tags.Items {\n\t\t\tassert.NotNil(t, tag.ID)\n\t\t\tassert.NotNil(t, tag.Name)\n\t\t\tassert.GreaterOrEqual(t, tag.InstanceCount, 1)\n\n\t\t\tt.Run(\"ensure org relation is properly decoded\", func(t *testing.T) {\n\t\t\t\tassert.NotNil(t, tag.Organization)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"with query param Filter\", func(t *testing.T) {\n\t\ttags, err := client.OrganizationTags.List(ctx, orgTest.Name, &OrganizationTagsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   5,\n\t\t\t},\n\t\t\tFilter: testTagID,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 5, len(tags.Items))\n\n\t\tfor _, tag := range tags.Items {\n\t\t\t// ensure tag specified in filter param was omitted from results\n\t\t\tassert.NotNil(t, tag.ID, testTagID)\n\n\t\t\tt.Run(\"ensure org relation is properly decoded\", func(t *testing.T) {\n\t\t\t\tassert.NotNil(t, tag.Organization)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"with query param Query\", func(t *testing.T) {\n\t\ttags, err := client.OrganizationTags.List(ctx, orgTest.Name, &OrganizationTagsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   5,\n\t\t\t},\n\t\t\tQuery: testTagName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.Len(t, tags.Items, 1)\n\n\t\tassert.Equal(t, tags.Items[0].Name, testTagName)\n\t\tassert.NotNil(t, tags.Items[0].Organization)\n\t})\n}\n\nfunc TestOrganizationTagsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tassert.NotNil(t, orgTest)\n\n\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer workspaceTestCleanup()\n\n\tassert.NotNil(t, workspaceTest)\n\n\tvar tags []*Tag\n\tfor i := 0; i < 10; i++ {\n\t\ttags = append(tags, &Tag{\n\t\t\tName: fmt.Sprintf(\"tag%d\", i),\n\t\t})\n\t}\n\n\terr := client.Workspaces.AddTags(ctx, workspaceTest.ID, WorkspaceAddTagsOptions{\n\t\tTags: tags,\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"delete tags by id\", func(t *testing.T) {\n\t\ttags, err := client.OrganizationTags.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tvar tagIds []string\n\t\t// since we added 10 tags to the org, grab a subset\n\t\tfor i := 0; i < 5; i++ {\n\t\t\tassert.NotNil(t, tags.Items[i].ID)\n\t\t\ttagIds = append(tagIds, tags.Items[i].ID)\n\t\t}\n\n\t\terr = client.OrganizationTags.Delete(ctx, orgTest.Name, OrganizationTagsDeleteOptions{\n\t\t\tIDs: tagIds,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// sanity check ensure tags were deleted from the organization\n\t\ttags, err = client.OrganizationTags.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 5, len(tags.Items))\n\t})\n}\n\nfunc TestOrganizationTagsAddWorkspace(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tassert.NotNil(t, orgTest)\n\n\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer workspaceTestCleanup()\n\n\tassert.NotNil(t, workspaceTest)\n\n\tvar tags []*Tag\n\tfor i := 0; i < 2; i++ {\n\t\ttags = append(tags, &Tag{\n\t\t\tName: fmt.Sprintf(\"tag%d\", i),\n\t\t})\n\t}\n\n\terr := client.Workspaces.AddTags(ctx, workspaceTest.ID, WorkspaceAddTagsOptions{\n\t\tTags: tags,\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"add tags to new workspaces\", func(t *testing.T) {\n\t\t// fetch tag ids to associate to workspace\n\t\ttags, err := client.OrganizationTags.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\ttagID := tags.Items[0].ID\n\n\t\t// create the workspaces we'll use to associate tags\n\t\tworkspaceToAdd1, workspaceToAdd1Cleanup := createWorkspace(t, client, orgTest)\n\t\tdefer workspaceToAdd1Cleanup()\n\n\t\tworkspaceToAdd2, workspaceToAdd2Cleanup := createWorkspace(t, client, orgTest)\n\t\tdefer workspaceToAdd2Cleanup()\n\n\t\terr = client.OrganizationTags.AddWorkspaces(ctx, tagID, AddWorkspacesToTagOptions{\n\t\t\tWorkspaceIDs: []string{workspaceToAdd1.ID, workspaceToAdd2.ID},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// Ensure the tag was properly associated with the workspaces\n\t\tfetched, err := client.Workspaces.ListTags(ctx, workspaceToAdd1.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, fetched.Items[0].ID, tagID)\n\n\t\tfetched, err = client.Workspaces.ListTags(ctx, workspaceToAdd2.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, fetched.Items[0].ID, tagID)\n\t})\n}\n"
  },
  {
    "path": "organization_token.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ OrganizationTokens = (*organizationTokens)(nil)\n\ntype TokenType string\n\nconst (\n\t// A token which can only access the Audit Trails of an HCP Terraform Organization.\n\t// See https://developer.hashicorp.com/terraform/cloud-docs/api-docs/audit-trails-tokens\n\tAuditTrailToken TokenType = \"audit-trails\"\n)\n\n// OrganizationTokens describes all the organization token related methods\n// that the Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organization-tokens\ntype OrganizationTokens interface {\n\t// Create a new organization token, replacing any existing token.\n\tCreate(ctx context.Context, organization string) (*OrganizationToken, error)\n\n\t// CreateWithOptions a new organization token with options, replacing any existing token.\n\tCreateWithOptions(ctx context.Context, organization string, options OrganizationTokenCreateOptions) (*OrganizationToken, error)\n\n\t// Read an organization token.\n\tRead(ctx context.Context, organization string) (*OrganizationToken, error)\n\n\t// Read an organization token with options.\n\tReadWithOptions(ctx context.Context, organization string, options OrganizationTokenReadOptions) (*OrganizationToken, error)\n\n\t// Delete an organization token.\n\tDelete(ctx context.Context, organization string) error\n\n\t// Delete an organization token with options.\n\tDeleteWithOptions(ctx context.Context, organization string, options OrganizationTokenDeleteOptions) error\n}\n\n// organizationTokens implements OrganizationTokens.\ntype organizationTokens struct {\n\tclient *Client\n}\n\n// OrganizationToken represents a Terraform Enterprise organization token.\ntype OrganizationToken struct {\n\tID          string           `jsonapi:\"primary,authentication-tokens\"`\n\tCreatedAt   time.Time        `jsonapi:\"attr,created-at,iso8601\"`\n\tDescription string           `jsonapi:\"attr,description\"`\n\tLastUsedAt  time.Time        `jsonapi:\"attr,last-used-at,iso8601\"`\n\tToken       string           `jsonapi:\"attr,token\"`\n\tExpiredAt   time.Time        `jsonapi:\"attr,expired-at,iso8601\"`\n\tCreatedBy   *CreatedByChoice `jsonapi:\"polyrelation,created-by\"`\n}\n\n// OrganizationTokenCreateOptions contains the options for creating an organization token.\ntype OrganizationTokenCreateOptions struct {\n\t// Optional: The token's expiration date.\n\t// This feature is available in TFE release v202305-1 and later\n\tExpiredAt *time.Time `jsonapi:\"attr,expired-at,iso8601,omitempty\" url:\"-\"`\n\t// Optional: What type of token to create\n\t// This option is only applicable to HCP Terraform and is ignored by TFE.\n\tTokenType *TokenType `url:\"token,omitempty\"`\n}\n\n// OrganizationTokenReadOptions contains the options for reading an organization token.\ntype OrganizationTokenReadOptions struct {\n\t// Optional: What type of token to read\n\t// This option is only applicable to HCP Terraform and is ignored by TFE.\n\tTokenType *TokenType `url:\"token,omitempty\"`\n}\n\n// OrganizationTokenDeleteOptions contains the options for deleting an organization token.\ntype OrganizationTokenDeleteOptions struct {\n\t// Optional: What type of token to delete\n\t// This option is only applicable to HCP Terraform and is ignored by TFE.\n\tTokenType *TokenType `url:\"token,omitempty\"`\n}\n\n// Create a new organization token, replacing any existing token.\nfunc (s *organizationTokens) Create(ctx context.Context, organization string) (*OrganizationToken, error) {\n\treturn s.CreateWithOptions(ctx, organization, OrganizationTokenCreateOptions{})\n}\n\n// CreateWithOptions a new organization token with options, replacing any existing token.\nfunc (s *organizationTokens) CreateWithOptions(ctx context.Context, organization string, options OrganizationTokenCreateOptions) (*OrganizationToken, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/authentication-token\", url.PathEscape(organization))\n\tqp, err := decodeQueryParams(options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequestWithAdditionalQueryParams(\"POST\", u, &options, qp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tot := &OrganizationToken{}\n\terr = req.Do(ctx, ot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ot, err\n}\n\n// Read an organization token.\nfunc (s *organizationTokens) Read(ctx context.Context, organization string) (*OrganizationToken, error) {\n\treturn s.ReadWithOptions(ctx, organization, OrganizationTokenReadOptions{})\n}\n\n// Read an organization token with options.\nfunc (s *organizationTokens) ReadWithOptions(ctx context.Context, organization string, options OrganizationTokenReadOptions) (*OrganizationToken, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/authentication-token\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tot := &OrganizationToken{}\n\terr = req.Do(ctx, ot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ot, err\n}\n\n// Delete an organization token.\nfunc (s *organizationTokens) Delete(ctx context.Context, organization string) error {\n\treturn s.DeleteWithOptions(ctx, organization, OrganizationTokenDeleteOptions{})\n}\n\n// Delete an organization token with options\nfunc (s *organizationTokens) DeleteWithOptions(ctx context.Context, organization string, options OrganizationTokenDeleteOptions) error {\n\tif !validStringID(&organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/authentication-token\", url.PathEscape(organization))\n\tqp, err := decodeQueryParams(options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq, err := s.client.NewRequestWithAdditionalQueryParams(\"DELETE\", u, nil, qp)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "organization_token_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOrganizationTokensCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tvar tkToken string\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.Create(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, ot.Token)\n\t\trequireExactlyOneNotEmpty(t, ot.CreatedBy.Organization, ot.CreatedBy.Team, ot.CreatedBy.User)\n\t\ttkToken = ot.Token\n\t})\n\n\tt.Run(\"when a token already exists\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.Create(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, ot.Token)\n\t\tassert.NotEqual(t, tkToken, ot.Token)\n\t})\n\n\tt.Run(\"without valid organization\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.Create(ctx, badIdentifier)\n\t\tassert.Nil(t, ot)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestOrganizationTokens_CreateWithOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\t// We need to update the organization to business so we can create an audit trails token later.\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tvar tkToken string\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.CreateWithOptions(ctx, orgTest.Name, OrganizationTokenCreateOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, ot.Token)\n\t\ttkToken = ot.Token\n\t})\n\n\tt.Run(\"when a token already exists\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.CreateWithOptions(ctx, orgTest.Name, OrganizationTokenCreateOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, ot.Token)\n\t\tassert.NotEqual(t, tkToken, ot.Token)\n\t})\n\n\tt.Run(\"without valid organization\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.CreateWithOptions(ctx, badIdentifier, OrganizationTokenCreateOptions{})\n\t\tassert.Nil(t, ot)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"without an expiration date\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.CreateWithOptions(ctx, orgTest.Name, OrganizationTokenCreateOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, ot.Token)\n\t\tassert.NotZero(t, ot.ExpiredAt)\n\t\texpectedExpiry := ot.CreatedAt.AddDate(defaultTokenExpirationYears, 0, 0)\n\t\t// Allow a small buffer (1 minute) for timestamp precision differences.\n\t\tassert.WithinDuration(t, expectedExpiry, ot.ExpiredAt, time.Minute)\n\t\ttkToken = ot.Token\n\t})\n\n\tt.Run(\"with an expiration date\", func(t *testing.T) {\n\t\tcurrentTime := time.Now().UTC().Truncate(time.Second)\n\t\toneDayLater := currentTime.Add(24 * time.Hour)\n\t\tot, err := client.OrganizationTokens.CreateWithOptions(ctx, orgTest.Name, OrganizationTokenCreateOptions{\n\t\t\tExpiredAt: &oneDayLater,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, ot.Token)\n\t\tassert.Equal(t, ot.ExpiredAt, oneDayLater)\n\t\ttkToken = ot.Token\n\t})\n\n\tt.Run(\"with a token type\", func(t *testing.T) {\n\t\ttt := AuditTrailToken\n\t\tot, err := client.OrganizationTokens.CreateWithOptions(ctx, orgTest.Name, OrganizationTokenCreateOptions{\n\t\t\tTokenType: &tt,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, ot.Token)\n\t})\n}\n\nfunc TestOrganizationTokensRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\t_, otTestCleanup := createOrganizationToken(t, client, orgTest)\n\n\t\tot, err := client.OrganizationTokens.Read(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, ot)\n\n\t\totTestCleanup()\n\t})\n\n\tt.Run(\"with an expiration date passed as a valid option\", func(t *testing.T) {\n\t\tcurrentTime := time.Now().UTC().Truncate(time.Second)\n\t\toneDayLater := currentTime.Add(24 * time.Hour)\n\n\t\t_, otTestCleanup := createOrganizationTokenWithOptions(t, client, orgTest, OrganizationTokenCreateOptions{ExpiredAt: &oneDayLater})\n\n\t\tot, err := client.OrganizationTokens.Read(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, ot)\n\t\tassert.Equal(t, ot.ExpiredAt, oneDayLater)\n\n\t\totTestCleanup()\n\t})\n\n\tt.Run(\"when a token doesn't exists\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.Read(ctx, orgTest.Name)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t\tassert.Nil(t, ot)\n\t})\n\n\tt.Run(\"without valid organization\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, ot)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestOrganizationTokensReadWithOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\t// We need to update the organization to business so we can create an audit trails token later.\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\ttt := AuditTrailToken\n\tnoTypeToken, _ := createOrganizationToken(t, client, orgTest)\n\tauditTypeToken, _ := createOrganizationTokenWithOptions(t, client, orgTest, OrganizationTokenCreateOptions{TokenType: &tt})\n\n\tt.Run(\"with empty options\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.ReadWithOptions(ctx, orgTest.Name, OrganizationTokenReadOptions{})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, ot)\n\t\tassert.Equal(t, ot.ID, noTypeToken.ID)\n\t})\n\n\tt.Run(\"with a specific token type\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.ReadWithOptions(ctx, orgTest.Name, OrganizationTokenReadOptions{TokenType: &tt})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, ot)\n\t\tassert.Equal(t, ot.ID, auditTypeToken.ID)\n\t})\n\n\tt.Run(\"without valid organization\", func(t *testing.T) {\n\t\tot, err := client.OrganizationTokens.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, ot)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestOrganizationTokensDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tcreateOrganizationToken(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.OrganizationTokens.Delete(ctx, orgTest.Name)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when a token does not exist\", func(t *testing.T) {\n\t\terr := client.OrganizationTokens.Delete(ctx, orgTest.Name)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without valid organization\", func(t *testing.T) {\n\t\terr := client.OrganizationTokens.Delete(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestOrganizationTokensDeleteWithOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\t// We need to update the organization to business so we can create an audit trails token later.\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tt.Run(\"with a token type\", func(t *testing.T) {\n\t\t// Create the token\n\t\ttt := AuditTrailToken\n\t\t_, err := client.OrganizationTokens.CreateWithOptions(ctx, orgTest.Name, OrganizationTokenCreateOptions{\n\t\t\tTokenType: &tt,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// Delete it\n\t\tdeleteOptions := OrganizationTokenDeleteOptions{\n\t\t\tTokenType: &tt,\n\t\t}\n\t\terr = client.OrganizationTokens.DeleteWithOptions(ctx, orgTest.Name, deleteOptions)\n\t\trequire.NoError(t, err)\n\n\t\t// Reload the token\n\t\tot, err := client.OrganizationTokens.ReadWithOptions(ctx, orgTest.Name, OrganizationTokenReadOptions{\n\t\t\tTokenType: &tt,\n\t\t})\n\t\t// ... it should fail\n\t\tassert.Nil(t, ot)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\n\t\t// Delete it again\n\t\terr = client.OrganizationTokens.DeleteWithOptions(ctx, orgTest.Name, deleteOptions)\n\t\t// ... it should fail\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without valid organization\", func(t *testing.T) {\n\t\tdeleteOptions := OrganizationTokenDeleteOptions{}\n\t\terr := client.OrganizationTokens.DeleteWithOptions(ctx, badIdentifier, deleteOptions)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n"
  },
  {
    "path": "plan.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Plans = (*plans)(nil)\n\n// Plans describes all the plan related methods that the Terraform Enterprise\n// API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/plans\ntype Plans interface {\n\t// Read a plan by its ID.\n\tRead(ctx context.Context, planID string) (*Plan, error)\n\n\t// Logs retrieves the logs of a plan.\n\tLogs(ctx context.Context, planID string) (io.Reader, error)\n\n\t// Retrieve the JSON execution plan\n\tReadJSONOutput(ctx context.Context, planID string) ([]byte, error)\n}\n\n// plans implements Plans.\ntype plans struct {\n\tclient *Client\n}\n\n// PlanStatus represents a plan state.\ntype PlanStatus string\n\n// List all available plan statuses.\nconst (\n\tPlanCanceled    PlanStatus = \"canceled\"\n\tPlanCreated     PlanStatus = \"created\"\n\tPlanErrored     PlanStatus = \"errored\"\n\tPlanFinished    PlanStatus = \"finished\"\n\tPlanMFAWaiting  PlanStatus = \"mfa_waiting\"\n\tPlanPending     PlanStatus = \"pending\"\n\tPlanQueued      PlanStatus = \"queued\"\n\tPlanRunning     PlanStatus = \"running\"\n\tPlanUnreachable PlanStatus = \"unreachable\"\n)\n\n// Plan represents a Terraform Enterprise plan.\ntype Plan struct {\n\tID                     string                `jsonapi:\"primary,plans\"`\n\tHasChanges             bool                  `jsonapi:\"attr,has-changes\"`\n\tGeneratedConfiguration bool                  `jsonapi:\"attr,generated-configuration\"`\n\tLogReadURL             string                `jsonapi:\"attr,log-read-url\"`\n\tResourceAdditions      int                   `jsonapi:\"attr,resource-additions\"`\n\tResourceChanges        int                   `jsonapi:\"attr,resource-changes\"`\n\tResourceDestructions   int                   `jsonapi:\"attr,resource-destructions\"`\n\tResourceImports        int                   `jsonapi:\"attr,resource-imports\"`\n\tStatus                 PlanStatus            `jsonapi:\"attr,status\"`\n\tStatusTimestamps       *PlanStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\n\t// Relations\n\tExports              []*PlanExport         `jsonapi:\"relation,exports\"`\n\tHYOKEncryptedDataKey *HYOKEncryptedDataKey `jsonapi:\"relation,hyok-encrypted-data-key\"`\n\n\t// Links\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n}\n\n// PlanStatusTimestamps holds the timestamps for individual plan statuses.\ntype PlanStatusTimestamps struct {\n\tCanceledAt      time.Time `jsonapi:\"attr,canceled-at,rfc3339\"`\n\tErroredAt       time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tFinishedAt      time.Time `jsonapi:\"attr,finished-at,rfc3339\"`\n\tForceCanceledAt time.Time `jsonapi:\"attr,force-canceled-at,rfc3339\"`\n\tQueuedAt        time.Time `jsonapi:\"attr,queued-at,rfc3339\"`\n\tStartedAt       time.Time `jsonapi:\"attr,started-at,rfc3339\"`\n}\n\n// Read a plan by its ID.\nfunc (s *plans) Read(ctx context.Context, planID string) (*Plan, error) {\n\tif !validStringID(&planID) {\n\t\treturn nil, ErrInvalidPlanID\n\t}\n\n\tu := fmt.Sprintf(\"plans/%s\", url.PathEscape(planID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &Plan{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// Logs retrieves the logs of a plan.\nfunc (s *plans) Logs(ctx context.Context, planID string) (io.Reader, error) {\n\tif !validStringID(&planID) {\n\t\treturn nil, ErrInvalidPlanID\n\t}\n\n\t// Get the plan to make sure it exists.\n\tp, err := s.Read(ctx, planID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Return an error if the log URL is empty.\n\tif p.LogReadURL == \"\" {\n\t\treturn nil, fmt.Errorf(\"plan %s does not have a log URL\", planID)\n\t}\n\n\tu, err := url.Parse(p.LogReadURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid log URL: %w\", err)\n\t}\n\n\tdone := func() (bool, error) {\n\t\tp, err := s.Read(ctx, p.ID)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tswitch p.Status {\n\t\tcase PlanCanceled, PlanErrored, PlanFinished, PlanUnreachable:\n\t\t\treturn true, nil\n\t\tdefault:\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\treturn &LogReader{\n\t\tclient: s.client,\n\t\tctx:    ctx,\n\t\tdone:   done,\n\t\tlogURL: u,\n\t}, nil\n}\n\n// Retrieve the JSON execution plan\nfunc (s *plans) ReadJSONOutput(ctx context.Context, planID string) ([]byte, error) {\n\tif !validStringID(&planID) {\n\t\treturn nil, ErrInvalidPlanID\n\t}\n\n\tu := fmt.Sprintf(\"plans/%s/json-output\", url.PathEscape(planID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar buf bytes.Buffer\n\terr = req.Do(ctx, &buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n"
  },
  {
    "path": "plan_export.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ PlanExports = (*planExports)(nil)\n\n// PlanExports describes all the plan export related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/plan-exports\ntype PlanExports interface {\n\t// Export a plan by its ID with the given options.\n\tCreate(ctx context.Context, options PlanExportCreateOptions) (*PlanExport, error)\n\n\t// Read a plan export by its ID.\n\tRead(ctx context.Context, planExportID string) (*PlanExport, error)\n\n\t// Delete a plan export by its ID.\n\tDelete(ctx context.Context, planExportID string) error\n\n\t// Download the data of an plan export.\n\tDownload(ctx context.Context, planExportID string) ([]byte, error)\n}\n\n// planExports implements PlanExports.\ntype planExports struct {\n\tclient *Client\n}\n\n// PlanExportDataType represents the type of data exported from a plan.\ntype PlanExportDataType string\n\n// List all available plan export data types.\nconst (\n\tPlanExportSentinelMockBundleV0 PlanExportDataType = \"sentinel-mock-bundle-v0\"\n)\n\n// PlanExportStatus represents a plan export state.\ntype PlanExportStatus string\n\n// List all available plan export statuses.\nconst (\n\tPlanExportCanceled PlanExportStatus = \"canceled\"\n\tPlanExportErrored  PlanExportStatus = \"errored\"\n\tPlanExportExpired  PlanExportStatus = \"expired\"\n\tPlanExportFinished PlanExportStatus = \"finished\"\n\tPlanExportPending  PlanExportStatus = \"pending\"\n\tPlanExportQueued   PlanExportStatus = \"queued\"\n)\n\n// PlanExportStatusTimestamps holds the timestamps for plan export statuses.\ntype PlanExportStatusTimestamps struct {\n\tCanceledAt time.Time `jsonapi:\"attr,canceled-at,rfc3339\"`\n\tErroredAt  time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tExpiredAt  time.Time `jsonapi:\"attr,expired-at,rfc3339\"`\n\tFinishedAt time.Time `jsonapi:\"attr,finished-at,rfc3339\"`\n\tQueuedAt   time.Time `jsonapi:\"attr,queued-at,rfc3339\"`\n}\n\n// PlanExport represents an export of Terraform Enterprise plan data.\ntype PlanExport struct {\n\tID               string                      `jsonapi:\"primary,plan-exports\"`\n\tDataType         PlanExportDataType          `jsonapi:\"attr,data-type\"`\n\tStatus           PlanExportStatus            `jsonapi:\"attr,status\"`\n\tStatusTimestamps *PlanExportStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n}\n\n// PlanExportCreateOptions represents the options for exporting data from a plan.\ntype PlanExportCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,plan-exports\"`\n\n\t// Required: The plan to export.\n\tPlan *Plan `jsonapi:\"relation,plan\"`\n\n\t// Required: The name of the policy set.\n\tDataType *PlanExportDataType `jsonapi:\"attr,data-type\"`\n}\n\n// Create a plan export\nfunc (s *planExports) Create(ctx context.Context, options PlanExportCreateOptions) (*PlanExport, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", \"plan-exports\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpe := &PlanExport{}\n\terr = req.Do(ctx, pe)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pe, err\n}\n\n// Read a plan export by its ID.\nfunc (s *planExports) Read(ctx context.Context, planExportID string) (*PlanExport, error) {\n\tif !validStringID(&planExportID) {\n\t\treturn nil, ErrInvalidPlanExportID\n\t}\n\n\tu := fmt.Sprintf(\"plan-exports/%s\", url.PathEscape(planExportID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpe := &PlanExport{}\n\terr = req.Do(ctx, pe)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pe, nil\n}\n\n// Delete a plan export by ID.\nfunc (s *planExports) Delete(ctx context.Context, planExportID string) error {\n\tif !validStringID(&planExportID) {\n\t\treturn ErrInvalidPlanExportID\n\t}\n\n\tu := fmt.Sprintf(\"plan-exports/%s\", url.PathEscape(planExportID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Download a plan export's data. Data is exported in a .tar.gz format.\nfunc (s *planExports) Download(ctx context.Context, planExportID string) ([]byte, error) {\n\tif !validStringID(&planExportID) {\n\t\treturn nil, ErrInvalidPlanExportID\n\t}\n\n\tu := fmt.Sprintf(\"plan-exports/%s/download\", url.PathEscape(planExportID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar buf bytes.Buffer\n\terr = req.Do(ctx, &buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\nfunc (o PlanExportCreateOptions) valid() error {\n\tif o.Plan == nil {\n\t\treturn ErrRequiredPlan\n\t}\n\tif o.DataType == nil {\n\t\treturn ErrRequiredDataType\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "plan_export_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPlanExportsCreate_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\trTest, rTestCleanup := createPlannedRun(t, client, nil)\n\tdefer rTestCleanup()\n\n\tpTest, err := client.Plans.Read(ctx, rTest.Plan.ID)\n\trequire.NoError(t, err)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := PlanExportCreateOptions{\n\t\t\tPlan:     pTest,\n\t\t\tDataType: PlanExportType(PlanExportSentinelMockBundleV0),\n\t\t}\n\n\t\tpe, err := client.PlanExports.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, pe.ID)\n\t\tassert.Equal(t, PlanExportSentinelMockBundleV0, pe.DataType)\n\t})\n\n\tt.Run(\"without a plan\", func(t *testing.T) {\n\t\toptions := PlanExportCreateOptions{\n\t\t\tPlan:     nil,\n\t\t\tDataType: PlanExportType(PlanExportSentinelMockBundleV0),\n\t\t}\n\n\t\tpe, err := client.PlanExports.Create(ctx, options)\n\t\tassert.Nil(t, pe)\n\t\tassert.Equal(t, err, ErrRequiredPlan)\n\t})\n\n\tt.Run(\"without a data type\", func(t *testing.T) {\n\t\toptions := PlanExportCreateOptions{\n\t\t\tPlan:     pTest,\n\t\t\tDataType: nil,\n\t\t}\n\n\t\tpe, err := client.PlanExports.Create(ctx, options)\n\t\tassert.Nil(t, pe)\n\t\tassert.Equal(t, err, ErrRequiredDataType)\n\t})\n}\n\nfunc TestPlanExportsRead_RunDependent(t *testing.T) {\n\t// TODO: Investigate why this test keeps tripping the test suite timeout\n\tt.Skip()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpeTest, peTestCleanup := createPlanExport(t, client, nil)\n\tdefer peTestCleanup()\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\tpe, err := client.PlanExports.Read(ctx, peTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, peTest.ID, pe.ID)\n\t\tassert.Equal(t, peTest.DataType, pe.DataType)\n\t\tassert.NotEmpty(t, pe.StatusTimestamps)\n\t\tassert.NotNil(t, pe.StatusTimestamps.QueuedAt)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\tpe, err := client.PlanExports.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, pe)\n\t\tassert.Equal(t, err, ErrInvalidPlanExportID)\n\t})\n}\n\nfunc TestPlanExportsDelete_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpeTest, peTestCleanup := createPlanExport(t, client, nil)\n\tdefer peTestCleanup()\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\terr := client.PlanExports.Delete(ctx, peTest.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the export does not exist\", func(t *testing.T) {\n\t\terr := client.Policies.Delete(ctx, \"pe-doesntexist\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PlanExports.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidPlanExportID)\n\t})\n}\n\nfunc TestPlanExportsDownload_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpeTest, peCleanup := createPlanExport(t, client, nil)\n\tdefer peCleanup()\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\tpe, err := client.PlanExports.Download(ctx, peTest.ID)\n\t\tassert.NotNil(t, pe)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\tpe, err := client.PlanExports.Download(ctx, badIdentifier)\n\t\tassert.Nil(t, pe)\n\t\tassert.Equal(t, err, ErrInvalidPlanExportID)\n\t})\n}\n\nfunc TestPlanExport_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"plan-exports\",\n\t\t\t\"id\":   \"1\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"data-type\": PlanExportSentinelMockBundleV0,\n\t\t\t\t\"status\":    PlanExportCanceled,\n\t\t\t\t\"status-timestamps\": map[string]string{\n\t\t\t\t\t\"queued-at\":  \"2020-03-16T23:15:59+00:00\",\n\t\t\t\t\t\"errored-at\": \"2019-03-16T23:23:59+00:00\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\tpe := &PlanExport{}\n\terr = unmarshalResponse(responseBody, pe)\n\trequire.NoError(t, err)\n\n\tqueuedParsedTime, err := time.Parse(time.RFC3339, \"2020-03-16T23:15:59+00:00\")\n\trequire.NoError(t, err)\n\terroredParsedTime, err := time.Parse(time.RFC3339, \"2019-03-16T23:23:59+00:00\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, pe.DataType, PlanExportSentinelMockBundleV0)\n\tassert.Equal(t, pe.Status, PlanExportCanceled)\n\tassert.NotEmpty(t, pe.StatusTimestamps)\n\tassert.Equal(t, pe.StatusTimestamps.QueuedAt, queuedParsedTime)\n\tassert.Equal(t, pe.StatusTimestamps.ErroredAt, erroredParsedTime)\n}\n"
  },
  {
    "path": "plan_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPlansRead_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\trTest, rTestCleanup := createPlannedRun(t, client, nil)\n\tdefer rTestCleanup()\n\n\tt.Run(\"when the plan exists\", func(t *testing.T) {\n\t\tp, err := client.Plans.Read(ctx, rTest.Plan.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, p.HasChanges)\n\t\tassert.NotEmpty(t, p.LogReadURL)\n\t\tassert.Equal(t, p.Status, PlanFinished)\n\t\tassert.NotEmpty(t, p.StatusTimestamps)\n\t\tassert.NotNil(t, p.StatusTimestamps.StartedAt)\n\t})\n\n\tt.Run(\"when the plan does not exist\", func(t *testing.T) {\n\t\tp, err := client.Plans.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid plan ID\", func(t *testing.T) {\n\t\tp, err := client.Plans.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrInvalidPlanID)\n\t})\n\n\tt.Run(\"read hyok encrypted data key of a plan\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid plan ID that has a hyok encrypted data key\n\t\thyokPlanID := os.Getenv(\"HYOK_PLAN_ID\")\n\t\tif hyokPlanID == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_PLAN_ID before running this test!\")\n\t\t}\n\n\t\tp, err := client.Plans.Read(ctx, hyokPlanID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, p.HYOKEncryptedDataKey)\n\t})\n\n\tt.Run(\"read sanitized plan of a plan\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid plan ID that has a sanitized plan link\n\t\thyokPlanID := os.Getenv(\"HYOK_PLAN_ID\")\n\t\tif hyokPlanID == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_PLAN_ID before running this test!\")\n\t\t}\n\n\t\tp, err := client.Plans.Read(ctx, hyokPlanID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, p.Links[\"sanitized-plan\"])\n\t})\n}\n\nfunc TestPlansLogs_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\trTest, rTestCleanup := createPlannedRun(t, client, nil)\n\tdefer rTestCleanup()\n\n\tt.Run(\"when the log exists\", func(t *testing.T) {\n\t\tp, err := client.Plans.Read(ctx, rTest.Plan.ID)\n\t\trequire.NoError(t, err)\n\n\t\tlogReader, err := client.Plans.Logs(ctx, p.ID)\n\t\trequire.NoError(t, err)\n\n\t\tlogs, err := io.ReadAll(logReader)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Contains(t, string(logs), \"1 to add, 0 to change, 0 to destroy\")\n\t})\n\n\tt.Run(\"when the log does not exist\", func(t *testing.T) {\n\t\tlogs, err := client.Plans.Logs(ctx, \"nonexisting\")\n\t\tassert.Nil(t, logs)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestPlan_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"plans\",\n\t\t\t\"id\":   \"1\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"has-changes\":           true,\n\t\t\t\t\"log-read-url\":          \"hashicorp.com\",\n\t\t\t\t\"resource-additions\":    1,\n\t\t\t\t\"resource-changes\":      1,\n\t\t\t\t\"resource-destructions\": 1,\n\t\t\t\t\"status\":                PlanCanceled,\n\t\t\t\t\"status-timestamps\": map[string]string{\n\t\t\t\t\t\"queued-at\":  \"2020-03-16T23:15:59+00:00\",\n\t\t\t\t\t\"errored-at\": \"2019-03-16T23:23:59+00:00\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\tplan := &Plan{}\n\terr = unmarshalResponse(responseBody, plan)\n\trequire.NoError(t, err)\n\n\tqueuedParsedTime, err := time.Parse(time.RFC3339, \"2020-03-16T23:15:59+00:00\")\n\trequire.NoError(t, err)\n\terroredParsedTime, err := time.Parse(time.RFC3339, \"2019-03-16T23:23:59+00:00\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, plan.HasChanges, true)\n\tassert.Equal(t, plan.LogReadURL, \"hashicorp.com\")\n\tassert.Equal(t, plan.ResourceAdditions, 1)\n\tassert.Equal(t, plan.ResourceChanges, 1)\n\tassert.Equal(t, plan.ResourceDestructions, 1)\n\tassert.Equal(t, plan.Status, PlanCanceled)\n\tassert.NotEmpty(t, plan.StatusTimestamps)\n\tassert.Equal(t, plan.StatusTimestamps.QueuedAt, queuedParsedTime)\n\tassert.Equal(t, plan.StatusTimestamps.ErroredAt, erroredParsedTime)\n}\n\nfunc TestPlansJSONOutput_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\trTest, rTestCleanup := createPlannedRun(t, client, nil)\n\tdefer rTestCleanup()\n\n\tt.Run(\"when the JSON output exists\", func(t *testing.T) {\n\t\td, err := client.Plans.ReadJSONOutput(ctx, rTest.Plan.ID)\n\t\trequire.NoError(t, err)\n\t\tvar m map[string]interface{}\n\t\terr = json.Unmarshal(d, &m)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, m, \"planned_values\")\n\t\tassert.Contains(t, m, \"terraform_version\")\n\t})\n\n\tt.Run(\"when the JSON output does not exist\", func(t *testing.T) {\n\t\td, err := client.Plans.ReadJSONOutput(ctx, \"nonexisting\")\n\t\tassert.Nil(t, d)\n\t\tassert.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "policy.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Policies = (*policies)(nil)\n\n// Policies describes all the policy related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policies\ntype Policies interface {\n\t// List all the policies for a given organization\n\tList(ctx context.Context, organization string, options *PolicyListOptions) (*PolicyList, error)\n\n\t// Create a policy and associate it with an organization.\n\tCreate(ctx context.Context, organization string, options PolicyCreateOptions) (*Policy, error)\n\n\t// Read a policy by its ID.\n\tRead(ctx context.Context, policyID string) (*Policy, error)\n\n\t// Update an existing policy.\n\tUpdate(ctx context.Context, policyID string, options PolicyUpdateOptions) (*Policy, error)\n\n\t// Delete a policy by its ID.\n\tDelete(ctx context.Context, policyID string) error\n\n\t// Upload the policy content of the policy.\n\tUpload(ctx context.Context, policyID string, content []byte) error\n\n\t// Download the policy content of the policy.\n\tDownload(ctx context.Context, policyID string) ([]byte, error)\n}\n\n// policies implements Policies.\ntype policies struct {\n\tclient *Client\n}\n\n// EnforcementLevel represents an enforcement level.\ntype EnforcementLevel string\n\n// List the available enforcement types.\nconst (\n\tEnforcementAdvisory  EnforcementLevel = \"advisory\"\n\tEnforcementHard      EnforcementLevel = \"hard-mandatory\"\n\tEnforcementSoft      EnforcementLevel = \"soft-mandatory\"\n\tEnforcementMandatory EnforcementLevel = \"mandatory\"\n)\n\n// PolicyList represents a list of policies..\ntype PolicyList struct {\n\t*Pagination\n\tItems []*Policy\n}\n\n// Policy represents a Terraform Enterprise policy.\ntype Policy struct {\n\tID          string     `jsonapi:\"primary,policies\"`\n\tName        string     `jsonapi:\"attr,name\"`\n\tKind        PolicyKind `jsonapi:\"attr,kind\"`\n\tQuery       *string    `jsonapi:\"attr,query\"`\n\tDescription string     `jsonapi:\"attr,description\"`\n\t// Deprecated: Use EnforcementLevel instead.\n\tEnforce          []*Enforcement   `jsonapi:\"attr,enforce\"`\n\tEnforcementLevel EnforcementLevel `jsonapi:\"attr,enforcement-level\"`\n\tPolicySetCount   int              `jsonapi:\"attr,policy-set-count\"`\n\tUpdatedAt        time.Time        `jsonapi:\"attr,updated-at,iso8601\"`\n\n\t// Relations\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n}\n\n// Enforcement describes a enforcement.\ntype Enforcement struct {\n\tPath string           `jsonapi:\"attr,path\"`\n\tMode EnforcementLevel `jsonapi:\"attr,mode\"`\n}\n\n// EnforcementOptions represents the enforcement options of a policy.\ntype EnforcementOptions struct {\n\tPath *string           `json:\"path\"`\n\tMode *EnforcementLevel `json:\"mode\"`\n}\n\n// PolicyListOptions represents the options for listing policies.\ntype PolicyListOptions struct {\n\tListOptions\n\n\t// Optional: A search string (partial policy name) used to filter the results.\n\tSearch string `url:\"search[name],omitempty\"`\n\n\t// Optional: A kind string used to filter the results by the policy kind.\n\tKind PolicyKind `url:\"filter[kind],omitempty\"`\n}\n\n// PolicyCreateOptions represents the options for creating a new policy.\ntype PolicyCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,policies\"`\n\n\t// Required: The name of the policy.\n\tName *string `jsonapi:\"attr,name\"`\n\n\t// Optional: The underlying technology that the policy supports. Defaults to Sentinel if not specified for PolicyCreate.\n\tKind PolicyKind `jsonapi:\"attr,kind,omitempty\"`\n\n\t// Optional: The query passed to policy evaluation to determine the result of the policy. Only valid for OPA.\n\tQuery *string `jsonapi:\"attr,query,omitempty\"`\n\n\t// Optional: A description of the policy's purpose.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// The enforcements of the policy.\n\t//\n\t// Deprecated: Use EnforcementLevel instead.\n\tEnforce []*EnforcementOptions `jsonapi:\"attr,enforce,omitempty\"`\n\n\t// Required: The enforcement level of the policy.\n\t// Either EnforcementLevel or Enforce must be set.\n\tEnforcementLevel *EnforcementLevel `jsonapi:\"attr,enforcement-level,omitempty\"`\n}\n\n// PolicyUpdateOptions represents the options for updating a policy.\ntype PolicyUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,policies\"`\n\n\t// Optional: A description of the policy's purpose.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Optional: The query passed to policy evaluation to determine the result of the policy. Only valid for OPA.\n\tQuery *string `jsonapi:\"attr,query,omitempty\"`\n\n\t// Optional: The enforcements of the policy.\n\t//\n\t// Deprecated: Use EnforcementLevel instead.\n\tEnforce []*EnforcementOptions `jsonapi:\"attr,enforce,omitempty\"`\n\n\t// Optional: The enforcement level of the policy.\n\tEnforcementLevel *EnforcementLevel `jsonapi:\"attr,enforcement-level,omitempty\"`\n}\n\n// List all the policies for a given organization\nfunc (s *policies) List(ctx context.Context, organization string, options *PolicyListOptions) (*PolicyList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/policies\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpl := &PolicyList{}\n\terr = req.Do(ctx, pl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pl, nil\n}\n\n// Create a policy and associate it with an organization.\nfunc (s *policies) Create(ctx context.Context, organization string, options PolicyCreateOptions) (*Policy, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/policies\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &Policy{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, err\n}\n\n// Read a policy by its ID.\nfunc (s *policies) Read(ctx context.Context, policyID string) (*Policy, error) {\n\tif !validStringID(&policyID) {\n\t\treturn nil, ErrInvalidPolicyID\n\t}\n\n\tu := fmt.Sprintf(\"policies/%s\", url.PathEscape(policyID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &Policy{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, err\n}\n\n// Update an existing policy.\nfunc (s *policies) Update(ctx context.Context, policyID string, options PolicyUpdateOptions) (*Policy, error) {\n\tif !validStringID(&policyID) {\n\t\treturn nil, ErrInvalidPolicyID\n\t}\n\n\tu := fmt.Sprintf(\"policies/%s\", url.PathEscape(policyID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &Policy{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, err\n}\n\n// Delete a policy by its ID.\nfunc (s *policies) Delete(ctx context.Context, policyID string) error {\n\tif !validStringID(&policyID) {\n\t\treturn ErrInvalidPolicyID\n\t}\n\n\tu := fmt.Sprintf(\"policies/%s\", url.PathEscape(policyID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Upload the policy content of the policy.\nfunc (s *policies) Upload(ctx context.Context, policyID string, content []byte) error {\n\tif !validStringID(&policyID) {\n\t\treturn ErrInvalidPolicyID\n\t}\n\n\tu := fmt.Sprintf(\"policies/%s/upload\", url.PathEscape(policyID))\n\treq, err := s.client.NewRequest(\"PUT\", u, content)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Download the policy content of the policy.\nfunc (s *policies) Download(ctx context.Context, policyID string) ([]byte, error) {\n\tif !validStringID(&policyID) {\n\t\treturn nil, ErrInvalidPolicyID\n\t}\n\n\tu := fmt.Sprintf(\"policies/%s/download\", url.PathEscape(policyID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar buf bytes.Buffer\n\terr = req.Do(ctx, &buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\nfunc (o PolicyCreateOptions) valid() error {\n\tif !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\tif !validStringID(o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\tif o.Kind == OPA && !validString(o.Query) {\n\t\treturn ErrRequiredQuery\n\t}\n\tif o.Enforce == nil && o.EnforcementLevel == nil {\n\t\treturn ErrRequiredEnforce\n\t}\n\tif o.Enforce != nil && o.EnforcementLevel != nil {\n\t\treturn ErrConflictingEnforceEnforcementLevel\n\t}\n\tif o.Enforce != nil {\n\t\tfor _, e := range o.Enforce {\n\t\t\tif !validString(e.Path) {\n\t\t\t\treturn ErrRequiredEnforcementPath\n\t\t\t}\n\t\t\tif e.Mode == nil {\n\t\t\t\treturn ErrRequiredEnforcementMode\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "policy_check.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ PolicyChecks = (*policyChecks)(nil)\n\n// PolicyChecks describes all the policy check related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-checks\ntype PolicyChecks interface {\n\t// List all policy checks of the given run.\n\tList(ctx context.Context, runID string, options *PolicyCheckListOptions) (*PolicyCheckList, error)\n\n\t// Read a policy check by its ID.\n\tRead(ctx context.Context, policyCheckID string) (*PolicyCheck, error)\n\n\t// Override a soft-mandatory or warning policy.\n\tOverride(ctx context.Context, policyCheckID string) (*PolicyCheck, error)\n\n\t// Logs retrieves the logs of a policy check.\n\tLogs(ctx context.Context, policyCheckID string) (io.Reader, error)\n}\n\n// policyChecks implements PolicyChecks.\ntype policyChecks struct {\n\tclient *Client\n}\n\n// PolicyScope represents a policy scope.\ntype PolicyScope string\n\n// List all available policy scopes.\nconst (\n\tPolicyScopeOrganization PolicyScope = \"organization\"\n\tPolicyScopeWorkspace    PolicyScope = \"workspace\"\n)\n\n// PolicyStatus represents a policy check state.\ntype PolicyStatus string\n\n// List all available policy check statuses.\nconst (\n\tPolicyCanceled    PolicyStatus = \"canceled\"\n\tPolicyErrored     PolicyStatus = \"errored\"\n\tPolicyHardFailed  PolicyStatus = \"hard_failed\"\n\tPolicyOverridden  PolicyStatus = \"overridden\"\n\tPolicyPasses      PolicyStatus = \"passed\"\n\tPolicyPending     PolicyStatus = \"pending\"\n\tPolicyQueued      PolicyStatus = \"queued\"\n\tPolicySoftFailed  PolicyStatus = \"soft_failed\"\n\tPolicyUnreachable PolicyStatus = \"unreachable\"\n)\n\n// PolicyCheckList represents a list of policy checks.\ntype PolicyCheckList struct {\n\t*Pagination\n\tItems []*PolicyCheck\n}\n\n// PolicyCheck represents a Terraform Enterprise policy check..\ntype PolicyCheck struct {\n\tID               string                  `jsonapi:\"primary,policy-checks\"`\n\tActions          *PolicyActions          `jsonapi:\"attr,actions\"`\n\tPermissions      *PolicyPermissions      `jsonapi:\"attr,permissions\"`\n\tResult           *PolicyResult           `jsonapi:\"attr,result\"`\n\tScope            PolicyScope             `jsonapi:\"attr,scope\"`\n\tStatus           PolicyStatus            `jsonapi:\"attr,status\"`\n\tStatusTimestamps *PolicyStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\tRun              *Run                    `jsonapi:\"relation,run\"`\n}\n\n// PolicyActions represents the policy check actions.\ntype PolicyActions struct {\n\tIsOverridable bool `jsonapi:\"attr,is-overridable\"`\n}\n\n// PolicyPermissions represents the policy check permissions.\ntype PolicyPermissions struct {\n\tCanOverride bool `jsonapi:\"attr,can-override\"`\n}\n\n// PolicyResult represents the complete policy check result,\ntype PolicyResult struct {\n\tAdvisoryFailed int  `jsonapi:\"attr,advisory-failed\"`\n\tDuration       int  `jsonapi:\"attr,duration\"`\n\tHardFailed     int  `jsonapi:\"attr,hard-failed\"`\n\tPassed         int  `jsonapi:\"attr,passed\"`\n\tResult         bool `jsonapi:\"attr,result\"`\n\tSoftFailed     int  `jsonapi:\"attr,soft-failed\"`\n\tTotalFailed    int  `jsonapi:\"attr,total-failed\"`\n\tSentinel       any  `jsonapi:\"attr,sentinel\"`\n}\n\n// PolicyStatusTimestamps holds the timestamps for individual policy check\n// statuses.\ntype PolicyStatusTimestamps struct {\n\tErroredAt    time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tHardFailedAt time.Time `jsonapi:\"attr,hard-failed-at,rfc3339\"`\n\tPassedAt     time.Time `jsonapi:\"attr,passed-at,rfc3339\"`\n\tQueuedAt     time.Time `jsonapi:\"attr,queued-at,rfc3339\"`\n\tSoftFailedAt time.Time `jsonapi:\"attr,soft-failed-at,rfc3339\"`\n}\n\n// A list of relations to include\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-checks#available-related-resources\ntype PolicyCheckIncludeOpt string\n\nconst (\n\tPolicyCheckRunWorkspace PolicyCheckIncludeOpt = \"run.workspace\"\n\tPolicyCheckRun          PolicyCheckIncludeOpt = \"run\"\n)\n\n// PolicyCheckListOptions represents the options for listing policy checks.\ntype PolicyCheckListOptions struct {\n\tListOptions\n\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-checks#available-related-resources\n\tInclude []PolicyCheckIncludeOpt `url:\"include,omitempty\"`\n}\n\n// List all policy checks of the given run.\nfunc (s *policyChecks) List(ctx context.Context, runID string, options *PolicyCheckListOptions) (*PolicyCheckList, error) {\n\tif !validStringID(&runID) {\n\t\treturn nil, ErrInvalidRunID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s/policy-checks\", url.PathEscape(runID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpcl := &PolicyCheckList{}\n\terr = req.Do(ctx, pcl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pcl, nil\n}\n\n// Read a policy check by its ID.\nfunc (s *policyChecks) Read(ctx context.Context, policyCheckID string) (*PolicyCheck, error) {\n\tif !validStringID(&policyCheckID) {\n\t\treturn nil, ErrInvalidPolicyCheckID\n\t}\n\n\tu := fmt.Sprintf(\"policy-checks/%s\", url.PathEscape(policyCheckID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpc := &PolicyCheck{}\n\terr = req.Do(ctx, pc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pc, nil\n}\n\n// Override a soft-mandatory or warning policy.\nfunc (s *policyChecks) Override(ctx context.Context, policyCheckID string) (*PolicyCheck, error) {\n\tif !validStringID(&policyCheckID) {\n\t\treturn nil, ErrInvalidPolicyCheckID\n\t}\n\n\tu := fmt.Sprintf(\"policy-checks/%s/actions/override\", url.PathEscape(policyCheckID))\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpc := &PolicyCheck{}\n\terr = req.Do(ctx, pc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pc, nil\n}\n\n// Logs retrieves the logs of a policy check.\nfunc (s *policyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) {\n\tif !validStringID(&policyCheckID) {\n\t\treturn nil, ErrInvalidPolicyCheckID\n\t}\n\n\t// Loop until the context is canceled or the policy check is finished\n\t// running. The policy check logs are not streamed and so only available\n\t// once the check is finished.\n\tfor {\n\t\tpc, err := s.Read(ctx, policyCheckID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tswitch pc.Status {\n\t\tcase PolicyPending, PolicyQueued:\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn nil, ctx.Err()\n\t\t\tcase <-time.After(500 * time.Millisecond):\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tu := fmt.Sprintf(\"policy-checks/%s/output\", url.PathEscape(policyCheckID))\n\t\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlogs := bytes.NewBuffer(nil)\n\t\terr = req.Do(ctx, logs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn logs, nil\n\t}\n}\n\nfunc (o *PolicyCheckListOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "policy_check_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPolicyChecksList_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest1, policyCleanup1 := createUploadedPolicy(t, client, true, orgTest)\n\tdefer policyCleanup1()\n\tpTest2, policyCleanup2 := createUploadedPolicy(t, client, true, orgTest)\n\tdefer policyCleanup2()\n\twTest, wsCleanup := createWorkspace(t, client, orgTest)\n\tdefer wsCleanup()\n\tcreatePolicySet(t, client, orgTest, []*Policy{pTest1, pTest2}, []*Workspace{wTest}, nil, nil, \"\")\n\n\trTest, runCleanup := createPolicyCheckedRun(t, client, wTest)\n\tdefer runCleanup()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tpcl, err := client.PolicyChecks.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, len(pcl.Items))\n\t\tassert.NotEmpty(t, pcl.Items[0].Permissions)\n\t\trequire.NotEmpty(t, pcl.Items[0].Result)\n\t\tassert.Equal(t, 2, pcl.Items[0].Result.Passed)\n\t\tassert.NotEmpty(t, pcl.Items[0].StatusTimestamps)\n\t\tassert.NotNil(t, pcl.Items[0].StatusTimestamps.QueuedAt)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tpcl, err := client.PolicyChecks.List(ctx, rTest.ID, &PolicyCheckListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, pcl.Items)\n\t\tassert.Equal(t, 999, pcl.CurrentPage)\n\t\tassert.Equal(t, 1, pcl.TotalCount)\n\t})\n\n\tt.Run(\"with Include option\", func(t *testing.T) {\n\t\tpcl, err := client.PolicyChecks.List(ctx, rTest.ID, &PolicyCheckListOptions{\n\t\t\tInclude: []PolicyCheckIncludeOpt{PolicyCheckRun},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, pcl.Items)\n\t\trequire.NotNil(t, pcl.Items[0])\n\t\trequire.NotNil(t, pcl.Items[0].Run)\n\t\tassert.NotEmpty(t, pcl.Items[0].Run.Status)\n\t})\n\n\tt.Run(\"without a valid run ID\", func(t *testing.T) {\n\t\tpcl, err := client.PolicyChecks.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, pcl)\n\t\tassert.EqualError(t, err, ErrInvalidRunID.Error())\n\t})\n}\n\nfunc TestPolicyChecksRead_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest, _ := createUploadedPolicy(t, client, true, orgTest)\n\twTest, _ := createWorkspace(t, client, orgTest)\n\tcreatePolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, nil, nil, \"\")\n\n\trTest, _ := createPolicyCheckedRun(t, client, wTest)\n\trequire.Equal(t, 1, len(rTest.PolicyChecks))\n\n\tt.Run(\"when the policy check exists\", func(t *testing.T) {\n\t\tpc, err := client.PolicyChecks.Read(ctx, rTest.PolicyChecks[0].ID)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, pc.Result)\n\t\tassert.NotEmpty(t, pc.Permissions)\n\t\tassert.Equal(t, PolicyScopeOrganization, pc.Scope)\n\t\tassert.Equal(t, PolicyPasses, pc.Status)\n\t\tassert.NotEmpty(t, pc.StatusTimestamps)\n\t\tassert.Equal(t, 1, pc.Result.Passed)\n\t\tassert.NotEmpty(t, pc.Run)\n\t\tassert.NotEmpty(t, pc.Result.Sentinel)\n\n\t\tif reflect.TypeOf(pc.Result.Sentinel) != reflect.TypeOf(map[string]interface{}{}) {\n\t\t\tassert.Fail(t, \"Sentinel is not a map[string]interface{}\")\n\t\t}\n\t})\n\n\tt.Run(\"when the policy check does not exist\", func(t *testing.T) {\n\t\tpc, err := client.PolicyChecks.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, pc)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"without a valid policy check ID\", func(t *testing.T) {\n\t\tpc, err := client.PolicyChecks.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, pc)\n\t\tassert.Equal(t, err, ErrInvalidPolicyCheckID)\n\t})\n}\n\nfunc TestPolicyChecksOverride_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"when the policy failed\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\tpTest, pTestCleanup := createUploadedPolicy(t, client, false, orgTest)\n\t\tdefer pTestCleanup()\n\n\t\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\t\tdefer wTestCleanup()\n\t\tcreatePolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, nil, nil, \"\")\n\t\trTest, tTestCleanup := createPolicyCheckedRun(t, client, wTest)\n\t\tdefer tTestCleanup()\n\n\t\tpcl, err := client.PolicyChecks.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, len(pcl.Items))\n\t\trequire.Equal(t, PolicySoftFailed, pcl.Items[0].Status)\n\n\t\tpc, err := client.PolicyChecks.Override(ctx, pcl.Items[0].ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, pc.Result)\n\t\tassert.Equal(t, PolicyOverridden, pc.Status)\n\t})\n\n\tt.Run(\"when the policy passed\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\tpTest, pTestCleanup := createUploadedPolicy(t, client, true, orgTest)\n\t\tdefer pTestCleanup()\n\n\t\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\t\tdefer wTestCleanup()\n\t\tcreatePolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, nil, nil, \"\")\n\t\trTest, rTestCleanup := createPolicyCheckedRun(t, client, wTest)\n\t\tdefer rTestCleanup()\n\n\t\tpcl, err := client.PolicyChecks.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, len(pcl.Items))\n\t\trequire.Equal(t, PolicyPasses, pcl.Items[0].Status)\n\n\t\t_, err = client.PolicyChecks.Override(ctx, pcl.Items[0].ID)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"without a valid policy check ID\", func(t *testing.T) {\n\t\tp, err := client.PolicyChecks.Override(ctx, badIdentifier)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrInvalidPolicyCheckID)\n\t})\n}\n\nfunc TestPolicyChecksLogs_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest, pTestCleanup := createUploadedPolicy(t, client, true, orgTest)\n\tdefer pTestCleanup()\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\tcreatePolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, nil, nil, \"\")\n\n\trTest, rTestCleanup := createPolicyCheckedRun(t, client, wTest)\n\tdefer rTestCleanup()\n\trequire.Equal(t, 1, len(rTest.PolicyChecks))\n\n\tt.Run(\"when the log exists\", func(t *testing.T) {\n\t\tpc, err := client.PolicyChecks.Read(ctx, rTest.PolicyChecks[0].ID)\n\t\trequire.NoError(t, err)\n\n\t\tlogReader, err := client.PolicyChecks.Logs(ctx, pc.ID)\n\t\trequire.NoError(t, err)\n\n\t\tlogs, err := io.ReadAll(logReader)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Contains(t, string(logs), \"1 policies evaluated\")\n\t})\n\n\tt.Run(\"when the log does not exist\", func(t *testing.T) {\n\t\tlogs, err := client.PolicyChecks.Logs(ctx, \"nonexisting\")\n\t\tassert.Nil(t, logs)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestPolicyCheck_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"policy-checks\",\n\t\t\t\"id\":   \"1\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"actions\": map[string]interface{}{\n\t\t\t\t\t\"is-overridable\": true,\n\t\t\t\t},\n\t\t\t\t\"permissions\": map[string]interface{}{\n\t\t\t\t\t\"can-override\": true,\n\t\t\t\t},\n\t\t\t\t\"result\": map[string]interface{}{\n\t\t\t\t\t\"advisory-failed\": 1,\n\t\t\t\t\t\"duration\":        1,\n\t\t\t\t\t\"hard-failed\":     1,\n\t\t\t\t\t\"passed\":          1,\n\t\t\t\t\t\"result\":          true,\n\t\t\t\t\t\"soft-failed\":     1,\n\t\t\t\t\t\"total-failed\":    1,\n\t\t\t\t},\n\t\t\t\t\"scope\":  PolicyScopeOrganization,\n\t\t\t\t\"status\": PolicyOverridden,\n\t\t\t\t\"status-timestamps\": map[string]string{\n\t\t\t\t\t\"queued-at\":  \"2020-03-16T23:15:59+00:00\",\n\t\t\t\t\t\"errored-at\": \"2019-03-16T23:23:59+00:00\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\tpc := &PolicyCheck{}\n\terr = unmarshalResponse(responseBody, pc)\n\trequire.NoError(t, err)\n\n\tqueuedParsedTime, err := time.Parse(time.RFC3339, \"2020-03-16T23:15:59+00:00\")\n\trequire.NoError(t, err)\n\terroredParsedTime, err := time.Parse(time.RFC3339, \"2019-03-16T23:23:59+00:00\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, pc.ID, \"1\")\n\tassert.Equal(t, pc.Actions.IsOverridable, true)\n\tassert.Equal(t, pc.Permissions.CanOverride, true)\n\tassert.Equal(t, pc.Result.AdvisoryFailed, 1)\n\tassert.Equal(t, pc.Result.Duration, 1)\n\tassert.Equal(t, pc.Result.HardFailed, 1)\n\tassert.Equal(t, pc.Result.Passed, 1)\n\tassert.Equal(t, pc.Result.Result, true)\n\tassert.Equal(t, pc.Result.SoftFailed, 1)\n\tassert.Equal(t, pc.Result.TotalFailed, 1)\n\tassert.Equal(t, pc.Scope, PolicyScopeOrganization)\n\tassert.Equal(t, pc.Status, PolicyOverridden)\n\tassert.Equal(t, pc.StatusTimestamps.QueuedAt, queuedParsedTime)\n\tassert.Equal(t, pc.StatusTimestamps.ErroredAt, erroredParsedTime)\n}\n"
  },
  {
    "path": "policy_evaluation.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ PolicyEvaluations = (*policyEvaluation)(nil)\n\n// PolicyEvaluationStatus is an enum that represents all possible statuses for a policy evaluation\ntype PolicyEvaluationStatus string\n\nconst (\n\tPolicyEvaluationPassed      PolicyEvaluationStatus = \"passed\"\n\tPolicyEvaluationFailed      PolicyEvaluationStatus = \"failed\"\n\tPolicyEvaluationPending     PolicyEvaluationStatus = \"pending\"\n\tPolicyEvaluationRunning     PolicyEvaluationStatus = \"running\"\n\tPolicyEvaluationUnreachable PolicyEvaluationStatus = \"unreachable\"\n\tPolicyEvaluationOverridden  PolicyEvaluationStatus = \"overridden\"\n\tPolicyEvaluationCanceled    PolicyEvaluationStatus = \"canceled\"\n\tPolicyEvaluationErrored     PolicyEvaluationStatus = \"errored\"\n)\n\n// PolicyResultCount represents the count of the policy results\ntype PolicyResultCount struct {\n\tAdvisoryFailed  int `jsonapi:\"attr,advisory-failed\"`\n\tMandatoryFailed int `jsonapi:\"attr,mandatory-failed\"`\n\tPassed          int `jsonapi:\"attr,passed\"`\n\tErrored         int `jsonapi:\"attr,errored\"`\n}\n\n// The task stage the policy evaluation belongs to\ntype PolicyAttachable struct {\n\tID   string `jsonapi:\"attr,id\"`\n\tType string `jsonapi:\"attr,type\"`\n}\n\n// PolicyEvaluationStatusTimestamps represents the set of timestamps recorded for a policy evaluation\ntype PolicyEvaluationStatusTimestamps struct {\n\tErroredAt  time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tRunningAt  time.Time `jsonapi:\"attr,running-at,rfc3339\"`\n\tCanceledAt time.Time `jsonapi:\"attr,canceled-at,rfc3339\"`\n\tFailedAt   time.Time `jsonapi:\"attr,failed-at,rfc3339\"`\n\tPassedAt   time.Time `jsonapi:\"attr,passed-at,rfc3339\"`\n}\n\n// PolicyEvaluation represents the policy evaluations that are part of the task stage.\ntype PolicyEvaluation struct {\n\tID               string                           `jsonapi:\"primary,policy-evaluations\"`\n\tStatus           PolicyEvaluationStatus           `jsonapi:\"attr,status\"`\n\tPolicyKind       PolicyKind                       `jsonapi:\"attr,policy-kind\"`\n\tStatusTimestamps PolicyEvaluationStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\tResultCount      *PolicyResultCount               `jsonapi:\"attr,result-count\"`\n\tCreatedAt        time.Time                        `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt        time.Time                        `jsonapi:\"attr,updated-at,iso8601\"`\n\n\t// The task stage this evaluation belongs to\n\tTaskStage *PolicyAttachable `jsonapi:\"relation,policy-attachable\"`\n}\n\n// PolicyEvalutations describes all the policy evaluation related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-checks\ntype PolicyEvaluations interface {\n\t// **Note: This method is still in BETA and subject to change.**\n\t// List all policy evaluations in the task stage. Only available for OPA policies.\n\tList(ctx context.Context, taskStageID string, options *PolicyEvaluationListOptions) (*PolicyEvaluationList, error)\n}\n\n// policyEvaluation implements PolicyEvaluations.\ntype policyEvaluation struct {\n\tclient *Client\n}\n\n// PolicyEvaluationListOptions represents the options for listing policy evaluations.\ntype PolicyEvaluationListOptions struct {\n\tListOptions\n}\n\n// PolicyEvaluationList represents a list of policy evaluation.\ntype PolicyEvaluationList struct {\n\t*Pagination\n\tItems []*PolicyEvaluation\n}\n\n// List all policy evaluations in a task stage.\nfunc (s *policyEvaluation) List(ctx context.Context, taskStageID string, options *PolicyEvaluationListOptions) (*PolicyEvaluationList, error) {\n\tif !validStringID(&taskStageID) {\n\t\treturn nil, ErrInvalidTaskStageID\n\t}\n\n\tu := fmt.Sprintf(\"task-stages/%s/policy-evaluations\", url.PathEscape(taskStageID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpcl := &PolicyEvaluationList{}\n\terr = req.Do(ctx, pcl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pcl, nil\n}\n\n// Compile-time proof of interface implementation.\nvar _ PolicySetOutcomes = (*policySetOutcome)(nil)\n\n// PolicySetOutcomes describes all the policy set outcome related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-checks\ntype PolicySetOutcomes interface {\n\t// **Note: This method is still in BETA and subject to change.**\n\t// List all policy set outcomes in the policy evaluation. Only available for OPA policies.\n\tList(ctx context.Context, policyEvaluationID string, options *PolicySetOutcomeListOptions) (*PolicySetOutcomeList, error)\n\n\t// **Note: This method is still in BETA and subject to change.**\n\t// Read a policy set outcome by its ID. Only available for OPA policies.\n\tRead(ctx context.Context, policySetOutcomeID string) (*PolicySetOutcome, error)\n}\n\n// policySetOutcome implements PolicySetOutcomes.\ntype policySetOutcome struct {\n\tclient *Client\n}\n\n// PolicySetOutcomeListFilter represents the filters that are supported while listing a policy set outcome\ntype PolicySetOutcomeListFilter struct {\n\t// Optional: A status string used to filter the results.\n\t// Must be either \"passed\", \"failed\", or \"errored\".\n\tStatus string\n\n\t// Optional: The enforcement level used to filter the results.\n\t// Must be either \"advisory\" or \"mandatory\".\n\tEnforcementLevel string\n}\n\n// PolicySetOutcomeListOptions represents the options for listing policy set outcomes.\ntype PolicySetOutcomeListOptions struct {\n\t*ListOptions\n\n\t// Optional: A filter map used to filter the results of the policy outcome.\n\t// You can use filter[n] to combine combinations of statuses and enforcement levels filters\n\tFilter map[string]PolicySetOutcomeListFilter\n}\n\n// PolicySetOutcomeList represents a list of policy set outcomes.\ntype PolicySetOutcomeList struct {\n\t*Pagination\n\tItems []*PolicySetOutcome\n}\n\n// Outcome represents the outcome of the individual policy\ntype Outcome struct {\n\tEnforcementLevel EnforcementLevel `jsonapi:\"attr,enforcement_level\"`\n\tQuery            string           `jsonapi:\"attr,query\"`\n\tStatus           string           `jsonapi:\"attr,status\"`\n\tPolicyName       string           `jsonapi:\"attr,policy_name\"`\n\tDescription      string           `jsonapi:\"attr,description\"`\n}\n\n// PolicySetOutcome represents outcome of the policy set that are part of the policy evaluation\ntype PolicySetOutcome struct {\n\tID                   string            `jsonapi:\"primary,policy-set-outcomes\"`\n\tOutcomes             []Outcome         `jsonapi:\"attr,outcomes\"`\n\tError                string            `jsonapi:\"attr,error\"`\n\tOverridable          *bool             `jsonapi:\"attr,overridable\"`\n\tPolicySetName        string            `jsonapi:\"attr,policy-set-name\"`\n\tPolicySetDescription string            `jsonapi:\"attr,policy-set-description\"`\n\tResultCount          PolicyResultCount `jsonapi:\"attr,result_count\"`\n\n\t// The policy evaluation that this outcome belongs to\n\tPolicyEvaluation *PolicyEvaluation `jsonapi:\"relation,policy-evaluation\"`\n}\n\n// List all policy set outcomes in a policy evaluation.\nfunc (s *policySetOutcome) List(ctx context.Context, policyEvaluationID string, options *PolicySetOutcomeListOptions) (*PolicySetOutcomeList, error) {\n\tif !validStringID(&policyEvaluationID) {\n\t\treturn nil, ErrInvalidPolicyEvaluationID\n\t}\n\n\tadditionalQueryParams := options.buildQueryString()\n\n\tu := fmt.Sprintf(\"policy-evaluations/%s/policy-set-outcomes\", url.QueryEscape(policyEvaluationID))\n\n\tvar opts *ListOptions\n\tif options != nil && options.ListOptions != nil {\n\t\topts = options.ListOptions\n\t}\n\n\treq, err := s.client.NewRequestWithAdditionalQueryParams(\"GET\", u, opts, additionalQueryParams)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpsol := &PolicySetOutcomeList{}\n\terr = req.Do(ctx, psol)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn psol, nil\n}\n\n// buildQueryString takes the PolicySetOutcomeListOptions and returns a filters map.\n// This function is required due to the limitations of the current library,\n// we cannot encode map of objects using the current library that is used by go-tfe: https://github.com/google/go-querystring/issues/7\nfunc (opts *PolicySetOutcomeListOptions) buildQueryString() map[string][]string {\n\tresult := make(map[string][]string)\n\tif opts == nil || opts.Filter == nil {\n\t\treturn nil\n\t}\n\tfor k, v := range opts.Filter {\n\t\tif v.Status != \"\" {\n\t\t\tnewKey := fmt.Sprintf(\"filter[%s][status]\", k)\n\t\t\tresult[newKey] = append(result[newKey], v.Status)\n\t\t}\n\t\tif v.EnforcementLevel != \"\" {\n\t\t\tnewKey := fmt.Sprintf(\"filter[%s][enforcement_level]\", k)\n\t\t\tresult[newKey] = append(result[newKey], v.EnforcementLevel)\n\t\t}\n\t}\n\treturn result\n}\n\n// Read reads a policy set outcome by its ID\nfunc (s *policySetOutcome) Read(ctx context.Context, policySetOutcomeID string) (*PolicySetOutcome, error) {\n\tif !validStringID(&policySetOutcomeID) {\n\t\treturn nil, ErrInvalidPolicySetOutcomeID\n\t}\n\n\tu := fmt.Sprintf(\"policy-set-outcomes/%s\", url.PathEscape(policySetOutcomeID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpso := &PolicySetOutcome{}\n\terr = req.Do(ctx, pso)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pso, err\n}\n"
  },
  {
    "path": "policy_evaluation_beta_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPolicyEvaluationList_Beta_RunDependent(t *testing.T) {\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\toptions := PolicyCreateOptions{\n\t\tDescription: String(\"A sample policy\"),\n\t\tKind:        OPA,\n\t\tQuery:       String(\"data.example.rule\"),\n\t\tEnforce: []*EnforcementOptions{\n\t\t\t{\n\t\t\t\tMode: EnforcementMode(EnforcementAdvisory),\n\t\t\t},\n\t\t},\n\t}\n\tpolicyTest, policyTestCleanup := createUploadedPolicyWithOptions(t, client, true, orgTest, options)\n\tdefer policyTestCleanup()\n\n\tpolicySet := []*Policy{policyTest}\n\t_, psTestCleanup1 := createPolicySet(t, client, orgTest, policySet, []*Workspace{wkspaceTest}, nil, nil, OPA)\n\tdefer psTestCleanup1()\n\n\trTest, rTestCleanup := createRun(t, client, wkspaceTest)\n\tdefer rTestCleanup()\n\n\tt.Run(\"with no params\", func(t *testing.T) {\n\t\ttaskStageList, err := client.TaskStages.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, taskStageList.Items)\n\t\tassert.NotEmpty(t, taskStageList.Items[0].ID)\n\t\tassert.Equal(t, 1, len(taskStageList.Items[0].PolicyEvaluations))\n\n\t\tpolEvaluation, err := client.PolicyEvaluations.List(ctx, taskStageList.Items[0].ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, polEvaluation.Items)\n\t\tassert.NotEmpty(t, polEvaluation.Items[0].ID)\n\t})\n\n\tt.Run(\"with a invalid policy evaluation ID\", func(t *testing.T) {\n\t\tpolicyEvaluationeID := \"invalid ID\"\n\n\t\t_, err := client.PolicyEvaluations.List(ctx, policyEvaluationeID, nil)\n\t\trequire.Errorf(t, err, \"invalid value for policy evaluation ID\")\n\t})\n}\n\nfunc TestPolicySetOutcomeList_Beta_RunDependent(t *testing.T) {\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\toptions := PolicyCreateOptions{\n\t\tDescription: String(\"A sample policy\"),\n\t\tKind:        OPA,\n\t\tQuery:       String(\"data.example.rule\"),\n\t\tEnforce: []*EnforcementOptions{\n\t\t\t{\n\t\t\t\tMode: EnforcementMode(EnforcementAdvisory),\n\t\t\t},\n\t\t},\n\t}\n\tpolicyTest, policyTestCleanup := createUploadedPolicyWithOptions(t, client, true, orgTest, options)\n\tdefer policyTestCleanup()\n\n\tpolicySet := []*Policy{policyTest}\n\t_, psTestCleanup1 := createPolicySet(t, client, orgTest, policySet, []*Workspace{wkspaceTest}, nil, nil, OPA)\n\tdefer psTestCleanup1()\n\n\trTest, rTestCleanup := createPlannedRun(t, client, wkspaceTest)\n\tdefer rTestCleanup()\n\n\tt.Run(\"with no params\", func(t *testing.T) {\n\t\ttaskStageList, err := client.TaskStages.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, taskStageList.Items)\n\t\tassert.NotEmpty(t, taskStageList.Items[0].ID)\n\t\tassert.Equal(t, 1, len(taskStageList.Items[0].PolicyEvaluations))\n\t\tassert.NotEmpty(t, 1, len(taskStageList.Items[0].PolicyEvaluations[0].ID))\n\n\t\tpolEvaluationID := taskStageList.Items[0].PolicyEvaluations[0].ID\n\n\t\tpolSetOutcomesList, err := client.PolicySetOutcomes.List(ctx, polEvaluationID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, polSetOutcomesList.Items)\n\t\tassert.NotEmpty(t, polSetOutcomesList.Items[0].ID)\n\t\tassert.NotEmpty(t, polSetOutcomesList.Items[0].Outcomes)\n\t\tassert.NotEmpty(t, polSetOutcomesList.Items[0].PolicySetName)\n\t})\n\n\tt.Run(\"with non-matching filters\", func(t *testing.T) {\n\t\ttaskStageList, err := client.TaskStages.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, taskStageList.Items)\n\t\tassert.NotEmpty(t, taskStageList.Items[0].ID)\n\t\tassert.Equal(t, 1, len(taskStageList.Items[0].PolicyEvaluations))\n\t\tassert.NotEmpty(t, 1, len(taskStageList.Items[0].PolicyEvaluations[0].ID))\n\n\t\tpolEvaluationID := taskStageList.Items[0].PolicyEvaluations[0].ID\n\n\t\topts := &PolicySetOutcomeListOptions{\n\t\t\tFilter: map[string]PolicySetOutcomeListFilter{\n\t\t\t\t\"0\": {\n\t\t\t\t\tStatus: \"errored\",\n\t\t\t\t},\n\t\t\t\t\"1\": {\n\t\t\t\t\tEnforcementLevel: \"mandatory\",\n\t\t\t\t\tStatus:           \"failed\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tpolSetOutcomesList, err := client.PolicySetOutcomes.List(ctx, polEvaluationID, opts)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Empty(t, polSetOutcomesList.Items)\n\t})\n\n\tt.Run(\"with matching filters\", func(t *testing.T) {\n\t\ttaskStageList, err := client.TaskStages.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, taskStageList.Items)\n\t\tassert.NotEmpty(t, taskStageList.Items[0].ID)\n\t\tassert.Equal(t, 1, len(taskStageList.Items[0].PolicyEvaluations))\n\t\tassert.NotEmpty(t, 1, len(taskStageList.Items[0].PolicyEvaluations[0].ID))\n\n\t\tpolEvaluationID := taskStageList.Items[0].PolicyEvaluations[0].ID\n\n\t\topts := &PolicySetOutcomeListOptions{\n\t\t\tFilter: map[string]PolicySetOutcomeListFilter{\n\t\t\t\t\"0\": {\n\t\t\t\t\tStatus:           \"passed\",\n\t\t\t\t\tEnforcementLevel: \"advisory\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tpolSetOutcomesList, err := client.PolicySetOutcomes.List(ctx, polEvaluationID, opts)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, polSetOutcomesList.Items)\n\t\tassert.NotEmpty(t, polSetOutcomesList.Items[0].ID)\n\t\tassert.Equal(t, 1, len(polSetOutcomesList.Items[0].Outcomes))\n\t\tassert.NotEmpty(t, polSetOutcomesList.Items[0].PolicySetName)\n\t})\n}\n\nfunc TestPolicySetOutcomeRead_Beta_RunDependent(t *testing.T) {\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\toptions := PolicyCreateOptions{\n\t\tDescription: String(\"A sample policy\"),\n\t\tKind:        OPA,\n\t\tQuery:       String(\"data.example.rule\"),\n\t\tEnforce: []*EnforcementOptions{\n\t\t\t{\n\t\t\t\tMode: EnforcementMode(EnforcementAdvisory),\n\t\t\t},\n\t\t},\n\t}\n\tpolicyTest, policyTestCleanup := createUploadedPolicyWithOptions(t, client, true, orgTest, options)\n\tdefer policyTestCleanup()\n\n\tpolicySet := []*Policy{policyTest}\n\t_, psTestCleanup1 := createPolicySet(t, client, orgTest, policySet, []*Workspace{wkspaceTest}, nil, nil, OPA)\n\tdefer psTestCleanup1()\n\n\trTest, rTestCleanup := createPlannedRun(t, client, wkspaceTest)\n\tdefer rTestCleanup()\n\n\tt.Run(\"with a valid policy set outcome ID\", func(t *testing.T) {\n\t\ttaskStageList, err := client.TaskStages.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, taskStageList.Items)\n\t\tassert.NotEmpty(t, taskStageList.Items[0].ID)\n\t\tassert.Equal(t, 1, len(taskStageList.Items[0].PolicyEvaluations))\n\t\tassert.NotEmpty(t, 1, len(taskStageList.Items[0].PolicyEvaluations[0].ID))\n\n\t\tpolEvaluationID := taskStageList.Items[0].PolicyEvaluations[0].ID\n\n\t\tpolSetOutcomesList, err := client.PolicySetOutcomes.List(ctx, polEvaluationID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, polSetOutcomesList.Items)\n\t\tassert.NotEmpty(t, polSetOutcomesList.Items[0].ID)\n\t\tassert.NotEmpty(t, polSetOutcomesList.Items[0].Outcomes)\n\t\tassert.NotEmpty(t, polSetOutcomesList.Items[0].PolicySetName)\n\n\t\tpolicySetOutcomeID := polSetOutcomesList.Items[0].ID\n\n\t\tpolicyOutcome, err := client.PolicySetOutcomes.Read(ctx, policySetOutcomeID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, policyOutcome.ID)\n\t\tassert.NotEmpty(t, policyOutcome.Outcomes)\n\t})\n\n\tt.Run(\"with a invalid policy set outcome ID\", func(t *testing.T) {\n\t\tpolicySetOutcomeID := \"invalid ID\"\n\n\t\t_, err := client.PolicySetOutcomes.Read(ctx, policySetOutcomeID)\n\t\trequire.Errorf(t, err, \"invalid value for policy set outcome ID\")\n\t})\n}\n"
  },
  {
    "path": "policy_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPoliciesList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest1, pTestCleanup1 := createPolicy(t, client, orgTest)\n\tdefer pTestCleanup1()\n\tpTest2, pTestCleanup2 := createPolicy(t, client, orgTest)\n\tdefer pTestCleanup2()\n\n\topaOptions := PolicyCreateOptions{\n\t\tKind:  OPA,\n\t\tQuery: String(\"data.example.rule\"),\n\t\tEnforce: []*EnforcementOptions{\n\t\t\t{\n\t\t\t\tMode: EnforcementMode(EnforcementMandatory),\n\t\t\t},\n\t\t},\n\t}\n\tpTest3, pTestCleanup3 := createPolicyWithOptions(t, client, orgTest, opaOptions)\n\tdefer pTestCleanup3()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tpl, err := client.Policies.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, pl.Items, pTest1)\n\t\tassert.Contains(t, pl.Items, pTest2)\n\t\tassert.Contains(t, pl.Items, pTest3)\n\n\t\tassert.Equal(t, 1, pl.CurrentPage)\n\t\tassert.Equal(t, 3, pl.TotalCount)\n\t})\n\n\tt.Run(\"with pagination\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tpl, err := client.Policies.List(ctx, orgTest.Name, &PolicyListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Empty(t, pl.Items)\n\t\tassert.Equal(t, 999, pl.CurrentPage)\n\t\tassert.Equal(t, 3, pl.TotalCount)\n\t})\n\n\tt.Run(\"with search\", func(t *testing.T) {\n\t\t// Search by one of the policy's names; we should get only that policy\n\t\t// and pagination data should reflect the search as well\n\t\tpl, err := client.Policies.List(ctx, orgTest.Name, &PolicyListOptions{\n\t\t\tSearch: pTest1.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Contains(t, pl.Items, pTest1)\n\t\tassert.NotContains(t, pl.Items, pTest2)\n\t\tassert.NotContains(t, pl.Items, pTest3)\n\t\tassert.Equal(t, 1, pl.CurrentPage)\n\t\tassert.Equal(t, 1, pl.TotalCount)\n\t})\n\n\tt.Run(\"with filter by kind\", func(t *testing.T) {\n\t\tpl, err := client.Policies.List(ctx, orgTest.Name, &PolicyListOptions{\n\t\t\tKind: OPA,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Contains(t, pl.Items, pTest3)\n\t\tassert.NotContains(t, pl.Items, pTest1)\n\t\tassert.NotContains(t, pl.Items, pTest2)\n\t\tassert.Equal(t, 1, pl.CurrentPage)\n\t\tassert.Equal(t, 1, pl.TotalCount)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tps, err := client.Policies.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, ps)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestPoliciesCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with no kind\", func(t *testing.T) {\n\t\tname := randomString(t)\n\t\toptions := PolicyCreateOptions{\n\t\t\tName:        String(name),\n\t\t\tDescription: String(\"A sample policy\"),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(name + \".sentinel\"),\n\t\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Policies.Read(ctx, p.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Policy{\n\t\t\tp,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, Sentinel, item.Kind)\n\t\t\tassert.Equal(t, *options.Description, item.Description)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options - Sentinel\", func(t *testing.T) {\n\t\tname := randomString(t)\n\t\toptions := PolicyCreateOptions{\n\t\t\tName:        String(name),\n\t\t\tDescription: String(\"A sample policy\"),\n\t\t\tKind:        Sentinel,\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(name + \".sentinel\"),\n\t\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Policies.Read(ctx, p.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Policy{\n\t\t\tp,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, options.Kind, item.Kind)\n\t\t\tassert.Nil(t, options.Query)\n\t\t\tassert.Equal(t, *options.Description, item.Description)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options - OPA\", func(t *testing.T) {\n\t\tname := randomString(t)\n\t\toptions := PolicyCreateOptions{\n\t\t\tName:        String(name),\n\t\t\tDescription: String(\"A sample policy\"),\n\t\t\tKind:        OPA,\n\t\t\tQuery:       String(\"terraform.main\"),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(name + \".rego\"),\n\t\t\t\t\tMode: EnforcementMode(EnforcementMandatory),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Policies.Read(ctx, p.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Policy{\n\t\t\tp,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, options.Kind, item.Kind)\n\t\t\tassert.Equal(t, *options.Query, *item.Query)\n\t\t\tassert.Equal(t, *options.Description, item.Description)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options - Enforcement Level\", func(t *testing.T) {\n\t\tname := randomString(t)\n\t\toptions := PolicyCreateOptions{\n\t\t\tName:             String(name),\n\t\t\tDescription:      String(\"A sample policy\"),\n\t\t\tKind:             Sentinel,\n\t\t\tEnforcementLevel: EnforcementMode(EnforcementHard),\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Policies.Read(ctx, p.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Policy{\n\t\t\tp,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, options.Kind, item.Kind)\n\t\t\tassert.Nil(t, options.Query)\n\t\t\tassert.Equal(t, *options.Description, item.Description)\n\t\t\tassert.Equal(t, *options.EnforcementLevel, item.EnforcementLevel)\n\t\t}\n\t})\n\n\tt.Run(\"when options has an invalid name\", func(t *testing.T) {\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, PolicyCreateOptions{\n\t\t\tName: String(badIdentifier),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(badIdentifier + \".sentinel\"),\n\t\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tassert.Nil(t, p)\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"when options has an invalid name - OPA\", func(t *testing.T) {\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, PolicyCreateOptions{\n\t\t\tName:  String(badIdentifier),\n\t\t\tKind:  OPA,\n\t\t\tQuery: String(\"terraform.main\"),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(badIdentifier + \".rego\"),\n\t\t\t\t\tMode: EnforcementMode(EnforcementAdvisory),\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tassert.Nil(t, p)\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"when options is missing name\", func(t *testing.T) {\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, PolicyCreateOptions{\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(randomString(t) + \".sentinel\"),\n\t\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tassert.Nil(t, p)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"when options is missing name - OPA\", func(t *testing.T) {\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, PolicyCreateOptions{\n\t\t\tKind:  OPA,\n\t\t\tQuery: String(\"terraform.main\"),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(randomString(t) + \".rego\"),\n\t\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tassert.Nil(t, p)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"when options is missing query - OPA\", func(t *testing.T) {\n\t\tname := randomString(t)\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, PolicyCreateOptions{\n\t\t\tName: String(name),\n\t\t\tKind: OPA,\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(randomString(t) + \".rego\"),\n\t\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrRequiredQuery)\n\t})\n\n\tt.Run(\"when options is missing an enforcement-OPA\", func(t *testing.T) {\n\t\toptions := PolicyCreateOptions{\n\t\t\tName:  String(randomString(t)),\n\t\t\tKind:  OPA,\n\t\t\tQuery: String(\"terraform.main\"),\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrRequiredEnforce)\n\t})\n\n\tt.Run(\"when options is missing an enforcement-Sentinel\", func(t *testing.T) {\n\t\toptions := PolicyCreateOptions{\n\t\t\tName: String(randomString(t)),\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrRequiredEnforce)\n\t})\n\n\tt.Run(\"when options is missing enforcement path-Sentinel\", func(t *testing.T) {\n\t\toptions := PolicyCreateOptions{\n\t\t\tName: String(randomString(t)),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrRequiredEnforcementPath)\n\t})\n\n\tt.Run(\"when options is missing enforcement path-OPA\", func(t *testing.T) {\n\t\toptions := PolicyCreateOptions{\n\t\t\tName:  String(randomString(t)),\n\t\t\tKind:  OPA,\n\t\t\tQuery: String(\"terraform.main\"),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrRequiredEnforcementPath)\n\t})\n\n\tt.Run(\"when options is missing enforcement path\", func(t *testing.T) {\n\t\tname := randomString(t)\n\t\toptions := PolicyCreateOptions{\n\t\t\tName: String(name),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(name + \".sentinel\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrRequiredEnforcementMode)\n\t})\n\n\tt.Run(\"when options is missing enforcement mode-OPA\", func(t *testing.T) {\n\t\tname := randomString(t)\n\t\toptions := PolicyCreateOptions{\n\t\t\tName:  String(name),\n\t\t\tKind:  OPA,\n\t\t\tQuery: String(\"terraform.main\"),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(name + \".sentinel\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrRequiredEnforcementMode)\n\t})\n\n\tt.Run(\"when options is have both enforcement level and enforcement path\", func(t *testing.T) {\n\t\tname := randomString(t)\n\t\toptions := PolicyCreateOptions{\n\t\t\tName: String(name),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(randomString(t) + \".sentinel\"),\n\t\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t\t},\n\t\t\t},\n\t\t\tEnforcementLevel: EnforcementMode(EnforcementMandatory),\n\t\t}\n\n\t\tp, err := client.Policies.Create(ctx, orgTest.Name, options)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrConflictingEnforceEnforcementLevel)\n\t})\n\n\tt.Run(\"when options has an invalid organization\", func(t *testing.T) {\n\t\tp, err := client.Policies.Create(ctx, badIdentifier, PolicyCreateOptions{\n\t\t\tName: String(\"foo\"),\n\t\t})\n\t\tassert.Nil(t, p)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestPoliciesRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest, pTestCleanup := createPolicy(t, client, orgTest)\n\tdefer pTestCleanup()\n\n\tt.Run(\"when the policy exists without content\", func(t *testing.T) {\n\t\tp, err := client.Policies.Read(ctx, pTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, pTest.ID, p.ID)\n\t\tassert.Equal(t, pTest.Name, p.Name)\n\t\tassert.Equal(t, pTest.PolicySetCount, p.PolicySetCount)\n\t\tassert.Empty(t, p.Enforce)\n\t\tassert.Equal(t, p.EnforcementLevel, pTest.EnforcementLevel)\n\t\tassert.Equal(t, pTest.Organization.Name, p.Organization.Name)\n\t})\n\n\terr := client.Policies.Upload(ctx, pTest.ID, []byte(`main = rule { true }`))\n\trequire.NoError(t, err)\n\n\tt.Run(\"when the policy exists with content\", func(t *testing.T) {\n\t\tp, err := client.Policies.Read(ctx, pTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, pTest.ID, p.ID)\n\t\tassert.Equal(t, pTest.Name, p.Name)\n\t\tassert.Equal(t, pTest.Description, p.Description)\n\t\tassert.Equal(t, pTest.PolicySetCount, p.PolicySetCount)\n\t\tassert.NotEmpty(t, p.Enforce)\n\t\tassert.NotEmpty(t, p.Enforce[0].Path)\n\t\tassert.NotEmpty(t, p.Enforce[0].Mode)\n\t\tassert.Equal(t, p.EnforcementLevel, pTest.EnforcementLevel)\n\t\tassert.Equal(t, pTest.Organization.Name, p.Organization.Name)\n\t})\n\n\tt.Run(\"when the policy does not exist\", func(t *testing.T) {\n\t\tp, err := client.Policies.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"without a valid policy ID\", func(t *testing.T) {\n\t\tp, err := client.Policies.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrInvalidPolicyID)\n\t})\n}\n\nfunc TestPoliciesUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"when updating with an existing path\", func(t *testing.T) {\n\t\tpBefore, pBeforeCleanup := createUploadedPolicy(t, client, true, orgTest)\n\t\tdefer pBeforeCleanup()\n\n\t\trequire.Equal(t, 1, len(pBefore.Enforce))\n\n\t\tpAfter, err := client.Policies.Update(ctx, pBefore.ID, PolicyUpdateOptions{\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(pBefore.Enforce[0].Path),\n\t\t\t\t\tMode: EnforcementMode(EnforcementAdvisory),\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, len(pAfter.Enforce))\n\n\t\tassert.Equal(t, pBefore.ID, pAfter.ID)\n\t\tassert.Equal(t, pBefore.Name, pAfter.Name)\n\t\tassert.Equal(t, pBefore.Description, pAfter.Description)\n\t\tassert.Equal(t, pBefore.Enforce[0].Path, pAfter.Enforce[0].Path)\n\t\tassert.Equal(t, EnforcementAdvisory, pAfter.Enforce[0].Mode)\n\t})\n\n\tt.Run(\"when updating with a nonexisting path\", func(t *testing.T) {\n\t\t// Weirdly enough pAfter is not equal to pBefore as updating\n\t\t// a nonexisting path causes the enforce mode to reset to the default\n\t\t// hard-mandatory\n\t\tt.Skip(\"see comment...\")\n\n\t\tpBefore, pBeforeCleanup := createUploadedPolicy(t, client, true, orgTest)\n\t\tdefer pBeforeCleanup()\n\n\t\trequire.Equal(t, 1, len(pBefore.Enforce))\n\t\tpathBefore := pBefore.Enforce[0].Path\n\t\tmodeBefore := pBefore.Enforce[0].Mode\n\n\t\tpAfter, err := client.Policies.Update(ctx, pBefore.ID, PolicyUpdateOptions{\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tPath: String(\"nonexisting\"),\n\t\t\t\t\tMode: EnforcementMode(EnforcementAdvisory),\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, 1, len(pAfter.Enforce))\n\t\tassert.Equal(t, pBefore, pAfter)\n\t\tassert.Equal(t, pathBefore, pAfter.Enforce[0].Path)\n\t\tassert.Equal(t, modeBefore, pAfter.Enforce[0].Mode)\n\t})\n\n\tt.Run(\"with a new description\", func(t *testing.T) {\n\t\tpBefore, pBeforeCleanup := createUploadedPolicy(t, client, true, orgTest)\n\t\tdefer pBeforeCleanup()\n\n\t\tpAfter, err := client.Policies.Update(ctx, pBefore.ID, PolicyUpdateOptions{\n\t\t\tDescription: String(\"A brand new description\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, pBefore.Name, pAfter.Name)\n\t\tassert.Equal(t, pBefore.Enforce, pAfter.Enforce)\n\t\tassert.NotEqual(t, pBefore.Description, pAfter.Description)\n\t\tassert.Equal(t, \"A brand new description\", pAfter.Description)\n\t})\n\n\tt.Run(\"with a new query\", func(t *testing.T) {\n\t\toptions := PolicyCreateOptions{\n\t\t\tDescription: String(\"A sample OPA policy\"),\n\t\t\tKind:        OPA,\n\t\t\tQuery:       String(\"data.example.rule\"),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tMode: EnforcementMode(EnforcementMandatory),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tpBefore, pBeforeCleanup := createUploadedPolicyWithOptions(t, client, true, orgTest, options)\n\t\tdefer pBeforeCleanup()\n\n\t\tpAfter, err := client.Policies.Update(ctx, pBefore.ID, PolicyUpdateOptions{\n\t\t\tQuery: String(\"terraform.policy1.deny\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, pBefore.Name, pAfter.Name)\n\t\tassert.Equal(t, pBefore.Enforce, pAfter.Enforce)\n\t\tassert.NotEqual(t, *pBefore.Query, *pAfter.Query)\n\t\tassert.Equal(t, \"terraform.policy1.deny\", *pAfter.Query)\n\t})\n\n\tt.Run(\"with a new enforcement level\", func(t *testing.T) {\n\t\toptions := PolicyCreateOptions{\n\t\t\tDescription:      String(\"A sample OPA policy\"),\n\t\t\tKind:             OPA,\n\t\t\tQuery:            String(\"data.example.rule\"),\n\t\t\tEnforcementLevel: EnforcementMode(EnforcementMandatory),\n\t\t}\n\t\tpBefore, pBeforeCleanup := createUploadedPolicyWithOptions(t, client, true, orgTest, options)\n\t\tdefer pBeforeCleanup()\n\n\t\tpAfter, err := client.Policies.Update(ctx, pBefore.ID, PolicyUpdateOptions{\n\t\t\tEnforcementLevel: EnforcementMode(EnforcementAdvisory),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, pBefore.Name, pAfter.Name)\n\t\tassert.Equal(t, pBefore.EnforcementLevel, EnforcementMandatory)\n\t\tassert.Equal(t, pAfter.EnforcementLevel, EnforcementAdvisory)\n\t})\n\n\tt.Run(\"update query when kind is not OPA\", func(t *testing.T) {\n\t\tpBefore, pBeforeCleanup := createUploadedPolicy(t, client, true, orgTest)\n\t\tdefer pBeforeCleanup()\n\n\t\tpAfter, err := client.Policies.Update(ctx, pBefore.ID, PolicyUpdateOptions{\n\t\t\tQuery: String(\"terraform.policy1.deny\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, pBefore.Name, pAfter.Name)\n\t\tassert.Equal(t, pBefore.Enforce, pAfter.Enforce)\n\t\tassert.Equal(t, Sentinel, pAfter.Kind)\n\t\tassert.Nil(t, pAfter.Query)\n\t})\n\n\tt.Run(\"without a valid policy ID\", func(t *testing.T) {\n\t\tp, err := client.Policies.Update(ctx, badIdentifier, PolicyUpdateOptions{})\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrInvalidPolicyID)\n\t})\n}\n\nfunc TestPoliciesDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest, _ := createPolicy(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.Policies.Delete(ctx, pTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the policy - it should fail.\n\t\t_, err = client.Policies.Read(ctx, pTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the policy does not exist\", func(t *testing.T) {\n\t\terr := client.Policies.Delete(ctx, pTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the policy ID is invalid\", func(t *testing.T) {\n\t\terr := client.Policies.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidPolicyID)\n\t})\n}\n\nfunc TestPoliciesUpload(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpTest, pTestCleanup := createPolicy(t, client, nil)\n\tdefer pTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.Policies.Upload(ctx, pTest.ID, []byte(`main = rule { true }`))\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with empty content\", func(t *testing.T) {\n\t\terr := client.Policies.Upload(ctx, pTest.ID, []byte{})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without any content\", func(t *testing.T) {\n\t\terr := client.Policies.Upload(ctx, pTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without a valid policy ID\", func(t *testing.T) {\n\t\terr := client.Policies.Upload(ctx, badIdentifier, []byte(`main = rule { true }`))\n\t\tassert.Equal(t, err, ErrInvalidPolicyID)\n\t})\n}\n\nfunc TestPoliciesDownload(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpTest, pTestCleanup := createPolicy(t, client, nil)\n\tdefer pTestCleanup()\n\n\ttestContent := []byte(`main = rule { true }`)\n\n\tt.Run(\"without existing content\", func(t *testing.T) {\n\t\tcontent, err := client.Policies.Download(ctx, pTest.ID)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t\tassert.Nil(t, content)\n\t})\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.Policies.Upload(ctx, pTest.ID, testContent)\n\t\trequire.NoError(t, err)\n\n\t\tcontent, err := client.Policies.Download(ctx, pTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, testContent, content)\n\t})\n\n\tt.Run(\"without a valid policy ID\", func(t *testing.T) {\n\t\tcontent, err := client.Policies.Download(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidPolicyID)\n\t\tassert.Nil(t, content)\n\t})\n}\n\nfunc TestPolicy_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"policies\",\n\t\t\t\"id\":   \"policy-ntv3HbhJqvFzamy7\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"name\":        \"general\",\n\t\t\t\t\"description\": \"general policy\",\n\t\t\t\t\"enforce\": []interface{}{\n\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\"path\": \"some/path\",\n\t\t\t\t\t\t\"mode\": string(EnforcementAdvisory),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"updated-at\":       \"2018-03-02T23:42:06.651Z\",\n\t\t\t\t\"policy-set-count\": 1,\n\t\t\t},\n\t\t},\n\t}\n\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\tpolicy := &Policy{}\n\terr = unmarshalResponse(responseBody, policy)\n\trequire.NoError(t, err)\n\n\tiso8601TimeFormat := \"2006-01-02T15:04:05Z\"\n\tparsedTime, err := time.Parse(iso8601TimeFormat, \"2018-03-02T23:42:06.651Z\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, policy.ID, \"policy-ntv3HbhJqvFzamy7\")\n\tassert.Equal(t, policy.Name, \"general\")\n\tassert.Equal(t, policy.Description, \"general policy\")\n\tassert.Equal(t, policy.PolicySetCount, 1)\n\tassert.Equal(t, policy.Enforce[0].Path, \"some/path\")\n\tassert.Equal(t, policy.Enforce[0].Mode, EnforcementAdvisory)\n\tassert.Equal(t, policy.UpdatedAt, parsedTime)\n}\n\nfunc TestPolicyCreateOptions_Marshal(t *testing.T) {\n\tt.Parallel()\n\topts := PolicyCreateOptions{\n\t\tName:        String(\"my-policy\"),\n\t\tDescription: String(\"details\"),\n\t\tEnforce: []*EnforcementOptions{\n\t\t\t{\n\t\t\t\tPath: String(\"/foo\"),\n\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t},\n\t\t\t{\n\t\t\t\tPath: String(\"/bar\"),\n\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t},\n\t\t},\n\t}\n\n\treqBody, err := serializeRequestBody(&opts)\n\trequire.NoError(t, err)\n\treq, err := retryablehttp.NewRequest(\"POST\", \"url\", reqBody)\n\trequire.NoError(t, err)\n\tbodyBytes, err := req.BodyBytes()\n\trequire.NoError(t, err)\n\n\texpectedBody := `{\"data\":{\"type\":\"policies\",\"attributes\":{\"description\":\"details\",\"enforce\":[{\"path\":\"/foo\",\"mode\":\"soft-mandatory\"},{\"path\":\"/bar\",\"mode\":\"soft-mandatory\"}],\"name\":\"my-policy\"}}}\n`\n\tassert.Equal(t, expectedBody, string(bodyBytes))\n}\n\nfunc TestPolicyUpdateOptions_Marshal(t *testing.T) {\n\tt.Parallel()\n\topts := PolicyUpdateOptions{\n\t\tDescription: String(\"details\"),\n\t\tEnforce: []*EnforcementOptions{\n\t\t\t{\n\t\t\t\tPath: String(\"/foo\"),\n\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t},\n\t\t\t{\n\t\t\t\tPath: String(\"/bar\"),\n\t\t\t\tMode: EnforcementMode(EnforcementSoft),\n\t\t\t},\n\t\t},\n\t}\n\n\treqBody, err := serializeRequestBody(&opts)\n\trequire.NoError(t, err)\n\treq, err := retryablehttp.NewRequest(\"POST\", \"url\", reqBody)\n\trequire.NoError(t, err)\n\tbodyBytes, err := req.BodyBytes()\n\trequire.NoError(t, err)\n\n\texpectedBody := `{\"data\":{\"type\":\"policies\",\"attributes\":{\"description\":\"details\",\"enforce\":[{\"path\":\"/foo\",\"mode\":\"soft-mandatory\"},{\"path\":\"/bar\",\"mode\":\"soft-mandatory\"}]}}}\n`\n\tassert.Equal(t, expectedBody, string(bodyBytes))\n}\n"
  },
  {
    "path": "policy_set.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ PolicySets = (*policySets)(nil)\n\n// PolicyKind is an indicator of the underlying technology that the policy or policy set supports.\n// There are two kinds documented in the enum.\ntype PolicyKind string\n\nconst (\n\tOPA      PolicyKind = \"opa\"\n\tSentinel PolicyKind = \"sentinel\"\n)\n\n// PolicySets describes all the policy set related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-sets\ntype PolicySets interface {\n\t// List all the policy sets for a given organization.\n\tList(ctx context.Context, organization string, options *PolicySetListOptions) (*PolicySetList, error)\n\n\t// Create a policy set and associate it with an organization.\n\tCreate(ctx context.Context, organization string, options PolicySetCreateOptions) (*PolicySet, error)\n\n\t// Read a policy set by its ID.\n\tRead(ctx context.Context, policySetID string) (*PolicySet, error)\n\n\t// ReadWithOptions reads a policy set by its ID using the options supplied.\n\tReadWithOptions(ctx context.Context, policySetID string, options *PolicySetReadOptions) (*PolicySet, error)\n\n\t// Update an existing policy set.\n\tUpdate(ctx context.Context, policySetID string, options PolicySetUpdateOptions) (*PolicySet, error)\n\n\t// Add policies to a policy set. This function can only be used when\n\t// there is no VCS repository associated with the policy set.\n\tAddPolicies(ctx context.Context, policySetID string, options PolicySetAddPoliciesOptions) error\n\n\t// Remove policies from a policy set. This function can only be used\n\t// when there is no VCS repository associated with the policy set.\n\tRemovePolicies(ctx context.Context, policySetID string, options PolicySetRemovePoliciesOptions) error\n\n\t// Add workspaces to a policy set.\n\tAddWorkspaces(ctx context.Context, policySetID string, options PolicySetAddWorkspacesOptions) error\n\n\t// Remove workspaces from a policy set.\n\tRemoveWorkspaces(ctx context.Context, policySetID string, options PolicySetRemoveWorkspacesOptions) error\n\n\t// Add workspace exclusions to a policy set.\n\tAddWorkspaceExclusions(ctx context.Context, policySetID string, options PolicySetAddWorkspaceExclusionsOptions) error\n\n\t// Remove workspace exclusions from a policy set.\n\tRemoveWorkspaceExclusions(ctx context.Context, policySetID string, options PolicySetRemoveWorkspaceExclusionsOptions) error\n\n\t// Add projects to a policy set.\n\tAddProjects(ctx context.Context, policySetID string, options PolicySetAddProjectsOptions) error\n\n\t// Remove projects from a policy set.\n\tRemoveProjects(ctx context.Context, policySetID string, options PolicySetRemoveProjectsOptions) error\n\n\t// Add Project exclusions to a policy set.\n\tAddProjectExclusions(ctx context.Context, policySetID string, options PolicySetAddProjectExclusionsOptions) error\n\n\t// Remove project exclusions from a policy set.\n\tRemoveProjectExclusions(ctx context.Context, policySetID string, options PolicySetRemoveProjectExclusionsOptions) error\n\n\t// Delete a policy set by its ID.\n\tDelete(ctx context.Context, policyID string) error\n}\n\n// policySets implements PolicySets.\ntype policySets struct {\n\tclient *Client\n}\n\n// PolicySetList represents a list of policy sets.\ntype PolicySetList struct {\n\t*Pagination\n\tItems []*PolicySet\n}\n\n// PolicySet represents a Terraform Enterprise policy set.\ntype PolicySet struct {\n\tID           string     `jsonapi:\"primary,policy-sets\"`\n\tName         string     `jsonapi:\"attr,name\"`\n\tDescription  string     `jsonapi:\"attr,description\"`\n\tKind         PolicyKind `jsonapi:\"attr,kind\"`\n\tOverridable  *bool      `jsonapi:\"attr,overridable\"`\n\tGlobal       bool       `jsonapi:\"attr,global\"`\n\tPoliciesPath string     `jsonapi:\"attr,policies-path\"`\n\t// **Note: This field is still in BETA and subject to change.**\n\tPolicyCount       int       `jsonapi:\"attr,policy-count\"`\n\tVCSRepo           *VCSRepo  `jsonapi:\"attr,vcs-repo\"`\n\tWorkspaceCount    int       `jsonapi:\"attr,workspace-count\"`\n\tProjectCount      int       `jsonapi:\"attr,project-count\"`\n\tCreatedAt         time.Time `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt         time.Time `jsonapi:\"attr,updated-at,iso8601\"`\n\tAgentEnabled      bool      `jsonapi:\"attr,agent-enabled\"`\n\tPolicyToolVersion string    `jsonapi:\"attr,policy-tool-version\"`\n\n\tPolicyUpdatePatterns []string `jsonapi:\"attr,policy-update-patterns\"`\n\n\t// Relations\n\t// The organization to which the policy set belongs to.\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n\t// The workspaces to which the policy set applies.\n\tWorkspaces []*Workspace `jsonapi:\"relation,workspaces\"`\n\t// Individually managed policies which are associated with the policy set.\n\tPolicies []*Policy `jsonapi:\"relation,policies\"`\n\t// The most recently created policy set version, regardless of status.\n\t// Note that this relationship may include an errored and unusable version,\n\t// and is intended to allow checking for errors.\n\tNewestVersion *PolicySetVersion `jsonapi:\"relation,newest-version\"`\n\t// The most recent successful policy set version.\n\tCurrentVersion *PolicySetVersion `jsonapi:\"relation,current-version\"`\n\t// The workspace exclusions to which the policy set applies.\n\tWorkspaceExclusions []*Workspace `jsonapi:\"relation,workspace-exclusions\"`\n\t// The projects to which the policy set applies.\n\tProjects []*Project `jsonapi:\"relation,projects\"`\n\t// The project exclusions to which the policy set applies.\n\tProjectExclusions []*Project `jsonapi:\"relation,project-exclusions\"`\n}\n\n// PolicySetIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-sets#available-related-resources\ntype PolicySetIncludeOpt string\n\nconst (\n\tPolicySetPolicies            PolicySetIncludeOpt = \"policies\"\n\tPolicySetWorkspaces          PolicySetIncludeOpt = \"workspaces\"\n\tPolicySetCurrentVersion      PolicySetIncludeOpt = \"current_version\"\n\tPolicySetNewestVersion       PolicySetIncludeOpt = \"newest_version\"\n\tPolicySetProjects            PolicySetIncludeOpt = \"projects\"\n\tPolicySetWorkspaceExclusions PolicySetIncludeOpt = \"workspace_exclusions\"\n\tPolicySetProjectExclusions   PolicySetIncludeOpt = \"project_exclusions\"\n)\n\n// PolicySetListOptions represents the options for listing policy sets.\ntype PolicySetListOptions struct {\n\tListOptions\n\n\t// Optional: A search string (partial policy set name) used to filter the results.\n\tSearch string `url:\"search[name],omitempty\"`\n\n\t// Optional: A kind string used to filter the results by the policy set kind.\n\tKind PolicyKind `url:\"filter[kind],omitempty\"`\n\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-sets#available-related-resources\n\tInclude []PolicySetIncludeOpt `url:\"include,omitempty\"`\n}\n\n// PolicySetReadOptions are read options.\n// For a full list of relations, please see:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-sets#relationships\ntype PolicySetReadOptions struct {\n\t// Optional: A list of relations to include. See available resources\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-sets#available-related-resources\n\tInclude []PolicySetIncludeOpt `url:\"include,omitempty\"`\n}\n\n// PolicySetCreateOptions represents the options for creating a new policy set.\ntype PolicySetCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,policy-sets\"`\n\n\t// Required: The name of the policy set.\n\tName *string `jsonapi:\"attr,name\"`\n\n\t// Optional: The description of the policy set.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Optional: Whether or not the policy set is global.\n\tGlobal *bool `jsonapi:\"attr,global,omitempty\"`\n\n\t// Optional: The underlying technology that the policy set supports\n\tKind PolicyKind `jsonapi:\"attr,kind,omitempty\"`\n\n\t// Optional: Whether or not users can override this policy when it fails during a run. Only valid for policy evaluations.\n\t// https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/manage-policy-sets#policy-checks-versus-policy-evaluations\n\tOverridable *bool `jsonapi:\"attr,overridable,omitempty\"`\n\n\t// Optional: Whether or not the policy is run as an evaluation inside the agent.\n\tAgentEnabled *bool `jsonapi:\"attr,agent-enabled,omitempty\"`\n\n\t// Optional: The policy tool version to run the evaluation against.\n\tPolicyToolVersion *string `jsonapi:\"attr,policy-tool-version,omitempty\"`\n\n\t// Optional: A list of glob patterns that trigger policy set updates.\n\tPolicyUpdatePatterns []string `jsonapi:\"attr,policy-update-patterns,omitempty\"`\n\n\t// Optional: The sub-path within the attached VCS repository to ingress. All\n\t// files and directories outside of this sub-path will be ignored.\n\t// This option may only be specified when a VCS repo is present.\n\tPoliciesPath *string `jsonapi:\"attr,policies-path,omitempty\"`\n\n\t// Optional: The initial members of the policy set.\n\tPolicies []*Policy `jsonapi:\"relation,policies,omitempty\"`\n\n\t// Optional: VCS repository information. When present, the policies and\n\t// configuration will be sourced from the specified VCS repository\n\t// instead of being defined within the policy set itself. Note that\n\t// this option is mutually exclusive with the Policies option and\n\t// both cannot be used at the same time.\n\tVCSRepo *VCSRepoOptions `jsonapi:\"attr,vcs-repo,omitempty\"`\n\n\t// Optional: The initial list of workspaces for which the policy set should be enforced.\n\tWorkspaces []*Workspace `jsonapi:\"relation,workspaces,omitempty\"`\n\n\t// Optional: The initial list of workspace exclusions for which the policy set should be enforced.\n\tWorkspaceExclusions []*Workspace `jsonapi:\"relation,workspace-exclusions,omitempty\"`\n\n\t// Optional: The initial list of projects for which the policy set should be enforced.\n\tProjects []*Project `jsonapi:\"relation,projects,omitempty\"`\n\n\t// Optional: The initial list of project exclusions for which the policy set should be enforced.\n\tProjectExclusions []*Project `jsonapi:\"relation,project-exclusions,omitempty\"`\n}\n\n// PolicySetUpdateOptions represents the options for updating a policy set.\ntype PolicySetUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,policy-sets\"`\n\n\t// Optional: The name of the policy set.\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: The description of the policy set.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Optional: Whether or not the policy set is global.\n\tGlobal *bool `jsonapi:\"attr,global,omitempty\"`\n\n\t// Optional: Whether or not users can override this policy when it fails during a run. Only valid for policy evaluations.\n\t// https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/manage-policy-sets#policy-checks-versus-policy-evaluations\n\tOverridable *bool `jsonapi:\"attr,overridable,omitempty\"`\n\n\t// Optional: Whether or not the policy is run as an evaluation inside the agent.\n\tAgentEnabled *bool `jsonapi:\"attr,agent-enabled,omitempty\"`\n\n\t// Optional: The policy tool version to run the evaluation against.\n\tPolicyToolVersion *string `jsonapi:\"attr,policy-tool-version,omitempty\"`\n\n\t// Optional: A list of glob patterns that trigger policy set updates.\n\tPolicyUpdatePatterns []string `jsonapi:\"attr,policy-update-patterns,omitempty\"`\n\n\t// Optional: The sub-path within the attached VCS repository to ingress. All\n\t// files and directories outside of this sub-path will be ignored.\n\t// This option may only be specified when a VCS repo is present.\n\tPoliciesPath *string `jsonapi:\"attr,policies-path,omitempty\"`\n\n\t// Optional: VCS repository information. When present, the policies and\n\t// configuration will be sourced from the specified VCS repository\n\t// instead of being defined within the policy set itself. Note that\n\t// specifying this option may only be used on policy sets with no\n\t// directly-attached policies (*PolicySet.Policies). Specifying this\n\t// option when policies are already present will result in an error.\n\tVCSRepo *VCSRepoOptions `jsonapi:\"attr,vcs-repo,omitempty\"`\n}\n\n// PolicySetAddPoliciesOptions represents the options for adding policies\n// to a policy set.\ntype PolicySetAddPoliciesOptions struct {\n\t// The policies to add to the policy set.\n\tPolicies []*Policy\n}\n\n// PolicySetRemovePoliciesOptions represents the options for removing\n// policies from a policy set.\ntype PolicySetRemovePoliciesOptions struct {\n\t// The policies to remove from the policy set.\n\tPolicies []*Policy\n}\n\n// PolicySetAddWorkspacesOptions represents the options for adding workspaces\n// to a policy set.\ntype PolicySetAddWorkspacesOptions struct {\n\t// The workspaces to add to the policy set.\n\tWorkspaces []*Workspace\n}\n\n// PolicySetRemoveWorkspacesOptions represents the options for removing\n// workspaces from a policy set.\ntype PolicySetRemoveWorkspacesOptions struct {\n\t// The workspaces to remove from the policy set.\n\tWorkspaces []*Workspace\n}\n\n// PolicySetAddWorkspaceExclusionsOptions represents the options for adding workspace exclusions to a policy set.\ntype PolicySetAddWorkspaceExclusionsOptions struct {\n\t// The workspaces to add to the policy set exclusion list.\n\tWorkspaceExclusions []*Workspace\n}\n\n// PolicySetRemoveWorkspaceExclusionsOptions represents the options for removing workspace exclusions from a policy set.\ntype PolicySetRemoveWorkspaceExclusionsOptions struct {\n\t// The workspaces to remove from the policy set exclusion list.\n\tWorkspaceExclusions []*Workspace\n}\n\n// PolicySetAddProjectExclusionsOptions represents the options for adding project exclusions to a policy set.\ntype PolicySetAddProjectExclusionsOptions struct {\n\t// The projects to add to the policy set exclusion list.\n\tProjectExclusions []*Project\n}\n\n// PolicySetRemoveProjectExclusionsOptions represents the options for removing project exclusions from a policy set.\ntype PolicySetRemoveProjectExclusionsOptions struct {\n\t// The projects to remove from the policy set exclusion list.\n\tProjectExclusions []*Project\n}\n\n// PolicySetAddProjectsOptions represents the options for adding projects\n// to a policy set.\ntype PolicySetAddProjectsOptions struct {\n\t// The projects to add to the policy set.\n\tProjects []*Project\n}\n\n// PolicySetRemoveProjectsOptions represents the options for removing\n// projects from a policy set.\ntype PolicySetRemoveProjectsOptions struct {\n\t// The projects to remove from the policy set.\n\tProjects []*Project\n}\n\n// List all the policies for a given organization.\nfunc (s *policySets) List(ctx context.Context, organization string, options *PolicySetListOptions) (*PolicySetList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/policy-sets\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpsl := &PolicySetList{}\n\terr = req.Do(ctx, psl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn psl, nil\n}\n\n// Create a policy set and associate it with an organization.\nfunc (s *policySets) Create(ctx context.Context, organization string, options PolicySetCreateOptions) (*PolicySet, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/policy-sets\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tps := &PolicySet{}\n\terr = req.Do(ctx, ps)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ps, err\n}\n\n// Read a policy set by its ID.\nfunc (s *policySets) Read(ctx context.Context, policySetID string) (*PolicySet, error) {\n\treturn s.ReadWithOptions(ctx, policySetID, nil)\n}\n\n// ReadWithOptions reads a policy by its ID using the options supplied.\nfunc (s *policySets) ReadWithOptions(ctx context.Context, policySetID string, options *PolicySetReadOptions) (*PolicySet, error) {\n\tif !validStringID(&policySetID) {\n\t\treturn nil, ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tps := &PolicySet{}\n\terr = req.Do(ctx, ps)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ps, err\n}\n\n// Update an existing policy set.\nfunc (s *policySets) Update(ctx context.Context, policySetID string, options PolicySetUpdateOptions) (*PolicySet, error) {\n\tif !validStringID(&policySetID) {\n\t\treturn nil, ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tps := &PolicySet{}\n\terr = req.Do(ctx, ps)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ps, err\n}\n\n// AddPolicies adds policies to a policy set\nfunc (s *policySets) AddPolicies(ctx context.Context, policySetID string, options PolicySetAddPoliciesOptions) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/relationships/policies\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.Policies)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// RemovePolicies remove policies from a policy set\nfunc (s *policySets) RemovePolicies(ctx context.Context, policySetID string, options PolicySetRemovePoliciesOptions) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/relationships/policies\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.Policies)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Addworkspaces adds workspaces to a policy set.\nfunc (s *policySets) AddWorkspaces(ctx context.Context, policySetID string, options PolicySetAddWorkspacesOptions) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/relationships/workspaces\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.Workspaces)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// RemoveWorkspaces removes workspaces from a policy set.\nfunc (s *policySets) RemoveWorkspaces(ctx context.Context, policySetID string, options PolicySetRemoveWorkspacesOptions) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/relationships/workspaces\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.Workspaces)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// AddWorkspaceExclusions adds workspace exclusions to a policy set.\nfunc (s *policySets) AddWorkspaceExclusions(ctx context.Context, policySetID string, options PolicySetAddWorkspaceExclusionsOptions) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/relationships/workspace-exclusions\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.WorkspaceExclusions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// RemoveWorkspaceExclusions removes workspace exclusions from a policy set.\nfunc (s *policySets) RemoveWorkspaceExclusions(ctx context.Context, policySetID string, options PolicySetRemoveWorkspaceExclusionsOptions) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/relationships/workspace-exclusions\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.WorkspaceExclusions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// AddProjects adds projects to a given policy set.\nfunc (s *policySets) AddProjects(ctx context.Context, policySetID string, options PolicySetAddProjectsOptions) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/relationships/projects\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.Projects)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// RemoveProjects removes projects from a policy set.\nfunc (s *policySets) RemoveProjects(ctx context.Context, policySetID string, options PolicySetRemoveProjectsOptions) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/relationships/projects\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.Projects)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// AddProjectExclusions adds project exclusions to a given policy set.\nfunc (s *policySets) AddProjectExclusions(ctx context.Context, policySetID string, options PolicySetAddProjectExclusionsOptions) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/relationships/project-exclusions\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.ProjectExclusions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// RemoveProjectExclusions removes project exclusions to a given policy set.\nfunc (s *policySets) RemoveProjectExclusions(ctx context.Context, policySetID string, options PolicySetRemoveProjectExclusionsOptions) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/relationships/project-exclusions\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.ProjectExclusions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Delete a policy set by its ID.\nfunc (s *policySets) Delete(ctx context.Context, policySetID string) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o PolicySetCreateOptions) valid() error {\n\tif !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\tif !validStringID(o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetRemoveWorkspacesOptions) valid() error {\n\tif o.Workspaces == nil {\n\t\treturn ErrWorkspacesRequired\n\t}\n\tif len(o.Workspaces) == 0 {\n\t\treturn ErrWorkspaceMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetRemoveWorkspaceExclusionsOptions) valid() error {\n\tif o.WorkspaceExclusions == nil {\n\t\treturn ErrWorkspacesRequired\n\t}\n\tif len(o.WorkspaceExclusions) == 0 {\n\t\treturn ErrWorkspaceMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetRemoveProjectsOptions) valid() error {\n\tif o.Projects == nil {\n\t\treturn ErrRequiredProject\n\t}\n\tif len(o.Projects) == 0 {\n\t\treturn ErrProjectMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetUpdateOptions) valid() error {\n\tif o.Name != nil && !validStringID(o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetAddPoliciesOptions) valid() error {\n\tif o.Policies == nil {\n\t\treturn ErrRequiredPolicies\n\t}\n\tif len(o.Policies) == 0 {\n\t\treturn ErrInvalidPolicies\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetRemovePoliciesOptions) valid() error {\n\tif o.Policies == nil {\n\t\treturn ErrRequiredPolicies\n\t}\n\tif len(o.Policies) == 0 {\n\t\treturn ErrInvalidPolicies\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetAddWorkspacesOptions) valid() error {\n\tif o.Workspaces == nil {\n\t\treturn ErrWorkspacesRequired\n\t}\n\tif len(o.Workspaces) == 0 {\n\t\treturn ErrWorkspaceMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetAddWorkspaceExclusionsOptions) valid() error {\n\tif o.WorkspaceExclusions == nil {\n\t\treturn ErrWorkspacesRequired\n\t}\n\tif len(o.WorkspaceExclusions) == 0 {\n\t\treturn ErrWorkspaceMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetAddProjectsOptions) valid() error {\n\tif o.Projects == nil {\n\t\treturn ErrRequiredProject\n\t}\n\tif len(o.Projects) == 0 {\n\t\treturn ErrProjectMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetAddProjectExclusionsOptions) valid() error {\n\tif o.ProjectExclusions == nil {\n\t\treturn ErrRequiredProject\n\t}\n\tif len(o.ProjectExclusions) == 0 {\n\t\treturn ErrProjectMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o PolicySetRemoveProjectExclusionsOptions) valid() error {\n\tif o.ProjectExclusions == nil {\n\t\treturn ErrRequiredProject\n\t}\n\tif len(o.ProjectExclusions) == 0 {\n\t\treturn ErrProjectMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o *PolicySetReadOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "policy_set_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPolicySetsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tversion := createAdminSentinelVersion()\n\topts := AdminSentinelVersionCreateOptions{\n\t\tVersion:    version,\n\t\tURL:        \"https://www.hashicorp.com\",\n\t\tSHA:        genSha(t),\n\t\tOfficial:   Bool(false),\n\t\tDeprecated: Bool(false),\n\t\tEnabled:    Bool(true),\n\t\tBeta:       Bool(false),\n\t}\n\tsv, err := client.Admin.SentinelVersions.Create(ctx, opts)\n\trequire.NoError(t, err)\n\n\tworkspace, workspaceCleanup := createWorkspace(t, client, orgTest)\n\tdefer workspaceCleanup()\n\texcludedWorkspace, excludedWorkspaceCleanup := createWorkspace(t, client, orgTest)\n\tdefer excludedWorkspaceCleanup()\n\n\toptions := PolicySetCreateOptions{\n\t\tKind:              Sentinel,\n\t\tAgentEnabled:      Bool(true),\n\t\tPolicyToolVersion: String(sv.Version),\n\t\tOverridable:       Bool(true),\n\t}\n\n\tpsTest1, psTestCleanup1 := createPolicySetWithOptions(t, client, orgTest, nil, []*Workspace{workspace}, []*Workspace{excludedWorkspace}, nil, options)\n\tdefer psTestCleanup1()\n\tpsTest2, psTestCleanup2 := createPolicySetWithOptions(t, client, orgTest, nil, []*Workspace{workspace}, []*Workspace{excludedWorkspace}, nil, options)\n\tdefer psTestCleanup2()\n\tpsTest3, psTestCleanup3 := createPolicySet(t, client, orgTest, nil, []*Workspace{workspace}, []*Workspace{excludedWorkspace}, nil, OPA)\n\tdefer psTestCleanup3()\n\tdefer func() {\n\t\terr := client.Admin.SentinelVersions.Delete(ctx, sv.ID)\n\t\trequire.NoError(t, err)\n\t}()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tpsl, err := client.PolicySets.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Contains(t, psl.Items, psTest1)\n\t\tassert.Contains(t, psl.Items, psTest2)\n\t\tassert.Contains(t, psl.Items, psTest3)\n\t\tassert.Equal(t, true, psl.Items[0].AgentEnabled)\n\t\tassert.Equal(t, 1, psl.CurrentPage)\n\t\tassert.Equal(t, 3, psl.TotalCount)\n\t})\n\n\tt.Run(\"with pagination\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tpsl, err := client.PolicySets.List(ctx, orgTest.Name, &PolicySetListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Empty(t, psl.Items)\n\t\tassert.Equal(t, 999, psl.CurrentPage)\n\t\tassert.Equal(t, 3, psl.TotalCount)\n\t})\n\n\tt.Run(\"with search\", func(t *testing.T) {\n\t\t// Search by one of the policy set's names; we should get only that policy\n\t\t// set and pagination data should reflect the search as well\n\t\tpsl, err := client.PolicySets.List(ctx, orgTest.Name, &PolicySetListOptions{\n\t\t\tSearch: psTest1.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Contains(t, psl.Items, psTest1)\n\t\tassert.NotContains(t, psl.Items, psTest2)\n\t\tassert.Equal(t, 1, psl.CurrentPage)\n\t\tassert.Equal(t, 1, psl.TotalCount)\n\t})\n\n\tt.Run(\"with include param\", func(t *testing.T) {\n\t\tpsl, err := client.PolicySets.List(ctx, orgTest.Name, &PolicySetListOptions{\n\t\t\tInclude: []PolicySetIncludeOpt{PolicySetWorkspaces},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 3, len(psl.Items))\n\n\t\tassert.NotNil(t, psl.Items[0].Workspaces)\n\t\tassert.Equal(t, 1, len(psl.Items[0].Workspaces))\n\t\tassert.Equal(t, workspace.ID, psl.Items[0].Workspaces[0].ID)\n\t})\n\n\tt.Run(\"with workspace exclusion include param\", func(t *testing.T) {\n\t\tpsl, err := client.PolicySets.List(ctx, orgTest.Name, &PolicySetListOptions{\n\t\t\tInclude: []PolicySetIncludeOpt{PolicySetWorkspaceExclusions},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 3, len(psl.Items))\n\n\t\tassert.NotNil(t, psl.Items[0].WorkspaceExclusions)\n\t\tassert.Equal(t, 1, len(psl.Items[0].WorkspaceExclusions))\n\t\tassert.Equal(t, excludedWorkspace.ID, psl.Items[0].WorkspaceExclusions[0].ID)\n\t\tassert.Equal(t, excludedWorkspace.Name, psl.Items[0].WorkspaceExclusions[0].Name)\n\t\tassert.Equal(t, excludedWorkspace.CreatedAt, psl.Items[0].WorkspaceExclusions[0].CreatedAt)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tps, err := client.PolicySets.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, ps)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestPolicySetsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tversion := createAdminSentinelVersion()\n\topts := AdminSentinelVersionCreateOptions{\n\t\tVersion:    version,\n\t\tURL:        \"https://www.hashicorp.com\",\n\t\tSHA:        genSha(t),\n\t\tOfficial:   Bool(false),\n\t\tDeprecated: Bool(false),\n\t\tEnabled:    Bool(true),\n\t\tBeta:       Bool(false),\n\t}\n\tsv, err := client.Admin.SentinelVersions.Create(ctx, opts)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\terr := client.Admin.SentinelVersions.Delete(ctx, sv.ID)\n\t\trequire.NoError(t, err)\n\t}()\n\n\tvar vcsPolicyID string\n\n\tt.Run(\"with valid attributes\", func(t *testing.T) {\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:              String(randomString(t)),\n\t\t\tPolicyToolVersion: String(sv.Version),\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, \"\")\n\t\tassert.False(t, ps.Global)\n\t})\n\n\tt.Run(\"OPA policy set with valid attributes\", func(t *testing.T) {\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName: String(\"opa-policy-set\"),\n\t\t\tKind: OPA,\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, \"\")\n\t\tassert.Equal(t, ps.Kind, OPA)\n\t\tassert.False(t, ps.Global)\n\t})\n\n\tt.Run(\"OPA policy set with policy update patterns\", func(t *testing.T) {\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:                 String(randomString(t)),\n\t\t\tKind:                 OPA,\n\t\t\tPolicyUpdatePatterns: []string{\"*.rego\", \"policies/**\"},\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Kind, OPA)\n\t\tassert.Equal(t, options.PolicyUpdatePatterns, ps.PolicyUpdatePatterns)\n\t})\n\n\tt.Run(\"with pinned policy runtime version valid attributes\", func(t *testing.T) {\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:              String(randomString(t)),\n\t\t\tKind:              Sentinel,\n\t\t\tAgentEnabled:      Bool(true),\n\t\t\tPolicyToolVersion: String(sv.Version),\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, \"\")\n\t\tassert.Equal(t, ps.Kind, Sentinel)\n\t\tassert.Equal(t, ps.AgentEnabled, true)\n\t\tassert.Equal(t, ps.PolicyToolVersion, sv.Version)\n\t\tassert.False(t, ps.Global)\n\t})\n\n\tt.Run(\"with pinned policy runtime version and missing kind\", func(t *testing.T) {\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:              String(randomString(t)),\n\t\t\tAgentEnabled:      Bool(true),\n\t\t\tPolicyToolVersion: String(sv.Version),\n\t\t\tOverridable:       Bool(true),\n\t\t}\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, \"\")\n\t\tassert.Equal(t, ps.Kind, Sentinel)\n\t\tassert.Equal(t, ps.AgentEnabled, true)\n\t\tassert.Equal(t, ps.PolicyToolVersion, sv.Version)\n\t\tassert.False(t, ps.Global)\n\t})\n\n\tt.Run(\"with kind missing\", func(t *testing.T) {\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName: String(\"policy-set1\"),\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, \"\")\n\t\tassert.Equal(t, ps.Kind, Sentinel)\n\t\tassert.False(t, ps.Global)\n\t})\n\n\tt.Run(\"with agent enabled missing\", func(t *testing.T) {\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName: String(randomString(t)),\n\t\t\tKind: Sentinel,\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, \"\")\n\t\tassert.Equal(t, ps.Kind, Sentinel)\n\t\tassert.Equal(t, ps.AgentEnabled, false)\n\t\tassert.False(t, ps.Global)\n\t})\n\n\tt.Run(\"with all attributes provided - sentinel\", func(t *testing.T) {\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:        String(\"global\"),\n\t\t\tDescription: String(\"Policies in this set will be checked in ALL workspaces!\"),\n\t\t\tKind:        Sentinel,\n\t\t\tGlobal:      Bool(true),\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, *options.Description)\n\t\tassert.Equal(t, ps.Kind, Sentinel)\n\t\tassert.True(t, ps.Global)\n\t})\n\n\tt.Run(\"with all attributes provided - OPA\", func(t *testing.T) {\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:        String(\"global1\"),\n\t\t\tDescription: String(\"Policies in this set will be checked in ALL workspaces!\"),\n\t\t\tKind:        OPA,\n\t\t\tOverridable: Bool(true),\n\t\t\tGlobal:      Bool(true),\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, *options.Description)\n\t\tassert.Equal(t, ps.Overridable, options.Overridable)\n\t\tassert.Equal(t, ps.Kind, OPA)\n\t\tassert.True(t, ps.Global)\n\t})\n\n\tt.Run(\"with missing overridable attribute\", func(t *testing.T) {\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:        String(\"global2\"),\n\t\t\tDescription: String(\"Policies in this set will be checked in ALL workspaces!\"),\n\t\t\tKind:        OPA,\n\t\t\tGlobal:      Bool(true),\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, *options.Description)\n\t\tassert.Equal(t, ps.Overridable, Bool(false))\n\t\tassert.Equal(t, ps.Kind, OPA)\n\t\tassert.True(t, ps.Global)\n\t})\n\n\tt.Run(\"with policies and workspaces provided\", func(t *testing.T) {\n\t\tpTest, pTestCleanup := createPolicy(t, client, orgTest)\n\t\tdefer pTestCleanup()\n\t\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\t\tdefer wTestCleanup()\n\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:       String(\"populated-policy-set\"),\n\t\t\tPolicies:   []*Policy{pTest},\n\t\t\tKind:       Sentinel,\n\t\t\tWorkspaces: []*Workspace{wTest},\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.PolicyCount, 1)\n\t\tassert.Equal(t, ps.Policies[0].ID, pTest.ID)\n\t\tassert.Equal(t, ps.WorkspaceCount, 1)\n\t\tassert.Equal(t, ps.Kind, Sentinel)\n\t\tassert.Equal(t, ps.Workspaces[0].ID, wTest.ID)\n\t})\n\n\tt.Run(\"with policies, workspaces and projects provided\", func(t *testing.T) {\n\t\tpTest, pTestCleanup := createPolicy(t, client, orgTest)\n\t\tdefer pTestCleanup()\n\t\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\t\tdefer wTestCleanup()\n\t\tprjTest, prjTestCleanup := createProject(t, client, orgTest)\n\t\tdefer prjTestCleanup()\n\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:       String(\"project-policy-set\"),\n\t\t\tPolicies:   []*Policy{pTest},\n\t\t\tWorkspaces: []*Workspace{wTest},\n\t\t\tProjects:   []*Project{prjTest},\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.PolicyCount, 1)\n\t\tassert.Equal(t, ps.Policies[0].ID, pTest.ID)\n\t\tassert.Equal(t, ps.WorkspaceCount, 1)\n\t\tassert.Equal(t, ps.Workspaces[0].ID, wTest.ID)\n\t\tassert.Equal(t, ps.ProjectCount, 1)\n\t\tassert.Equal(t, ps.Projects[0].ID, prjTest.ID)\n\t})\n\n\tt.Run(\"with policies and excluded workspaces provided\", func(t *testing.T) {\n\t\tpTest, pTestCleanup := createPolicy(t, client, orgTest)\n\t\tdefer pTestCleanup()\n\t\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\t\tdefer wTestCleanup()\n\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:                String(\"exclusion-policy-set\"),\n\t\t\tPolicies:            []*Policy{pTest},\n\t\t\tWorkspaceExclusions: []*Workspace{wTest},\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.PolicyCount, 1)\n\t\tassert.Equal(t, ps.Policies[0].ID, pTest.ID)\n\t\tassert.Equal(t, ps.WorkspaceExclusions[0].ID, wTest.ID)\n\t\tassert.Equal(t, len(ps.WorkspaceExclusions), 1)\n\t})\n\n\tt.Run(\"with vcs policy set\", func(t *testing.T) {\n\t\tgithubIdentifier := os.Getenv(\"GITHUB_POLICY_SET_IDENTIFIER\")\n\t\tif githubIdentifier == \"\" {\n\t\t\tt.Skip(\"Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test\")\n\t\t}\n\n\t\t// We are deliberately ignoring the cleanup func here, because there's a potential race condition\n\t\t// against the subsequent subtest -- HCP Terraform performs some async cleanup on VCS repos when deleting an\n\t\t// OAuthClient, and we've seen evidence that it will zero out the next test's NEW VCSRepo values if\n\t\t// they manage to slip in before the async stuff completes, even though the new values link it to a\n\t\t// new OAuthToken. Anyway, there's a deferred cleanup for orgTest in the outer scope, so the org's\n\t\t// dependent: destroy clause on OAuthClients will clean this up when the test as a whole ends.\n\t\toc, _ := createOAuthToken(t, client, orgTest)\n\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:         String(\"vcs-policy-set\"),\n\t\t\tKind:         Sentinel,\n\t\t\tPoliciesPath: String(\"/policy-sets/foo\"),\n\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\tBranch:            String(\"policies\"),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oc.ID),\n\t\t\t\tIngressSubmodules: Bool(true),\n\t\t\t},\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Save policy ID to be used by update func\n\t\tvcsPolicyID = ps.ID\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, \"\")\n\t\tassert.False(t, ps.Global)\n\t\tassert.Equal(t, ps.Kind, Sentinel)\n\t\tassert.Equal(t, ps.PoliciesPath, \"/policy-sets/foo\")\n\t\tassert.Equal(t, ps.VCSRepo.Branch, \"policies\")\n\t\tassert.Equal(t, ps.VCSRepo.DisplayIdentifier, githubIdentifier)\n\t\tassert.Equal(t, ps.VCSRepo.Identifier, githubIdentifier)\n\t\tassert.Equal(t, ps.VCSRepo.IngressSubmodules, true)\n\t\tassert.Equal(t, ps.VCSRepo.OAuthTokenID, oc.ID)\n\t\tassert.Equal(t, ps.VCSRepo.RepositoryHTTPURL, fmt.Sprintf(\"https://github.com/%s\", githubIdentifier))\n\t\tassert.Equal(t, ps.VCSRepo.ServiceProvider, string(ServiceProviderGithub))\n\t\tassert.Regexp(t, fmt.Sprintf(\"^%s/webhooks/vcs/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$\", regexp.QuoteMeta(DefaultConfig().Address)), ps.VCSRepo.WebhookURL)\n\t})\n\n\tt.Run(\"with vcs policy updated\", func(t *testing.T) {\n\t\tgithubIdentifier := os.Getenv(\"GITHUB_POLICY_SET_IDENTIFIER\")\n\t\tif githubIdentifier == \"\" {\n\t\t\tt.Skip(\"Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test\")\n\t\t}\n\n\t\t// We are deliberately ignoring the cleanup func here, because it's not really necessary: there's a\n\t\t// deferred cleanup for orgTest in the outer scope, so the org's dependent: destroy clause on\n\t\t// OAuthClients will clean this up when the test as a whole ends. Unlike the one in the previous\n\t\t// subtest, there's no known race condition here because there aren't any later subtests that modify\n\t\t// this same policy set. But I'm being consistent with the prior case just to reduce risks from future\n\t\t// copypasta code.\n\t\toc, _ := createOAuthToken(t, client, orgTest)\n\n\t\toptions := PolicySetUpdateOptions{\n\t\t\tName:         String(\"vcs-policy-set\"),\n\t\t\tPoliciesPath: String(\"/policy-sets/bar\"),\n\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\tBranch:            String(\"policies\"),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oc.ID),\n\t\t\t\tIngressSubmodules: Bool(false),\n\t\t\t},\n\t\t}\n\n\t\tps, err := client.PolicySets.Update(ctx, vcsPolicyID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, \"\")\n\t\tassert.False(t, ps.Global)\n\t\tassert.Equal(t, ps.PoliciesPath, \"/policy-sets/bar\")\n\t\tassert.Equal(t, ps.VCSRepo.Branch, \"policies\")\n\t\tassert.Equal(t, ps.VCSRepo.DisplayIdentifier, githubIdentifier)\n\t\tassert.Equal(t, ps.VCSRepo.Identifier, githubIdentifier)\n\t\tassert.Equal(t, ps.VCSRepo.IngressSubmodules, false)\n\t\tassert.Equal(t, ps.VCSRepo.OAuthTokenID, oc.ID)\n\t\tassert.Equal(t, ps.VCSRepo.RepositoryHTTPURL, fmt.Sprintf(\"https://github.com/%s\", githubIdentifier))\n\t\tassert.Equal(t, ps.VCSRepo.ServiceProvider, string(ServiceProviderGithub))\n\t\tassert.Regexp(t, fmt.Sprintf(\"^%s/webhooks/vcs/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$\", regexp.QuoteMeta(DefaultConfig().Address)), ps.VCSRepo.WebhookURL)\n\t})\n\n\tt.Run(\"without a name provided\", func(t *testing.T) {\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, PolicySetCreateOptions{})\n\t\tassert.Nil(t, ps)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"with an invalid name provided\", func(t *testing.T) {\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, PolicySetCreateOptions{\n\t\t\tName: String(\"nope/nope!\"),\n\t\t})\n\t\tassert.Nil(t, ps)\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tps, err := client.PolicySets.Create(ctx, badIdentifier, PolicySetCreateOptions{\n\t\t\tName: String(\"policy-set\"),\n\t\t})\n\t\tassert.Nil(t, ps)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestPolicySetsCreateWithGithubApp(t *testing.T) {\n\tt.Parallel()\n\tgHAInstallationID := os.Getenv(\"GITHUB_APP_INSTALLATION_ID\")\n\tif gHAInstallationID == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_APP_INSTALLATION_ID before running this test!\")\n\t}\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tvar vcsPolicyID string\n\tt.Run(\"with vcs policy set\", func(t *testing.T) {\n\t\tgithubIdentifier := os.Getenv(\"GITHUB_POLICY_SET_IDENTIFIER\")\n\t\tif githubIdentifier == \"\" {\n\t\t\tt.Skip(\"Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test\")\n\t\t}\n\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:         String(\"vcs-policy-set\"),\n\t\t\tPoliciesPath: String(\"/policy-sets/foo\"),\n\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\tBranch:            String(\"policies\"),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tGHAInstallationID: String(gHAInstallationID),\n\t\t\t\tIngressSubmodules: Bool(true),\n\t\t\t},\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Save policy ID to be used by update func\n\t\tvcsPolicyID = ps.ID\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, \"\")\n\t\tassert.False(t, ps.Global)\n\t\tassert.Equal(t, ps.PoliciesPath, \"/policy-sets/foo\")\n\t\tassert.Equal(t, ps.VCSRepo.Branch, \"policies\")\n\t\tassert.Equal(t, ps.VCSRepo.DisplayIdentifier, githubIdentifier)\n\t\tassert.Equal(t, ps.VCSRepo.Identifier, githubIdentifier)\n\t\tassert.Equal(t, ps.VCSRepo.IngressSubmodules, true)\n\t\tassert.Equal(t, ps.VCSRepo.GHAInstallationID, gHAInstallationID)\n\t\tassert.Equal(t, ps.VCSRepo.RepositoryHTTPURL, fmt.Sprintf(\"https://github.com/%s\", githubIdentifier))\n\t})\n\n\tt.Run(\"with vcs policy updated\", func(t *testing.T) {\n\t\tgithubIdentifier := os.Getenv(\"GITHUB_POLICY_SET_IDENTIFIER\")\n\t\tif githubIdentifier == \"\" {\n\t\t\tt.Skip(\"Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test\")\n\t\t}\n\n\t\toptions := PolicySetUpdateOptions{\n\t\t\tName:         String(\"vcs-policy-set\"),\n\t\t\tPoliciesPath: String(\"/policy-sets/bar\"),\n\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\tBranch:            String(\"policies\"),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tGHAInstallationID: String(gHAInstallationID),\n\t\t\t\tIngressSubmodules: Bool(false),\n\t\t\t},\n\t\t}\n\n\t\tps, err := client.PolicySets.Update(ctx, vcsPolicyID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, \"\")\n\t\tassert.False(t, ps.Global)\n\t\tassert.Equal(t, ps.PoliciesPath, \"/policy-sets/bar\")\n\t\tassert.Equal(t, ps.VCSRepo.Branch, \"policies\")\n\t\tassert.Equal(t, ps.VCSRepo.DisplayIdentifier, githubIdentifier)\n\t\tassert.Equal(t, ps.VCSRepo.Identifier, githubIdentifier)\n\t\tassert.Equal(t, ps.VCSRepo.IngressSubmodules, false)\n\t\tassert.Equal(t, ps.VCSRepo.GHAInstallationID, gHAInstallationID)\n\t\tassert.Equal(t, ps.VCSRepo.RepositoryHTTPURL, fmt.Sprintf(\"https://github.com/%s\", githubIdentifier))\n\t\tassert.Equal(t, ps.VCSRepo.ServiceProvider, string(\"github_app\"))\n\t})\n}\nfunc TestPolicySetsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpsTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\tps, err := client.PolicySets.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.ID, psTest.ID)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\tps, err := client.PolicySets.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, ps)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n\n\tt.Run(\"with policy set version\", func(t *testing.T) {\n\t\tpsv, psvCleanup := createPolicySetVersion(t, client, psTest)\n\t\tdefer psvCleanup()\n\n\t\tps, err := client.PolicySets.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// The newest one is the policy set version created in this test.\n\t\tassert.Equal(t, ps.NewestVersion.ID, psv.ID)\n\t\t// The current policy set version is nil because nothing has been uploaded\n\t\tassert.Nil(t, ps.CurrentVersion)\n\n\t\tpsvNew, psvCleanupNew := createPolicySetVersion(t, client, psTest)\n\t\tdefer psvCleanupNew()\n\t\terr = client.PolicySetVersions.Upload(\n\t\t\tctx,\n\t\t\t*psv,\n\t\t\t\"test-fixtures/policy-set-version\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t// give HCP Terraform some time to process uploading the\n\t\t// policy set version before reading.\n\t\ttime.Sleep(waitForPolicySetVersionUpload)\n\n\t\topts := &PolicySetReadOptions{\n\t\t\tInclude: []PolicySetIncludeOpt{PolicySetCurrentVersion, PolicySetNewestVersion},\n\t\t}\n\t\tpsWithOptions, err := client.PolicySets.ReadWithOptions(ctx, psTest.ID, opts)\n\t\trequire.NoError(t, err)\n\n\t\t// The newest policy set version is changed to the most recent one\n\t\t// that was created.\n\t\trequire.NotNil(t, psWithOptions.NewestVersion)\n\t\tassert.Equal(t, psWithOptions.NewestVersion.ID, psvNew.ID)\n\t\tassert.Equal(t, psWithOptions.NewestVersion.Status, PolicySetVersionPending)\n\t\t// The current one is now set because policies were uploaded to the\n\t\t// policy set version. Notice how it is set to the one that was uplaoded,\n\t\t// not the newest policy set version.\n\t\trequire.NotNil(t, psWithOptions.CurrentVersion)\n\t\tassert.Equal(t, psWithOptions.CurrentVersion.ID, psv.ID)\n\t\tassert.Equal(t, psWithOptions.CurrentVersion.Status, PolicySetVersionReady)\n\t})\n}\n\nfunc TestPolicySetsUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tversion := createAdminSentinelVersion()\n\topts := AdminSentinelVersionCreateOptions{\n\t\tVersion:    version,\n\t\tURL:        \"https://www.hashicorp.com\",\n\t\tSHA:        genSha(t),\n\t\tOfficial:   Bool(false),\n\t\tDeprecated: Bool(false),\n\t\tEnabled:    Bool(true),\n\t\tBeta:       Bool(false),\n\t}\n\tsv, err := client.Admin.SentinelVersions.Create(ctx, opts)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\terr := client.Admin.SentinelVersions.Delete(ctx, sv.ID)\n\t\trequire.NoError(t, err)\n\t}()\n\n\toptions := PolicySetCreateOptions{\n\t\tKind:              Sentinel,\n\t\tAgentEnabled:      Bool(true),\n\t\tPolicyToolVersion: String(sv.Version),\n\t\tOverridable:       Bool(true),\n\t}\n\n\tpsTest, psTestCleanup := createPolicySetWithOptions(t, client, orgTest, nil, nil, nil, nil, options)\n\tdefer psTestCleanup()\n\tpsTest2, psTestCleanup2 := createPolicySet(t, client, orgTest, nil, nil, nil, nil, \"opa\")\n\tdefer psTestCleanup2()\n\n\tt.Run(\"with valid attributes\", func(t *testing.T) {\n\t\toptions := PolicySetUpdateOptions{\n\t\t\tAgentEnabled: Bool(false),\n\t\t\tName:         String(\"global\"),\n\t\t\tDescription:  String(\"Policies in this set will be checked in ALL workspaces!\"),\n\t\t\tGlobal:       Bool(true),\n\t\t}\n\n\t\tps, err := client.PolicySets.Update(ctx, psTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.AgentEnabled, false)\n\t\tassert.Equal(t, ps.PolicyToolVersion, \"\")\n\t\tassert.Nil(t, ps.Overridable)\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, *options.Description)\n\t\tassert.True(t, ps.Global)\n\t})\n\n\tt.Run(\"with valid attributes-OPA\", func(t *testing.T) {\n\t\toptions := PolicySetUpdateOptions{\n\t\t\tName:        String(\"global2\"),\n\t\t\tDescription: String(\"Policies in this set will be checked in ALL workspaces!\"),\n\t\t\tGlobal:      Bool(true),\n\t\t\tOverridable: Bool(true),\n\t\t}\n\n\t\tps, err := client.PolicySets.Update(ctx, psTest2.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ps.Name, *options.Name)\n\t\tassert.Equal(t, ps.Description, *options.Description)\n\t\tassert.True(t, ps.Global)\n\t\tassert.True(t, *ps.Overridable)\n\t})\n\n\tt.Run(\"with policy update patterns\", func(t *testing.T) {\n\t\toptions := PolicySetUpdateOptions{\n\t\t\tPolicyUpdatePatterns: []string{\"*.rego\", \"policies/**\"},\n\t\t}\n\n\t\tps, err := client.PolicySets.Update(ctx, psTest2.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, options.PolicyUpdatePatterns, ps.PolicyUpdatePatterns)\n\t})\n\n\tt.Run(\"with invalid attributes\", func(t *testing.T) {\n\t\tps, err := client.PolicySets.Update(ctx, psTest.ID, PolicySetUpdateOptions{\n\t\t\tName: String(\"nope/nope!\"),\n\t\t})\n\t\tassert.Nil(t, ps)\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\tps, err := client.PolicySets.Update(ctx, badIdentifier, PolicySetUpdateOptions{\n\t\t\tName: String(\"policy-set\"),\n\t\t})\n\t\tassert.Nil(t, ps)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetsAddPolicies(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpTest1, pTestCleanup1 := createPolicy(t, client, orgTest)\n\tdefer pTestCleanup1()\n\tpTest2, pTestCleanup2 := createPolicy(t, client, orgTest)\n\tdefer pTestCleanup2()\n\tpsTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with policies provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddPolicies(ctx, psTest.ID, PolicySetAddPoliciesOptions{\n\t\t\tPolicies: []*Policy{pTest1, pTest2},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.PolicySets.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, ps.PolicyCount, 2)\n\n\t\tids := []string{}\n\t\tfor _, policy := range ps.Policies {\n\t\t\tids = append(ids, policy.ID)\n\t\t}\n\n\t\tassert.Contains(t, ids, pTest1.ID)\n\t\tassert.Contains(t, ids, pTest2.ID)\n\t})\n\n\tt.Run(\"without policies provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddPolicies(ctx, psTest.ID, PolicySetAddPoliciesOptions{})\n\t\tassert.Equal(t, err, ErrRequiredPolicies)\n\t})\n\n\tt.Run(\"with empty policies slice\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddPolicies(ctx, psTest.ID, PolicySetAddPoliciesOptions{\n\t\t\tPolicies: []*Policy{},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidPolicies)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddPolicies(ctx, badIdentifier, PolicySetAddPoliciesOptions{\n\t\t\tPolicies: []*Policy{pTest1, pTest2},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetsRemovePolicies(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpTest1, pTestCleanup1 := createPolicy(t, client, orgTest)\n\tdefer pTestCleanup1()\n\tpTest2, pTestCleanup2 := createPolicy(t, client, orgTest)\n\tdefer pTestCleanup2()\n\tpsTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with policies provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemovePolicies(ctx, psTest.ID, PolicySetRemovePoliciesOptions{\n\t\t\tPolicies: []*Policy{pTest1, pTest2},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.PolicySets.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 0, ps.PolicyCount)\n\t\tassert.Empty(t, ps.Policies)\n\t})\n\n\tt.Run(\"without policies provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemovePolicies(ctx, psTest.ID, PolicySetRemovePoliciesOptions{})\n\t\tassert.Equal(t, err, ErrRequiredPolicies)\n\t})\n\n\tt.Run(\"with empty policies slice\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemovePolicies(ctx, psTest.ID, PolicySetRemovePoliciesOptions{\n\t\t\tPolicies: []*Policy{},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidPolicies)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemovePolicies(ctx, badIdentifier, PolicySetRemovePoliciesOptions{\n\t\t\tPolicies: []*Policy{pTest1, pTest2},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetsAddWorkspaces(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twTest1, wTestCleanup1 := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup1()\n\twTest2, wTestCleanup2 := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup2()\n\tpsTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with workspaces provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddWorkspaces(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddWorkspacesOptions{\n\t\t\t\tWorkspaces: []*Workspace{wTest1, wTest2},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.PolicySets.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 2, ps.WorkspaceCount)\n\n\t\tids := []string{}\n\t\tfor _, ws := range ps.Workspaces {\n\t\t\tids = append(ids, ws.ID)\n\t\t}\n\n\t\tassert.Contains(t, ids, wTest1.ID)\n\t\tassert.Contains(t, ids, wTest2.ID)\n\t})\n\n\tt.Run(\"without workspaces provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddWorkspaces(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddWorkspacesOptions{},\n\t\t)\n\t\tassert.Equal(t, err, ErrWorkspacesRequired)\n\t})\n\n\tt.Run(\"with empty workspaces slice\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddWorkspaces(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddWorkspacesOptions{Workspaces: []*Workspace{}},\n\t\t)\n\t\tassert.Equal(t, err, ErrWorkspaceMinLimit)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddWorkspaces(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tPolicySetAddWorkspacesOptions{\n\t\t\t\tWorkspaces: []*Workspace{wTest1, wTest2},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetsRemoveWorkspaces(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twTest1, wTestCleanup1 := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup1()\n\twTest2, wTestCleanup2 := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup2()\n\tpsTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, []*Workspace{wTest1, wTest2}, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with workspaces provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveWorkspaces(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveWorkspacesOptions{\n\t\t\t\tWorkspaces: []*Workspace{wTest1, wTest2},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.PolicySets.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 0, ps.WorkspaceCount)\n\t\tassert.Empty(t, ps.Workspaces)\n\t})\n\n\tt.Run(\"without workspaces provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveWorkspaces(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveWorkspacesOptions{},\n\t\t)\n\t\tassert.Equal(t, err, ErrWorkspacesRequired)\n\t})\n\n\tt.Run(\"with empty workspaces slice\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveWorkspaces(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveWorkspacesOptions{Workspaces: []*Workspace{}},\n\t\t)\n\t\tassert.Equal(t, err, ErrWorkspaceMinLimit)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveWorkspaces(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tPolicySetRemoveWorkspacesOptions{\n\t\t\t\tWorkspaces: []*Workspace{wTest1, wTest2},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetsAddWorkspaceExclusions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twTest1, wTestCleanup1 := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup1()\n\twTest2, wTestCleanup2 := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup2()\n\tpsTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with workspace exclusions provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddWorkspaceExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddWorkspaceExclusionsOptions{\n\t\t\t\tWorkspaceExclusions: []*Workspace{wTest1, wTest2},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.PolicySets.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 2, len(ps.WorkspaceExclusions))\n\n\t\tids := []string{}\n\t\tfor _, ws := range ps.WorkspaceExclusions {\n\t\t\tids = append(ids, ws.ID)\n\t\t}\n\n\t\tassert.Contains(t, ids, wTest1.ID)\n\t\tassert.Contains(t, ids, wTest2.ID)\n\t})\n\n\tt.Run(\"without workspace exclusions provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddWorkspaceExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddWorkspaceExclusionsOptions{},\n\t\t)\n\t\tassert.Equal(t, err, ErrWorkspacesRequired)\n\t})\n\n\tt.Run(\"with empty workspace exclusions slice\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddWorkspaceExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddWorkspaceExclusionsOptions{WorkspaceExclusions: []*Workspace{}},\n\t\t)\n\t\tassert.Equal(t, err, ErrWorkspaceMinLimit)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddWorkspaceExclusions(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tPolicySetAddWorkspaceExclusionsOptions{\n\t\t\t\tWorkspaceExclusions: []*Workspace{wTest1, wTest2},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetsRemoveWorkspaceExclusions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twTest1, wTestCleanup1 := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup1()\n\twTest2, wTestCleanup2 := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup2()\n\tpsTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, []*Workspace{wTest1, wTest2}, nil, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with workspace exclusions provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveWorkspaceExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveWorkspaceExclusionsOptions{\n\t\t\t\tWorkspaceExclusions: []*Workspace{wTest1, wTest2},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.PolicySets.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 0, len(ps.WorkspaceExclusions))\n\t\tassert.Empty(t, ps.WorkspaceExclusions)\n\t})\n\n\tt.Run(\"without workspaces provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveWorkspaceExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveWorkspaceExclusionsOptions{},\n\t\t)\n\t\tassert.Equal(t, err, ErrWorkspacesRequired)\n\t})\n\n\tt.Run(\"with empty workspaces slice\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveWorkspaceExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveWorkspaceExclusionsOptions{WorkspaceExclusions: []*Workspace{}},\n\t\t)\n\t\tassert.Equal(t, err, ErrWorkspaceMinLimit)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveWorkspaceExclusions(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tPolicySetRemoveWorkspaceExclusionsOptions{\n\t\t\t\tWorkspaceExclusions: []*Workspace{wTest1, wTest2},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetsAddProjects(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpTest1, pTestCleanup1 := createProject(t, client, orgTest)\n\tdefer pTestCleanup1()\n\tpTest2, pTestCleanup2 := createProject(t, client, orgTest)\n\tdefer pTestCleanup2()\n\tpsTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with projects provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddProjectsOptions{\n\t\t\t\tProjects: []*Project{pTest1, pTest2},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.PolicySets.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 2, ps.ProjectCount)\n\n\t\tids := []string{}\n\t\tfor _, ws := range ps.Projects {\n\t\t\tids = append(ids, ws.ID)\n\t\t}\n\n\t\tassert.Contains(t, ids, pTest1.ID)\n\t\tassert.Contains(t, ids, pTest2.ID)\n\t})\n\n\tt.Run(\"without projects provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddProjectsOptions{},\n\t\t)\n\t\tassert.Equal(t, err, ErrRequiredProject)\n\t})\n\n\tt.Run(\"with empty projects slice\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddProjectsOptions{Projects: []*Project{}},\n\t\t)\n\t\tassert.Equal(t, err, ErrProjectMinLimit)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddProjects(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tPolicySetAddProjectsOptions{\n\t\t\t\tProjects: []*Project{pTest1, pTest2},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetsRemoveProjects(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpTest1, pTestCleanup1 := createProject(t, client, orgTest)\n\tdefer pTestCleanup1()\n\tpTest2, pTestCleanup2 := createProject(t, client, orgTest)\n\tdefer pTestCleanup2()\n\tpsTest, psTestCleanup := createPolicySet(t, client, orgTest, nil, nil, nil, []*Project{pTest1, pTest2}, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with projects provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveProjectsOptions{\n\t\t\t\tProjects: []*Project{pTest1, pTest2},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.PolicySets.Read(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 0, ps.ProjectCount)\n\t\tassert.Empty(t, ps.Projects)\n\t})\n\n\tt.Run(\"without projects provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveProjectsOptions{},\n\t\t)\n\t\tassert.Equal(t, err, ErrRequiredProject)\n\t})\n\n\tt.Run(\"with empty projects slice\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveProjects(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveProjectsOptions{Projects: []*Project{}},\n\t\t)\n\t\tassert.Equal(t, err, ErrProjectMinLimit)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveProjects(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tPolicySetRemoveProjectsOptions{\n\t\t\t\tProjects: []*Project{pTest1, pTest2},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetAddProjectExclusions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpTest1, pTestCleanup1 := createProject(t, client, orgTest)\n\tdefer pTestCleanup1()\n\tpTest2, pTestCleanup2 := createProject(t, client, orgTest)\n\tdefer pTestCleanup2()\n\tpTest3, pTestCleanup3 := createProject(t, client, orgTest)\n\tdefer pTestCleanup3()\n\n\tpsTest, psTestCleanup := createPolicySetWithOptions(t, client, orgTest, nil, nil, nil, nil, PolicySetCreateOptions{\n\t\tGlobal: Bool(true),\n\t})\n\tdefer psTestCleanup()\n\n\tt.Run(\"with project exclusions provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddProjectExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddProjectExclusionsOptions{\n\t\t\t\tProjectExclusions: []*Project{pTest1, pTest2, pTest3},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.PolicySets.ReadWithOptions(ctx, psTest.ID, &PolicySetReadOptions{\n\t\t\tInclude: []PolicySetIncludeOpt{\n\t\t\t\tPolicySetProjectExclusions,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 3, len(ps.ProjectExclusions))\n\n\t\tids := []string{}\n\t\tfor _, p := range ps.ProjectExclusions {\n\t\t\tids = append(ids, p.ID)\n\t\t}\n\n\t\tassert.Contains(t, ids, pTest1.ID)\n\t\tassert.Contains(t, ids, pTest2.ID)\n\t\tassert.Contains(t, ids, pTest3.ID)\n\t})\n\n\tt.Run(\"without project exclusions provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddProjectExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddProjectExclusionsOptions{},\n\t\t)\n\t\tassert.Equal(t, err, ErrRequiredProject)\n\t})\n\n\tt.Run(\"with empty project exclusions slice\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddProjectExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetAddProjectExclusionsOptions{ProjectExclusions: []*Project{}},\n\t\t)\n\t\tassert.Equal(t, err, ErrProjectMinLimit)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PolicySets.AddProjectExclusions(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tPolicySetAddProjectExclusionsOptions{\n\t\t\t\tProjectExclusions: []*Project{pTest1, pTest2, pTest3},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetRemoveProjectExclusions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpTest1, pTestCleanup1 := createProject(t, client, orgTest)\n\tdefer pTestCleanup1()\n\tpTest2, pTestCleanup2 := createProject(t, client, orgTest)\n\tdefer pTestCleanup2()\n\tpTest3, pTestCleanup3 := createProject(t, client, orgTest)\n\tdefer pTestCleanup3()\n\n\tpsTest, psTestCleanup := createPolicySetWithOptions(t, client, orgTest, nil, nil, nil, nil, PolicySetCreateOptions{\n\t\tGlobal: Bool(true),\n\t\tProjectExclusions: []*Project{\n\t\t\tpTest1,\n\t\t\tpTest2,\n\t\t\tpTest3,\n\t\t},\n\t})\n\tdefer psTestCleanup()\n\n\tt.Run(\"with project exclusions provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveProjectExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveProjectExclusionsOptions{\n\t\t\t\tProjectExclusions: []*Project{pTest1, pTest2, pTest3},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tps, err := client.PolicySets.ReadWithOptions(ctx, psTest.ID, &PolicySetReadOptions{\n\t\t\tInclude: []PolicySetIncludeOpt{\n\t\t\t\t\"project_exclusions\",\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 0, len(ps.ProjectExclusions))\n\t\tassert.Empty(t, ps.ProjectExclusions)\n\t})\n\n\tt.Run(\"without project exclusions provided\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveProjectExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveProjectExclusionsOptions{},\n\t\t)\n\t\tassert.Equal(t, err, ErrRequiredProject)\n\t})\n\n\tt.Run(\"with empty project exclusions slice\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveProjectExclusions(\n\t\t\tctx,\n\t\t\tpsTest.ID,\n\t\t\tPolicySetRemoveProjectExclusionsOptions{ProjectExclusions: []*Project{}},\n\t\t)\n\t\tassert.Equal(t, err, ErrProjectMinLimit)\n\t})\n\n\tt.Run(\"without a valid ID\", func(t *testing.T) {\n\t\terr := client.PolicySets.RemoveProjectExclusions(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\tPolicySetRemoveProjectExclusionsOptions{\n\t\t\t\tProjectExclusions: []*Project{pTest1, pTest2, pTest3},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tpsTest, _ := createPolicySet(t, client, orgTest, nil, nil, nil, nil, \"\")\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.PolicySets.Delete(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the policy - it should fail.\n\t\t_, err = client.PolicySets.Read(ctx, psTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the policy does not exist\", func(t *testing.T) {\n\t\terr := client.PolicySets.Delete(ctx, psTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the policy ID is invalid\", func(t *testing.T) {\n\t\terr := client.PolicySets.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n"
  },
  {
    "path": "policy_set_parameter.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ PolicySetParameters = (*policySetParameters)(nil)\n\n// PolicySetParameters describes all the parameter related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-set-params\ntype PolicySetParameters interface {\n\t// List all the parameters associated with the given policy-set.\n\tList(ctx context.Context, policySetID string, options *PolicySetParameterListOptions) (*PolicySetParameterList, error)\n\n\t// Create is used to create a new parameter.\n\tCreate(ctx context.Context, policySetID string, options PolicySetParameterCreateOptions) (*PolicySetParameter, error)\n\n\t// Read a parameter by its ID.\n\tRead(ctx context.Context, policySetID string, parameterID string) (*PolicySetParameter, error)\n\n\t// Update values of an existing parameter.\n\tUpdate(ctx context.Context, policySetID string, parameterID string, options PolicySetParameterUpdateOptions) (*PolicySetParameter, error)\n\n\t// Delete a parameter by its ID.\n\tDelete(ctx context.Context, policySetID string, parameterID string) error\n}\n\n// policySetParameters implements Parameters.\ntype policySetParameters struct {\n\tclient *Client\n}\n\n// PolicySetParameterList represents a list of parameters.\ntype PolicySetParameterList struct {\n\t*Pagination\n\tItems []*PolicySetParameter\n}\n\n// PolicySetParameter represents a Policy Set parameter\ntype PolicySetParameter struct {\n\tID        string       `jsonapi:\"primary,vars\"`\n\tKey       string       `jsonapi:\"attr,key\"`\n\tValue     string       `jsonapi:\"attr,value\"`\n\tCategory  CategoryType `jsonapi:\"attr,category\"`\n\tSensitive bool         `jsonapi:\"attr,sensitive\"`\n\n\t// Relations\n\tPolicySet *PolicySet `jsonapi:\"relation,configurable\"`\n}\n\n// PolicySetParameterListOptions represents the options for listing parameters.\ntype PolicySetParameterListOptions struct {\n\tListOptions\n}\n\n// PolicySetParameterCreateOptions represents the options for creating a new parameter.\ntype PolicySetParameterCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,vars\"`\n\n\t// Required: The name of the parameter.\n\tKey *string `jsonapi:\"attr,key\"`\n\n\t// Optional: The value of the parameter.\n\tValue *string `jsonapi:\"attr,value,omitempty\"`\n\n\t// Required: The Category of the parameter, should always be \"policy-set\"\n\tCategory *CategoryType `jsonapi:\"attr,category\"`\n\n\t// Optional: Whether the value is sensitive.\n\tSensitive *bool `jsonapi:\"attr,sensitive,omitempty\"`\n}\n\n// PolicySetParameterUpdateOptions represents the options for updating a parameter.\ntype PolicySetParameterUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,vars\"`\n\n\t// Optional: The name of the parameter.\n\tKey *string `jsonapi:\"attr,key,omitempty\"`\n\n\t// Optional: The value of the parameter.\n\tValue *string `jsonapi:\"attr,value,omitempty\"`\n\n\t// Optional: Whether the value is sensitive.\n\tSensitive *bool `jsonapi:\"attr,sensitive,omitempty\"`\n}\n\n// List all the parameters associated with the given policy-set.\nfunc (s *policySetParameters) List(ctx context.Context, policySetID string, options *PolicySetParameterListOptions) (*PolicySetParameterList, error) {\n\tif !validStringID(&policySetID) {\n\t\treturn nil, ErrInvalidPolicySetID\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/parameters\", policySetID)\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvl := &PolicySetParameterList{}\n\terr = req.Do(ctx, vl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vl, nil\n}\n\n// Create is used to create a new parameter.\nfunc (s *policySetParameters) Create(ctx context.Context, policySetID string, options PolicySetParameterCreateOptions) (*PolicySetParameter, error) {\n\tif !validStringID(&policySetID) {\n\t\treturn nil, ErrInvalidPolicySetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/parameters\", url.PathEscape(policySetID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &PolicySetParameter{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// Read a parameter by its ID.\nfunc (s *policySetParameters) Read(ctx context.Context, policySetID, parameterID string) (*PolicySetParameter, error) {\n\tif !validStringID(&policySetID) {\n\t\treturn nil, ErrInvalidPolicySetID\n\t}\n\tif !validStringID(&parameterID) {\n\t\treturn nil, ErrInvalidParamID\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/parameters/%s\", url.PathEscape(policySetID), url.PathEscape(parameterID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &PolicySetParameter{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, err\n}\n\n// Update values of an existing parameter.\nfunc (s *policySetParameters) Update(ctx context.Context, policySetID, parameterID string, options PolicySetParameterUpdateOptions) (*PolicySetParameter, error) {\n\tif !validStringID(&policySetID) {\n\t\treturn nil, ErrInvalidPolicySetID\n\t}\n\tif !validStringID(&parameterID) {\n\t\treturn nil, ErrInvalidParamID\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/parameters/%s\", url.PathEscape(policySetID), url.PathEscape(parameterID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &PolicySetParameter{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// Delete a parameter by its ID.\nfunc (s *policySetParameters) Delete(ctx context.Context, policySetID, parameterID string) error {\n\tif !validStringID(&policySetID) {\n\t\treturn ErrInvalidPolicySetID\n\t}\n\tif !validStringID(&parameterID) {\n\t\treturn ErrInvalidParamID\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/parameters/%s\", url.PathEscape(policySetID), url.PathEscape(parameterID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o PolicySetParameterCreateOptions) valid() error {\n\tif !validString(o.Key) {\n\t\treturn ErrRequiredKey\n\t}\n\tif o.Category == nil {\n\t\treturn ErrRequiredCategory\n\t}\n\tif *o.Category != CategoryPolicySet {\n\t\treturn ErrInvalidCategory\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "policy_set_parameter_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPolicySetParametersList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpsTest, pTestCleanup := createPolicySet(t, client, orgTest, nil, nil, nil, nil, \"\")\n\tdefer pTestCleanup()\n\n\tpTest1, pTestCleanup1 := createPolicySetParameter(t, client, psTest)\n\tdefer pTestCleanup1()\n\tpTest2, pTestCleanup2 := createPolicySetParameter(t, client, psTest)\n\tdefer pTestCleanup2()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tpl, err := client.PolicySetParameters.List(ctx, psTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, pl.Items, pTest1)\n\t\tassert.Contains(t, pl.Items, pTest2)\n\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\tassert.Equal(t, 1, pl.CurrentPage)\n\t\tassert.Equal(t, 2, pl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tpl, err := client.PolicySetParameters.List(ctx, psTest.ID, &PolicySetParameterListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, pl.Items)\n\t\tassert.Equal(t, 999, pl.CurrentPage)\n\t\tassert.Equal(t, 2, pl.TotalCount)\n\t})\n\n\tt.Run(\"when policy set ID is invalid ID\", func(t *testing.T) {\n\t\tpl, err := client.PolicySetParameters.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, pl)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetParametersCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpsTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := PolicySetParameterCreateOptions{\n\t\t\tKey:      String(randomKeyValue(t)),\n\t\t\tValue:    String(randomKeyValue(t)),\n\t\t\tCategory: Category(CategoryPolicySet),\n\t\t}\n\n\t\tp, err := client.PolicySetParameters.Create(ctx, psTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, p.ID)\n\t\tassert.Equal(t, *options.Key, p.Key)\n\t\tassert.Equal(t, *options.Value, p.Value)\n\t\tassert.Equal(t, *options.Category, p.Category)\n\t\tassert.Equal(t, psTest.ID, p.PolicySet.ID)\n\t\t// The policy set isn't returned correcly by the API.\n\t\t// assert.Equal(t, *options.PolicySet, v.PolicySet)\n\t})\n\n\tt.Run(\"when options has an empty string value\", func(t *testing.T) {\n\t\toptions := PolicySetParameterCreateOptions{\n\t\t\tKey:      String(randomKeyValue(t)),\n\t\t\tValue:    String(\"\"),\n\t\t\tCategory: Category(CategoryPolicySet),\n\t\t}\n\n\t\tp, err := client.PolicySetParameters.Create(ctx, psTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, p.ID)\n\t\tassert.Equal(t, *options.Key, p.Key)\n\t\tassert.Equal(t, *options.Value, p.Value)\n\t\tassert.Equal(t, *options.Category, p.Category)\n\t})\n\n\tt.Run(\"when options is missing value\", func(t *testing.T) {\n\t\toptions := PolicySetParameterCreateOptions{\n\t\t\tKey:      String(randomKeyValue(t)),\n\t\t\tCategory: Category(CategoryPolicySet),\n\t\t}\n\n\t\tp, err := client.PolicySetParameters.Create(ctx, psTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, p.ID)\n\t\tassert.Equal(t, *options.Key, p.Key)\n\t\tassert.Equal(t, \"\", p.Value)\n\t\tassert.Equal(t, *options.Category, p.Category)\n\t})\n\n\tt.Run(\"when options is missing key\", func(t *testing.T) {\n\t\toptions := PolicySetParameterCreateOptions{\n\t\t\tValue:    String(randomKeyValue(t)),\n\t\t\tCategory: Category(CategoryPolicySet),\n\t\t}\n\n\t\t_, err := client.PolicySetParameters.Create(ctx, psTest.ID, options)\n\t\tassert.Equal(t, err, ErrRequiredKey)\n\t})\n\n\tt.Run(\"when options has an empty key\", func(t *testing.T) {\n\t\toptions := PolicySetParameterCreateOptions{\n\t\t\tKey:      String(\"\"),\n\t\t\tValue:    String(randomKeyValue(t)),\n\t\t\tCategory: Category(CategoryPolicySet),\n\t\t}\n\n\t\t_, err := client.PolicySetParameters.Create(ctx, psTest.ID, options)\n\t\tassert.Equal(t, err, ErrRequiredKey)\n\t})\n\n\tt.Run(\"when options is missing category\", func(t *testing.T) {\n\t\toptions := PolicySetParameterCreateOptions{\n\t\t\tKey:   String(randomKeyValue(t)),\n\t\t\tValue: String(randomKeyValue(t)),\n\t\t}\n\n\t\t_, err := client.PolicySetParameters.Create(ctx, psTest.ID, options)\n\t\tassert.Equal(t, err, ErrRequiredCategory)\n\t})\n\n\tt.Run(\"when policy set ID is invalid\", func(t *testing.T) {\n\t\toptions := PolicySetParameterCreateOptions{\n\t\t\tKey:      String(randomKeyValue(t)),\n\t\t\tValue:    String(randomKeyValue(t)),\n\t\t\tCategory: Category(CategoryPolicySet),\n\t\t}\n\n\t\t_, err := client.PolicySetParameters.Create(ctx, badIdentifier, options)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetParametersRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpTest, pTestCleanup := createPolicySetParameter(t, client, nil)\n\tdefer pTestCleanup()\n\n\tt.Run(\"when the parameter exists\", func(t *testing.T) {\n\t\tp, err := client.PolicySetParameters.Read(ctx, pTest.PolicySet.ID, pTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, pTest.ID, p.ID)\n\t\tassert.Equal(t, pTest.Category, p.Category)\n\t\tassert.Equal(t, pTest.Key, p.Key)\n\t\tassert.Equal(t, pTest.Sensitive, p.Sensitive)\n\t\tassert.Equal(t, pTest.Value, p.Value)\n\t\tassert.Equal(t, pTest.PolicySet.ID, p.PolicySet.ID)\n\t})\n\n\tt.Run(\"when the parameter does not exist\", func(t *testing.T) {\n\t\tp, err := client.PolicySetParameters.Read(ctx, pTest.PolicySet.ID, \"nonexisting\")\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"without a valid policy set ID\", func(t *testing.T) {\n\t\tp, err := client.PolicySetParameters.Read(ctx, badIdentifier, pTest.ID)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n\n\tt.Run(\"without a valid parameter ID\", func(t *testing.T) {\n\t\tp, err := client.PolicySetParameters.Read(ctx, pTest.PolicySet.ID, badIdentifier)\n\t\tassert.Nil(t, p)\n\t\tassert.Equal(t, err, ErrInvalidParamID)\n\t})\n}\n\nfunc TestPolicySetParametersUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpTest, pTestCleanup := createPolicySetParameter(t, client, nil)\n\tdefer pTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := PolicySetParameterUpdateOptions{\n\t\t\tKey:   String(\"newname\"),\n\t\t\tValue: String(\"newvalue\"),\n\t\t}\n\n\t\tp, err := client.PolicySetParameters.Update(ctx, pTest.PolicySet.ID, pTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Key, p.Key)\n\t\tassert.Equal(t, *options.Value, p.Value)\n\t})\n\n\tt.Run(\"when updating a subset of values\", func(t *testing.T) {\n\t\toptions := PolicySetParameterUpdateOptions{\n\t\t\tKey: String(\"someothername\"),\n\t\t}\n\n\t\tp, err := client.PolicySetParameters.Update(ctx, pTest.PolicySet.ID, pTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Key, p.Key)\n\t})\n\n\tt.Run(\"with sensitive set\", func(t *testing.T) {\n\t\toptions := PolicySetParameterUpdateOptions{\n\t\t\tSensitive: Bool(true),\n\t\t}\n\n\t\tp, err := client.PolicySetParameters.Update(ctx, pTest.PolicySet.ID, pTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Sensitive, p.Sensitive)\n\t\tassert.Empty(t, p.Value) // Because its now sensitive\n\t})\n\n\tt.Run(\"without any changes\", func(t *testing.T) {\n\t\tpTest, pTestCleanup := createPolicySetParameter(t, client, nil)\n\t\tdefer pTestCleanup()\n\n\t\tp, err := client.PolicySetParameters.Update(ctx, pTest.PolicySet.ID, pTest.ID, PolicySetParameterUpdateOptions{})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, pTest, p)\n\t})\n\n\tt.Run(\"with invalid parameter ID\", func(t *testing.T) {\n\t\t_, err := client.PolicySetParameters.Update(ctx, badIdentifier, pTest.ID, PolicySetParameterUpdateOptions{})\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n\n\tt.Run(\"with invalid parameter ID\", func(t *testing.T) {\n\t\t_, err := client.PolicySetParameters.Update(ctx, pTest.PolicySet.ID, badIdentifier, PolicySetParameterUpdateOptions{})\n\t\tassert.Equal(t, err, ErrInvalidParamID)\n\t})\n}\n\nfunc TestPolicySetParametersDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpsTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\tpTest, _ := createPolicySetParameter(t, client, psTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.PolicySetParameters.Delete(ctx, psTest.ID, pTest.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with non existing parameter ID\", func(t *testing.T) {\n\t\terr := client.PolicySetParameters.Delete(ctx, psTest.ID, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid policy set ID\", func(t *testing.T) {\n\t\terr := client.PolicySetParameters.Delete(ctx, badIdentifier, pTest.ID)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n\n\tt.Run(\"with invalid parameter ID\", func(t *testing.T) {\n\t\terr := client.PolicySetParameters.Delete(ctx, psTest.ID, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidParamID)\n\t})\n}\n"
  },
  {
    "path": "policy_set_version.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ PolicySetVersions = (*policySetVersions)(nil)\n\n// PolicySetVersions describes all the Policy Set Version related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/policy-sets#create-a-policy-set-version\ntype PolicySetVersions interface {\n\t// Create is used to create a new Policy Set Version.\n\tCreate(ctx context.Context, policySetID string) (*PolicySetVersion, error)\n\n\t// Read is used to read a Policy Set Version by its ID.\n\tRead(ctx context.Context, policySetVersionID string) (*PolicySetVersion, error)\n\n\t// Upload uploads policy files. It takes a Policy Set Version and a path\n\t// to the set of sentinel files, which will be packaged by hashicorp/go-slug\n\t// before being uploaded.\n\tUpload(ctx context.Context, psv PolicySetVersion, path string) error\n}\n\n// policySetVersions implements PolicySetVersions.\ntype policySetVersions struct {\n\tclient *Client\n}\n\n// PolicySetVersionSource represents a source type of a policy set version.\ntype PolicySetVersionSource string\n\n// List all available sources for a Policy Set Version.\nconst (\n\tPolicySetVersionSourceAPI       PolicySetVersionSource = \"tfe-api\"\n\tPolicySetVersionSourceADO       PolicySetVersionSource = \"ado\"\n\tPolicySetVersionSourceBitBucket PolicySetVersionSource = \"bitbucket\"\n\tPolicySetVersionSourceGitHub    PolicySetVersionSource = \"github\"\n\tPolicySetVersionSourceGitLab    PolicySetVersionSource = \"gitlab\"\n)\n\n// PolicySetVersionStatus represents a policy set version status.\ntype PolicySetVersionStatus string\n\n// List all available policy set version statuses.\nconst (\n\tPolicySetVersionErrored    PolicySetVersionStatus = \"errored\"\n\tPolicySetVersionIngressing PolicySetVersionStatus = \"ingressing\"\n\tPolicySetVersionPending    PolicySetVersionStatus = \"pending\"\n\tPolicySetVersionReady      PolicySetVersionStatus = \"ready\"\n)\n\n// PolicySetVersionStatusTimestamps holds the timestamps for individual policy\n// set version statuses.\ntype PolicySetVersionStatusTimestamps struct {\n\tPendingAt    time.Time `jsonapi:\"attr,pending-at,rfc3339\"`\n\tIngressingAt time.Time `jsonapi:\"attr,ingressing-at,rfc3339\"`\n\tReadyAt      time.Time `jsonapi:\"attr,ready-at,rfc3339\"`\n\tErroredAt    time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n}\n\ntype PolicySetIngressAttributes struct {\n\tCommitSHA  string `jsonapi:\"attr,commit-sha\"`\n\tCommitURL  string `jsonapi:\"attr,commit-url\"`\n\tIdentifier string `jsonapi:\"attr,identifier\"`\n}\n\n// PolicySetVersion represents a Terraform Enterprise Policy Set Version\ntype PolicySetVersion struct {\n\tID                string                           `jsonapi:\"primary,policy-set-versions\"`\n\tSource            PolicySetVersionSource           `jsonapi:\"attr,source\"`\n\tStatus            PolicySetVersionStatus           `jsonapi:\"attr,status\"`\n\tStatusTimestamps  PolicySetVersionStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\tError             string                           `jsonapi:\"attr,error\"`\n\tErrorMessage      string                           `jsonapi:\"attr,error-message\"`\n\tCreatedAt         time.Time                        `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt         time.Time                        `jsonapi:\"attr,updated-at,iso8601\"`\n\tIngressAttributes *PolicySetIngressAttributes      `jsonapi:\"attr,ingress-attributes\"`\n\n\t// Relations\n\tPolicySet *PolicySet `jsonapi:\"relation,policy-set\"`\n\n\t// Links\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n}\n\nfunc (p PolicySetVersion) uploadURL() (string, error) {\n\tuploadURL, ok := p.Links[\"upload\"].(string)\n\tif !ok {\n\t\treturn uploadURL, fmt.Errorf(\"the Policy Set Version does not contain an upload link\")\n\t}\n\n\tif uploadURL == \"\" {\n\t\treturn uploadURL, fmt.Errorf(\"the Policy Set Version upload URL is empty\")\n\t}\n\n\treturn uploadURL, nil\n}\n\n// Create is used to create a new Policy Set Version.\nfunc (p *policySetVersions) Create(ctx context.Context, policySetID string) (*PolicySetVersion, error) {\n\tif !validStringID(&policySetID) {\n\t\treturn nil, ErrInvalidPolicySetID\n\t}\n\n\tu := fmt.Sprintf(\"policy-sets/%s/versions\", url.PathEscape(policySetID))\n\treq, err := p.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpsv := &PolicySetVersion{}\n\terr = req.Do(ctx, psv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn psv, nil\n}\n\n// Read is used to read a Policy Set Version by its ID.\nfunc (p *policySetVersions) Read(ctx context.Context, policySetVersionID string) (*PolicySetVersion, error) {\n\tif !validStringID(&policySetVersionID) {\n\t\treturn nil, ErrInvalidPolicySetID\n\t}\n\n\tu := fmt.Sprintf(\"policy-set-versions/%s\", url.PathEscape(policySetVersionID))\n\treq, err := p.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpsv := &PolicySetVersion{}\n\terr = req.Do(ctx, psv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn psv, nil\n}\n\n// Upload uploads policy files. It takes a Policy Set Version and a path\n// to the set of sentinel files, which will be packaged by hashicorp/go-slug\n// before being uploaded.\nfunc (p *policySetVersions) Upload(ctx context.Context, psv PolicySetVersion, path string) error {\n\tuploadURL, err := psv.uploadURL()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tbody, err := packContents(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn p.client.doForeignPUTRequest(ctx, uploadURL, body)\n}\n"
  },
  {
    "path": "policy_set_version_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst waitForPolicySetVersionUpload = 500 * time.Millisecond\n\nfunc TestPolicySetVersionsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpsTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\tt.Run(\"with valid identifier\", func(t *testing.T) {\n\t\tpsv, err := client.PolicySetVersions.Create(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, psv.ID)\n\t\tassert.Equal(t, psv.Source, PolicySetVersionSourceAPI)\n\t\tassert.Equal(t, psv.PolicySet.ID, psTest.ID)\n\t})\n\n\tt.Run(\"with invalid identifier\", func(t *testing.T) {\n\t\t_, err := client.PolicySetVersions.Create(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidPolicySetID)\n\t})\n}\n\nfunc TestPolicySetVersionsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpsTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil, nil, nil, \"\")\n\tdefer psTestCleanup()\n\n\torigPSV, err := client.PolicySetVersions.Create(ctx, psTest.ID)\n\trequire.NoError(t, err)\n\n\tt.Run(\"with valid id\", func(t *testing.T) {\n\t\tpsv, err := client.PolicySetVersions.Read(ctx, origPSV.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, psv.Source, origPSV.Source)\n\t\tassert.Equal(t, psv.Status, origPSV.Status)\n\t})\n\n\tt.Run(\"with invalid id\", func(t *testing.T) {\n\t\t_, err := client.PolicySetVersions.Read(ctx, randomString(t))\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc TestPolicySetVersionsUpload(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpsv, psvCleanup := createPolicySetVersion(t, client, nil)\n\tdefer psvCleanup()\n\n\tt.Run(\"with valid upload URL\", func(t *testing.T) {\n\t\tpsv, err := client.PolicySetVersions.Read(ctx, psv.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, psv.Status, PolicySetVersionPending)\n\n\t\terr = client.PolicySetVersions.Upload(\n\t\t\tctx,\n\t\t\t*psv,\n\t\t\t\"test-fixtures/policy-set-version\",\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t// give HCP Terraform some time to process uploading the\n\t\t// policy set version before reading.\n\t\ttime.Sleep(waitForPolicySetVersionUpload)\n\n\t\tpsv, err = client.PolicySetVersions.Read(ctx, psv.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, PolicySetVersionReady, psv.Status)\n\t})\n\n\tt.Run(\"with missing upload URL\", func(t *testing.T) {\n\t\tdelete(psv.Links, \"upload\")\n\n\t\terr := client.PolicySetVersions.Upload(\n\t\t\tctx,\n\t\t\t*psv,\n\t\t\t\"test-fixtures/policy-set-version\",\n\t\t)\n\t\tassert.EqualError(t, err, \"the Policy Set Version does not contain an upload link\")\n\t})\n}\n\nfunc TestPolicySetVersionsUploadURL(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"successfully returns upload link\", func(t *testing.T) {\n\t\tlinks := map[string]interface{}{\n\t\t\t\"upload\": \"example.com\",\n\t\t}\n\t\tpsv := PolicySetVersion{\n\t\t\tLinks: links,\n\t\t}\n\n\t\tuploadURL, err := psv.uploadURL()\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, uploadURL, \"example.com\")\n\t})\n\n\tt.Run(\"errors when there is no upload key in the Links\", func(t *testing.T) {\n\t\tlinks := map[string]interface{}{\n\t\t\t\"bad-field\": \"example.com\",\n\t\t}\n\t\tpsv := PolicySetVersion{\n\t\t\tLinks: links,\n\t\t}\n\n\t\t_, err := psv.uploadURL()\n\t\tassert.EqualError(t, err, \"the Policy Set Version does not contain an upload link\")\n\t})\n\n\tt.Run(\"errors when the upload link is empty\", func(t *testing.T) {\n\t\tlinks := map[string]interface{}{\n\t\t\t\"upload\": \"\",\n\t\t}\n\t\tpsv := PolicySetVersion{\n\t\t\tLinks: links,\n\t\t}\n\n\t\t_, err := psv.uploadURL()\n\t\tassert.EqualError(t, err, \"the Policy Set Version upload URL is empty\")\n\t})\n}\n\nfunc TestPolicySetVersionsIngressAttributes(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\tt.Run(\"with vcs\", func(t *testing.T) {\n\t\tgithubIdentifier := os.Getenv(\"GITHUB_POLICY_SET_IDENTIFIER\")\n\t\tif githubIdentifier == \"\" {\n\t\t\tt.Skip(\"Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test\")\n\t\t}\n\n\t\totTest, otTestCleanup := createOAuthToken(t, client, orgTest)\n\t\tt.Cleanup(otTestCleanup)\n\n\t\toptions := PolicySetCreateOptions{\n\t\t\tName:         String(\"vcs-policy-set\"),\n\t\t\tKind:         Sentinel,\n\t\t\tPoliciesPath: String(\"policy-sets/foo\"),\n\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\tBranch:            String(\"policies\"),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(otTest.ID),\n\t\t\t\tIngressSubmodules: Bool(true),\n\t\t\t},\n\t\t}\n\n\t\tps, err := client.PolicySets.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tps, err = client.PolicySets.ReadWithOptions(ctx, ps.ID, &PolicySetReadOptions{\n\t\t\tInclude: []PolicySetIncludeOpt{\n\t\t\t\tPolicySetNewestVersion,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tpsv, err := client.PolicySetVersions.Read(ctx, ps.NewestVersion.ID)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotNil(t, psv.IngressAttributes)\n\t\tassert.NotZero(t, psv.IngressAttributes.CommitSHA)\n\t\tassert.NotZero(t, psv.IngressAttributes.CommitURL)\n\t\tassert.NotZero(t, psv.IngressAttributes.Identifier)\n\t})\n\n\tt.Run(\"without vcs\", func(t *testing.T) {\n\t\tpsTest, psTestCleanup := createPolicySet(t, client, nil, nil, nil, nil, nil, \"\")\n\t\tt.Cleanup(psTestCleanup)\n\n\t\tpsv, err := client.PolicySetVersions.Create(ctx, psTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, psv.ID)\n\t\tassert.Equal(t, psv.Source, PolicySetVersionSourceAPI)\n\t\tassert.Equal(t, psv.PolicySet.ID, psTest.ID)\n\n\t\tassert.Nil(t, psv.IngressAttributes)\n\t})\n}\n"
  },
  {
    "path": "project.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\n\t\"github.com/hashicorp/jsonapi\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Projects = (*projects)(nil)\n\n// Projects describes all the project related methods that the Terraform\n// Enterprise API supports\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/projects\ntype Projects interface {\n\t// List all projects in the given organization\n\tList(ctx context.Context, organization string, options *ProjectListOptions) (*ProjectList, error)\n\n\t// Create a new project.\n\tCreate(ctx context.Context, organization string, options ProjectCreateOptions) (*Project, error)\n\n\t// Read a project by its ID.\n\tRead(ctx context.Context, projectID string) (*Project, error)\n\n\t// ReadWithOptions a project by its ID.\n\tReadWithOptions(ctx context.Context, projectID string, options ProjectReadOptions) (*Project, error)\n\n\t// Update a project.\n\tUpdate(ctx context.Context, projectID string, options ProjectUpdateOptions) (*Project, error)\n\n\t// Delete a project.\n\tDelete(ctx context.Context, projectID string) error\n\n\t// ListTagBindings lists all tag bindings associated with the project.\n\tListTagBindings(ctx context.Context, projectID string) ([]*TagBinding, error)\n\n\t// ListEffectiveTagBindings lists all tag bindings associated with the project. In practice,\n\t// this should be the same as ListTagBindings since projects do not currently inherit\n\t// tag bindings.\n\tListEffectiveTagBindings(ctx context.Context, workspaceID string) ([]*EffectiveTagBinding, error)\n\n\t// AddTagBindings adds or modifies the value of existing tag binding keys for a project.\n\tAddTagBindings(ctx context.Context, projectID string, options ProjectAddTagBindingsOptions) ([]*TagBinding, error)\n\n\t// DeleteAllTagBindings removes all existing tag bindings for a project.\n\tDeleteAllTagBindings(ctx context.Context, projectID string) error\n}\n\n// projects implements Projects\ntype projects struct {\n\tclient *Client\n}\n\n// ProjectList represents a list of projects\ntype ProjectList struct {\n\t*Pagination\n\tItems []*Project\n}\n\n// Project represents a Terraform Enterprise project\ntype Project struct {\n\tID                          string                       `jsonapi:\"primary,projects\"`\n\tAutoDestroyActivityDuration jsonapi.NullableAttr[string] `jsonapi:\"attr,auto-destroy-activity-duration,omitempty\"`\n\tDefaultExecutionMode        string                       `jsonapi:\"attr,default-execution-mode\"`\n\tDescription                 string                       `jsonapi:\"attr,description\"`\n\tIsUnified                   bool                         `jsonapi:\"attr,is-unified\"`\n\tName                        string                       `jsonapi:\"attr,name\"`\n\tSettingOverwrites           *ProjectSettingOverwrites    `jsonapi:\"attr,setting-overwrites\"`\n\n\t// Relations\n\tDefaultAgentPool     *AgentPool             `jsonapi:\"relation,default-agent-pool\"`\n\tEffectiveTagBindings []*EffectiveTagBinding `jsonapi:\"relation,effective-tag-bindings\"`\n\tOrganization         *Organization          `jsonapi:\"relation,organization\"`\n}\n\n// Note: the fields of this struct are bool pointers instead of bool values, in order to simplify support for\n// future TFE versions that support *some but not all* of the inherited defaults that go-tfe knows about.\ntype ProjectSettingOverwrites struct {\n\tExecutionMode *bool `jsonapi:\"attr,default-execution-mode\"`\n\tAgentPool     *bool `jsonapi:\"attr,default-agent-pool\"`\n}\n\ntype ProjectIncludeOpt string\n\nconst (\n\tProjectEffectiveTagBindings ProjectIncludeOpt = \"effective_tag_bindings\"\n)\n\n// ProjectListOptions represents the options for listing projects\ntype ProjectListOptions struct {\n\tListOptions\n\n\t// Optional: String (complete project name) used to filter the results.\n\t// If multiple, comma separated values are specified, projects matching\n\t// any of the names are returned.\n\tName string `url:\"filter[names],omitempty\"`\n\n\t// Optional: A query string to search projects by names.\n\tQuery string `url:\"q,omitempty\"`\n\n\t// Optional: A filter string to list projects filtered by key/value tags.\n\t// These are not annotated and therefore not encoded by go-querystring\n\tTagBindings []*TagBinding\n\n\t// Optional: A list of relations to include\n\tInclude []ProjectIncludeOpt `url:\"include,omitempty\"`\n}\n\ntype ProjectReadOptions struct {\n\t// Optional: A list of relations to include\n\tInclude []ProjectIncludeOpt `url:\"include,omitempty\"`\n}\n\n// ProjectCreateOptions represents the options for creating a project\ntype ProjectCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,projects\"`\n\n\t// Required: A name to identify the project.\n\tName string `jsonapi:\"attr,name\"`\n\n\t// Optional: A description for the project.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Associated TagBindings of the project.\n\tTagBindings []*TagBinding `jsonapi:\"relation,tag-bindings,omitempty\"`\n\n\t// Optional: For all workspaces in the project, the period of time to wait\n\t// after workspace activity to trigger a destroy run. The format should roughly\n\t// match a Go duration string limited to days and hours, e.g. \"24h\" or \"1d\".\n\tAutoDestroyActivityDuration jsonapi.NullableAttr[string] `jsonapi:\"attr,auto-destroy-activity-duration,omitempty\"`\n\n\t// Optional: DefaultExecutionMode the default execution mode for workspaces in the project\n\tDefaultExecutionMode *string `jsonapi:\"attr,default-execution-mode,omitempty\"`\n\n\t// Optional: DefaultAgentPoolID default agent pool for workspaces in the project,\n\t// required when DefaultExecutionMode is set to `agent`\n\tDefaultAgentPoolID *string `jsonapi:\"attr,default-agent-pool-id,omitempty\"`\n\n\t// Optional: Struct of booleans, which indicate whether the project\n\t// specifies its own values for various settings. If you mark a setting as\n\t// `false` in this struct, it will clear the project's existing value for\n\t// that setting and defer to the default value that its organization provides.\n\t//\n\t// In general, it's not necessary to mark a setting as `true` in this\n\t// struct; if you provide a literal value for a setting, HCP Terraform will\n\t// automatically update its overwrites field to `true`. If you do choose to\n\t// manually mark a setting as overwritten, you must provide a value for that\n\t// setting at the same time.\n\tSettingOverwrites *ProjectSettingOverwrites `jsonapi:\"attr,setting-overwrites,omitempty\"`\n}\n\n// ProjectUpdateOptions represents the options for updating a project\ntype ProjectUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,projects\"`\n\n\t// Optional: A name to identify the project\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: A description for the project.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Associated TagBindings of the project. Note that this will replace\n\t// all existing tag bindings.\n\tTagBindings []*TagBinding `jsonapi:\"relation,tag-bindings,omitempty\"`\n\n\t// Optional: For all workspaces in the project, the period of time to wait\n\t// after workspace activity to trigger a destroy run. The format should roughly\n\t// match a Go duration string limited to days and hours, e.g. \"24h\" or \"1d\".\n\tAutoDestroyActivityDuration jsonapi.NullableAttr[string] `jsonapi:\"attr,auto-destroy-activity-duration,omitempty\"`\n\n\t// Optional: DefaultExecutionMode the default execution mode for workspaces\n\tDefaultExecutionMode *string `jsonapi:\"attr,default-execution-mode,omitempty\"`\n\n\t// Optional: DefaultAgentPoolID default agent pool for workspaces in the project,\n\t// required when DefaultExecutionMode is set to `agent`\n\tDefaultAgentPoolID *string `jsonapi:\"attr,default-agent-pool-id,omitempty\"`\n\n\t// Optional: Struct of booleans, which indicate whether the project\n\t// specifies its own values for various settings. If you mark a setting as\n\t// `false` in this struct, it will clear the project's existing value for\n\t// that setting and defer to the default value that its organization provides.\n\t//\n\t// In general, it's not necessary to mark a setting as `true` in this\n\t// struct; if you provide a literal value for a setting, HCP Terraform will\n\t// automatically update its overwrites field to `true`. If you do choose to\n\t// manually mark a setting as overwritten, you must provide a value for that\n\t// setting at the same time.\n\tSettingOverwrites *ProjectSettingOverwrites `jsonapi:\"attr,setting-overwrites,omitempty\"`\n}\n\n// ProjectAddTagBindingsOptions represents the options for adding tag bindings\n// to a project.\ntype ProjectAddTagBindingsOptions struct {\n\tTagBindings []*TagBinding\n}\n\n// List all projects.\nfunc (s *projects) List(ctx context.Context, organization string, options *ProjectListOptions) (*ProjectList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tvar tagFilters map[string][]string\n\tif options != nil {\n\t\ttagFilters = encodeTagFiltersAsParams(options.TagBindings)\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/projects\", url.PathEscape(organization))\n\treq, err := s.client.NewRequestWithAdditionalQueryParams(\"GET\", u, options, tagFilters)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &ProjectList{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// Create a project with the given options\nfunc (s *projects) Create(ctx context.Context, organization string, options ProjectCreateOptions) (*Project, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/projects\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &Project{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// ReadWithOptions a project by its ID.\nfunc (s *projects) ReadWithOptions(ctx context.Context, projectID string, options ProjectReadOptions) (*Project, error) {\n\tif !validStringID(&projectID) {\n\t\treturn nil, ErrInvalidProjectID\n\t}\n\n\tu := fmt.Sprintf(\"projects/%s\", url.PathEscape(projectID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &Project{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// Read a single project by its ID.\nfunc (s *projects) Read(ctx context.Context, projectID string) (*Project, error) {\n\tif !validStringID(&projectID) {\n\t\treturn nil, ErrInvalidProjectID\n\t}\n\n\tu := fmt.Sprintf(\"projects/%s\", url.PathEscape(projectID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &Project{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\nfunc (s *projects) ListTagBindings(ctx context.Context, projectID string) ([]*TagBinding, error) {\n\tif !validStringID(&projectID) {\n\t\treturn nil, ErrInvalidProjectID\n\t}\n\n\tu := fmt.Sprintf(\"projects/%s/tag-bindings\", url.PathEscape(projectID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar list struct {\n\t\t*Pagination\n\t\tItems []*TagBinding\n\t}\n\n\terr = req.Do(ctx, &list)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn list.Items, nil\n}\n\nfunc (s *projects) ListEffectiveTagBindings(ctx context.Context, projectID string) ([]*EffectiveTagBinding, error) {\n\tif !validStringID(&projectID) {\n\t\treturn nil, ErrInvalidProjectID\n\t}\n\n\tu := fmt.Sprintf(\"projects/%s/effective-tag-bindings\", url.PathEscape(projectID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar list struct {\n\t\t*Pagination\n\t\tItems []*EffectiveTagBinding\n\t}\n\n\terr = req.Do(ctx, &list)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn list.Items, nil\n}\n\n// AddTagBindings adds or modifies the value of existing tag binding keys for a project\nfunc (s *projects) AddTagBindings(ctx context.Context, projectID string, options ProjectAddTagBindingsOptions) ([]*TagBinding, error) {\n\tif !validStringID(&projectID) {\n\t\treturn nil, ErrInvalidProjectID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"projects/%s/tag-bindings\", url.PathEscape(projectID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, options.TagBindings)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar response = struct {\n\t\t*Pagination\n\t\tItems []*TagBinding\n\t}{}\n\terr = req.Do(ctx, &response)\n\n\treturn response.Items, err\n}\n\n// Update a project by its ID\nfunc (s *projects) Update(ctx context.Context, projectID string, options ProjectUpdateOptions) (*Project, error) {\n\tif !validStringID(&projectID) {\n\t\treturn nil, ErrInvalidProjectID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"projects/%s\", url.PathEscape(projectID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &Project{}\n\terr = req.Do(ctx, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// Delete a project by its ID\nfunc (s *projects) Delete(ctx context.Context, projectID string) error {\n\tif !validStringID(&projectID) {\n\t\treturn ErrInvalidProjectID\n\t}\n\n\tu := fmt.Sprintf(\"projects/%s\", url.PathEscape(projectID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Delete all tag bindings associated with a project.\nfunc (s *projects) DeleteAllTagBindings(ctx context.Context, projectID string) error {\n\tif !validStringID(&projectID) {\n\t\treturn ErrInvalidProjectID\n\t}\n\n\ttype aliasOpts struct {\n\t\tType        string        `jsonapi:\"primary,projects\"`\n\t\tTagBindings []*TagBinding `jsonapi:\"relation,tag-bindings\"`\n\t}\n\n\topts := &aliasOpts{\n\t\tTagBindings: []*TagBinding{},\n\t}\n\n\tu := fmt.Sprintf(\"projects/%s\", url.PathEscape(projectID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o ProjectCreateOptions) valid() error {\n\tif !validString(&o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\treturn nil\n}\n\nfunc (o ProjectUpdateOptions) valid() error {\n\treturn nil\n}\n\nfunc (o ProjectAddTagBindingsOptions) valid() error {\n\tif len(o.TagBindings) == 0 {\n\t\treturn ErrRequiredTagBindings\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "projects_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/hashicorp/jsonapi\"\n)\n\nfunc TestProjectsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tpTest1, pTestCleanup := createProject(t, client, orgTest)\n\tt.Cleanup(pTestCleanup)\n\n\tpTest2, pTestCleanup := createProject(t, client, orgTest)\n\tt.Cleanup(pTestCleanup)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tpl, err := client.Projects.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, pl.Items, pTest1)\n\n\t\tassert.Equal(t, 1, pl.CurrentPage)\n\t\tassert.Equal(t, 3, pl.TotalCount)\n\t})\n\n\tt.Run(\"with pagination list options\", func(t *testing.T) {\n\t\tpl, err := client.Projects.List(ctx, orgTest.Name, &ProjectListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, pl.Items, pTest1)\n\t\tassert.Contains(t, pl.Items, pTest2)\n\t\tassert.Equal(t, true, containsProject(pl.Items, \"Default Project\"))\n\t\tassert.Equal(t, 3, len(pl.Items))\n\t})\n\n\tt.Run(\"with query list option\", func(t *testing.T) {\n\t\tpl, err := client.Projects.List(ctx, orgTest.Name, &ProjectListOptions{\n\t\t\tQuery: \"Default\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, true, containsProject(pl.Items, \"Default Project\"))\n\t\tassert.Equal(t, 1, len(pl.Items))\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tpl, err := client.Projects.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, pl)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when using a tags filter\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\tp1, pTestCleanup1 := createProjectWithOptions(t, client, orgTest, ProjectCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key1\", Value: \"value1\"},\n\t\t\t\t{Key: \"key2\", Value: \"value2a\"},\n\t\t\t},\n\t\t})\n\t\tp2, pTestCleanup2 := createProjectWithOptions(t, client, orgTest, ProjectCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key2\", Value: \"value2b\"},\n\t\t\t\t{Key: \"key3\", Value: \"value3\"},\n\t\t\t},\n\t\t})\n\t\tt.Cleanup(pTestCleanup1)\n\t\tt.Cleanup(pTestCleanup2)\n\n\t\t// List all the workspaces under the given tag\n\t\tpl, err := client.Projects.List(ctx, orgTest.Name, &ProjectListOptions{\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key1\"},\n\t\t\t},\n\t\t})\n\t\tassert.NoError(t, err)\n\t\tassert.Len(t, pl.Items, 1)\n\t\tassert.Contains(t, pl.Items, p1)\n\n\t\tpl2, err := client.Projects.List(ctx, orgTest.Name, &ProjectListOptions{\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key2\"},\n\t\t\t},\n\t\t})\n\t\tassert.NoError(t, err)\n\t\tassert.Len(t, pl2.Items, 2)\n\t\tassert.Contains(t, pl2.Items, p1, p2)\n\n\t\tpl3, err := client.Projects.List(ctx, orgTest.Name, &ProjectListOptions{\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key2\", Value: \"value2b\"},\n\t\t\t},\n\t\t})\n\t\tassert.NoError(t, err)\n\t\tassert.Len(t, pl3.Items, 1)\n\t\tassert.Contains(t, pl3.Items, p2)\n\t})\n\n\tt.Run(\"when including effective tags relationship\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\torgTest2, orgTest2Cleanup := createOrganization(t, client)\n\t\tt.Cleanup(orgTest2Cleanup)\n\n\t\t_, pTestCleanup1 := createProjectWithOptions(t, client, orgTest2, ProjectCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key1\", Value: \"value1\"},\n\t\t\t\t{Key: \"key2\", Value: \"value2a\"},\n\t\t\t},\n\t\t})\n\t\tt.Cleanup(pTestCleanup1)\n\n\t\tpl, err := client.Projects.List(ctx, orgTest2.Name, &ProjectListOptions{\n\t\t\tInclude: []ProjectIncludeOpt{ProjectEffectiveTagBindings},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, pl.Items, 2)\n\t\trequire.Len(t, pl.Items[0].EffectiveTagBindings, 2)\n\t\tassert.NotEmpty(t, pl.Items[0].EffectiveTagBindings[0].Key)\n\t\tassert.NotEmpty(t, pl.Items[0].EffectiveTagBindings[0].Value)\n\t\tassert.NotEmpty(t, pl.Items[0].EffectiveTagBindings[1].Key)\n\t\tassert.NotEmpty(t, pl.Items[0].EffectiveTagBindings[1].Value)\n\t})\n}\n\nfunc TestProjectsReadWithOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tpTest, pTestCleanup := createProjectWithOptions(t, client, orgTest, ProjectCreateOptions{\n\t\tName: \"project-with-tags\",\n\t\tTagBindings: []*TagBinding{\n\t\t\t{Key: \"foo\", Value: \"bar\"},\n\t\t},\n\t})\n\tt.Cleanup(pTestCleanup)\n\n\tt.Run(\"when the project exists\", func(t *testing.T) {\n\t\tp, err := client.Projects.ReadWithOptions(ctx, pTest.ID, ProjectReadOptions{\n\t\t\tInclude: []ProjectIncludeOpt{ProjectEffectiveTagBindings},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, orgTest.Name, p.Organization.Name)\n\n\t\t// Tag data is included\n\t\trequire.Len(t, p.EffectiveTagBindings, 1)\n\t\tassert.Equal(t, \"foo\", p.EffectiveTagBindings[0].Key)\n\t\tassert.Equal(t, \"bar\", p.EffectiveTagBindings[0].Value)\n\t})\n}\n\nfunc TestProjectsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tpTest, pTestCleanup := createProject(t, client, orgTest)\n\tt.Cleanup(pTestCleanup)\n\n\tt.Run(\"when the project exists\", func(t *testing.T) {\n\t\tw, err := client.Projects.Read(ctx, pTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, pTest, w)\n\t\tassert.Equal(t, orgTest.Name, w.Organization.Name)\n\t})\n\n\tt.Run(\"when the project does not exist\", func(t *testing.T) {\n\t\tw, err := client.Projects.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, w)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"without a valid project ID\", func(t *testing.T) {\n\t\tw, err := client.Projects.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidProjectID.Error())\n\t})\n\n\tt.Run(\"with default execution mode of 'agent'\", func(t *testing.T) {\n\t\tagentPoolTest, agentPoolTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(agentPoolTestCleanup)\n\n\t\tproj, projCleanup := createProjectWithOptions(t, client, orgTest, ProjectCreateOptions{\n\t\t\tName:                 \"project-with-agent-pool\",\n\t\t\tDefaultExecutionMode: String(\"agent\"),\n\t\t\tDefaultAgentPoolID:   String(agentPoolTest.ID),\n\t\t})\n\t\tt.Cleanup(projCleanup)\n\n\t\tt.Run(\"execution mode and agent pool are properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, \"agent\", proj.DefaultExecutionMode)\n\t\t\tassert.NotNil(t, proj.DefaultAgentPool)\n\t\t\tassert.Equal(t, proj.DefaultAgentPool.ID, agentPoolTest.ID)\n\t\t})\n\t})\n\n\tt.Run(\"when project is inheriting the default execution mode\", func(t *testing.T) {\n\t\tdefaultExecutionOrgTest, defaultExecutionOrgTestCleanup := createOrganizationWithDefaultAgentPool(t, client)\n\t\tt.Cleanup(defaultExecutionOrgTestCleanup)\n\n\t\toptions := ProjectCreateOptions{\n\t\t\tName: fmt.Sprintf(\"tst-%s\", randomString(t)[0:20]),\n\t\t\tSettingOverwrites: &ProjectSettingOverwrites{\n\t\t\t\tExecutionMode: Bool(false),\n\t\t\t\tAgentPool:     Bool(false),\n\t\t\t},\n\t\t}\n\n\t\tpDefaultTest, pDefaultTestCleanup := createProjectWithOptions(t, client, defaultExecutionOrgTest, options)\n\t\tt.Cleanup(pDefaultTestCleanup)\n\n\t\tt.Run(\"and project execution mode is default\", func(t *testing.T) {\n\t\t\tp, err := client.Projects.Read(ctx, pDefaultTest.ID)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.NotEmpty(t, p)\n\n\t\t\tassert.Equal(t, defaultExecutionOrgTest.DefaultExecutionMode, p.DefaultExecutionMode)\n\t\t\trequire.NotNil(t, p.SettingOverwrites)\n\t\t\tassert.Equal(t, false, *p.SettingOverwrites.ExecutionMode)\n\t\t\tassert.Equal(t, false, *p.SettingOverwrites.ExecutionMode)\n\t\t})\n\t})\n}\n\nfunc TestProjectsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := ProjectCreateOptions{\n\t\t\tName:        \"foo\",\n\t\t\tDescription: String(\"qux\"),\n\t\t}\n\n\t\tw, err := client.Projects.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\trefreshed, err := client.Projects.Read(ctx, w.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Project{\n\t\t\tw,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, options.Name, item.Name)\n\t\t\tassert.Equal(t, *options.Description, item.Description)\n\t\t}\n\t})\n\n\tt.Run(\"when options is missing name\", func(t *testing.T) {\n\t\tw, err := client.Projects.Create(ctx, orgTest.Name, ProjectCreateOptions{})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"when options has an invalid name\", func(t *testing.T) {\n\t\tw, err := client.Projects.Create(ctx, orgTest.Name, ProjectCreateOptions{\n\t\t\tName: badIdentifier,\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.Contains(t, err.Error(), \"invalid attribute\\n\\nName may only contain\")\n\t})\n\n\tt.Run(\"when options has an invalid organization\", func(t *testing.T) {\n\t\tw, err := client.Projects.Create(ctx, badIdentifier, ProjectCreateOptions{\n\t\t\tName: \"foo\",\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when options has an invalid auto destroy activity duration\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\tw, err := client.Projects.Create(ctx, orgTest.Name, ProjectCreateOptions{\n\t\t\tName:                        \"foo\",\n\t\t\tAutoDestroyActivityDuration: jsonapi.NewNullableAttrWithValue(\"20m\"),\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.Contains(t, err.Error(), \"invalid attribute\\n\\nAuto destroy activity duration has an incorrect format, we expect up to 4 numeric digits and 1 unit ('d' or 'h')\")\n\t})\n\n\tt.Run(\"when a default agent pool ID is specified without 'agent' execution mode\", func(t *testing.T) {\n\t\tagentPoolTest, agentPoolTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(agentPoolTestCleanup)\n\n\t\tp, err := client.Projects.Create(ctx, orgTest.Name, ProjectCreateOptions{\n\t\t\tName:                 fmt.Sprintf(\"foo-%s\", randomString(t)),\n\t\t\tDefaultExecutionMode: String(\"remote\"),\n\t\t\tDefaultAgentPoolID:   String(agentPoolTest.ID),\n\t\t})\n\n\t\tassert.Nil(t, p)\n\t\tassert.ErrorContains(t, err, \"must not be specified unless using 'agent' execution mode\")\n\t})\n\n\tt.Run(\"when 'agent' execution mode is specified without an a default agent pool ID\", func(t *testing.T) {\n\t\tp, err := client.Projects.Create(ctx, orgTest.Name, ProjectCreateOptions{\n\t\t\tName:                 fmt.Sprintf(\"foo-%s\", randomString(t)),\n\t\t\tDefaultExecutionMode: String(\"agent\"),\n\t\t})\n\n\t\tassert.Nil(t, p)\n\t\tassert.ErrorContains(t, err, \"must be specified when using 'agent' execution mode\")\n\t})\n\n\tt.Run(\"when no execution mode is specified, in an organization with local as default execution mode\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\t\tName:                 String(\"tst-\" + randomString(t)[0:20]),\n\t\t\tEmail:                String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t\tDefaultExecutionMode: String(\"local\"),\n\t\t})\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\toptions := ProjectCreateOptions{\n\t\t\tName: fmt.Sprintf(\"foo-%s\", randomString(t)),\n\t\t\tSettingOverwrites: &ProjectSettingOverwrites{\n\t\t\t\tExecutionMode: Bool(false),\n\t\t\t\tAgentPool:     Bool(false),\n\t\t\t},\n\t\t}\n\n\t\tp, err := client.Projects.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Projects.Read(ctx, p.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, \"local\", refreshed.DefaultExecutionMode)\n\t})\n\n\tt.Run(\"when agent pool and execution mode setting overwrites do not match\", func(t *testing.T) {\n\t\tagentPoolTest, agentPoolTestCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(agentPoolTestCleanup)\n\n\t\tp, err := client.Projects.Create(ctx, orgTest.Name, ProjectCreateOptions{\n\t\t\tName:                 fmt.Sprintf(\"foo-%s\", randomString(t)),\n\t\t\tDefaultExecutionMode: String(\"agent\"),\n\t\t\tDefaultAgentPoolID:   String(agentPoolTest.ID),\n\t\t\tSettingOverwrites: &ProjectSettingOverwrites{\n\t\t\t\tAgentPool:     Bool(false),\n\t\t\t\tExecutionMode: Bool(true),\n\t\t\t},\n\t\t})\n\n\t\tassert.Nil(t, p)\n\t\tassert.Contains(t, err.Error(), \"If agent-pool and execution-mode are both included in setting-overwrites, their values must be the same.\")\n\t})\n\n\tt.Run(\"when organization has a default execution mode\", func(t *testing.T) {\n\t\tdefaultExecutionOrgTest, defaultExecutionOrgTestCleanup := createOrganizationWithDefaultAgentPool(t, client)\n\t\tt.Cleanup(defaultExecutionOrgTestCleanup)\n\n\t\tt.Run(\"with setting overwrites set to false, project inherits the default execution mode\", func(t *testing.T) {\n\t\t\toptions := ProjectCreateOptions{\n\t\t\t\tName: fmt.Sprintf(\"tst-proj-%s\", randomString(t)[0:20]),\n\t\t\t\tSettingOverwrites: &ProjectSettingOverwrites{\n\t\t\t\t\tExecutionMode: Bool(false),\n\t\t\t\t\tAgentPool:     Bool(false),\n\t\t\t\t},\n\t\t\t}\n\t\t\tp, err := client.Projects.Create(ctx, defaultExecutionOrgTest.Name, options)\n\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, \"agent\", p.DefaultExecutionMode)\n\t\t})\n\n\t\tt.Run(\"with setting overwrites set to true, project ignores the default execution mode\", func(t *testing.T) {\n\t\t\toptions := ProjectCreateOptions{\n\t\t\t\tName:                 fmt.Sprintf(\"tst-proj-%s\", randomString(t)[0:20]),\n\t\t\t\tDefaultExecutionMode: String(\"local\"),\n\t\t\t\tSettingOverwrites: &ProjectSettingOverwrites{\n\t\t\t\t\tExecutionMode: Bool(true),\n\t\t\t\t\tAgentPool:     Bool(true),\n\t\t\t\t},\n\t\t\t}\n\t\t\tp, err := client.Projects.Create(ctx, defaultExecutionOrgTest.Name, options)\n\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, \"local\", p.DefaultExecutionMode)\n\t\t})\n\n\t\tt.Run(\"when explicitly setting default execution mode, project ignores the org default execution mode\", func(t *testing.T) {\n\t\t\toptions := ProjectCreateOptions{\n\t\t\t\tName:                 fmt.Sprintf(\"tst-proj-%s\", randomString(t)[0:20]),\n\t\t\t\tDefaultExecutionMode: String(\"remote\"),\n\t\t\t}\n\t\t\tp, err := client.Projects.Create(ctx, defaultExecutionOrgTest.Name, options)\n\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, \"remote\", p.DefaultExecutionMode)\n\t\t})\n\t})\n}\n\nfunc TestProjectsUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tagentPoolTest, agentPoolTestCleanup := createAgentPool(t, client, orgTest)\n\tt.Cleanup(agentPoolTestCleanup)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tkAfter, err := client.Projects.Update(ctx, kBefore.ID, ProjectUpdateOptions{\n\t\t\tName:        String(\"new project name\"),\n\t\t\tDescription: String(\"updated description\"),\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"foo\", Value: \"bar\"},\n\t\t\t},\n\t\t\tDefaultExecutionMode: String(\"agent\"),\n\t\t\tDefaultAgentPoolID:   String(agentPoolTest.ID),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.ID, kAfter.ID)\n\t\tassert.NotEqual(t, kBefore.Name, kAfter.Name)\n\t\tassert.NotEqual(t, kBefore.Description, kAfter.Description)\n\t\tassert.NotEqual(t, kBefore.DefaultExecutionMode, kAfter.DefaultExecutionMode)\n\t\tassert.NotEqual(t, kBefore.DefaultAgentPool, kAfter.DefaultAgentPool)\n\n\t\tif betaFeaturesEnabled() {\n\t\t\tbindings, err := client.Projects.ListTagBindings(ctx, kAfter.ID)\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Len(t, bindings, 1)\n\t\t\tassert.Equal(t, \"foo\", bindings[0].Key)\n\t\t\tassert.Equal(t, \"bar\", bindings[0].Value)\n\n\t\t\teffectiveBindings, err := client.Projects.ListEffectiveTagBindings(ctx, kAfter.ID)\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Len(t, effectiveBindings, 1)\n\t\t\tassert.Equal(t, \"foo\", effectiveBindings[0].Key)\n\t\t\tassert.Equal(t, \"bar\", effectiveBindings[0].Value)\n\n\t\t\tws, err := client.Workspaces.Create(ctx, orgTest.Name, WorkspaceCreateOptions{\n\t\t\t\tName:    String(\"new-workspace-inherits-tags\"),\n\t\t\t\tProject: kAfter,\n\t\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t\t{Key: \"baz\", Value: \"qux\"},\n\t\t\t\t},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tt.Cleanup(func() {\n\t\t\t\terr := client.Workspaces.DeleteByID(ctx, ws.ID)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Error destroying workspace! WARNING: Dangling resources\\n\"+\n\t\t\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\t\t\"Error: %s\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\twsEffectiveBindings, err := client.Workspaces.ListEffectiveTagBindings(ctx, ws.ID)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.Len(t, wsEffectiveBindings, 2)\n\t\t\tfor _, b := range wsEffectiveBindings {\n\t\t\t\tswitch b.Key {\n\t\t\t\tcase \"foo\":\n\t\t\t\t\tassert.Equal(t, \"bar\", b.Value)\n\t\t\t\tcase \"baz\":\n\t\t\t\t\tassert.Equal(t, \"qux\", b.Value)\n\t\t\t\tdefault:\n\t\t\t\t\tassert.Fail(t, \"unexpected tag binding %q\", b.Key)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"when updating with invalid name\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tkAfter, err := client.Projects.Update(ctx, kBefore.ID, ProjectUpdateOptions{\n\t\t\tName: String(badIdentifier),\n\t\t})\n\t\tassert.Nil(t, kAfter)\n\t\tassert.Contains(t, err.Error(), \"invalid attribute\\n\\nName may only contain\")\n\t})\n\n\tt.Run(\"without a valid projects ID\", func(t *testing.T) {\n\t\tw, err := client.Projects.Update(ctx, badIdentifier, ProjectUpdateOptions{})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidProjectID.Error())\n\t})\n\n\tt.Run(\"without a valid projects auto destroy activity duration\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\t\tkBefore, kTestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tw, err := client.Projects.Update(ctx, kBefore.ID, ProjectUpdateOptions{\n\t\t\tAutoDestroyActivityDuration: jsonapi.NewNullableAttrWithValue(\"bar\"),\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.Contains(t, err.Error(), \"invalid attribute\\n\\nAuto destroy activity duration has an incorrect format, we expect up to 4 numeric digits and 1 unit ('d' or 'h')\")\n\t})\n\n\tt.Run(\"with agent pool provided, but remote execution mode\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\tpool, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(agentPoolCleanup)\n\n\t\tproj, err := client.Projects.Update(ctx, kBefore.ID, ProjectUpdateOptions{\n\t\t\tDefaultExecutionMode: String(\"local\"),\n\t\t\tDefaultAgentPoolID:   String(pool.ID),\n\t\t})\n\t\tassert.Nil(t, proj)\n\t\tassert.ErrorContains(t, err, \"must not be specified unless using 'agent' execution mode\")\n\t})\n\n\tt.Run(\"with different default execution modes\", func(t *testing.T) {\n\t\tproj, projCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(projCleanup)\n\n\t\tagentPool, agenPoolCleanup := createAgentPool(t, client, orgTest)\n\t\tt.Cleanup(agenPoolCleanup)\n\n\t\tassert.Equal(t, \"remote\", proj.DefaultExecutionMode)\n\t\tassert.Nil(t, proj.DefaultAgentPool)\n\n\t\t// assert that project's execution mode can be updated from 'remote' -> 'agent'\n\t\tproj, err := client.Projects.Update(ctx, proj.ID, ProjectUpdateOptions{\n\t\t\tDefaultExecutionMode: String(\"agent\"),\n\t\t\tDefaultAgentPoolID:   String(agentPool.ID),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"agent\", proj.DefaultExecutionMode)\n\t\tassert.Equal(t, agentPool.ID, proj.DefaultAgentPool.ID)\n\n\t\t// assert that project's execution mode can be updated from 'agent' -> 'remote'\n\t\tproj, err = client.Projects.Update(ctx, proj.ID, ProjectUpdateOptions{\n\t\t\tDefaultExecutionMode: String(\"remote\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"remote\", proj.DefaultExecutionMode)\n\t\tassert.Nil(t, proj.DefaultAgentPool)\n\n\t\t// assert that project's execution mode can be updated from 'remote' -> 'local'\n\t\tproj, err = client.Projects.Update(ctx, proj.ID, ProjectUpdateOptions{\n\t\t\tDefaultExecutionMode: String(\"local\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"local\", proj.DefaultExecutionMode)\n\t\tassert.Nil(t, proj.DefaultAgentPool)\n\t})\n\n\tt.Run(\"with setting overwrites set to true, project ignores the default execution mode\", func(t *testing.T) {\n\t\tdefaultExecutionOrgTest, defaultExecutionOrgTestCleanup := createOrganizationWithDefaultAgentPool(t, client)\n\t\tt.Cleanup(defaultExecutionOrgTestCleanup)\n\n\t\tkBefore, kTestCleanup := createProject(t, client, defaultExecutionOrgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\toptions := ProjectUpdateOptions{\n\t\t\tDefaultExecutionMode: String(\"local\"),\n\t\t\tSettingOverwrites: &ProjectSettingOverwrites{\n\t\t\t\tExecutionMode: Bool(true),\n\t\t\t\tAgentPool:     Bool(true),\n\t\t\t},\n\t\t}\n\t\tp, err := client.Projects.Update(ctx, kBefore.ID, options)\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"local\", p.DefaultExecutionMode)\n\t})\n\n\tt.Run(\"with setting overwrites set to false, project inherits the default execution mode\", func(t *testing.T) {\n\t\tdefaultExecutionOrgTest, defaultExecutionOrgTestCleanup := createOrganizationWithDefaultAgentPool(t, client)\n\t\tt.Cleanup(defaultExecutionOrgTestCleanup)\n\n\t\tkBefore, kTestCleanup := createProject(t, client, defaultExecutionOrgTest)\n\t\tt.Cleanup(kTestCleanup)\n\n\t\toptions := ProjectUpdateOptions{\n\t\t\tSettingOverwrites: &ProjectSettingOverwrites{\n\t\t\t\tExecutionMode: Bool(false),\n\t\t\t\tAgentPool:     Bool(false),\n\t\t\t},\n\t\t}\n\t\tp, err := client.Projects.Update(ctx, kBefore.ID, options)\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"agent\", p.DefaultExecutionMode)\n\t})\n}\n\nfunc TestProjectsAddTagBindings(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpTest, wCleanup := createProject(t, client, nil)\n\tt.Cleanup(wCleanup)\n\n\tt.Run(\"when adding tag bindings to a project\", func(t *testing.T) {\n\t\ttagBindings := []*TagBinding{\n\t\t\t{Key: \"foo\", Value: \"bar\"},\n\t\t\t{Key: \"baz\", Value: \"qux\"},\n\t\t}\n\n\t\tbindings, err := client.Projects.AddTagBindings(ctx, pTest.ID, ProjectAddTagBindingsOptions{\n\t\t\tTagBindings: tagBindings,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.Len(t, bindings, 2)\n\t\tassert.Equal(t, tagBindings[0].Key, bindings[0].Key)\n\t\tassert.Equal(t, tagBindings[0].Value, bindings[0].Value)\n\t\tassert.Equal(t, tagBindings[1].Key, bindings[1].Key)\n\t\tassert.Equal(t, tagBindings[1].Value, bindings[1].Value)\n\t})\n\n\tt.Run(\"when adding 26 tags\", func(t *testing.T) {\n\t\ttagBindings := []*TagBinding{\n\t\t\t{Key: \"alpha\"},\n\t\t\t{Key: \"bravo\"},\n\t\t\t{Key: \"charlie\"},\n\t\t\t{Key: \"delta\"},\n\t\t\t{Key: \"echo\"},\n\t\t\t{Key: \"foxtrot\"},\n\t\t\t{Key: \"golf\"},\n\t\t\t{Key: \"hotel\"},\n\t\t\t{Key: \"india\"},\n\t\t\t{Key: \"juliet\"},\n\t\t\t{Key: \"kilo\"},\n\t\t\t{Key: \"lima\"},\n\t\t\t{Key: \"mike\"},\n\t\t\t{Key: \"november\"},\n\t\t\t{Key: \"oscar\"},\n\t\t\t{Key: \"papa\"},\n\t\t\t{Key: \"quebec\"},\n\t\t\t{Key: \"romeo\"},\n\t\t\t{Key: \"sierra\"},\n\t\t\t{Key: \"tango\"},\n\t\t\t{Key: \"uniform\"},\n\t\t\t{Key: \"victor\"},\n\t\t\t{Key: \"whiskey\"},\n\t\t\t{Key: \"xray\"},\n\t\t\t{Key: \"yankee\"},\n\t\t\t{Key: \"zulu\"},\n\t\t}\n\n\t\t_, err := client.Workspaces.AddTagBindings(ctx, pTest.ID, WorkspaceAddTagBindingsOptions{\n\t\t\tTagBindings: tagBindings,\n\t\t})\n\t\trequire.Error(t, err, \"cannot exceed 10 bindings per resource\")\n\t})\n}\n\nfunc TestProjects_DeleteAllTagBindings(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tpTest, wCleanup := createProject(t, client, nil)\n\tt.Cleanup(wCleanup)\n\n\ttagBindings := []*TagBinding{\n\t\t{Key: \"foo\", Value: \"bar\"},\n\t\t{Key: \"baz\", Value: \"qux\"},\n\t}\n\n\t_, err := client.Projects.AddTagBindings(ctx, pTest.ID, ProjectAddTagBindingsOptions{\n\t\tTagBindings: tagBindings,\n\t})\n\trequire.NoError(t, err)\n\n\terr = client.Projects.DeleteAllTagBindings(ctx, pTest.ID)\n\trequire.NoError(t, err)\n\n\tbindings, err := client.Projects.ListTagBindings(ctx, pTest.ID)\n\trequire.NoError(t, err)\n\trequire.Empty(t, bindings)\n}\n\nfunc TestProjectsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tpTest, _ := createProject(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.Projects.Delete(ctx, pTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the project - it should fail.\n\t\t_, err = client.Projects.Read(ctx, pTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the project does not exist\", func(t *testing.T) {\n\t\terr := client.Projects.Delete(ctx, pTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the project ID is invalid\", func(t *testing.T) {\n\t\terr := client.Projects.Delete(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidProjectID.Error())\n\t})\n}\n\nfunc TestProjectsAutoDestroy(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tt.Run(\"when creating workspace in project with autodestroy\", func(t *testing.T) {\n\t\toptions := ProjectCreateOptions{\n\t\t\tName:                        \"foo\",\n\t\t\tDescription:                 String(\"qux\"),\n\t\t\tAutoDestroyActivityDuration: jsonapi.NewNullableAttrWithValue(\"3d\"),\n\t\t}\n\n\t\tp, err := client.Projects.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tw, _ := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName:    String(randomString(t)),\n\t\t\tProject: p,\n\t\t})\n\n\t\tassert.Equal(t, p.AutoDestroyActivityDuration, w.AutoDestroyActivityDuration)\n\t})\n}\n"
  },
  {
    "path": "query_runs.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ QueryRuns = (*queryRuns)(nil)\n\n// QueryRuns describes all the run related methods that the Terraform Enterprise\n// API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/query-runs\ntype QueryRuns interface {\n\t// List all the query runs of the given workspace.\n\tList(ctx context.Context, workspaceID string, options *QueryRunListOptions) (*QueryRunList, error)\n\n\t// Create a new query run with the given options.\n\tCreate(ctx context.Context, options QueryRunCreateOptions) (*QueryRun, error)\n\n\t// Read a query run by its ID.\n\tRead(ctx context.Context, queryRunID string) (*QueryRun, error)\n\n\t// ReadWithOptions reads a query run by its ID using the options supplied\n\tReadWithOptions(ctx context.Context, queryRunID string, options *QueryRunReadOptions) (*QueryRun, error)\n\n\t// Logs retrieves the logs of a query run.\n\tLogs(ctx context.Context, queryRunID string) (io.Reader, error)\n\n\t// Cancel a query run by its ID.\n\tCancel(ctx context.Context, runID string) error\n\n\t// Force-cancel a query run by its ID.\n\tForceCancel(ctx context.Context, runID string) error\n}\n\n// QueryRunCreateOptions represents the options for creating a new run.\ntype QueryRunCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,queries\"`\n\n\t// TerraformVersion specifies the Terraform version to use in this query run.\n\tTerraformVersion *string `jsonapi:\"attr,terraform-version,omitempty\"`\n\n\tSource QueryRunSource `jsonapi:\"attr,source\"`\n\n\t// Specifies the configuration version to use for this query run. If the\n\t// configuration version object is omitted, the run will be created using the\n\t// workspace's latest configuration version.\n\tConfigurationVersion *ConfigurationVersion `jsonapi:\"relation,configuration-version\"`\n\n\t// Specifies the workspace where the query run will be executed.\n\tWorkspace *Workspace `jsonapi:\"relation,workspace\"`\n\n\t// Variables allows you to specify terraform input variables for\n\t// a particular run, prioritized over variables defined on the workspace.\n\tVariables []*RunVariable `jsonapi:\"attr,variables,omitempty\"`\n}\n\n// QueryRunStatusTimestamps holds the timestamps for individual run statuses.\ntype QueryRunStatusTimestamps struct {\n\tCanceledAt      time.Time `jsonapi:\"attr,canceled-at,rfc3339\"`\n\tErroredAt       time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tForceCanceledAt time.Time `jsonapi:\"attr,force-canceled-at,rfc3339\"`\n\tQueuingAt       time.Time `jsonapi:\"attr,queuing-at,rfc3339\"`\n\tFinishedAt      time.Time `jsonapi:\"attr,finished-at,rfc3339\"`\n\tRunningAt       time.Time `jsonapi:\"attr,running-at,rfc3339\"`\n}\n\n// QueryRunIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#available-related-resources\ntype QueryRunIncludeOpt string\n\n// QueryRunSource represents the available sources for query runs.\ntype QueryRunSource string\n\n// QueryRunStatus is the query run state\ntype QueryRunStatus string\n\n// List all available run statuses.\nconst (\n\tQueryRunCanceled QueryRunStatus = \"canceled\"\n\tQueryRunErrored  QueryRunStatus = \"errored\"\n\tQueryRunPending  QueryRunStatus = \"pending\"\n\tQueryRunQueued   QueryRunStatus = \"queued\"\n\tQueryRunRunning  QueryRunStatus = \"running\"\n\tQueryRunFinished QueryRunStatus = \"finished\"\n)\n\n// List all available run sources.\nconst (\n\tQueryRunSourceAPI QueryRunSource = \"tfe-api\"\n)\n\nconst (\n\tQueryRunCreatedBy QueryRunIncludeOpt = \"created_by\"\n\tQueryRunConfigVer QueryRunIncludeOpt = \"configuration_version\"\n)\n\n// queryRuns implements QueryRuns.\ntype queryRuns struct {\n\tclient *Client\n}\n\n// QueryRunList represents a list of query runs.\ntype QueryRunList struct {\n\t*Pagination\n\tItems []*QueryRun\n}\n\n// QueryRunListOptions represents the options for listing query runs.\ntype QueryRunListOptions struct {\n\tListOptions\n\tInclude []QueryRunIncludeOpt `url:\"include,omitempty\"`\n}\n\n// QueryRunReadOptions represents the options for reading a query run.\ntype QueryRunReadOptions struct {\n\tInclude []QueryRunIncludeOpt `url:\"include,omitempty\"`\n}\n\n// QueryRun represents a Terraform Enterprise query run.\ntype QueryRun struct {\n\tID               string                    `jsonapi:\"primary,queries\"`\n\tCreatedAt        time.Time                 `jsonapi:\"attr,created-at,iso8601\"`\n\tSource           QueryRunSource            `jsonapi:\"attr,source\"`\n\tStatus           QueryRunStatus            `jsonapi:\"attr,status\"`\n\tStatusTimestamps *QueryRunStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\tTerraformVersion string                    `jsonapi:\"attr,terraform-version\"`\n\tVariables        []*RunVariableAttr        `jsonapi:\"attr,variables\"`\n\tLogReadURL       string                    `jsonapi:\"attr,log-read-url\"`\n\n\t// Relations\n\tConfigurationVersion *ConfigurationVersion `jsonapi:\"relation,configuration-version\"`\n\tCreatedBy            *User                 `jsonapi:\"relation,created-by\"`\n\tCanceledBy           *User                 `jsonapi:\"relation,canceled-by\"`\n\tWorkspace            *Workspace            `jsonapi:\"relation,workspace\"`\n}\n\nfunc (o *QueryRunListOptions) valid() error {\n\treturn nil\n}\n\nfunc (o QueryRunCreateOptions) valid() error {\n\tif o.Workspace == nil {\n\t\treturn ErrRequiredWorkspace\n\t}\n\n\treturn nil\n}\n\nfunc (r *queryRuns) List(ctx context.Context, workspaceID string, options *QueryRunListOptions) (*QueryRunList, error) {\n\tif workspaceID == \"\" {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/queries\", url.PathEscape(workspaceID))\n\treq, err := r.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar runs QueryRunList\n\tif err := req.Do(ctx, &runs); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &runs, nil\n}\n\nfunc (r *queryRuns) Create(ctx context.Context, options QueryRunCreateOptions) (*QueryRun, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := r.client.NewRequest(\"POST\", \"queries\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar run QueryRun\n\tif err := req.Do(ctx, &run); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &run, nil\n}\n\nfunc (r *queryRuns) Read(ctx context.Context, queryRunID string) (*QueryRun, error) {\n\treturn r.ReadWithOptions(ctx, queryRunID, &QueryRunReadOptions{})\n}\n\nfunc (r *queryRuns) ReadWithOptions(ctx context.Context, queryRunID string, options *QueryRunReadOptions) (*QueryRun, error) {\n\tif queryRunID == \"\" {\n\t\treturn nil, ErrInvalidQueryRunID\n\t}\n\n\tu := fmt.Sprintf(\"queries/%s\", url.PathEscape(queryRunID))\n\treq, err := r.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar run QueryRun\n\tif err := req.Do(ctx, &run); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &run, nil\n}\n\nfunc (r *queryRuns) Logs(ctx context.Context, queryRunID string) (io.Reader, error) {\n\tif !validStringID(&queryRunID) {\n\t\treturn nil, ErrInvalidQueryRunID\n\t}\n\n\t// Get the query to make sure it exists.\n\tq, err := r.Read(ctx, queryRunID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Return an error if the log URL is empty.\n\tif q.LogReadURL == \"\" {\n\t\treturn nil, fmt.Errorf(\"query %s does not have a log URL\", queryRunID)\n\t}\n\n\tu, err := url.Parse(q.LogReadURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid log URL: %w\", err)\n\t}\n\n\tdone := func() (bool, error) {\n\t\tp, err := r.Read(ctx, q.ID)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tswitch p.Status {\n\t\tcase QueryRunCanceled, QueryRunErrored, QueryRunFinished:\n\t\t\treturn true, nil\n\t\tdefault:\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\treturn &LogReader{\n\t\tclient: r.client,\n\t\tctx:    ctx,\n\t\tdone:   done,\n\t\tlogURL: u,\n\t}, nil\n}\n\nfunc (r *queryRuns) Cancel(ctx context.Context, queryRunID string) error {\n\tif queryRunID == \"\" {\n\t\treturn ErrInvalidQueryRunID\n\t}\n\n\tu := fmt.Sprintf(\"queries/%s/actions/cancel\", url.PathEscape(queryRunID))\n\treq, err := r.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (r *queryRuns) ForceCancel(ctx context.Context, queryRunID string) error {\n\tif queryRunID == \"\" {\n\t\treturn ErrInvalidQueryRunID\n\t}\n\n\tu := fmt.Sprintf(\"queries/%s/actions/force-cancel\", url.PathEscape(queryRunID))\n\treq, err := r.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "query_runs_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// createQueryRun creates a query run in the given workspace.\n// A configuration version is created and uploaded to ensure the run can be processed.\nfunc createQueryRun(t *testing.T, client *Client, workspace *Workspace) *QueryRun {\n\tt.Helper()\n\tcreateUploadedConfigurationVersion(t, client, workspace)\n\toptions := QueryRunCreateOptions{\n\t\tWorkspace: workspace,\n\t\tSource:    QueryRunSourceAPI,\n\t}\n\tqueryRun, err := client.QueryRuns.Create(context.Background(), options)\n\trequire.NoError(t, err)\n\treturn queryRun\n}\n\nfunc TestQueryRunsList_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, _ := createWorkspace(t, client, orgTest)\n\t_ = createQueryRun(t, client, wTest)\n\t_ = createQueryRun(t, client, wTest)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tqrl, err := client.QueryRuns.List(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// The API returns Run objects, not QueryRun objects.\n\t\t// We can't easily correlate the created QueryRun with the returned Run.\n\t\t// So we just check the count.\n\t\tassert.Len(t, qrl.Items, 2)\n\t\tassert.Equal(t, 1, qrl.CurrentPage)\n\t\tassert.Equal(t, 2, qrl.TotalCount)\n\t})\n\n\tt.Run(\"without list options and include as nil\", func(t *testing.T) {\n\t\tqrl, err := client.QueryRuns.List(ctx, wTest.ID, &QueryRunListOptions{\n\t\t\tInclude: []QueryRunIncludeOpt{},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, qrl.Items)\n\n\t\tassert.Len(t, qrl.Items, 2)\n\t\tassert.Equal(t, 1, qrl.CurrentPage)\n\t\tassert.Equal(t, 2, qrl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tqrl, err := client.QueryRuns.List(ctx, wTest.ID, &QueryRunListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, qrl.Items)\n\t\tassert.Equal(t, 999, qrl.CurrentPage)\n\t\tassert.Equal(t, 2, qrl.TotalCount)\n\t})\n\n\tt.Run(\"with created_by included\", func(t *testing.T) {\n\t\tqrl, err := client.QueryRuns.List(ctx, wTest.ID, &QueryRunListOptions{\n\t\t\t// The QueryRunIncludeOpt constants in query_runs.go have the wrong type.\n\t\t\t// We use a string literal here as a workaround.\n\t\t\tInclude: []QueryRunIncludeOpt{\"created-by\"},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, qrl.Items)\n\t\t// The items are of type *Run, which has a CreatedBy field.\n\t\trequire.NotNil(t, qrl.Items[0].CreatedBy)\n\t\tassert.NotEmpty(t, qrl.Items[0].CreatedBy.Username)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\tqrl, err := client.QueryRuns.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, qrl)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n\nfunc TestQueryRunsCreate_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\tcvTest, _ := createUploadedConfigurationVersion(t, client, wTest)\n\n\tt.Run(\"without a configuration version\", func(t *testing.T) {\n\t\toptions := QueryRunCreateOptions{\n\t\t\tWorkspace: wTest,\n\t\t\tSource:    QueryRunSourceAPI,\n\t\t}\n\n\t\tqr, err := client.QueryRuns.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, qr.ID)\n\t\tassert.NotNil(t, qr.CreatedAt)\n\t\tassert.NotNil(t, qr.Source)\n\t\trequire.NotNil(t, qr.StatusTimestamps)\n\t})\n\n\tt.Run(\"with a configuration version\", func(t *testing.T) {\n\t\toptions := QueryRunCreateOptions{\n\t\t\tConfigurationVersion: cvTest,\n\t\t\tWorkspace:            wTest,\n\t\t\tSource:               QueryRunSourceAPI,\n\t\t}\n\n\t\tqr, err := client.QueryRuns.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, qr.ConfigurationVersion)\n\t\tassert.Equal(t, cvTest.ID, qr.ConfigurationVersion.ID)\n\t})\n\n\tt.Run(\"without a workspace\", func(t *testing.T) {\n\t\tqr, err := client.QueryRuns.Create(ctx, QueryRunCreateOptions{\n\t\t\tSource: QueryRunSourceAPI,\n\t\t})\n\t\tassert.Nil(t, qr)\n\t\tassert.Equal(t, err, ErrRequiredWorkspace)\n\t})\n\n\tt.Run(\"with variables\", func(t *testing.T) {\n\t\tt.Skip(\"Variables not yet implemented\")\n\n\t\tvars := []*RunVariable{\n\t\t\t{\n\t\t\t\tKey:   \"test_variable\",\n\t\t\t\tValue: \"Hello, World!\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   \"test_foo\",\n\t\t\t\tValue: \"Hello, Foo!\",\n\t\t\t},\n\t\t}\n\n\t\toptions := QueryRunCreateOptions{\n\t\t\tWorkspace: wTest,\n\t\t\tVariables: vars,\n\t\t\tSource:    QueryRunSourceAPI,\n\t\t}\n\n\t\tqr, err := client.QueryRuns.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, qr.Variables)\n\t\tassert.Equal(t, len(vars), len(qr.Variables))\n\n\t\tfor _, v := range qr.Variables {\n\t\t\tswitch v.Key {\n\t\t\tcase \"test_foo\":\n\t\t\t\tassert.Equal(t, v.Value, \"Hello, Foo!\")\n\t\t\tcase \"test_variable\":\n\t\t\t\tassert.Equal(t, v.Value, \"Hello, World!\")\n\t\t\tdefault:\n\t\t\t\tt.Fatalf(\"Unexpected variable key: %s\", v.Key)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestQueryRunsRead_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\tqrTest := createQueryRun(t, client, wTest)\n\n\tt.Run(\"when the query run exists\", func(t *testing.T) {\n\t\tqr, err := client.QueryRuns.Read(ctx, qrTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, qrTest.ID, qr.ID)\n\t})\n\n\tt.Run(\"when the query run does not exist\", func(t *testing.T) {\n\t\tqr, err := client.QueryRuns.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, qr)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid query run ID\", func(t *testing.T) {\n\t\tqr, err := client.QueryRuns.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, qr)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n\nfunc TestQueryRunsReadWithOptions_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\tqrTest := createQueryRun(t, client, wTest)\n\n\tt.Run(\"when the query run exists\", func(t *testing.T) {\n\t\tcurOpts := &QueryRunReadOptions{\n\t\t\t// The QueryRunIncludeOpt constants in query_runs.go have the wrong type.\n\t\t\t// We use a string literal here as a workaround.\n\t\t\tInclude: []QueryRunIncludeOpt{\"created-by\"},\n\t\t}\n\n\t\tqr, err := client.QueryRuns.ReadWithOptions(ctx, qrTest.ID, curOpts)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, qr.CreatedBy)\n\t\tassert.NotEmpty(t, qr.CreatedBy.Username)\n\t})\n}\n\nfunc TestQueryRunsCancel_RunDependent(t *testing.T) {\n\tt.Skip(\"Cancel not yet implemented\")\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\t// We need to create 2 query runs here. The first run associated with the query\n\t// will automatically be planned so that one cannot be cancelled. The second one will\n\t// be pending until the first one is confirmed or discarded, so we\n\t// can cancel that one.\n\t_ = createQueryRun(t, client, wTest)\n\tqrTest2 := createQueryRun(t, client, wTest)\n\n\tt.Run(\"when the query run exists and is cancelable\", func(t *testing.T) {\n\t\t// We assume the second query run is in a state that can be canceled.\n\t\terr := client.QueryRuns.Cancel(ctx, qrTest2.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the query run does not exist\", func(t *testing.T) {\n\t\terr := client.QueryRuns.Cancel(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid query run ID\", func(t *testing.T) {\n\t\terr := client.QueryRuns.Cancel(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n\nfunc TestQueryRunsLogs_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\tqr, cleanup := createQueryRunWaitForAnyStatuses(t, client, wTest, []QueryRunStatus{QueryRunErrored, QueryRunFinished})\n\tt.Cleanup(cleanup)\n\n\tt.Run(\"when the query run exists\", func(t *testing.T) {\n\t\t// We assume the second query run is in a state that can be canceled.\n\t\treader, err := client.QueryRuns.Logs(ctx, qr.ID)\n\t\trequire.NoError(t, err)\n\n\t\tlogs, err := io.ReadAll(reader)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, logs, \"some logs should be returned\")\n\t})\n}\n\nfunc TestQueryRunsForceCancel_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\t_ = createQueryRun(t, client, wTest)\n\tqrTest2 := createQueryRun(t, client, wTest)\n\n\t// A force-cancel is not needed in any normal circumstance.\n\t// We can't easily get a query run into a state where it can be force-canceled.\n\t// So we'll just test the negative paths.\n\n\tt.Run(\"when the query run is not in a force-cancelable state\", func(t *testing.T) {\n\t\t// This will likely return an error, but we are testing that the call can be made.\n\t\t// The API should return a 409 Conflict in this case.\n\t\terr := client.QueryRuns.ForceCancel(ctx, qrTest2.ID)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"when the query run does not exist\", func(t *testing.T) {\n\t\terr := client.QueryRuns.ForceCancel(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid query run ID\", func(t *testing.T) {\n\t\terr := client.QueryRuns.ForceCancel(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n"
  },
  {
    "path": "registry_module.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n)\n\ntype AgentExecutionMode string\n\nconst (\n\tAgentExecutionModeAgent  AgentExecutionMode = \"agent\"\n\tAgentExecutionModeRemote AgentExecutionMode = \"remote\"\n)\n\nfunc (a *AgentExecutionMode) UnmarshalText(text []byte) error {\n\t*a = AgentExecutionMode(string(text))\n\treturn nil\n}\n\nfunc (a AgentExecutionMode) MarshalText() ([]byte, error) {\n\treturn []byte(string(a)), nil\n}\n\n// Compile-time proof of interface implementation.\nvar _ RegistryModules = (*registryModules)(nil)\n\n// RegistryModules describes all the registry module related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/modules\ntype RegistryModules interface {\n\t// List all the registry modules within an organization\n\tList(ctx context.Context, organization string, options *RegistryModuleListOptions) (*RegistryModuleList, error)\n\n\t// ListCommits List the commits for the registry module\n\t// This returns the latest 20 commits for the connected VCS repo.\n\t// Pagination is not applicable due to inconsistent support from the VCS providers.\n\tListCommits(ctx context.Context, moduleID RegistryModuleID) (*CommitList, error)\n\n\t// Create a registry module without a VCS repo\n\tCreate(ctx context.Context, organization string, options RegistryModuleCreateOptions) (*RegistryModule, error)\n\n\t// Create a registry module version\n\tCreateVersion(ctx context.Context, moduleID RegistryModuleID, options RegistryModuleCreateVersionOptions) (*RegistryModuleVersion, error)\n\n\t// Create and publish a registry module with a VCS repo\n\tCreateWithVCSConnection(ctx context.Context, options RegistryModuleCreateWithVCSConnectionOptions) (*RegistryModule, error)\n\n\t// Read a registry module\n\tRead(ctx context.Context, moduleID RegistryModuleID) (*RegistryModule, error)\n\n\t// ReadVersion Read a registry module version\n\tReadVersion(ctx context.Context, moduleID RegistryModuleID, version string) (*RegistryModuleVersion, error)\n\n\t// ReadTerraformRegistryModule Reads a registry module from the Terraform\n\t// Registry, as opposed to Read or ReadVersion which read from the private\n\t// registry of a Terraform organization.\n\t// https://developer.hashicorp.com/terraform/enterprise/api-docs/private-registry/modules#hcp-terraform-registry-implementation\n\tReadTerraformRegistryModule(ctx context.Context, moduleID RegistryModuleID, version string) (*TerraformRegistryModule, error)\n\n\t// Delete a registry module\n\t// Warning: This method is deprecated and will be removed from a future version of go-tfe. Use DeleteByName instead.\n\tDelete(ctx context.Context, organization string, name string) error\n\n\t// Delete a registry module by name\n\tDeleteByName(ctx context.Context, module RegistryModuleID) error\n\n\t// Delete a specified provider for the given module along with all its versions\n\tDeleteProvider(ctx context.Context, moduleID RegistryModuleID) error\n\n\t// Delete a specified version for the given provider of the module\n\tDeleteVersion(ctx context.Context, moduleID RegistryModuleID, version string) error\n\n\t// Update properties of a registry module\n\tUpdate(ctx context.Context, moduleID RegistryModuleID, options RegistryModuleUpdateOptions) (*RegistryModule, error)\n\n\t// Upload Terraform configuration files for the provided registry module version. It\n\t// requires a path to the configuration files on disk, which will be packaged by\n\t// hashicorp/go-slug before being uploaded.\n\tUpload(ctx context.Context, rmv RegistryModuleVersion, path string) error\n\n\t// Upload a tar gzip archive to the specified configuration version upload URL.\n\tUploadTarGzip(ctx context.Context, url string, r io.Reader) error\n}\n\n// TerraformRegistryModule contains data about a module from the Terraform Registry.\ntype TerraformRegistryModule struct {\n\tID              string   `json:\"id\"`\n\tOwner           string   `json:\"owner\"`\n\tNamespace       string   `json:\"namespace\"`\n\tName            string   `json:\"name\"`\n\tVersion         string   `json:\"version\"`\n\tProvider        string   `json:\"provider\"`\n\tProviderLogoURL string   `json:\"provider_logo_url\"`\n\tDescription     string   `json:\"description\"`\n\tSource          string   `json:\"source\"`\n\tTag             string   `json:\"tag\"`\n\tPublishedAt     string   `json:\"published_at\"`\n\tDownloads       int      `json:\"downloads\"`\n\tVerified        bool     `json:\"verified\"`\n\tRoot            Root     `json:\"root\"`\n\tProviders       []string `json:\"providers\"`\n\tVersions        []string `json:\"versions\"`\n}\n\ntype Root struct {\n\tPath                 string               `json:\"path\"`\n\tName                 string               `json:\"name\"`\n\tReadme               string               `json:\"readme\"`\n\tEmpty                bool                 `json:\"empty\"`\n\tInputs               []Input              `json:\"inputs\"`\n\tOutputs              []Output             `json:\"outputs\"`\n\tProviderDependencies []ProviderDependency `json:\"provider_dependencies\"`\n\tResources            []Resource           `json:\"resources\"`\n}\n\ntype Input struct {\n\tName        string `json:\"name\"`\n\tType        string `json:\"type\"`\n\tDescription string `json:\"description\"`\n\tDefault     string `json:\"default\"`\n\tRequired    bool   `json:\"required\"`\n}\n\ntype Output struct {\n\tName        string `json:\"name\"`\n\tDescription string `json:\"description\"`\n}\n\ntype ProviderDependency struct {\n\tName      string `json:\"name\"`\n\tNamespace string `json:\"namespace\"`\n\tSource    string `json:\"source\"`\n\tVersion   string `json:\"version\"`\n}\n\ntype Resource struct {\n\tName string `json:\"name\"`\n\tType string `json:\"type\"`\n}\n\n// registryModules implements RegistryModules.\ntype registryModules struct {\n\tclient *Client\n}\n\n// RegistryModuleStatus represents the status of the registry module\ntype RegistryModuleStatus string\n\n// List of available registry module statuses\nconst (\n\tRegistryModuleStatusPending       RegistryModuleStatus = \"pending\"\n\tRegistryModuleStatusNoVersionTags RegistryModuleStatus = \"no_version_tags\"\n\tRegistryModuleStatusSetupFailed   RegistryModuleStatus = \"setup_failed\"\n\tRegistryModuleStatusSetupComplete RegistryModuleStatus = \"setup_complete\"\n)\n\n// RegistryModuleVersionStatus represents the status of a specific version of a registry module\ntype RegistryModuleVersionStatus string\n\n// List of available registry module version statuses\nconst (\n\tRegistryModuleVersionStatusPending             RegistryModuleVersionStatus = \"pending\"\n\tRegistryModuleVersionStatusCloning             RegistryModuleVersionStatus = \"cloning\"\n\tRegistryModuleVersionStatusCloneFailed         RegistryModuleVersionStatus = \"clone_failed\"\n\tRegistryModuleVersionStatusRegIngressReqFailed RegistryModuleVersionStatus = \"reg_ingress_req_failed\"\n\tRegistryModuleVersionStatusRegIngressing       RegistryModuleVersionStatus = \"reg_ingressing\"\n\tRegistryModuleVersionStatusRegIngressFailed    RegistryModuleVersionStatus = \"reg_ingress_failed\"\n\tRegistryModuleVersionStatusOk                  RegistryModuleVersionStatus = \"ok\"\n)\n\ntype PublishingMechanism string\n\nconst (\n\tPublishingMechanismBranch PublishingMechanism = \"branch\"\n\tPublishingMechanismTag    PublishingMechanism = \"git_tag\"\n)\n\n// RegistryModuleID represents the set of IDs that identify a RegistryModule\n// Use NewPublicRegistryModuleID or NewPrivateRegistryModuleID to build one\n\ntype RegistryModuleID struct {\n\t// The unique ID of the module. If given, the other fields are ignored.\n\tID string\n\t// The organization the module belongs to, see RegistryModule.Organization.Name\n\tOrganization string\n\t// The name of the module, see RegistryModule.Name\n\tName string\n\t// The module's provider, see RegistryModule.Provider\n\tProvider string\n\t// The namespace of the module. For private modules this is the name of the organization that owns the module\n\t// Required for public modules\n\tNamespace string\n\t// Either public or private. If not provided, defaults to private\n\tRegistryName RegistryName\n}\n\n// RegistryModuleList represents a list of registry modules.\ntype RegistryModuleList struct {\n\t*Pagination\n\tItems []*RegistryModule\n}\n\n// CommitList represents a list of the latest commits from the registry module\ntype CommitList struct {\n\t*Pagination\n\tItems []*Commit\n}\n\n// RegistryModule represents a registry module\ntype RegistryModule struct {\n\tID                  string                          `jsonapi:\"primary,registry-modules\"`\n\tName                string                          `jsonapi:\"attr,name\"`\n\tProvider            string                          `jsonapi:\"attr,provider\"`\n\tRegistryName        RegistryName                    `jsonapi:\"attr,registry-name\"`\n\tNamespace           string                          `jsonapi:\"attr,namespace\"`\n\tNoCode              bool                            `jsonapi:\"attr,no-code\"`\n\tPermissions         *RegistryModulePermissions      `jsonapi:\"attr,permissions\"`\n\tPublishingMechanism PublishingMechanism             `jsonapi:\"attr,publishing-mechanism\"`\n\tStatus              RegistryModuleStatus            `jsonapi:\"attr,status\"`\n\tTestConfig          *TestConfig                     `jsonapi:\"attr,test-config\"`\n\tVCSRepo             *VCSRepo                        `jsonapi:\"attr,vcs-repo\"`\n\tVersionStatuses     []RegistryModuleVersionStatuses `jsonapi:\"attr,version-statuses\"`\n\tCreatedAt           string                          `jsonapi:\"attr,created-at\"`\n\tUpdatedAt           string                          `jsonapi:\"attr,updated-at\"`\n\n\t// Relations\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n\n\tRegistryNoCodeModule []*RegistryNoCodeModule `jsonapi:\"relation,no-code-modules\"`\n}\n\n// Commit represents a commit\ntype Commit struct {\n\tID              string `jsonapi:\"primary,commit\"`\n\tSha             string `jsonapi:\"attr,sha\"`\n\tDate            string `jsonapi:\"attr,date\"`\n\tURL             string `jsonapi:\"attr,url\"`\n\tAuthor          string `jsonapi:\"attr,author\"`\n\tAuthorAvatarURL string `jsonapi:\"attr,author-avatar-url\"`\n\tAuthorHTMLURL   string `jsonapi:\"attr,author-html-url\"`\n\tMessage         string `jsonapi:\"attr,message\"`\n}\n\n// RegistryModuleVersion represents a registry module version\ntype RegistryModuleVersion struct {\n\tID        string                      `jsonapi:\"primary,registry-module-versions\"`\n\tSource    string                      `jsonapi:\"attr,source\"`\n\tStatus    RegistryModuleVersionStatus `jsonapi:\"attr,status\"`\n\tVersion   string                      `jsonapi:\"attr,version\"`\n\tCreatedAt string                      `jsonapi:\"attr,created-at\"`\n\tUpdatedAt string                      `jsonapi:\"attr,updated-at\"`\n\n\t// Relations\n\tRegistryModule *RegistryModule `jsonapi:\"relation,registry-module\"`\n\n\t// Links\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n}\n\ntype RegistryModulePermissions struct {\n\tCanDelete bool `jsonapi:\"attr,can-delete\"`\n\tCanResync bool `jsonapi:\"attr,can-resync\"`\n\tCanRetry  bool `jsonapi:\"attr,can-retry\"`\n}\n\ntype RegistryModuleVersionStatuses struct {\n\tVersion string                      `jsonapi:\"attr,version\"`\n\tStatus  RegistryModuleVersionStatus `jsonapi:\"attr,status\"`\n\tError   string                      `jsonapi:\"attr,error\"`\n}\n\n// RegistryModuleListOptions represents the options for listing registry modules.\ntype RegistryModuleListOptions struct {\n\tListOptions\n\n\t// Include is a list of relations to include.\n\tInclude []RegistryModuleListIncludeOpt `url:\"include,omitempty\"`\n\n\t// Search is a search query string. Modules are searchable by name, namespace, provider fields.\n\tSearch string `url:\"q,omitempty\"`\n\n\t// Provider filters results by provider name\n\tProvider string `url:\"filter[provider],omitempty\"`\n\n\t// RegistryName filters results by registry name (public or private)\n\tRegistryName RegistryName `url:\"filter[registry_name],omitempty\"`\n\n\t// OrganizationName filters results by organization name\n\tOrganizationName string `url:\"filter[organization_name],omitempty\"`\n}\n\ntype RegistryModuleListIncludeOpt string\n\nconst IncludeNoCodeModules RegistryModuleListIncludeOpt = \"no-code-modules\"\n\n// RegistryModuleCreateOptions is used when creating a registry module without a VCS repo\ntype RegistryModuleCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,registry-modules\"`\n\t// Required:\n\tName *string `jsonapi:\"attr,name\"`\n\t// Required:\n\tProvider *string `jsonapi:\"attr,provider\"`\n\t// Optional: Whether this is a publicly maintained module or private. Must be either public or private.\n\t// Defaults to private if not specified\n\tRegistryName RegistryName `jsonapi:\"attr,registry-name,omitempty\"`\n\t// Optional: The namespace of this module. Required for public modules only.\n\tNamespace string `jsonapi:\"attr,namespace,omitempty\"`\n\t// Optional: If set to true the module is enabled for no-code provisioning.\n\t// **Note: This field is still in BETA and subject to change.**\n\tNoCode *bool `jsonapi:\"attr,no-code,omitempty\"`\n}\n\n// RegistryModuleCreateVersionOptions is used when creating a registry module version\ntype RegistryModuleCreateVersionOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,registry-module-versions\"`\n\n\tVersion *string `jsonapi:\"attr,version\"`\n\n\tCommitSHA *string `jsonapi:\"attr,commit-sha\"`\n}\n\n// RegistryModuleCreateWithVCSConnectionOptions is used when creating a registry module with a VCS repo\ntype RegistryModuleCreateWithVCSConnectionOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,registry-modules\"`\n\n\t// Optional: The Name of the Module. If not provided, will be inferred from the VCS repository identifier.\n\t// Required for monorepos with source_directory where the repository name doesn't follow the terraform-<provider>-<name> convention.\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: The Name of the Provider. If not provided, will be inferred from the VCS repository identifier.\n\t// Required for monorepos with source_directory where the repository name doesn't follow the terraform-<provider>-<name> convention.\n\tProvider *string `jsonapi:\"attr,provider,omitempty\"`\n\n\t// Required: VCS repository information\n\tVCSRepo *RegistryModuleVCSRepoOptions `jsonapi:\"attr,vcs-repo\"`\n\n\t// Optional: If Branch is set within VCSRepo then InitialVersion sets the\n\t// initial version of the newly created branch-based registry module. If\n\t// Branch is not set within VCSRepo then InitialVersion is ignored.\n\t//\n\t// Defaults to \"0.0.0\".\n\t//\n\t// **Note: This field is still in BETA and subject to change.**\n\tInitialVersion *string `jsonapi:\"attr,initial-version,omitempty\"`\n\n\t// Optional: Flag to enable tests for the module\n\t// **Note: This field is still in BETA and subject to change.**\n\tTestConfig *RegistryModuleTestConfigOptions `jsonapi:\"attr,test-config,omitempty\"`\n}\n\n// RegistryModuleCreateVersionOptions is used when updating a registry module\ntype RegistryModuleUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-updating\n\tType string `jsonapi:\"primary,registry-modules\"`\n\n\t// Optional: Flag to enable no-code provisioning for the whole module.\n\t// **Note: This field is still in BETA and subject to change.**\n\tNoCode *bool `jsonapi:\"attr,no-code,omitempty\"`\n\n\t// Optional: Flag to enable tests for the module\n\t// **Note: This field is still in BETA and subject to change.**\n\tTestConfig *RegistryModuleTestConfigOptions `jsonapi:\"attr,test-config,omitempty\"`\n\n\tVCSRepo *RegistryModuleVCSRepoUpdateOptions `jsonapi:\"attr,vcs-repo,omitempty\"`\n}\n\ntype RegistryModuleTestConfigOptions struct {\n\tTestsEnabled       *bool               `jsonapi:\"attr,tests-enabled,omitempty\"`\n\tAgentExecutionMode *AgentExecutionMode `jsonapi:\"attr,agent-execution-mode,omitempty\"`\n\tAgentPoolID        *string             `jsonapi:\"attr,agent-pool-id,omitempty\"`\n}\n\ntype RegistryModuleVCSRepoOptions struct {\n\tIdentifier        *string `json:\"identifier\"` // Required\n\tOAuthTokenID      *string `json:\"oauth-token-id,omitempty\"`\n\tDisplayIdentifier *string `json:\"display-identifier,omitempty\"` // Required\n\tGHAInstallationID *string `json:\"github-app-installation-id,omitempty\"`\n\tOrganizationName  *string `json:\"organization-name,omitempty\"`\n\n\t// Optional: If set, the newly created registry module will be branch-based\n\t// with the starting branch set to Branch.\n\t//\n\t// **Note: This field is still in BETA and subject to change.**\n\tBranch *string `json:\"branch,omitempty\"`\n\tTags   *bool   `json:\"tags,omitempty\"`\n\n\t// Optional: If set, the registry module will be branch-based or tag-based\n\tSourceDirectory *string `json:\"source-directory,omitempty\"`\n\tTagPrefix       *string `json:\"tag-prefix,omitempty\"`\n}\n\ntype RegistryModuleVCSRepoUpdateOptions struct {\n\t// The Branch and Tag fields are used to determine\n\t// the PublishingMechanism for a RegistryModule that has a VCS a connection.\n\t// When a value for Branch is provided, the Tags field is removed on the server\n\t// When a value for Tags is provided, the Branch field is removed on the server\n\t// **Note: This field is still in BETA and subject to change.**\n\tBranch *string `json:\"branch,omitempty\"`\n\tTags   *bool   `json:\"tags,omitempty\"`\n\n\t// Optional: If set, the registry module will be branch-based or tag-based\n\tSourceDirectory *string `json:\"source-directory,omitempty\"`\n\tTagPrefix       *string `json:\"tag-prefix,omitempty\"`\n}\n\n// List all the registry modules within an organization.\nfunc (r *registryModules) List(ctx context.Context, organization string, options *RegistryModuleListOptions) (*RegistryModuleList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/registry-modules\", url.PathEscape(organization))\n\treq, err := r.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tml := &RegistryModuleList{}\n\terr = req.Do(ctx, ml)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ml, nil\n}\n\n// List the last 20 commits for the registry modules within an organization.\nfunc (r *registryModules) ListCommits(ctx context.Context, moduleID RegistryModuleID) (*CommitList, error) {\n\tif !validStringID(&moduleID.Organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-modules/private/%s/%s/%s/commits\",\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(moduleID.Name),\n\t\turl.PathEscape(moduleID.Provider),\n\t)\n\treq, err := r.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcl := &CommitList{}\n\terr = req.Do(ctx, cl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cl, nil\n}\n\n// Upload uploads Terraform configuration files for the provided registry module version. It\n// requires a path to the configuration files on disk, which will be packaged by\n// hashicorp/go-slug before being uploaded.\nfunc (r *registryModules) Upload(ctx context.Context, rmv RegistryModuleVersion, path string) error {\n\tuploadURL, ok := rmv.Links[\"upload\"].(string)\n\tif !ok {\n\t\treturn fmt.Errorf(\"provided RegistryModuleVersion does not contain an upload link\")\n\t}\n\n\tbody, err := packContents(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn r.UploadTarGzip(ctx, uploadURL, body)\n}\n\n// UploadTarGzip is used to upload Terraform configuration files contained a tar gzip archive.\n// Any stream implementing io.Reader can be passed into this method. This method is also\n// particularly useful for tar streams created by non-default go-slug configurations.\n//\n// **Note**: This method does not validate the content being uploaded and is therefore the caller's\n// responsibility to ensure the raw content is a valid Terraform configuration.\nfunc (r *registryModules) UploadTarGzip(ctx context.Context, uploadURL string, archive io.Reader) error {\n\treturn r.client.doForeignPUTRequest(ctx, uploadURL, archive)\n}\n\n// Create a new registry module without a VCS repo\nfunc (r *registryModules) Create(ctx context.Context, organization string, options RegistryModuleCreateOptions) (*RegistryModule, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif options.NoCode != nil {\n\t\tlog.Println(\"[WARN] Support for using the NoCode field is deprecated as of release 1.22.0 and may be removed in a future version. The preferred way to create a no-code module is with the registryNoCodeModules.Create method.\")\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-modules\",\n\t\turl.PathEscape(organization),\n\t)\n\treq, err := r.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trm := &RegistryModule{}\n\terr = req.Do(ctx, rm)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rm, nil\n}\n\nfunc (r *registryModules) Update(ctx context.Context, moduleID RegistryModuleID, options RegistryModuleUpdateOptions) (*RegistryModule, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif moduleID.RegistryName == \"\" {\n\t\tlog.Println(\"[WARN] Support for using the RegistryModuleID without RegistryName is deprecated as of release 1.5.0 and may be removed in a future version. The preferred method is to include the RegistryName in RegistryModuleID.\")\n\t\tmoduleID.RegistryName = PrivateRegistry\n\t}\n\n\tif moduleID.RegistryName == PrivateRegistry && strings.TrimSpace(moduleID.Namespace) == \"\" {\n\t\tlog.Println(\"[WARN] Support for using the RegistryModuleID without Namespace is deprecated as of release 1.5.0 and may be removed in a future version. The preferred method is to include the Namespace in RegistryModuleID.\")\n\t\tmoduleID.Namespace = moduleID.Organization\n\t}\n\n\tif options.NoCode != nil {\n\t\tlog.Println(\"[WARN] Support for using the NoCode field is deprecated as of release 1.22.0 and may be removed in a future version. The preferred way to update a no-code module is with the registryNoCodeModules.Update method.\")\n\t}\n\n\tif options.VCSRepo != nil {\n\t\tif options.VCSRepo.Tags != nil && *options.VCSRepo.Tags && validString(options.VCSRepo.Branch) {\n\t\t\treturn nil, ErrBranchMustBeEmptyWhenTagsEnabled\n\t\t}\n\t}\n\n\tif options.TestConfig != nil && options.TestConfig.AgentExecutionMode != nil {\n\t\tif *options.TestConfig.AgentExecutionMode == AgentExecutionModeRemote && options.TestConfig.AgentPoolID != nil {\n\t\t\treturn nil, ErrAgentPoolNotRequiredForRemoteExecution\n\t\t}\n\t}\n\n\torg := url.PathEscape(moduleID.Organization)\n\tregistryName := url.PathEscape(string(moduleID.RegistryName))\n\tnamespace := url.PathEscape(moduleID.Namespace)\n\tname := url.PathEscape(moduleID.Name)\n\tprovider := url.PathEscape(moduleID.Provider)\n\tregistryModuleURL := fmt.Sprintf(\"organizations/%s/registry-modules/%s/%s/%s/%s\", org, registryName, namespace, name, provider)\n\n\treq, err := r.client.NewRequest(http.MethodPatch, registryModuleURL, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trm := &RegistryModule{}\n\tif err := req.Do(ctx, rm); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rm, nil\n}\n\n// CreateVersion creates a new registry module version\nfunc (r *registryModules) CreateVersion(ctx context.Context, moduleID RegistryModuleID, options RegistryModuleCreateVersionOptions) (*RegistryModuleVersion, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"registry-modules/%s/%s/%s/versions\",\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(moduleID.Name),\n\t\turl.PathEscape(moduleID.Provider),\n\t)\n\treq, err := r.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trmv := &RegistryModuleVersion{}\n\terr = req.Do(ctx, rmv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rmv, nil\n}\n\n// CreateWithVCSConnection is used to create and publish a new registry module with a VCS repo\nfunc (r *registryModules) CreateWithVCSConnection(ctx context.Context, options RegistryModuleCreateWithVCSConnectionOptions) (*RegistryModule, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\tvar u string\n\tif options.VCSRepo.OAuthTokenID != nil && options.VCSRepo.Branch == nil {\n\t\tu = \"registry-modules\"\n\t} else {\n\t\tu = fmt.Sprintf(\n\t\t\t\"organizations/%s/registry-modules/vcs\",\n\t\t\turl.PathEscape(*options.VCSRepo.OrganizationName),\n\t\t)\n\t}\n\n\tif options.TestConfig != nil && options.TestConfig.AgentExecutionMode != nil {\n\t\tif *options.TestConfig.AgentExecutionMode == AgentExecutionModeRemote && options.TestConfig.AgentPoolID != nil {\n\t\t\treturn nil, ErrAgentPoolNotRequiredForRemoteExecution\n\t\t}\n\t}\n\n\treq, err := r.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trm := &RegistryModule{}\n\terr = req.Do(ctx, rm)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rm, nil\n}\n\n// Read a specific registry module\nfunc (r *registryModules) Read(ctx context.Context, moduleID RegistryModuleID) (*RegistryModule, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar u string\n\tif moduleID.ID == \"\" {\n\t\tif moduleID.RegistryName == \"\" {\n\t\t\tlog.Println(\"[WARN] Support for using the RegistryModuleID without RegistryName is deprecated as of release 1.5.0 and may be removed in a future version. The preferred method is to include the RegistryName in RegistryModuleID.\")\n\t\t\tmoduleID.RegistryName = PrivateRegistry\n\t\t}\n\n\t\tif moduleID.RegistryName == PrivateRegistry && strings.TrimSpace(moduleID.Namespace) == \"\" {\n\t\t\tlog.Println(\"[WARN] Support for using the RegistryModuleID without Namespace is deprecated as of release 1.5.0 and may be removed in a future version. The preferred method is to include the Namespace in RegistryModuleID.\")\n\t\t\tmoduleID.Namespace = moduleID.Organization\n\t\t}\n\n\t\tu = fmt.Sprintf(\n\t\t\t\"organizations/%s/registry-modules/%s/%s/%s/%s\",\n\t\t\turl.PathEscape(moduleID.Organization),\n\t\t\turl.PathEscape(string(moduleID.RegistryName)),\n\t\t\turl.PathEscape(moduleID.Namespace),\n\t\t\turl.PathEscape(moduleID.Name),\n\t\t\turl.PathEscape(moduleID.Provider),\n\t\t)\n\t} else {\n\t\tu = fmt.Sprintf(\"registry-modules/%s\", url.PathEscape(moduleID.ID))\n\t}\n\n\treq, err := r.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trm := &RegistryModule{}\n\terr = req.Do(ctx, rm)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rm, nil\n}\n\n// ReadTerraformRegistryModule fetches a registry module from the Terraform Registry.\nfunc (r *registryModules) ReadTerraformRegistryModule(ctx context.Context, moduleID RegistryModuleID, version string) (*TerraformRegistryModule, error) {\n\tu := fmt.Sprintf(\"/api/registry/v1/modules/%s/%s/%s/%s\",\n\t\tmoduleID.Namespace,\n\t\tmoduleID.Name,\n\t\tmoduleID.Provider,\n\t\tversion,\n\t)\n\n\tif moduleID.RegistryName == PublicRegistry {\n\t\tu = fmt.Sprintf(\"/api/registry/public/v1/modules/%s/%s/%s/%s\",\n\t\t\tmoduleID.Namespace,\n\t\t\tmoduleID.Name,\n\t\t\tmoduleID.Provider,\n\t\t\tversion,\n\t\t)\n\t}\n\treq, err := r.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttrm := &TerraformRegistryModule{}\n\terr = req.DoJSON(ctx, trm)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn trm, nil\n}\n\nfunc (r *registryModules) ReadVersion(ctx context.Context, moduleID RegistryModuleID, version string) (*RegistryModuleVersion, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\tif !validString(&version) {\n\t\treturn nil, ErrRequiredVersion\n\t}\n\tif !validStringID(&version) {\n\t\treturn nil, ErrInvalidVersion\n\t}\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-modules/private/%s/%s/%s/version?module_version=%s\",\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(moduleID.Name),\n\t\turl.PathEscape(moduleID.Provider),\n\t\turl.PathEscape(version),\n\t)\n\treq, err := r.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trmv := &RegistryModuleVersion{}\n\terr = req.Do(ctx, rmv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rmv, nil\n}\n\n// Delete is used to delete the entire registry module\n// Warning: This method is deprecated and will be removed from a future version of go-tfe. Use DeleteByName instead.\n// See API Docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/modules#delete-a-module\nfunc (r *registryModules) Delete(ctx context.Context, organization, name string) error {\n\tif !validStringID(&organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\tif !validString(&name) {\n\t\treturn ErrRequiredName\n\t}\n\tif !validStringID(&name) {\n\t\treturn ErrInvalidName\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"registry-modules/actions/delete/%s/%s\",\n\t\turl.PathEscape(organization),\n\t\turl.PathEscape(name),\n\t)\n\treq, err := r.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// DeleteByName is used to delete the entire registry module\nfunc (r *registryModules) DeleteByName(ctx context.Context, module RegistryModuleID) error {\n\tif err := module.validWhenDeleteByName(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-modules/%s/%s/%s\",\n\t\turl.PathEscape(module.Organization),\n\t\turl.PathEscape(string(module.RegistryName)),\n\t\turl.PathEscape(module.Namespace),\n\t\turl.PathEscape(module.Name),\n\t)\n\n\treq, err := r.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil && errors.Is(err, ErrResourceNotFound) {\n\t\treturn r.Delete(ctx, module.Organization, module.Name)\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Delete a specified provider for the given module along with all its versions\nfunc (r *registryModules) DeleteProvider(ctx context.Context, moduleID RegistryModuleID) error {\n\tif err := moduleID.validWhenDeleteByProvider(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-modules/%s/%s/%s/%s\",\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(string(moduleID.RegistryName)),\n\t\turl.PathEscape(moduleID.Namespace),\n\t\turl.PathEscape(moduleID.Name),\n\t\turl.PathEscape(moduleID.Provider),\n\t)\n\n\treq, err := r.client.NewRequest(\"DELETE\", u, nil)\n\n\tif err != nil && errors.Is(err, ErrResourceNotFound) {\n\t\treturn r.deprecatedDeleteProvider(ctx, moduleID)\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Delete a specified version for the given provider of the module\nfunc (r *registryModules) DeleteVersion(ctx context.Context, moduleID RegistryModuleID, version string) error {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn err\n\t}\n\tif !validString(&version) {\n\t\treturn ErrRequiredVersion\n\t}\n\tif !validVersion(version) {\n\t\treturn ErrInvalidVersion\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-modules/%s/%s/%s/%s/%s\",\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(string(moduleID.RegistryName)),\n\t\turl.PathEscape(moduleID.Namespace),\n\t\turl.PathEscape(moduleID.Name),\n\t\turl.PathEscape(moduleID.Provider),\n\t\turl.PathEscape(version),\n\t)\n\treq, err := r.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil && errors.Is(err, ErrResourceNotFound) {\n\t\treturn r.deprecatedDeleteVersion(ctx, moduleID, version)\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o RegistryModuleID) valid() error {\n\tif validString(&o.ID) && validStringID(&o.ID) {\n\t\treturn nil\n\t}\n\n\tif !validStringID(&o.Organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\n\tif !validString(&o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\n\tif !validStringID(&o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\n\tif !validString(&o.Provider) {\n\t\treturn ErrRequiredProvider\n\t}\n\n\tif !validStringID(&o.Provider) {\n\t\treturn ErrInvalidProvider\n\t}\n\n\tswitch o.RegistryName {\n\tcase PublicRegistry:\n\t\tif !validString(&o.Namespace) {\n\t\t\treturn ErrRequiredNamespace\n\t\t}\n\tcase PrivateRegistry:\n\tcase \"\":\n\t\t// no-op:  RegistryName is optional\n\t// for all other string\n\tdefault:\n\t\treturn ErrInvalidRegistryName\n\t}\n\n\treturn nil\n}\n\nfunc (o RegistryModuleID) validWhenDeleteByProvider() error {\n\tif !validStringID(&o.Organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\n\tif !validString(&o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\n\tif !validStringID(&o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\n\tif !validString(&o.Provider) {\n\t\treturn ErrRequiredProvider\n\t}\n\n\tif !validStringID(&o.Provider) {\n\t\treturn ErrInvalidProvider\n\t}\n\t// RegistryName is required in this DELETE call\n\tswitch o.RegistryName {\n\tcase PublicRegistry:\n\t\tif !validString(&o.Namespace) {\n\t\t\treturn ErrRequiredNamespace\n\t\t}\n\tcase PrivateRegistry:\n\tcase \"\":\n\t\treturn ErrInvalidRegistryName\n\tdefault:\n\t\treturn ErrInvalidRegistryName\n\t}\n\n\treturn nil\n}\n\nfunc (o RegistryModuleID) validWhenDeleteByName() error {\n\tif !validStringID(&o.Organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\n\tif !validString(&o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\n\tif !validStringID(&o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\n\t// RegistryName is required in this DELETE call\n\tswitch o.RegistryName {\n\tcase PublicRegistry:\n\t\tif !validString(&o.Namespace) {\n\t\t\treturn ErrRequiredNamespace\n\t\t}\n\tcase PrivateRegistry:\n\tcase \"\":\n\t\treturn ErrInvalidRegistryName\n\tdefault:\n\t\treturn ErrInvalidRegistryName\n\t}\n\n\treturn nil\n}\n\nfunc (o RegistryModuleCreateOptions) valid() error {\n\tif !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\tif !validStringID(o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\tif !validString(o.Provider) {\n\t\treturn ErrRequiredProvider\n\t}\n\tif !validStringID(o.Provider) {\n\t\treturn ErrInvalidProvider\n\t}\n\n\tswitch o.RegistryName {\n\tcase PublicRegistry:\n\t\tif !validString(&o.Namespace) {\n\t\t\treturn ErrRequiredNamespace\n\t\t}\n\tcase PrivateRegistry:\n\t\tif validString(&o.Namespace) {\n\t\t\treturn ErrUnsupportedBothNamespaceAndPrivateRegistryName\n\t\t}\n\tcase \"\":\n\t\t// no-op:  RegistryName is optional\n\t// for all other string\n\tdefault:\n\t\treturn ErrInvalidRegistryName\n\t}\n\treturn nil\n}\n\nfunc (o RegistryModuleCreateVersionOptions) valid() error {\n\tif !validString(o.Version) {\n\t\treturn ErrRequiredVersion\n\t}\n\tif !validVersion(*o.Version) {\n\t\treturn ErrInvalidVersion\n\t}\n\treturn nil\n}\n\nfunc (o RegistryModuleCreateWithVCSConnectionOptions) valid() error {\n\tif o.VCSRepo == nil {\n\t\treturn ErrRequiredVCSRepo\n\t}\n\n\tif o.TestConfig != nil && o.TestConfig.TestsEnabled != nil {\n\t\tif *o.TestConfig.TestsEnabled {\n\t\t\tif !validString(o.VCSRepo.Branch) {\n\t\t\t\treturn ErrRequiredBranchWhenTestsEnabled\n\t\t\t}\n\t\t}\n\t}\n\n\tif o.VCSRepo.Tags != nil && *o.VCSRepo.Tags {\n\t\tif validString(o.VCSRepo.Branch) {\n\t\t\treturn ErrBranchMustBeEmptyWhenTagsEnabled\n\t\t}\n\t}\n\n\treturn o.VCSRepo.valid()\n}\n\nfunc (o RegistryModuleVCSRepoOptions) valid() error {\n\tif !validString(o.Identifier) {\n\t\treturn ErrRequiredIdentifier\n\t}\n\tif !validString(o.OAuthTokenID) && !validString(o.GHAInstallationID) {\n\t\treturn ErrRequiredOauthTokenOrGithubAppInstallationID\n\t}\n\tif (!validString(o.OAuthTokenID) && validString(o.GHAInstallationID)) || validString(o.Branch) {\n\t\tif !validString(o.OrganizationName) {\n\t\t\treturn ErrInvalidOrg\n\t\t}\n\t}\n\tif !validString(o.DisplayIdentifier) {\n\t\treturn ErrRequiredDisplayIdentifier\n\t}\n\treturn nil\n}\n\nfunc (r *registryModules) deprecatedDeleteProvider(ctx context.Context, moduleID RegistryModuleID) error {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"registry-modules/actions/delete/%s/%s/%s\",\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(moduleID.Name),\n\t\turl.PathEscape(moduleID.Provider),\n\t)\n\treq, err := r.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (r *registryModules) deprecatedDeleteVersion(ctx context.Context, moduleID RegistryModuleID, version string) error {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn err\n\t}\n\tif !validString(&version) {\n\t\treturn ErrRequiredVersion\n\t}\n\tif !validVersion(version) {\n\t\treturn ErrInvalidVersion\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"registry-modules/actions/delete/%s/%s/%s/%s\",\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(moduleID.Name),\n\t\turl.PathEscape(moduleID.Provider),\n\t\turl.PathEscape(version),\n\t)\n\treq, err := r.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc NewPublicRegistryModuleID(organization, namespace, name, provider string) RegistryModuleID {\n\treturn RegistryModuleID{\n\t\tOrganization: organization,\n\t\tNamespace:    namespace,\n\t\tName:         name,\n\t\tRegistryName: PublicRegistry,\n\t\tProvider:     provider,\n\t}\n}\n\nfunc NewPrivateRegistryModuleID(organization, name, provider string) RegistryModuleID {\n\treturn RegistryModuleID{\n\t\tOrganization: organization,\n\t\tNamespace:    organization,\n\t\tName:         name,\n\t\tRegistryName: PrivateRegistry,\n\t\tProvider:     provider,\n\t}\n}\n"
  },
  {
    "path": "registry_module_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n\tslug \"github.com/hashicorp/go-slug\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRegistryModulesList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest1, registryModuleTest1Cleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\tdefer registryModuleTest1Cleanup()\n\tregistryModuleTest2, registryModuleTest2Cleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\tdefer registryModuleTest2Cleanup()\n\n\tt.Run(\"with no list options\", func(t *testing.T) {\n\t\tmodl, err := client.RegistryModules.List(ctx, orgTest.Name, &RegistryModuleListOptions{})\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, modl.Items, registryModuleTest1)\n\t\tassert.Contains(t, modl.Items, registryModuleTest2)\n\t\tassert.Equal(t, 1, modl.CurrentPage)\n\t\tassert.Equal(t, 2, modl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tmodl, err := client.RegistryModules.List(ctx, orgTest.Name, &RegistryModuleListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\t// Out of range page number, so the items should be empty\n\t\tassert.Empty(t, modl.Items)\n\t\tassert.Equal(t, 999, modl.CurrentPage)\n\n\t\tmodl, err = client.RegistryModules.List(ctx, orgTest.Name, &RegistryModuleListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, modl.Items)\n\t\tassert.Equal(t, 1, modl.CurrentPage)\n\t})\n\n\tt.Run(\"include no-code modules\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateOptions{\n\t\t\tName:         String(\"iam\"),\n\t\t\tProvider:     String(\"aws\"),\n\t\t\tNoCode:       Bool(true),\n\t\t\tRegistryName: PrivateRegistry,\n\t\t}\n\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tmodl, err := client.RegistryModules.List(ctx, orgTest.Name, &RegistryModuleListOptions{\n\t\t\tInclude: []RegistryModuleListIncludeOpt{\n\t\t\t\tIncludeNoCodeModules,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, modl.Items, 3)\n\t\tfor _, m := range modl.Items {\n\t\t\tif m.ID == rm.ID {\n\t\t\t\tassert.True(t, m.NoCode)\n\t\t\t\tassert.Len(t, m.RegistryNoCodeModule, 1)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"with search query\", func(t *testing.T) {\n\t\t// Search for modules by name\n\t\tmodl, err := client.RegistryModules.List(ctx, orgTest.Name, &RegistryModuleListOptions{\n\t\t\tSearch: registryModuleTest1.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// Should find at least the first test module\n\t\tfound := false\n\t\tfor _, m := range modl.Items {\n\t\t\tif m.ID == registryModuleTest1.ID {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tassert.True(t, found, \"Registry module should be found by name search\")\n\t})\n\n\tt.Run(\"with provider filter\", func(t *testing.T) {\n\t\t// Filter by provider\n\t\tmodl, err := client.RegistryModules.List(ctx, orgTest.Name, &RegistryModuleListOptions{\n\t\t\tProvider: registryModuleTest1.Provider,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// All returned modules should have the specified provider\n\t\tfor _, m := range modl.Items {\n\t\t\tassert.Equal(t, registryModuleTest1.Provider, m.Provider)\n\t\t}\n\t})\n\n\tt.Run(\"with registry name filter\", func(t *testing.T) {\n\t\t// Filter by registry name\n\t\tmodl, err := client.RegistryModules.List(ctx, orgTest.Name, &RegistryModuleListOptions{\n\t\t\tRegistryName: PrivateRegistry,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// All returned modules should have the specified registry name\n\t\tfor _, m := range modl.Items {\n\t\t\tassert.Equal(t, PrivateRegistry, m.RegistryName)\n\t\t}\n\t})\n\n\tt.Run(\"with organization name filter\", func(t *testing.T) {\n\t\t// Filter by organization name\n\t\tmodl, err := client.RegistryModules.List(ctx, orgTest.Name, &RegistryModuleListOptions{\n\t\t\tOrganizationName: orgTest.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// All returned modules should belong to the specified organization\n\t\tfor _, m := range modl.Items {\n\t\t\tassert.Equal(t, orgTest.Name, m.Namespace)\n\t\t}\n\t})\n\n\tt.Run(\"with combined search and filters\", func(t *testing.T) {\n\t\t// Combine search with filters\n\t\tmodl, err := client.RegistryModules.List(ctx, orgTest.Name, &RegistryModuleListOptions{\n\t\t\tSearch:           registryModuleTest1.Name,\n\t\t\tProvider:         registryModuleTest1.Provider,\n\t\t\tRegistryName:     PrivateRegistry,\n\t\t\tOrganizationName: orgTest.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// Should find the specific module when all criteria match\n\t\tfound := false\n\t\tfor _, m := range modl.Items {\n\t\t\tif m.ID != registryModuleTest1.ID {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfound = true\n\t\t\tassert.Equal(t, registryModuleTest1.Provider, m.Provider)\n\t\t\tassert.Equal(t, PrivateRegistry, m.RegistryName)\n\t\t\tassert.Equal(t, orgTest.Name, m.Namespace)\n\t\t\tbreak\n\t\t}\n\t\tassert.True(t, found, \"Registry module should be found with combined search and filters\")\n\t})\n}\n\nfunc TestRegistryModulesCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tassertRegistryModuleAttributes := func(t *testing.T, registryModule *RegistryModule) {\n\t\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\t\trequire.NotEmpty(t, registryModule.Permissions)\n\t\t\t\tassert.True(t, registryModule.Permissions.CanDelete)\n\t\t\t\tassert.True(t, registryModule.Permissions.CanResync)\n\t\t\t\tassert.True(t, registryModule.Permissions.CanRetry)\n\t\t\t})\n\n\t\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\t\trequire.NotEmpty(t, registryModule.Organization)\n\t\t\t\tassert.Equal(t, orgTest.Name, registryModule.Organization.Name)\n\t\t\t})\n\n\t\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\t\tassert.NotEmpty(t, registryModule.CreatedAt)\n\t\t\t\tassert.NotEmpty(t, registryModule.UpdatedAt)\n\t\t\t})\n\t\t}\n\n\t\tt.Run(\"without RegistryName\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tName:     String(\"name\"),\n\t\t\t\tProvider: String(\"provider\"),\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.NotEmpty(t, rm.ID)\n\t\t\tassert.Equal(t, *options.Name, rm.Name)\n\t\t\tassert.Equal(t, *options.Provider, rm.Provider)\n\t\t\tassert.Equal(t, PrivateRegistry, rm.RegistryName)\n\t\t\tassert.Equal(t, orgTest.Name, rm.Namespace)\n\t\t\tassert.False(t, rm.NoCode, \"no-code module attribute should be false by default\")\n\n\t\t\tassertRegistryModuleAttributes(t, rm)\n\t\t})\n\n\t\tt.Run(\"with private RegistryName\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tName:         String(\"another_name\"),\n\t\t\t\tProvider:     String(\"provider\"),\n\t\t\t\tRegistryName: PrivateRegistry,\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.NotEmpty(t, rm.ID)\n\t\t\tassert.Equal(t, *options.Name, rm.Name)\n\t\t\tassert.Equal(t, *options.Provider, rm.Provider)\n\t\t\tassert.Equal(t, options.RegistryName, rm.RegistryName)\n\t\t\tassert.Equal(t, orgTest.Name, rm.Namespace)\n\t\t\tassert.False(t, rm.NoCode, \"no-code module attribute should be false by default\")\n\n\t\t\tassertRegistryModuleAttributes(t, rm)\n\t\t})\n\n\t\tt.Run(\"with public RegistryName\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tName:         String(\"vpc\"),\n\t\t\t\tProvider:     String(\"aws\"),\n\t\t\t\tRegistryName: PublicRegistry,\n\t\t\t\tNamespace:    \"terraform-aws-modules\",\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.NotEmpty(t, rm.ID)\n\t\t\tassert.Equal(t, *options.Name, rm.Name)\n\t\t\tassert.Equal(t, *options.Provider, rm.Provider)\n\t\t\tassert.Equal(t, options.RegistryName, rm.RegistryName)\n\t\t\tassert.Equal(t, options.Namespace, rm.Namespace)\n\t\t\tassert.False(t, rm.NoCode, \"no-code module attribute should be false by default\")\n\n\t\t\tassertRegistryModuleAttributes(t, rm)\n\t\t})\n\n\t\tt.Run(\"with no-code attribute\", func(t *testing.T) {\n\t\t\tskipUnlessBeta(t)\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tName:         String(\"iam\"),\n\t\t\t\tProvider:     String(\"aws\"),\n\t\t\t\tNoCode:       Bool(true),\n\t\t\t\tRegistryName: PrivateRegistry,\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.NotEmpty(t, rm.ID)\n\t\t\tassert.Equal(t, *options.Name, rm.Name)\n\t\t\tassert.Equal(t, *options.Provider, rm.Provider)\n\t\t\tassert.Equal(t, options.RegistryName, rm.RegistryName)\n\t\t\tassert.Equal(t, orgTest.Name, rm.Namespace)\n\t\t\tassert.Equal(t, options.NoCode, Bool(rm.NoCode))\n\n\t\t\tassertRegistryModuleAttributes(t, rm)\n\t\t})\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\tt.Run(\"without a name\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tProvider: String(\"provider\"),\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t\t})\n\n\t\tt.Run(\"with an invalid name\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tName:     String(\"invalid name\"),\n\t\t\t\tProvider: String(\"provider\"),\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t\t})\n\n\t\tt.Run(\"without a provider\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tName: String(\"name\"),\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrRequiredProvider)\n\t\t})\n\n\t\tt.Run(\"with an invalid provider\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tName:     String(\"name\"),\n\t\t\t\tProvider: String(\"invalid provider\"),\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrInvalidProvider)\n\t\t})\n\n\t\tt.Run(\"with an invalid registry name\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tName:         String(\"name\"),\n\t\t\t\tProvider:     String(\"provider\"),\n\t\t\t\tRegistryName: \"PRIVATE\",\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrInvalidRegistryName)\n\t\t})\n\n\t\tt.Run(\"without a namespace for public registry name\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tName:         String(\"name\"),\n\t\t\t\tProvider:     String(\"provider\"),\n\t\t\t\tRegistryName: PublicRegistry,\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrRequiredNamespace)\n\t\t})\n\n\t\tt.Run(\"with a namespace for private registry name\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateOptions{\n\t\t\t\tName:         String(\"name\"),\n\t\t\t\tProvider:     String(\"provider\"),\n\t\t\t\tRegistryName: PrivateRegistry,\n\t\t\t\tNamespace:    \"namespace\",\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrUnsupportedBothNamespaceAndPrivateRegistryName)\n\t\t})\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateOptions{\n\t\t\tName:     String(\"name\"),\n\t\t\tProvider: String(\"provider\"),\n\t\t}\n\t\trm, err := client.RegistryModules.Create(ctx, badIdentifier, options)\n\t\tassert.Nil(t, rm)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestRegistryModuleUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\toptions := RegistryModuleCreateOptions{\n\t\tName:         String(\"vault\"),\n\t\tProvider:     String(\"aws\"),\n\t\tRegistryName: PublicRegistry,\n\t\tNamespace:    \"hashicorp\",\n\t}\n\trm, err := client.RegistryModules.Create(ctx, orgTest.Name, options)\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, rm.ID)\n\n\tt.Run(\"enable no-code\", func(t *testing.T) {\n\t\toptions := RegistryModuleUpdateOptions{\n\t\t\tNoCode: Bool(true),\n\t\t}\n\t\trm, err := client.RegistryModules.Update(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         \"vault\",\n\t\t\tProvider:     \"aws\",\n\t\t\tNamespace:    \"hashicorp\",\n\t\t\tRegistryName: PublicRegistry,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, rm.NoCode)\n\t})\n\n\tt.Run(\"disable no-code\", func(t *testing.T) {\n\t\toptions := RegistryModuleUpdateOptions{\n\t\t\tNoCode: Bool(false),\n\t\t}\n\t\trm, err := client.RegistryModules.Update(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         \"vault\",\n\t\t\tProvider:     \"aws\",\n\t\t\tNamespace:    \"hashicorp\",\n\t\t\tRegistryName: PublicRegistry,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, rm.NoCode)\n\t})\n}\n\nfunc TestRegistryModuleUpdateWithVCSConnection(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tgithubBranch := os.Getenv(\"GITHUB_REGISTRY_MODULE_BRANCH\")\n\tif githubBranch == \"\" {\n\t\tgithubBranch = \"main\"\n\t}\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, orgTest)\n\tdefer oauthTokenTestCleanup()\n\n\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t},\n\t}\n\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, rm.ID)\n\n\tt.Run(\"enable no-code\", func(t *testing.T) {\n\t\toptions := RegistryModuleUpdateOptions{\n\t\t\tNoCode: Bool(true),\n\t\t}\n\t\trm, err := client.RegistryModules.Update(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         rm.Name,\n\t\t\tProvider:     rm.Provider,\n\t\t\tNamespace:    rm.Namespace,\n\t\t\tRegistryName: rm.RegistryName,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, rm.NoCode)\n\t})\n\n\tt.Run(\"disable no-code\", func(t *testing.T) {\n\t\toptions := RegistryModuleUpdateOptions{\n\t\t\tNoCode: Bool(false),\n\t\t}\n\t\trm, err := client.RegistryModules.Update(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         rm.Name,\n\t\t\tProvider:     rm.Provider,\n\t\t\tNamespace:    rm.Namespace,\n\t\t\tRegistryName: rm.RegistryName,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, rm.NoCode)\n\t})\n\n\tt.Run(\"prevents setting the branch when using tag based publishing\", func(t *testing.T) {\n\t\toptions := RegistryModuleUpdateOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoUpdateOptions{\n\t\t\t\tBranch: String(\"main\"),\n\t\t\t\tTags:   Bool(true),\n\t\t\t},\n\t\t}\n\n\t\t_, err = client.RegistryModules.Update(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         rm.Name,\n\t\t\tProvider:     rm.Provider,\n\t\t\tNamespace:    rm.Namespace,\n\t\t\tRegistryName: rm.RegistryName,\n\t\t}, options)\n\n\t\tassert.Error(t, err)\n\t\tassert.EqualError(t, err, ErrBranchMustBeEmptyWhenTagsEnabled.Error())\n\n\t\toptions = RegistryModuleUpdateOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoUpdateOptions{\n\t\t\t\tBranch: String(\"\"),\n\t\t\t\tTags:   Bool(true),\n\t\t\t},\n\t\t}\n\n\t\trm, err = client.RegistryModules.Update(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         rm.Name,\n\t\t\tProvider:     rm.Provider,\n\t\t\tNamespace:    rm.Namespace,\n\t\t\tRegistryName: rm.RegistryName,\n\t\t}, options)\n\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"toggle between git tag-based and branch-based publishing\", func(t *testing.T) {\n\t\tassert.Equal(t, rm.PublishingMechanism, PublishingMechanismTag)\n\n\t\toptions := RegistryModuleUpdateOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoUpdateOptions{\n\t\t\t\tBranch: String(githubBranch),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.Update(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         rm.Name,\n\t\t\tProvider:     rm.Provider,\n\t\t\tNamespace:    rm.Namespace,\n\t\t\tRegistryName: rm.RegistryName,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, rm.PublishingMechanism, PublishingMechanismBranch)\n\t\tassert.Equal(t, false, rm.VCSRepo.Tags)\n\t\tassert.Equal(t, githubBranch, rm.VCSRepo.Branch)\n\n\t\toptions = RegistryModuleUpdateOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoUpdateOptions{\n\t\t\t\tBranch: String(\"\"),\n\t\t\t\tTags:   Bool(true),\n\t\t\t},\n\t\t}\n\t\trm, err = client.RegistryModules.Update(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         rm.Name,\n\t\t\tProvider:     rm.Provider,\n\t\t\tNamespace:    rm.Namespace,\n\t\t\tRegistryName: rm.RegistryName,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, rm.PublishingMechanism, PublishingMechanismTag)\n\t\tassert.Equal(t, true, rm.VCSRepo.Tags)\n\t\tassert.Equal(t, \"\", rm.VCSRepo.Branch)\n\n\t\toptions = RegistryModuleUpdateOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoUpdateOptions{\n\t\t\t\tBranch: String(githubBranch),\n\t\t\t},\n\t\t}\n\t\trm, err = client.RegistryModules.Update(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         rm.Name,\n\t\t\tProvider:     rm.Provider,\n\t\t\tNamespace:    rm.Namespace,\n\t\t\tRegistryName: rm.RegistryName,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, rm.PublishingMechanism, PublishingMechanismBranch)\n\t\tassert.Equal(t, false, rm.VCSRepo.Tags)\n\t\tassert.Equal(t, githubBranch, rm.VCSRepo.Branch)\n\t})\n}\n\nfunc TestRegistryModulesCreateVersion(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\tdefer registryModuleTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.3\"),\n\t\t}\n\n\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rmv.ID)\n\t\tassert.Equal(t, *options.Version, rmv.Version)\n\n\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, registryModuleTest.ID, rmv.RegistryModule.ID)\n\t\t})\n\n\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rmv.CreatedAt)\n\t\t\tassert.NotEmpty(t, rmv.UpdatedAt)\n\t\t})\n\n\t\tt.Run(\"links are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rmv.Links[\"upload\"])\n\t\t\tassert.Contains(t, rmv.Links[\"upload\"], \"/object/\")\n\t\t})\n\t})\n\n\tt.Run(\"with prerelease and metadata version\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.3-alpha+feature\"),\n\t\t}\n\n\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rmv.ID)\n\t\tassert.Equal(t, *options.Version, rmv.Version)\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\tt.Run(\"without version\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateVersionOptions{}\n\t\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\t\tOrganization: orgTest.Name,\n\t\t\t\tName:         registryModuleTest.Name,\n\t\t\t\tProvider:     registryModuleTest.Provider,\n\t\t\t}, options)\n\t\t\tassert.Nil(t, rmv)\n\t\t\tassert.Equal(t, err, ErrRequiredVersion)\n\t\t})\n\n\t\tt.Run(\"with invalid version\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\t\tVersion: String(\"invalid version\"),\n\t\t\t}\n\t\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\t\tOrganization: orgTest.Name,\n\t\t\t\tName:         registryModuleTest.Name,\n\t\t\t\tProvider:     registryModuleTest.Provider,\n\t\t\t}, options)\n\t\t\tassert.Nil(t, rmv)\n\t\t\tassert.Equal(t, err, ErrInvalidVersion)\n\t\t})\n\t})\n\n\tt.Run(\"without a name\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.3\"),\n\t\t}\n\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         \"\",\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, options)\n\t\tassert.Nil(t, rmv)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"with an invalid name\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.3\"),\n\t\t}\n\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         badIdentifier,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, options)\n\t\tassert.Nil(t, rmv)\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"without a provider\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.3\"),\n\t\t}\n\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     \"\",\n\t\t}, options)\n\t\tassert.Nil(t, rmv)\n\t\tassert.Equal(t, err, ErrRequiredProvider)\n\t})\n\n\tt.Run(\"with an invalid provider\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.3\"),\n\t\t}\n\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     badIdentifier,\n\t\t}, options)\n\t\tassert.Nil(t, rmv)\n\t\tassert.Equal(t, err, ErrInvalidProvider)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.3\"),\n\t\t}\n\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: badIdentifier,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, options)\n\t\tassert.Nil(t, rmv)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestRegistryModulesShowVersion(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\tdefer registryModuleTestCleanup()\n\n\tt.Run(\"when the version exists\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.7\"),\n\t\t}\n\n\t\tregistryModuleIDTest := RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}\n\n\t\trmv, err := client.RegistryModules.CreateVersion(ctx, registryModuleIDTest, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rmv.ID)\n\t\tassert.Equal(t, *options.Version, rmv.Version)\n\n\t\trmvRead, errRead := client.RegistryModules.ReadVersion(ctx, registryModuleIDTest, *options.Version)\n\n\t\trequire.NoError(t, errRead)\n\t\tassert.NotEmpty(t, rmvRead.ID)\n\n\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, registryModuleTest.ID, rmvRead.RegistryModule.ID)\n\t\t})\n\n\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rmvRead.CreatedAt)\n\t\t\tassert.NotEmpty(t, rmvRead.UpdatedAt)\n\t\t})\n\n\t\tt.Run(\"links are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rmvRead.Links[\"upload\"])\n\t\t\tassert.Contains(t, rmvRead.Links[\"upload\"], \"/object/\")\n\t\t})\n\t})\n\n\tt.Run(\"when reading a version that does not exist\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.3\"),\n\t\t}\n\n\t\tregistryModuleIDTest := RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}\n\n\t\trmv, err := client.RegistryModules.CreateVersion(ctx, registryModuleIDTest, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rmv.ID)\n\t\tassert.Equal(t, *options.Version, rmv.Version)\n\n\t\tinvalidVersion := String(\"1.5.5\")\n\n\t\trmvRead, errRead := client.RegistryModules.ReadVersion(ctx, registryModuleIDTest, *invalidVersion)\n\n\t\trequire.Error(t, errRead)\n\t\tassert.Equal(t, ErrResourceNotFound, errRead)\n\t\tassert.Empty(t, rmvRead)\n\t})\n}\n\nfunc TestRegistryModulesListCommit(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\trepositoryName := strings.Split(githubIdentifier, \"/\")[1]\n\tregistryModuleProvider := strings.SplitN(repositoryName, \"-\", 3)[1]\n\tregistryModuleName := strings.SplitN(repositoryName, \"-\", 3)[2]\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, orgTest)\n\tdefer oauthTokenTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, registryModuleName, rm.Name)\n\t\tassert.Equal(t, registryModuleProvider, rm.Provider)\n\t\tassert.Equal(t, rm.VCSRepo.Branch, \"\")\n\t\tassert.Equal(t, rm.VCSRepo.DisplayIdentifier, githubIdentifier)\n\t\tassert.Equal(t, rm.VCSRepo.Identifier, githubIdentifier)\n\t\tassert.Equal(t, rm.VCSRepo.IngressSubmodules, true)\n\t\tassert.Equal(t, rm.VCSRepo.OAuthTokenID, oauthTokenTest.ID)\n\t\tassert.Equal(t, rm.VCSRepo.RepositoryHTTPURL, fmt.Sprintf(\"https://github.com/%s\", githubIdentifier))\n\t\tassert.Equal(t, rm.VCSRepo.ServiceProvider, string(ServiceProviderGithub))\n\t\tassert.Regexp(t, fmt.Sprintf(\"^%s/webhooks/vcs/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$\", regexp.QuoteMeta(DefaultConfig().Address)), rm.VCSRepo.WebhookURL)\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\tassert.True(t, rm.Permissions.CanDelete)\n\t\t\tassert.True(t, rm.Permissions.CanResync)\n\t\t\tassert.True(t, rm.Permissions.CanRetry)\n\t\t})\n\n\t\tt.Run(\"listing commits\", func(t *testing.T) {\n\t\t\tcm, errCm := client.RegistryModules.ListCommits(ctx, RegistryModuleID{\n\t\t\t\tOrganization: orgTest.Name,\n\t\t\t\tProvider:     registryModuleProvider,\n\t\t\t\tName:         registryModuleName,\n\t\t\t})\n\n\t\t\tassert.NotEmpty(t, cm)\n\t\t\tassert.NotEmpty(t, cm.Items[0])\n\t\t\tassert.NotEmpty(t, cm.Items[0].ID)\n\t\t\tassert.NotEmpty(t, cm.Items[0].Sha)\n\t\t\tassert.NotEmpty(t, cm.Items[0].Message)\n\t\t\tassert.NotEmpty(t, cm.Items[0].Date)\n\t\t\trequire.NoError(t, errCm)\n\t\t})\n\t})\n\tt.Run(\"when a VCS connection is not present\", func(t *testing.T) {\n\t\tregistryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\t\tdefer registryModuleTestCleanup()\n\n\t\tt.Run(\"listing commits\", func(t *testing.T) {\n\t\t\tcm, errCm := client.RegistryModules.ListCommits(ctx, RegistryModuleID{\n\t\t\t\tOrganization: orgTest.Name,\n\t\t\t\tProvider:     registryModuleTest.Provider,\n\t\t\t\tName:         registryModuleTest.Name,\n\t\t\t})\n\n\t\t\tassert.Empty(t, cm)\n\t\t\trequire.Error(t, errCm)\n\t\t\tassert.Equal(t, ErrResourceNotFound, errCm)\n\t\t})\n\t})\n}\n\nfunc TestRegistryModulesCreateWithVCSConnection(t *testing.T) {\n\tt.Parallel()\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\trepositoryName := strings.Split(githubIdentifier, \"/\")[1]\n\tregistryModuleProvider := strings.SplitN(repositoryName, \"-\", 3)[1]\n\tregistryModuleName := strings.SplitN(repositoryName, \"-\", 3)[2]\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, orgTest)\n\tdefer oauthTokenTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, registryModuleName, rm.Name)\n\t\tassert.Equal(t, registryModuleProvider, rm.Provider)\n\t\tassert.Equal(t, rm.VCSRepo.Branch, \"\")\n\t\tassert.Equal(t, rm.VCSRepo.DisplayIdentifier, githubIdentifier)\n\t\tassert.Equal(t, rm.VCSRepo.Identifier, githubIdentifier)\n\t\tassert.Equal(t, rm.VCSRepo.IngressSubmodules, true)\n\t\tassert.Equal(t, rm.VCSRepo.OAuthTokenID, oauthTokenTest.ID)\n\t\tassert.Equal(t, rm.VCSRepo.RepositoryHTTPURL, fmt.Sprintf(\"https://github.com/%s\", githubIdentifier))\n\t\tassert.Equal(t, rm.VCSRepo.ServiceProvider, string(ServiceProviderGithub))\n\t\tassert.Regexp(t, fmt.Sprintf(\"^%s/webhooks/vcs/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$\", regexp.QuoteMeta(DefaultConfig().Address)), rm.VCSRepo.WebhookURL)\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\tassert.True(t, rm.Permissions.CanDelete)\n\t\t\tassert.True(t, rm.Permissions.CanResync)\n\t\t\tassert.True(t, rm.Permissions.CanRetry)\n\t\t})\n\n\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, orgTest.Name, rm.Organization.Name)\n\t\t})\n\n\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rm.CreatedAt)\n\t\t\tassert.NotEmpty(t, rm.UpdatedAt)\n\t\t})\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\tt.Run(\"without an identifier\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\t\tIdentifier:        String(\"\"),\n\t\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\t},\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrRequiredIdentifier)\n\t\t})\n\n\t\tt.Run(\"without an oauth token ID\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\t\tOAuthTokenID:      String(\"\"),\n\t\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\t},\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrRequiredOauthTokenOrGithubAppInstallationID)\n\t\t})\n\n\t\tt.Run(\"without a display identifier\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\t\tDisplayIdentifier: String(\"\"),\n\t\t\t\t},\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrRequiredDisplayIdentifier)\n\t\t})\n\n\t\tt.Run(\"when tags are enabled and a branch is provided\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\t\tTags:              Bool(true),\n\t\t\t\t\tBranch:            String(\"main\"),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrBranchMustBeEmptyWhenTagsEnabled)\n\t\t})\n\t})\n\n\tt.Run(\"without options\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\tassert.Nil(t, rm)\n\t\tassert.Equal(t, err, ErrRequiredVCSRepo)\n\t})\n}\n\nfunc TestRegistryModulesCreateBranchBasedWithVCSConnection(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\trepositoryName := strings.Split(githubIdentifier, \"/\")[1]\n\tregistryModuleProvider := strings.SplitN(repositoryName, \"-\", 3)[1]\n\tregistryModuleName := strings.SplitN(repositoryName, \"-\", 3)[2]\n\n\tgithubBranch := os.Getenv(\"GITHUB_REGISTRY_MODULE_BRANCH\")\n\tif githubBranch == \"\" {\n\t\tgithubBranch = \"main\"\n\t}\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, orgTest)\n\tdefer oauthTokenTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, registryModuleName, rm.Name)\n\t\tassert.Equal(t, registryModuleProvider, rm.Provider)\n\t\tassert.Equal(t, githubBranch, rm.VCSRepo.Branch)\n\t\tassert.Equal(t, false, rm.VCSRepo.Tags)\n\t})\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t},\n\t\t}\n\t\t_, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.Equal(t, err, ErrInvalidOrg)\n\t})\n}\n\nfunc TestRegistryModulesCreateMonorepoBranchBasedWithVCSConnection(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\trepositoryName := strings.Split(githubIdentifier, \"/\")[1]\n\tregistryModuleProvider := strings.SplitN(repositoryName, \"-\", 3)[1]\n\tregistryModuleName := strings.SplitN(repositoryName, \"-\", 3)[2]\n\n\tgithubBranch := os.Getenv(\"GITHUB_REGISTRY_MODULE_BRANCH\")\n\tif githubBranch == \"\" {\n\t\tgithubBranch = \"main\"\n\t}\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, orgTest)\n\tt.Cleanup(oauthTokenTestCleanup)\n\n\tt.Run(\"with valid options including source directory\", func(t *testing.T) {\n\t\tsourceDirectory := \"src\"\n\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t\tSourceDirectory:   String(sourceDirectory),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, registryModuleName, rm.Name)\n\t\tassert.Equal(t, registryModuleProvider, rm.Provider)\n\t\tassert.Equal(t, githubBranch, rm.VCSRepo.Branch)\n\t\tassert.Equal(t, false, rm.VCSRepo.Tags)\n\t\tassert.Equal(t, sourceDirectory, rm.VCSRepo.SourceDirectory)\n\t})\n}\n\nfunc TestRegistryModulesCreateMonorepoTagBasedWithVCSConnection(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\trepositoryName := strings.Split(githubIdentifier, \"/\")[1]\n\tregistryModuleProvider := strings.SplitN(repositoryName, \"-\", 3)[1]\n\tregistryModuleName := strings.SplitN(repositoryName, \"-\", 3)[2]\n\n\tgithubBranch := os.Getenv(\"GITHUB_REGISTRY_MODULE_BRANCH\")\n\tif githubBranch == \"\" {\n\t\tgithubBranch = \"main\"\n\t}\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, orgTest)\n\tt.Cleanup(oauthTokenTestCleanup)\n\n\tt.Run(\"with monorepo publishing\", func(t *testing.T) {\n\t\tsourceDirectory := \"src\"\n\t\ttagPrefix := \"v\"\n\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t\tSourceDirectory:   String(sourceDirectory),\n\t\t\t\tTagPrefix:         String(tagPrefix),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, registryModuleName, rm.Name)\n\t\tassert.Equal(t, registryModuleProvider, rm.Provider)\n\t\tassert.Equal(t, rm.VCSRepo.Branch, githubBranch)\n\t\tassert.Equal(t, rm.VCSRepo.Identifier, githubIdentifier)\n\t\tassert.Equal(t, rm.VCSRepo.IngressSubmodules, true)\n\t\tassert.Equal(t, rm.VCSRepo.OAuthTokenID, oauthTokenTest.ID)\n\t\tassert.Equal(t, rm.VCSRepo.ServiceProvider, string(ServiceProviderGithub))\n\t\tassert.Regexp(t, fmt.Sprintf(\"^%s/webhooks/vcs/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$\", regexp.QuoteMeta(DefaultConfig().Address)), rm.VCSRepo.WebhookURL)\n\n\t\tif rm.VCSRepo.SourceDirectory != sourceDirectory {\n\t\t\tt.Errorf(\"expected SourceDirectory %q, got %q\", sourceDirectory, rm.VCSRepo.SourceDirectory)\n\t\t}\n\t\tif rm.VCSRepo.TagPrefix != tagPrefix {\n\t\t\tt.Errorf(\"expected TagPrefix %q, got %q\", tagPrefix, rm.VCSRepo.TagPrefix)\n\t\t}\n\t})\n\n\tt.Run(\"without monorepo publishing\", func(t *testing.T) {\n\t\ttagPrefix := \"v\"\n\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t\tTagPrefix:         String(tagPrefix),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, registryModuleName, rm.Name)\n\t\tassert.Equal(t, registryModuleProvider, rm.Provider)\n\t\tassert.Equal(t, rm.VCSRepo.Branch, githubBranch)\n\t\tassert.Equal(t, rm.VCSRepo.Identifier, githubIdentifier)\n\t\tassert.Equal(t, rm.VCSRepo.IngressSubmodules, true)\n\t\tassert.Equal(t, rm.VCSRepo.OAuthTokenID, oauthTokenTest.ID)\n\t\tassert.Equal(t, rm.VCSRepo.ServiceProvider, string(ServiceProviderGithub))\n\t\tassert.Regexp(t, fmt.Sprintf(\"^%s/webhooks/vcs/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$\", regexp.QuoteMeta(DefaultConfig().Address)), rm.VCSRepo.WebhookURL)\n\n\t\tif rm.VCSRepo.SourceDirectory != \"\" {\n\t\t\tt.Errorf(\"expected SourceDirectory %q, got %q\", \"\", rm.VCSRepo.SourceDirectory)\n\t\t}\n\t\tif rm.VCSRepo.TagPrefix != tagPrefix {\n\t\t\tt.Errorf(\"expected TagPrefix %q, got %q\", tagPrefix, rm.VCSRepo.TagPrefix)\n\t\t}\n\t})\n}\n\nfunc TestRegistryModulesCreateMonorepoNonStandardName(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\t// This test uses a repository like \"private-modules\" or \"monorepo\" that doesn't\n\t// follow the terraform-<provider>-<name> pattern, which would previously fail\n\t// with \"Name is invalid\" error.\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\tgithubBranch := os.Getenv(\"GITHUB_REGISTRY_MODULE_BRANCH\")\n\tif githubBranch == \"\" {\n\t\tgithubBranch = \"main\"\n\t}\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, orgTest)\n\tt.Cleanup(oauthTokenTestCleanup)\n\n\tt.Run(\"with explicit name and provider for monorepo with tags\", func(t *testing.T) {\n\t\tsourceDirectory := \"modules/nestedA\"\n\t\tmoduleName := \"nestedA\"\n\t\tmoduleProvider := \"aws\"\n\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tName:     String(moduleName),\n\t\t\tProvider: String(moduleProvider),\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tSourceDirectory:   String(sourceDirectory),\n\t\t\t\tTags:              Bool(true),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, moduleName, rm.Name)\n\t\tassert.Equal(t, moduleProvider, rm.Provider)\n\t\tassert.Equal(t, sourceDirectory, rm.VCSRepo.SourceDirectory)\n\t\tassert.Equal(t, true, rm.VCSRepo.Tags)\n\t})\n\n\tt.Run(\"with explicit name and provider for monorepo with branch\", func(t *testing.T) {\n\t\tsourceDirectory := \"modules/nestedB\"\n\t\tmoduleName := \"nestedB\"\n\t\tmoduleProvider := \"gcp\"\n\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tName:     String(moduleName),\n\t\t\tProvider: String(moduleProvider),\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t\tSourceDirectory:   String(sourceDirectory),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, moduleName, rm.Name)\n\t\tassert.Equal(t, moduleProvider, rm.Provider)\n\t\tassert.Equal(t, sourceDirectory, rm.VCSRepo.SourceDirectory)\n\t\tassert.Equal(t, githubBranch, rm.VCSRepo.Branch)\n\t\tassert.Equal(t, false, rm.VCSRepo.Tags)\n\t})\n\n\tt.Run(\"with explicit name and provider for deeply nested path\", func(t *testing.T) {\n\t\tsourceDirectory := \"terraform/modules/aws/compute\"\n\t\tmoduleName := \"compute\"\n\t\tmoduleProvider := \"aws\"\n\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tName:     String(moduleName),\n\t\t\tProvider: String(moduleProvider),\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t\tSourceDirectory:   String(sourceDirectory),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, moduleName, rm.Name)\n\t\tassert.Equal(t, moduleProvider, rm.Provider)\n\t\tassert.Equal(t, sourceDirectory, rm.VCSRepo.SourceDirectory)\n\t})\n\n\tt.Run(\"with explicit name and provider for various providers\", func(t *testing.T) {\n\t\ttestCases := []struct {\n\t\t\tname            string\n\t\t\tmoduleName      string\n\t\t\tmoduleProvider  string\n\t\t\tsourceDirectory string\n\t\t}{\n\t\t\t{\n\t\t\t\tname:            \"azurerm provider\",\n\t\t\t\tmoduleName:      \"vnet\",\n\t\t\t\tmoduleProvider:  \"azurerm\",\n\t\t\t\tsourceDirectory: \"modules/azure-vnet\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:            \"random provider\",\n\t\t\t\tmoduleName:      \"pet\",\n\t\t\t\tmoduleProvider:  \"random\",\n\t\t\t\tsourceDirectory: \"modules/random-pet\",\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\t\t\tName:     String(tc.moduleName),\n\t\t\t\t\tProvider: String(tc.moduleProvider),\n\t\t\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\t\t\tBranch:            String(githubBranch),\n\t\t\t\t\t\tSourceDirectory:   String(tc.sourceDirectory),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.NotEmpty(t, rm.ID)\n\t\t\t\tassert.Equal(t, tc.moduleName, rm.Name)\n\t\t\t\tassert.Equal(t, tc.moduleProvider, rm.Provider)\n\t\t\t\tassert.Equal(t, tc.sourceDirectory, rm.VCSRepo.SourceDirectory)\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestRegistryModulesCreateBranchBasedWithVCSConnectionWithTesting(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\trepositoryName := strings.Split(githubIdentifier, \"/\")[1]\n\tregistryModuleProvider := strings.SplitN(repositoryName, \"-\", 3)[1]\n\tregistryModuleName := strings.SplitN(repositoryName, \"-\", 3)[2]\n\n\tgithubBranch := os.Getenv(\"GITHUB_REGISTRY_MODULE_BRANCH\")\n\tif githubBranch == \"\" {\n\t\tgithubBranch = \"main\"\n\t}\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, orgTest)\n\tdefer oauthTokenTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t},\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tTestsEnabled: Bool(true),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, registryModuleName, rm.Name)\n\t\tassert.Equal(t, registryModuleProvider, rm.Provider)\n\t\tassert.Equal(t, githubBranch, rm.VCSRepo.Branch)\n\t\tassert.Equal(t, false, rm.VCSRepo.Tags)\n\n\t\tt.Run(\"tests are enabled\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rm.TestConfig)\n\t\t\tassert.True(t, rm.TestConfig.TestsEnabled)\n\t\t})\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t},\n\t\t}\n\t\t_, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.Equal(t, err, ErrInvalidOrg)\n\n\t\tt.Run(\"when the the module is not branch based and test are enabled\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\t},\n\t\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\t\tTestsEnabled: Bool(true),\n\t\t\t\t},\n\t\t\t}\n\t\t\t_, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t\trequire.Equal(t, err, ErrRequiredBranchWhenTestsEnabled)\n\t\t})\n\t})\n}\n\nfunc TestRegistryModulesCreateWithGithubApp(t *testing.T) {\n\tt.Parallel()\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\tgHAInstallationID := os.Getenv(\"GITHUB_APP_INSTALLATION_ID\")\n\tif gHAInstallationID == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_APP_INSTALLATION_ID before running this test!\")\n\t}\n\n\trepositoryName := strings.Split(githubIdentifier, \"/\")[1]\n\tregistryModuleProvider := strings.SplitN(repositoryName, \"-\", 3)[1]\n\tregistryModuleName := strings.SplitN(repositoryName, \"-\", 3)[2]\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tGHAInstallationID: String(gHAInstallationID),\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t},\n\t\t}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.Equal(t, registryModuleName, rm.Name)\n\t\tassert.Equal(t, registryModuleProvider, rm.Provider)\n\t\tassert.Equal(t, rm.VCSRepo.Branch, \"\")\n\t\tassert.Equal(t, rm.VCSRepo.DisplayIdentifier, githubIdentifier)\n\t\tassert.Equal(t, rm.VCSRepo.Identifier, githubIdentifier)\n\t\tassert.Equal(t, rm.VCSRepo.IngressSubmodules, true)\n\t\tassert.Equal(t, rm.VCSRepo.GHAInstallationID, gHAInstallationID)\n\t\tassert.Equal(t, rm.VCSRepo.RepositoryHTTPURL, fmt.Sprintf(\"https://github.com/%s\", githubIdentifier))\n\t\tassert.Equal(t, rm.VCSRepo.ServiceProvider, string(\"github_app\"))\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\tassert.True(t, rm.Permissions.CanDelete)\n\t\t\tassert.True(t, rm.Permissions.CanResync)\n\t\t\tassert.True(t, rm.Permissions.CanRetry)\n\t\t})\n\n\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, orgTest.Name, rm.Organization.Name)\n\t\t})\n\n\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rm.CreatedAt)\n\t\t\tassert.NotEmpty(t, rm.UpdatedAt)\n\t\t})\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\tt.Run(\"without an github app installation ID\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\t},\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrRequiredOauthTokenOrGithubAppInstallationID)\n\t\t})\n\t\tt.Run(\"without an org name\", func(t *testing.T) {\n\t\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\t\tGHAInstallationID: String(gHAInstallationID),\n\t\t\t\t},\n\t\t\t}\n\t\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.Equal(t, err, ErrInvalidOrg)\n\t\t})\n\t})\n\n\tt.Run(\"without options\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{}\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\tassert.Nil(t, rm)\n\t\tassert.Equal(t, err, ErrRequiredVCSRepo)\n\t})\n}\n\nfunc TestRegistryModulesRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\tdefer registryModuleTestCleanup()\n\n\tpublicRegistryModuleTest, publicRegistryModuleTestCleanup := createRegistryModule(t, client, orgTest, PublicRegistry)\n\tdefer publicRegistryModuleTestCleanup()\n\n\tt.Run(\"with valid name and provider\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, registryModuleTest.ID, rm.ID)\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\tassert.True(t, rm.Permissions.CanDelete)\n\t\t\tassert.True(t, rm.Permissions.CanResync)\n\t\t\tassert.True(t, rm.Permissions.CanRetry)\n\t\t})\n\n\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rm.CreatedAt)\n\t\t\tassert.NotEmpty(t, rm.UpdatedAt)\n\t\t})\n\t})\n\n\tt.Run(\"with complete registry module ID fields for private module\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t\tNamespace:    orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, rm)\n\t\tassert.Equal(t, registryModuleTest.ID, rm.ID)\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\trequire.NotEmpty(t, rm.Permissions)\n\t\t\tassert.True(t, rm.Permissions.CanDelete)\n\t\t\tassert.True(t, rm.Permissions.CanResync)\n\t\t\tassert.True(t, rm.Permissions.CanRetry)\n\t\t})\n\n\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rm.CreatedAt)\n\t\t\tassert.NotEmpty(t, rm.UpdatedAt)\n\t\t})\n\t})\n\n\tt.Run(\"with complete registry module ID fields for public module\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         publicRegistryModuleTest.Name,\n\t\t\tProvider:     publicRegistryModuleTest.Provider,\n\t\t\tNamespace:    publicRegistryModuleTest.Namespace,\n\t\t\tRegistryName: PublicRegistry,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, rm)\n\t\tassert.Equal(t, publicRegistryModuleTest.ID, rm.ID)\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\trequire.NotEmpty(t, rm.Permissions)\n\t\t\tassert.True(t, rm.Permissions.CanDelete)\n\t\t\tassert.True(t, rm.Permissions.CanResync)\n\t\t\tassert.True(t, rm.Permissions.CanRetry)\n\t\t})\n\n\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rm.CreatedAt)\n\t\t\tassert.NotEmpty(t, rm.UpdatedAt)\n\t\t})\n\t})\n\n\tt.Run(\"with a unique ID field for private module\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tID: registryModuleTest.ID,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, rm)\n\t\tassert.Equal(t, registryModuleTest.ID, rm.ID)\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\trequire.NotEmpty(t, rm.Permissions)\n\t\t\tassert.True(t, rm.Permissions.CanDelete)\n\t\t\tassert.True(t, rm.Permissions.CanResync)\n\t\t\tassert.True(t, rm.Permissions.CanRetry)\n\t\t})\n\n\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rm.CreatedAt)\n\t\t\tassert.NotEmpty(t, rm.UpdatedAt)\n\t\t})\n\t})\n\n\tt.Run(\"without a name\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         \"\",\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\tassert.Nil(t, rm)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"with an invalid name\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         badIdentifier,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\tassert.Nil(t, rm)\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"without a provider\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     \"\",\n\t\t})\n\t\tassert.Nil(t, rm)\n\t\tassert.Equal(t, err, ErrRequiredProvider)\n\t})\n\n\tt.Run(\"with an invalid provider\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     badIdentifier,\n\t\t})\n\t\tassert.Nil(t, rm)\n\t\tassert.Equal(t, err, ErrInvalidProvider)\n\t})\n\n\tt.Run(\"with an invalid registry name\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t\tNamespace:    orgTest.Name,\n\t\t\tRegistryName: \"PRIVATE\",\n\t\t})\n\t\tassert.Nil(t, rm)\n\t\tassert.Equal(t, err, ErrInvalidRegistryName)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: badIdentifier,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\tassert.Nil(t, rm)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"without a valid namespace for public registry module\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         publicRegistryModuleTest.Name,\n\t\t\tProvider:     publicRegistryModuleTest.Provider,\n\t\t\tRegistryName: PublicRegistry,\n\t\t})\n\t\tassert.Nil(t, rm)\n\t\tassert.EqualError(t, err, ErrRequiredNamespace.Error())\n\t})\n\n\tt.Run(\"when the registry module does not exist\", func(t *testing.T) {\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         \"nonexisting\",\n\t\t\tProvider:     \"nonexisting\",\n\t\t})\n\t\tassert.Nil(t, rm)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestRegistryModulesReadTerraformRegistryModule(t *testing.T) {\n\tt.Parallel()\n\tt.Skip(\"Skipping due to persistent failures - see TF-31172\")\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\tr := require.New(t)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\t// NOTE: These test cases use time.Sleep to wait for the module to be ready,\n\t// an enhancement to these test cases would be to use a polling mechanism to\n\t// check if the module is ready, and then time out if it is not ready after a\n\t// certain amount of time.\n\n\tt.Run(\"fetch module from private registry\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\ttoken, cleanupToken := createOAuthToken(t, client, orgTest)\n\t\tdefer cleanupToken()\n\n\t\trmOpts := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tTags:              Bool(true),\n\t\t\t\tOAuthTokenID:      String(token.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t},\n\t\t}\n\n\t\tversion := \"1.0.0\"\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, rmOpts)\n\t\tr.NoError(err)\n\n\t\ttime.Sleep(time.Second * 10)\n\n\t\trmID := RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         rm.Name,\n\t\t\tProvider:     rm.Provider,\n\t\t\tNamespace:    rm.Namespace,\n\t\t\tRegistryName: rm.RegistryName,\n\t\t}\n\t\ttfm, err := client.RegistryModules.ReadTerraformRegistryModule(ctx, rmID, version)\n\t\tr.NoError(err)\n\t\tr.NotNil(tfm)\n\t\tr.Equal(fmt.Sprintf(\"%s/%s/%s/%s\", orgTest.Name, rm.Name, rm.Provider, version), tfm.ID)\n\t\tr.Equal(rm.Name, tfm.Name)\n\t\tr.Equal(\"A test Terraform module for use in CI pipelines\", tfm.Description)\n\t\tr.Equal(rm.Provider, tfm.Provider)\n\t\tr.Equal(rm.Namespace, tfm.Namespace)\n\t\tr.Equal(version, tfm.Version)\n\t\tr.Equal(\"\", tfm.Tag)\n\t\tr.Equal(0, tfm.Downloads)\n\t\tr.False(tfm.Verified)\n\t\tr.NotNil(tfm.Root)\n\t\tr.Equal(rm.Name, tfm.Root.Name)\n\t\tr.Equal(\"\", tfm.Root.Readme)\n\t\tr.False(tfm.Root.Empty)\n\t\tr.Len(tfm.Root.Inputs, 1)\n\t\tr.Len(tfm.Root.Outputs, 1)\n\t\tr.Len(tfm.Root.ProviderDependencies, 1)\n\t\tr.Len(tfm.Root.Resources, 1)\n\t})\n\n\tt.Run(\"fetch module from public registry\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\ttoken, cleanupToken := createOAuthToken(t, client, orgTest)\n\t\tdefer cleanupToken()\n\n\t\trmOpts := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tTags:              Bool(true),\n\t\t\t\tOAuthTokenID:      String(token.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t},\n\t\t}\n\n\t\tversion := \"1.0.0\"\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, rmOpts)\n\t\tr.NoError(err)\n\n\t\ttime.Sleep(time.Second * 10)\n\n\t\trmID := RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         rm.Name,\n\t\t\tProvider:     rm.Provider,\n\t\t\tNamespace:    rm.Namespace,\n\t\t\tRegistryName: rm.RegistryName,\n\t\t}\n\t\ttfm, err := client.RegistryModules.ReadTerraformRegistryModule(ctx, rmID, version)\n\t\tr.NoError(err)\n\t\tr.NotNil(tfm)\n\t\tr.Equal(fmt.Sprintf(\"%s/%s/%s/%s\", orgTest.Name, rm.Name, rm.Provider, version), tfm.ID)\n\t\tr.Equal(rm.Name, tfm.Name)\n\t\tr.Equal(\"A test Terraform module for use in CI pipelines\", tfm.Description)\n\t\tr.Equal(rm.Provider, tfm.Provider)\n\t\tr.Equal(rm.Namespace, tfm.Namespace)\n\t\tr.Equal(version, tfm.Version)\n\t\tr.Equal(\"\", tfm.Tag)\n\t\tr.Equal(0, tfm.Downloads)\n\t\tr.False(tfm.Verified)\n\t\tr.NotNil(tfm.Root)\n\t\tr.Equal(rm.Name, tfm.Root.Name)\n\t\tr.Equal(\"\", tfm.Root.Readme)\n\t\tr.False(tfm.Root.Empty)\n\t\tr.Len(tfm.Root.Inputs, 1)\n\t\tr.Len(tfm.Root.Outputs, 1)\n\t\tr.Len(tfm.Root.ProviderDependencies, 1)\n\t\tr.Len(tfm.Root.Resources, 1)\n\t})\n}\n\nfunc TestRegistryModulesDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest, _ := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\n\tt.Run(\"with valid name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.Delete(ctx, orgTest.Name, registryModuleTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\trm, err := client.RegistryModules.Read(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\tassert.Nil(t, rm)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"without a name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.Delete(ctx, orgTest.Name, \"\")\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"with an invalid name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.Delete(ctx, orgTest.Name, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\terr := client.RegistryModules.Delete(ctx, badIdentifier, registryModuleTest.Name)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when the registry module does not exist\", func(t *testing.T) {\n\t\terr := client.RegistryModules.Delete(ctx, orgTest.Name, \"nonexisting\")\n\t\tassert.Error(t, err)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n}\n\nfunc TestRegistryModulesDeleteByName(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest, _ := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\n\tassert.NotNil(t, orgTest)\n\n\tt.Run(\"with valid parameters\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteByName(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         registryModuleTest.Name,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the registry module does not exist\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteByName(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         \"\",\n\t\t})\n\t\tassert.Error(t, err)\n\t\tassert.Equal(t, err, ErrRequiredName)\n\t})\n\n\tt.Run(\"with invalid org\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteByName(ctx, RegistryModuleID{\n\t\t\tOrganization: badIdentifier,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         registryModuleTest.Name,\n\t\t})\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"with invalid registry name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteByName(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: badIdentifier,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         registryModuleTest.Name,\n\t\t})\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestRegistryModulesDeleteProvider(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest, _ := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\n\tassert.NotNil(t, orgTest)\n\n\tt.Run(\"with valid parameters\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteProvider(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    registryModuleTest.Organization.Name,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without a provider\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteProvider(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: registryModuleTest.RegistryName,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     \"\",\n\t\t})\n\t\tassert.Equal(t, err, ErrRequiredProvider)\n\t})\n\n\tt.Run(\"with an invalid provider\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteProvider(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: registryModuleTest.RegistryName,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     badIdentifier,\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidProvider)\n\t})\n\n\tt.Run(\"without a name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteProvider(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: registryModuleTest.RegistryName,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         \"\",\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"with an invalid name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteProvider(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: registryModuleTest.RegistryName,\n\t\t\tName:         badIdentifier,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"with invalid org\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteProvider(ctx, RegistryModuleID{\n\t\t\tOrganization: badIdentifier,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    \"terraform-aws-modules\",\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"without registry name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteProvider(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: badIdentifier,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\tassert.Equal(t, ErrInvalidRegistryName, err)\n\t})\n\n\tt.Run(\"with invalid registry name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteProvider(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: badIdentifier,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"with namespace and when registry name is private\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteProvider(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t})\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestRegistryModulesDeleteVersion(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest, registryModuleTestCleanup := createRegistryModuleWithVersion(t, client, orgTest)\n\tdefer registryModuleTestCleanup()\n\n\tassert.NotNil(t, orgTest)\n\n\tt.Run(\"create module version and delete with valid name and provider\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.3\"),\n\t\t}\n\t\tmod, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, mod.Version)\n\n\t\terr = client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, mod.Version)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without registry name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: badIdentifier,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, registryModuleTest.VersionStatuses[0].Version)\n\t\tassert.Equal(t, ErrInvalidRegistryName, err)\n\t})\n\n\tt.Run(\"with invalid registry name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: badIdentifier,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, registryModuleTest.VersionStatuses[0].Version)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"without a name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tName:         \"\",\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, registryModuleTest.VersionStatuses[0].Version)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"with an invalid name\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tName:         badIdentifier,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, registryModuleTest.VersionStatuses[0].Version)\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"without a provider\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     \"\",\n\t\t}, registryModuleTest.VersionStatuses[0].Version)\n\t\tassert.Equal(t, err, ErrRequiredProvider)\n\t})\n\n\tt.Run(\"with an invalid provider\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     badIdentifier,\n\t\t}, registryModuleTest.VersionStatuses[0].Version)\n\t\tassert.Equal(t, err, ErrInvalidProvider)\n\t})\n\n\tt.Run(\"without a version\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, \"\")\n\t\tassert.Equal(t, err, ErrRequiredVersion)\n\t})\n\n\tt.Run(\"with an invalid version\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidVersion)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\terr := client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: badIdentifier,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, registryModuleTest.VersionStatuses[0].Version)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"with prerelease and metadata version\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\tVersion: String(\"1.2.3-alpha+feature\"),\n\t\t}\n\t\tmod, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, mod.Version)\n\n\t\terr = client.RegistryModules.DeleteVersion(ctx, RegistryModuleID{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t\tName:         registryModuleTest.Name,\n\t\t\tNamespace:    registryModuleTest.Namespace,\n\t\t\tProvider:     registryModuleTest.Provider,\n\t\t}, mod.Version)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestRegistryModulesUpload(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trm, _ := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\n\toptionsModuleVersion := RegistryModuleCreateVersionOptions{\n\t\tVersion: String(\"1.0.0\"),\n\t}\n\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rm.Name,\n\t\tProvider:     rm.Provider,\n\t}, optionsModuleVersion)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Run(\"with valid upload URL\", func(t *testing.T) {\n\t\terr = client.RegistryModules.Upload(\n\t\t\tctx,\n\t\t\t*rmv,\n\t\t\t\"test-fixtures/config-version\",\n\t\t)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with missing upload URL\", func(t *testing.T) {\n\t\tdelete(rmv.Links, \"upload\")\n\n\t\terr = client.RegistryModules.Upload(\n\t\t\tctx,\n\t\t\t*rmv,\n\t\t\t\"test-fixtures/config-version\",\n\t\t)\n\t\tassert.EqualError(t, err, \"provided RegistryModuleVersion does not contain an upload link\")\n\t})\n}\n\nfunc TestRegistryModulesUploadTarGzip(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\trm, rmCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\tt.Cleanup(rmCleanup)\n\n\toptionsModuleVersion := RegistryModuleCreateVersionOptions{\n\t\tVersion: String(\"1.0.0\"),\n\t}\n\n\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rm.Name,\n\t\tProvider:     rm.Provider,\n\t}, optionsModuleVersion)\n\trequire.NoError(t, err)\n\n\tuploadURL, ok := rmv.Links[\"upload\"].(string)\n\trequire.True(t, ok)\n\n\tt.Run(\"with custom go-slug\", func(t *testing.T) {\n\t\tpacker, err := slug.NewPacker(\n\t\t\tslug.DereferenceSymlinks(),\n\t\t\tslug.ApplyTerraformIgnore(),\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tbody := bytes.NewBuffer(nil)\n\t\t_, err = packer.Pack(\"test-fixtures/config-version\", body)\n\t\trequire.NoError(t, err)\n\n\t\terr = client.RegistryModules.UploadTarGzip(ctx, uploadURL, body)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with custom tar archive\", func(t *testing.T) {\n\t\tarchivePath := \"test-fixtures/registry-module-archive.tar.gz\"\n\t\tcreateTarGzipArchive(t, []string{\"test-fixtures/config-version/main.tf\"}, archivePath)\n\n\t\tarchive, err := os.Open(archivePath)\n\t\trequire.NoError(t, err)\n\t\tdefer archive.Close()\n\n\t\terr = client.RegistryModules.UploadTarGzip(ctx, uploadURL, archive)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestRegistryModule_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"registry-modules\",\n\t\t\t\"id\":   \"1\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"name\":          \"module\",\n\t\t\t\t\"provider\":      \"tfe\",\n\t\t\t\t\"namespace\":     \"org-abc\",\n\t\t\t\t\"registry-name\": \"private\",\n\t\t\t\t\"permissions\": map[string]interface{}{\n\t\t\t\t\t\"can-delete\": true,\n\t\t\t\t\t\"can-resync\": true,\n\t\t\t\t\t\"can-retry\":  true,\n\t\t\t\t},\n\t\t\t\t\"status\": RegistryModuleStatusPending,\n\t\t\t\t\"vcs-repo\": map[string]interface{}{\n\t\t\t\t\t\"branch\":              \"main\",\n\t\t\t\t\t\"display-identifier\":  \"display\",\n\t\t\t\t\t\"identifier\":          \"identifier\",\n\t\t\t\t\t\"ingress-submodules\":  true,\n\t\t\t\t\t\"oauth-token-id\":      \"token\",\n\t\t\t\t\t\"repository-http-url\": \"github.com\",\n\t\t\t\t\t\"service-provider\":    \"github\",\n\t\t\t\t\t\"webhook-url\":         \"https://app.terraform.io/webhooks/vcs/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n\t\t\t\t},\n\t\t\t\t\"version-statuses\": []interface{}{\n\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\"version\": \"1.1.1\",\n\t\t\t\t\t\t\"status\":  RegistryModuleVersionStatusPending,\n\t\t\t\t\t\t\"error\":   \"no error\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\trm := &RegistryModule{}\n\terr = unmarshalResponse(responseBody, rm)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, rm.ID, \"1\")\n\tassert.Equal(t, rm.Name, \"module\")\n\tassert.Equal(t, rm.Provider, \"tfe\")\n\tassert.Equal(t, rm.Namespace, \"org-abc\")\n\tassert.Equal(t, rm.RegistryName, PrivateRegistry)\n\tassert.Equal(t, rm.Permissions.CanDelete, true)\n\tassert.Equal(t, rm.Permissions.CanRetry, true)\n\tassert.Equal(t, rm.Status, RegistryModuleStatusPending)\n\tassert.Equal(t, rm.VCSRepo.Branch, \"main\")\n\tassert.Equal(t, rm.VCSRepo.DisplayIdentifier, \"display\")\n\tassert.Equal(t, rm.VCSRepo.Identifier, \"identifier\")\n\tassert.Equal(t, rm.VCSRepo.IngressSubmodules, true)\n\tassert.Equal(t, rm.VCSRepo.OAuthTokenID, \"token\")\n\tassert.Equal(t, rm.VCSRepo.RepositoryHTTPURL, \"github.com\")\n\tassert.Equal(t, rm.VCSRepo.ServiceProvider, \"github\")\n\tassert.Equal(t, rm.VCSRepo.WebhookURL, \"https://app.terraform.io/webhooks/vcs/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\")\n\tassert.Equal(t, rm.Status, RegistryModuleStatusPending)\n\tassert.Equal(t, rm.VersionStatuses[0].Version, \"1.1.1\")\n\tassert.Equal(t, rm.VersionStatuses[0].Status, RegistryModuleVersionStatusPending)\n\tassert.Equal(t, rm.VersionStatuses[0].Error, \"no error\")\n}\n\nfunc TestRegistryCreateWithVCSOptions_Marshal(t *testing.T) {\n\tt.Parallel()\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/modules#sample-payload\n\topts := RegistryModuleCreateWithVCSConnectionOptions{\n\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\tIdentifier:        String(\"id\"),\n\t\t\tOAuthTokenID:      String(\"token\"),\n\t\t\tDisplayIdentifier: String(\"display-id\"),\n\t\t},\n\t}\n\n\treqBody, err := serializeRequestBody(&opts)\n\trequire.NoError(t, err)\n\treq, err := retryablehttp.NewRequest(\"POST\", \"url\", reqBody)\n\trequire.NoError(t, err)\n\tbodyBytes, err := req.BodyBytes()\n\trequire.NoError(t, err)\n\n\texpectedBody := `{\"data\":{\"type\":\"registry-modules\",\"attributes\":{\"vcs-repo\":{\"identifier\":\"id\",\"oauth-token-id\":\"token\",\"display-identifier\":\"display-id\"}}}}\n`\n\tassert.Equal(t, expectedBody, string(bodyBytes))\n}\n\nfunc TestRegistryModulesUpdate_AgentExecutionValidation(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\tgithubBranch := os.Getenv(\"GITHUB_REGISTRY_MODULE_BRANCH\")\n\tif githubBranch == \"\" {\n\t\tgithubBranch = \"main\"\n\t}\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tagentPool, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\tdefer agentPoolCleanup()\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, orgTest)\n\tdefer oauthTokenTestCleanup()\n\n\t// Create a VCS-connected registry module with tests enabled for testing updates\n\tcreateOptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\tBranch:            String(githubBranch),\n\t\t},\n\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\tTestsEnabled: Bool(true),\n\t\t},\n\t}\n\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, createOptions)\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, rm.ID)\n\n\tmoduleID := RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rm.Name,\n\t\tProvider:     rm.Provider,\n\t\tNamespace:    rm.Namespace,\n\t\tRegistryName: rm.RegistryName,\n\t}\n\n\t// Cleanup the created module\n\tdefer func() {\n\t\tif err := client.RegistryModules.Delete(ctx, orgTest.Name, rm.Name); err != nil {\n\t\t\tt.Logf(\"Error deleting registry module: %v\", err)\n\t\t}\n\t}()\n\n\tt.Run(\"errors when remote execution mode has agent pool ID\", func(t *testing.T) {\n\t\tupdateOptions := RegistryModuleUpdateOptions{\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tTestsEnabled:       Bool(true),\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeRemote),\n\t\t\t\tAgentPoolID:        String(agentPool.ID),\n\t\t\t},\n\t\t}\n\n\t\t_, err := client.RegistryModules.Update(ctx, moduleID, updateOptions)\n\t\tassert.Error(t, err)\n\t\tassert.Equal(t, ErrAgentPoolNotRequiredForRemoteExecution, err)\n\t})\n\n\tt.Run(\"succeeds when agent execution mode has agent pool ID\", func(t *testing.T) {\n\t\tupdateOptions := RegistryModuleUpdateOptions{\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tTestsEnabled:       Bool(true),\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeAgent),\n\t\t\t\tAgentPoolID:        String(agentPool.ID),\n\t\t\t},\n\t\t}\n\n\t\tupdatedRM, err := client.RegistryModules.Update(ctx, moduleID, updateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, updatedRM)\n\t\tassert.NotNil(t, updatedRM.TestConfig)\n\t\tassert.True(t, updatedRM.TestConfig.TestsEnabled)\n\n\t\t// Verify that AgentExecutionMode and AgentPoolID are returned correctly\n\t\tassert.NotNil(t, updatedRM.TestConfig.AgentExecutionMode)\n\t\tassert.Equal(t, string(AgentExecutionModeAgent), *updatedRM.TestConfig.AgentExecutionMode)\n\t\tassert.NotNil(t, updatedRM.TestConfig.AgentPoolID)\n\t\tassert.Equal(t, agentPool.ID, *updatedRM.TestConfig.AgentPoolID)\n\t})\n\n\tt.Run(\"succeeds when remote execution mode has no agent pool ID\", func(t *testing.T) {\n\t\tupdateOptions := RegistryModuleUpdateOptions{\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tTestsEnabled:       Bool(true),\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeRemote),\n\t\t\t},\n\t\t}\n\n\t\tupdatedRM, err := client.RegistryModules.Update(ctx, moduleID, updateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, updatedRM)\n\t\tassert.NotNil(t, updatedRM.TestConfig)\n\t\tassert.True(t, updatedRM.TestConfig.TestsEnabled)\n\n\t\t// Verify that AgentExecutionMode is returned correctly and AgentPoolID is nil\n\t\tassert.NotNil(t, updatedRM.TestConfig.AgentExecutionMode)\n\t\tassert.Equal(t, string(AgentExecutionModeRemote), *updatedRM.TestConfig.AgentExecutionMode)\n\t\tassert.Nil(t, updatedRM.TestConfig.AgentPoolID)\n\t})\n}\n\nfunc TestRegistryModulesCreateWithVCSConnection_AgentExecutionValidation(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\tgithubBranch := os.Getenv(\"GITHUB_REGISTRY_MODULE_BRANCH\")\n\tif githubBranch == \"\" {\n\t\tgithubBranch = \"main\"\n\t}\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tagentPool, agentPoolCleanup := createAgentPool(t, client, orgTest)\n\tdefer agentPoolCleanup()\n\n\toauthTokenTest, oauthTokenTestCleanup := createOAuthToken(t, client, orgTest)\n\tdefer oauthTokenTestCleanup()\n\n\tt.Run(\"errors when remote execution mode has agent pool ID\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t},\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tTestsEnabled:       Bool(true),\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeRemote),\n\t\t\t\tAgentPoolID:        String(agentPool.ID),\n\t\t\t},\n\t\t}\n\n\t\t_, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\tassert.Error(t, err)\n\t\tassert.Equal(t, ErrAgentPoolNotRequiredForRemoteExecution, err)\n\t})\n\n\tt.Run(\"succeeds when agent execution mode has agent pool ID\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t},\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tTestsEnabled:       Bool(true),\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeAgent),\n\t\t\t\tAgentPoolID:        String(agentPool.ID),\n\t\t\t},\n\t\t}\n\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.NotNil(t, rm.TestConfig)\n\t\tassert.True(t, rm.TestConfig.TestsEnabled)\n\n\t\t// Verify that AgentExecutionMode and AgentPoolID are returned correctly\n\t\tassert.NotNil(t, rm.TestConfig.AgentExecutionMode)\n\t\tassert.Equal(t, string(AgentExecutionModeAgent), *rm.TestConfig.AgentExecutionMode)\n\t\tassert.NotNil(t, rm.TestConfig.AgentPoolID)\n\t\tassert.Equal(t, agentPool.ID, *rm.TestConfig.AgentPoolID)\n\n\t\t// Cleanup the created module\n\t\tdefer func() {\n\t\t\tif err := client.RegistryModules.Delete(ctx, orgTest.Name, rm.Name); err != nil {\n\t\t\t\tt.Logf(\"Error deleting registry module: %v\", err)\n\t\t\t}\n\t\t}()\n\t})\n\n\tt.Run(\"succeeds when remote execution mode has no agent pool ID\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tOrganizationName:  String(orgTest.Name),\n\t\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\t\tOAuthTokenID:      String(oauthTokenTest.ID),\n\t\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t\t\tBranch:            String(githubBranch),\n\t\t\t},\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tTestsEnabled:       Bool(true),\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeRemote),\n\t\t\t},\n\t\t}\n\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rm.ID)\n\t\tassert.NotNil(t, rm.TestConfig)\n\t\tassert.True(t, rm.TestConfig.TestsEnabled)\n\n\t\t// Verify that AgentExecutionMode is returned correctly and AgentPoolID is nil\n\t\tassert.NotNil(t, rm.TestConfig.AgentExecutionMode)\n\t\tassert.Equal(t, string(AgentExecutionModeRemote), *rm.TestConfig.AgentExecutionMode)\n\t\tassert.Nil(t, rm.TestConfig.AgentPoolID)\n\n\t\t// Cleanup the created module\n\t\tdefer func() {\n\t\t\tif err := client.RegistryModules.Delete(ctx, orgTest.Name, rm.Name); err != nil {\n\t\t\t\tt.Logf(\"Error deleting registry module: %v\", err)\n\t\t\t}\n\t\t}()\n\t})\n}\n"
  },
  {
    "path": "registry_module_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestRegistryModules_Update_AgentExecutionValidation(t *testing.T) {\n\tt.Parallel()\n\t// Create a test server for API calls\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// This shouldn't be called for validation errors, but provide a response just in case\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\tdefer testServer.Close()\n\n\t// Create a client pointing to the test server\n\tclient, err := NewClient(&Config{\n\t\tAddress: testServer.URL,\n\t\tToken:   \"fake-token\",\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tctx := context.Background()\n\n\tt.Run(\"errors when remote execution mode has agent pool ID\", func(t *testing.T) {\n\t\tmoduleID := RegistryModuleID{\n\t\t\tOrganization: \"test-org\",\n\t\t\tName:         \"test-module\",\n\t\t\tProvider:     \"aws\",\n\t\t\tNamespace:    \"test-namespace\",\n\t\t\tRegistryName: PrivateRegistry,\n\t\t}\n\n\t\toptions := RegistryModuleUpdateOptions{\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeRemote),\n\t\t\t\tAgentPoolID:        String(\"apool-123\"),\n\t\t\t},\n\t\t}\n\n\t\trm, err := client.RegistryModules.Update(ctx, moduleID, options)\n\t\tassert.Error(t, err)\n\t\tassert.Equal(t, ErrAgentPoolNotRequiredForRemoteExecution, err)\n\t\tassert.Nil(t, rm)\n\t})\n\n\tt.Run(\"succeeds when agent execution mode has agent pool ID\", func(t *testing.T) {\n\t\tmoduleID := RegistryModuleID{\n\t\t\tOrganization: \"test-org\",\n\t\t\tName:         \"test-module\",\n\t\t\tProvider:     \"aws\",\n\t\t\tNamespace:    \"test-namespace\",\n\t\t\tRegistryName: PrivateRegistry,\n\t\t}\n\n\t\toptions := RegistryModuleUpdateOptions{\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeAgent),\n\t\t\t\tAgentPoolID:        String(\"apool-123\"),\n\t\t\t},\n\t\t}\n\n\t\t// This test only validates that the validation logic doesn't fail\n\t\t// The actual API call will fail since we're using a test client,\n\t\t// but that's expected and not what we're testing here\n\t\t_, err := client.RegistryModules.Update(ctx, moduleID, options)\n\t\t// We expect some error (likely network/API related), but NOT our validation error\n\t\tif err != nil {\n\t\t\tassert.NotEqual(t, ErrAgentPoolNotRequiredForRemoteExecution, err)\n\t\t}\n\t})\n\n\tt.Run(\"succeeds when remote execution mode has no agent pool ID\", func(t *testing.T) {\n\t\tmoduleID := RegistryModuleID{\n\t\t\tOrganization: \"test-org\",\n\t\t\tName:         \"test-module\",\n\t\t\tProvider:     \"aws\",\n\t\t\tNamespace:    \"test-namespace\",\n\t\t\tRegistryName: PrivateRegistry,\n\t\t}\n\n\t\toptions := RegistryModuleUpdateOptions{\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeRemote),\n\t\t\t\tAgentPoolID:        nil,\n\t\t\t},\n\t\t}\n\n\t\t// This test only validates that the validation logic doesn't fail\n\t\t_, err := client.RegistryModules.Update(ctx, moduleID, options)\n\t\t// We expect some error (likely network/API related), but NOT our validation error\n\t\tif err != nil {\n\t\t\tassert.NotEqual(t, ErrAgentPoolNotRequiredForRemoteExecution, err)\n\t\t}\n\t})\n}\n\nfunc TestRegistryModules_CreateWithVCSConnection_AgentExecutionValidation(t *testing.T) {\n\tt.Parallel()\n\t// Create a test server for API calls\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// This shouldn't be called for validation errors, but provide a response just in case\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\tdefer testServer.Close()\n\n\t// Create a client pointing to the test server\n\tclient, err := NewClient(&Config{\n\t\tAddress: testServer.URL,\n\t\tToken:   \"fake-token\",\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tctx := context.Background()\n\n\tt.Run(\"errors when remote execution mode has agent pool ID\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tIdentifier:        String(\"test/repo\"),\n\t\t\t\tOAuthTokenID:      String(\"ot-123\"),\n\t\t\t\tDisplayIdentifier: String(\"test/repo\"),\n\t\t\t},\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeRemote),\n\t\t\t\tAgentPoolID:        String(\"apool-123\"),\n\t\t\t},\n\t\t}\n\n\t\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\tassert.Error(t, err)\n\t\tassert.Equal(t, ErrAgentPoolNotRequiredForRemoteExecution, err)\n\t\tassert.Nil(t, rm)\n\t})\n\n\tt.Run(\"succeeds when agent execution mode has agent pool ID\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tIdentifier:        String(\"test/repo\"),\n\t\t\t\tOAuthTokenID:      String(\"ot-123\"),\n\t\t\t\tDisplayIdentifier: String(\"test/repo\"),\n\t\t\t},\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeAgent),\n\t\t\t\tAgentPoolID:        String(\"apool-123\"),\n\t\t\t},\n\t\t}\n\n\t\t// This test only validates that the validation logic doesn't fail\n\t\t_, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t// We expect some error (likely network/API related), but NOT our validation error\n\t\tif err != nil {\n\t\t\tassert.NotEqual(t, ErrAgentPoolNotRequiredForRemoteExecution, err)\n\t\t}\n\t})\n\n\tt.Run(\"succeeds when remote execution mode has no agent pool ID\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tIdentifier:        String(\"test/repo\"),\n\t\t\t\tOAuthTokenID:      String(\"ot-123\"),\n\t\t\t\tDisplayIdentifier: String(\"test/repo\"),\n\t\t\t},\n\t\t\tTestConfig: &RegistryModuleTestConfigOptions{\n\t\t\t\tAgentExecutionMode: AgentExecutionModePtr(AgentExecutionModeRemote),\n\t\t\t\tAgentPoolID:        nil,\n\t\t\t},\n\t\t}\n\n\t\t// This test only validates that the validation logic doesn't fail\n\t\t_, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t// We expect some error (likely network/API related), but NOT our validation error\n\t\tif err != nil {\n\t\t\tassert.NotEqual(t, ErrAgentPoolNotRequiredForRemoteExecution, err)\n\t\t}\n\t})\n\n\tt.Run(\"succeeds when TestConfig is nil\", func(t *testing.T) {\n\t\toptions := RegistryModuleCreateWithVCSConnectionOptions{\n\t\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\t\tIdentifier:        String(\"test/repo\"),\n\t\t\t\tOAuthTokenID:      String(\"ot-123\"),\n\t\t\t\tDisplayIdentifier: String(\"test/repo\"),\n\t\t\t},\n\t\t\tTestConfig: nil,\n\t\t}\n\n\t\t// This test only validates that the validation logic doesn't fail\n\t\t_, err := client.RegistryModules.CreateWithVCSConnection(ctx, options)\n\t\t// We expect some error (likely network/API related), but NOT our validation error\n\t\tif err != nil {\n\t\t\tassert.NotEqual(t, ErrAgentPoolNotRequiredForRemoteExecution, err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "registry_no_code_module.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ RegistryNoCodeModules = (*registryNoCodeModules)(nil)\n\n// RegistryNoCodeModules describes all the registry no-code module related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: (TODO: Add link to API docs)\ntype RegistryNoCodeModules interface {\n\n\t// Create a registry no-code module\n\t// **Note: This API is still in BETA and subject to change.**\n\tCreate(ctx context.Context, organization string, options RegistryNoCodeModuleCreateOptions) (*RegistryNoCodeModule, error)\n\n\t// Read a registry no-code  module\n\t// **Note: This API is still in BETA and subject to change.**\n\tRead(ctx context.Context, noCodeModuleID string, options *RegistryNoCodeModuleReadOptions) (*RegistryNoCodeModule, error)\n\n\t// ReadVariables returns the variables for a version of a no-code module\n\t// **Note: This API is still in BETA and subject to change.**\n\tReadVariables(ctx context.Context, noCodeModuleID, noCodeModuleVersion string, options *RegistryNoCodeModuleReadVariablesOptions) (*RegistryModuleVariableList, error)\n\n\t// Update a registry no-code module\n\t// **Note: This API is still in BETA and subject to change.**\n\tUpdate(ctx context.Context, noCodeModuleID string, options RegistryNoCodeModuleUpdateOptions) (*RegistryNoCodeModule, error)\n\n\t// Delete a registry no-code module\n\t// **Note: This API is still in BETA and subject to change.**\n\tDelete(ctx context.Context, ID string) error\n\n\t// CreateWorkspace creates a workspace using a no-code module.\n\tCreateWorkspace(ctx context.Context, noCodeModuleID string, options *RegistryNoCodeModuleCreateWorkspaceOptions) (*Workspace, error)\n\n\t// UpgradeWorkspace initiates an upgrade of an existing no-code module workspace.\n\tUpgradeWorkspace(ctx context.Context, noCodeModuleID string, workspaceID string, options *RegistryNoCodeModuleUpgradeWorkspaceOptions) (*WorkspaceUpgrade, error)\n}\n\n// RegistryModuleVariableList is a list of registry module variables.\n// **Note: This API is still in BETA and subject to change.**\ntype RegistryModuleVariableList struct {\n\tItems []*RegistryModuleVariable\n\n\t// NOTE: At the time of authoring this comment, the API endpoint to fetch\n\t// registry module variables does not support pagination. This field is\n\t// included to satisfy jsonapi unmarshaler implementation here:\n\t// https://github.com/hashicorp/go-tfe/blob/3d29602707fa4b10469d1a02685644bd159d3ccc/tfe.go#L859\n\t*Pagination\n}\n\n// RegistryModuleVariable represents a registry module variable.\ntype RegistryModuleVariable struct {\n\t// ID is the ID of the variable.\n\tID string `jsonapi:\"primary,registry-module-variables\"`\n\n\t// Name is the name of the variable.\n\tName string `jsonapi:\"attr,name\"`\n\n\t// VariableType is the type of the variable.\n\tVariableType string `jsonapi:\"attr,type\"`\n\n\t// Description is the description of the variable.\n\tDescription string `jsonapi:\"attr,description\"`\n\n\t// Required is a boolean indicating if the variable is required.\n\tRequired bool `jsonapi:\"attr,required\"`\n\n\t// Sensitive is a boolean indicating if the variable is sensitive.\n\tSensitive bool `jsonapi:\"attr,sensitive\"`\n\n\t// Options is a slice of strings representing the options for the variable.\n\tOptions []string `jsonapi:\"attr,options\"`\n\n\t// HasGlobal is a boolean indicating if the variable is global.\n\tHasGlobal bool `jsonapi:\"attr,has-global\"`\n}\n\ntype RegistryNoCodeModuleCreateWorkspaceOptions struct {\n\tType string `jsonapi:\"primary,no-code-module-workspace\"`\n\n\t// Name is the name of the workspace, which can only include letters,\n\t// numbers, and _. This will be used as an identifier and must be unique in\n\t// the organization.\n\tName string `jsonapi:\"attr,name\"`\n\n\t// Description is a description for the workspace.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\tAutoApply *bool `jsonapi:\"attr,auto-apply,omitempty\"`\n\n\t// Project is the associated project with the workspace. If not provided,\n\t// default project of the organization will be assigned to the workspace.\n\tProject *Project `jsonapi:\"relation,project,omitempty\"`\n\n\t// Variables is the slice of variables to be configured for the no-code\n\t// workspace.\n\tVariables []*Variable `jsonapi:\"relation,vars,omitempty\"`\n\n\t// SourceName is the name of the source of the workspace.\n\tSourceName *string `jsonapi:\"attr,source-name,omitempty\"`\n\n\t// SourceUrl is the URL of the source of the workspace.\n\tSourceURL *string `jsonapi:\"attr,source-url,omitempty\"`\n\n\t// ExecutionMode is the execution mode of the workspace.\n\tExecutionMode *string `jsonapi:\"attr,execution-mode,omitempty\"`\n\n\t// AgentPoolId is the ID of the agent pool to use for the workspace.\n\t// This is required when execution mode is set to \"agent\".\n\t// This must not be specified when execution mode is set to \"remote\".\n\tAgentPoolID *string `jsonapi:\"attr,agent-pool-id,omitempty\"`\n}\n\ntype RegistryNoCodeModuleUpgradeWorkspaceOptions struct {\n\tType string `jsonapi:\"primary,no-code-module-workspace\"`\n\n\t// Variables is the slice of variables to be configured for the no-code\n\t// workspace.\n\tVariables []*Variable `jsonapi:\"relation,vars,omitempty\"`\n}\n\n// registryNoCodeModules implements RegistryNoCodeModules.\ntype registryNoCodeModules struct {\n\tclient *Client\n}\n\n// RegistryNoCodeModule represents a registry no-code module\ntype RegistryNoCodeModule struct {\n\tID         string `jsonapi:\"primary,no-code-modules\"`\n\tVersionPin string `jsonapi:\"attr,version-pin\"`\n\tEnabled    bool   `jsonapi:\"attr,enabled\"`\n\n\t// Relations\n\tOrganization    *Organization           `jsonapi:\"relation,organization\"`\n\tRegistryModule  *RegistryModule         `jsonapi:\"relation,registry-module\"`\n\tVariableOptions []*NoCodeVariableOption `jsonapi:\"relation,variable-options\"`\n}\n\n// NoCodeVariableOption represents a registry no-code module variable and its\n// options.\ntype NoCodeVariableOption struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\tType string `jsonapi:\"primary,variable-options\"`\n\n\t// Required: The variable name\n\tVariableName string `jsonapi:\"attr,variable-name\"`\n\n\t// Required: The variable type\n\tVariableType string `jsonapi:\"attr,variable-type\"`\n\n\t// Optional: The options for the variable\n\tOptions []string `jsonapi:\"attr,options\"`\n}\n\n// RegistryNoCodeModuleCreateOptions is used when creating a registry no-code module\ntype RegistryNoCodeModuleCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,no-code-modules\"`\n\n\t// Required: the registry module to use for the no-code module (only the ID is used)\n\tRegistryModule *RegistryModule `jsonapi:\"relation,registry-module\"`\n\n\t// Optional: whether no-code is enabled for the module\n\tEnabled *bool `jsonapi:\"attr,enabled,omitempty\"`\n\n\t// Optional: the version pin for the module. valid values are \"latest\" or a semver string\n\tVersionPin string `jsonapi:\"attr,version-pin,omitempty\"`\n\n\t// Optional: the variable options for the registry module\n\tVariableOptions []*NoCodeVariableOption `jsonapi:\"relation,variable-options,omitempty\"`\n}\n\n// RegistryNoCodeModuleIncludeOpt represents the available options for include query params.\ntype RegistryNoCodeModuleIncludeOpt string\n\nvar (\n\t// RegistryNoCodeIncludeVariableOptions is used to include variable options in the response\n\tRegistryNoCodeIncludeVariableOptions RegistryNoCodeModuleIncludeOpt = \"variable-options\"\n)\n\n// RegistryNoCodeModuleReadOptions is used when reading a registry no-code module\ntype RegistryNoCodeModuleReadOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-updating\n\tType string `jsonapi:\"primary,no-code-modules\"`\n\n\t// Optional: Include is used to specify the related resources to include in the response.\n\tInclude []RegistryNoCodeModuleIncludeOpt `url:\"include,omitempty\"`\n}\n\n// RegistryNoCodeModuleReadVariablesOptions is used when reading the variables\n// for a no-code module.\ntype RegistryNoCodeModuleReadVariablesOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-updating\n\tType string `jsonapi:\"primary,no-code-modules\"`\n}\n\n// RegistryNoCodeModuleUpdateOptions is used when updating a registry no-code module\ntype RegistryNoCodeModuleUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-updating\n\tType string `jsonapi:\"primary,no-code-modules\"`\n\n\t// Required: the registry module to use for the no-code module (only the ID is used)\n\tRegistryModule *RegistryModule `jsonapi:\"relation,registry-module\"`\n\n\t// Optional: the version pin for the module. valid values are \"latest\" or a semver string\n\tVersionPin string `jsonapi:\"attr,version-pin,omitempty\"`\n\n\t// Optional: whether no-code is enabled for the module\n\tEnabled *bool `jsonapi:\"attr,enabled,omitempty\"`\n\n\t// Optional: are the variable options for the module\n\tVariableOptions []*NoCodeVariableOption `jsonapi:\"relation,variable-options,omitempty\"`\n}\n\n// WorkspaceUpgrade contains the data returned by the no-code workspace upgrade\n// API endpoint.\ntype WorkspaceUpgrade struct {\n\t// Status is the status of the run of the upgrade\n\tStatus string `jsonapi:\"attr,status\"`\n\n\t// PlanURL is the URL to the plan of the upgrade\n\tPlanURL string `jsonapi:\"attr,plan-url\"`\n\n\t// Message is the message returned by the API when an upgrade is not available.\n\tMessage string `jsonapi:\"attr,message\"`\n}\n\n// Create a new registry no-code module\nfunc (r *registryNoCodeModules) Create(ctx context.Context, organization string, options RegistryNoCodeModuleCreateOptions) (*RegistryNoCodeModule, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/no-code-modules\", url.PathEscape(organization))\n\treq, err := r.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trm := &RegistryNoCodeModule{}\n\terr = req.Do(ctx, rm)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rm, nil\n}\n\n// Read a registry no-code module\nfunc (r *registryNoCodeModules) Read(ctx context.Context, noCodeModuleID string, options *RegistryNoCodeModuleReadOptions) (*RegistryNoCodeModule, error) {\n\tif !validStringID(&noCodeModuleID) {\n\t\treturn nil, ErrInvalidModuleID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"no-code-modules/%s\", url.PathEscape(noCodeModuleID))\n\treq, err := r.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trm := &RegistryNoCodeModule{}\n\terr = req.Do(ctx, rm)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rm, nil\n}\n\n// ReadVariables retrieves the no-code variable options for a version of a\n// module.\nfunc (r *registryNoCodeModules) ReadVariables(\n\tctx context.Context,\n\tnoCodeModuleID, noCodeModuleVersion string,\n\toptions *RegistryNoCodeModuleReadVariablesOptions,\n) (*RegistryModuleVariableList, error) {\n\tif !validStringID(&noCodeModuleID) {\n\t\treturn nil, ErrInvalidModuleID\n\t}\n\tif !validVersion(noCodeModuleVersion) {\n\t\treturn nil, ErrInvalidVersion\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"no-code-modules/%s/versions/%s/module-variables\",\n\t\turl.PathEscape(noCodeModuleID),\n\t\turl.PathEscape(noCodeModuleVersion),\n\t)\n\treq, err := r.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp := &RegistryModuleVariableList{}\n\terr = req.Do(ctx, resp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn resp, nil\n}\n\n// Update a registry no-code module\nfunc (r *registryNoCodeModules) Update(ctx context.Context, noCodeModuleID string, options RegistryNoCodeModuleUpdateOptions) (*RegistryNoCodeModule, error) {\n\tif !validString(&noCodeModuleID) {\n\t\treturn nil, ErrInvalidModuleID\n\t}\n\tif !validStringID(&noCodeModuleID) {\n\t\treturn nil, ErrInvalidModuleID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"no-code-modules/%s\", url.PathEscape(noCodeModuleID))\n\treq, err := r.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trm := &RegistryNoCodeModule{}\n\terr = req.Do(ctx, rm)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rm, nil\n}\n\n// Delete is used to delete the registry no-code module\nfunc (r *registryNoCodeModules) Delete(ctx context.Context, noCodeModuleID string) error {\n\tif !validStringID(&noCodeModuleID) {\n\t\treturn ErrInvalidModuleID\n\t}\n\n\tu := fmt.Sprintf(\"no-code-modules/%s\", url.PathEscape(noCodeModuleID))\n\treq, err := r.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// CreateWorkspace creates a no-code workspace using a no-code module.\nfunc (r *registryNoCodeModules) CreateWorkspace(\n\tctx context.Context,\n\tnoCodeModuleID string,\n\toptions *RegistryNoCodeModuleCreateWorkspaceOptions,\n) (*Workspace, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"no-code-modules/%s/workspaces\", url.PathEscape(noCodeModuleID))\n\treq, err := r.client.NewRequest(\"POST\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// UpgradeWorkspace initiates an upgrade of an existing no-code module workspace.\nfunc (r *registryNoCodeModules) UpgradeWorkspace(\n\tctx context.Context,\n\tnoCodeModuleID string,\n\tworkspaceID string,\n\toptions *RegistryNoCodeModuleUpgradeWorkspaceOptions,\n) (*WorkspaceUpgrade, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"no-code-modules/%s/workspaces/%s/upgrade\",\n\t\turl.PathEscape(noCodeModuleID),\n\t\tworkspaceID,\n\t)\n\treq, err := r.client.NewRequest(\"POST\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twu := &WorkspaceUpgrade{}\n\terr = req.Do(ctx, wu)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn wu, nil\n}\n\nfunc (o RegistryNoCodeModuleCreateOptions) valid() error {\n\tif o.RegistryModule == nil || o.RegistryModule.ID == \"\" {\n\t\treturn ErrRequiredRegistryModule\n\t}\n\n\treturn nil\n}\n\nfunc (o *RegistryNoCodeModuleUpdateOptions) valid() error {\n\tif o == nil {\n\t\treturn nil // nothing to validate\n\t}\n\n\tif o.RegistryModule == nil || o.RegistryModule.ID == \"\" {\n\t\treturn ErrRequiredRegistryModule\n\t}\n\n\treturn nil\n}\n\nfunc (o *RegistryNoCodeModuleReadOptions) valid() error {\n\treturn nil\n}\n\nfunc (o *RegistryNoCodeModuleCreateWorkspaceOptions) valid() error {\n\tif !validString(&o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\n\treturn nil\n}\n\nfunc (o *RegistryNoCodeModuleUpgradeWorkspaceOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "registry_no_code_module_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRegistryNoCodeModulesCreate(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tt.Run(\"with no version given\", func(t *testing.T) {\n\t\t\tregistryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\t\t\tdefer registryModuleTestCleanup()\n\n\t\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\t\tVersion: String(\"1.2.3\"),\n\t\t\t}\n\t\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\t\tOrganization: orgTest.Name,\n\t\t\t\tName:         registryModuleTest.Name,\n\t\t\t\tProvider:     registryModuleTest.Provider,\n\t\t\t}, options)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotEmpty(t, rmv.Version)\n\n\t\t\tncOptions := RegistryNoCodeModuleCreateOptions{\n\t\t\t\tRegistryModule: registryModuleTest,\n\t\t\t}\n\n\t\t\tnoCodeModule, err := client.RegistryNoCodeModules.Create(ctx, orgTest.Name, ncOptions)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.NotEmpty(t, noCodeModule.ID)\n\t\t\trequire.NotEmpty(t, noCodeModule.Organization)\n\t\t\tassert.True(t, noCodeModule.Enabled)\n\t\t\trequire.NotEmpty(t, noCodeModule.RegistryModule)\n\t\t\tassert.Equal(t, orgTest.Name, noCodeModule.Organization.Name)\n\t\t\tassert.Equal(t, registryModuleTest.ID, noCodeModule.RegistryModule.ID)\n\t\t})\n\t\tt.Run(\"with version pin given\", func(t *testing.T) {\n\t\t\tregistryModuleTest, _ := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\n\t\t\toptions := RegistryModuleCreateVersionOptions{\n\t\t\t\tVersion: String(\"1.2.3\"),\n\t\t\t}\n\t\t\trmv, err := client.RegistryModules.CreateVersion(ctx, RegistryModuleID{\n\t\t\t\tOrganization: orgTest.Name,\n\t\t\t\tName:         registryModuleTest.Name,\n\t\t\t\tProvider:     registryModuleTest.Provider,\n\t\t\t}, options)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotEmpty(t, rmv.Version)\n\n\t\t\tncOptions := RegistryNoCodeModuleCreateOptions{\n\t\t\t\tVersionPin:     \"1.2.3\",\n\t\t\t\tRegistryModule: registryModuleTest,\n\t\t\t}\n\n\t\t\tnoCodeModule, err := client.RegistryNoCodeModules.Create(ctx, orgTest.Name, ncOptions)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.NotEmpty(t, noCodeModule.ID)\n\t\t\trequire.NotEmpty(t, noCodeModule.Organization)\n\t\t\trequire.NotEmpty(t, noCodeModule.RegistryModule)\n\t\t\tassert.True(t, noCodeModule.Enabled)\n\t\t\tassert.Equal(t, ncOptions.VersionPin, noCodeModule.VersionPin)\n\t\t\tassert.Equal(t, orgTest.Name, noCodeModule.Organization.Name)\n\t\t\tassert.Equal(t, registryModuleTest.ID, noCodeModule.RegistryModule.ID)\n\t\t})\n\t\tt.Run(\"with enabled set to false\", func(t *testing.T) {\n\t\t\tregistryModuleTest, _ := createRegistryModuleWithVersion(t, client, orgTest)\n\n\t\t\tncOptions := RegistryNoCodeModuleCreateOptions{\n\t\t\t\tRegistryModule: registryModuleTest,\n\t\t\t\tEnabled:        Bool(false),\n\t\t\t}\n\n\t\t\tnoCodeModule, err := client.RegistryNoCodeModules.Create(ctx, orgTest.Name, ncOptions)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.NotEmpty(t, noCodeModule.ID)\n\t\t\trequire.NotEmpty(t, noCodeModule.Organization)\n\t\t\trequire.NotEmpty(t, noCodeModule.RegistryModule)\n\t\t\tassert.False(t, noCodeModule.Enabled)\n\t\t\tassert.Equal(t, ncOptions.VersionPin, noCodeModule.VersionPin)\n\t\t\tassert.Equal(t, orgTest.Name, noCodeModule.Organization.Name)\n\t\t\tassert.Equal(t, registryModuleTest.ID, noCodeModule.RegistryModule.ID)\n\t\t})\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\tregistryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\t\tdefer registryModuleTestCleanup()\n\n\t\tt.Run(\"with version pinned to one that does not exist\", func(t *testing.T) {\n\t\t\toptions := RegistryNoCodeModuleCreateOptions{\n\t\t\t\tVersionPin:     \"1.2.5\",\n\t\t\t\tRegistryModule: registryModuleTest,\n\t\t\t}\n\n\t\t\tnoCodeModule, err := client.RegistryNoCodeModules.Create(ctx, orgTest.Name, options)\n\t\t\trequire.Error(t, err)\n\t\t\trequire.Nil(t, noCodeModule)\n\t\t})\n\t})\n\n\tt.Run(\"with variable options\", func(t *testing.T) {\n\t\tregistryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\t\tdefer registryModuleTestCleanup()\n\n\t\toptions := RegistryNoCodeModuleCreateOptions{\n\t\t\tRegistryModule: registryModuleTest,\n\t\t\tVariableOptions: []*NoCodeVariableOption{\n\t\t\t\t{\n\t\t\t\t\tVariableName: \"var1\",\n\t\t\t\t\tVariableType: \"string\",\n\t\t\t\t\tOptions:      []string{\"option1\", \"option2\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tVariableName: \"my_var\",\n\t\t\t\t\tVariableType: \"string\",\n\t\t\t\t\tOptions:      []string{\"my_option1\", \"my_option2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tnoCodeModule, err := client.RegistryNoCodeModules.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, noCodeModule.ID)\n\t\trequire.NotEmpty(t, noCodeModule.Organization)\n\t\trequire.NotEmpty(t, noCodeModule.RegistryModule)\n\t\trequire.True(t, noCodeModule.Enabled)\n\t\tassert.Equal(t, orgTest.Name, noCodeModule.Organization.Name)\n\t\tassert.Equal(t, registryModuleTest.ID, noCodeModule.RegistryModule.ID)\n\t\tassert.Equal(t, len(options.VariableOptions), len(noCodeModule.VariableOptions))\n\t})\n}\n\nfunc TestRegistryNoCodeModulesRead(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\tdefer registryModuleTestCleanup()\n\n\tt.Run(\"with valid ID\", func(t *testing.T) {\n\t\tnoCodeModule, noCodeModuleCleanup := createNoCodeRegistryModule(t, client, orgTest.Name, registryModuleTest, nil)\n\t\tdefer noCodeModuleCleanup()\n\n\t\tncm, err := client.RegistryNoCodeModules.Read(ctx, noCodeModule.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, noCodeModule.ID, ncm.ID)\n\t\tassert.True(t, noCodeModule.Enabled)\n\t\tassert.Equal(t, noCodeModule.Organization.Name, ncm.Organization.Name)\n\t\tassert.Equal(t, noCodeModule.RegistryModule.ID, ncm.RegistryModule.ID)\n\t})\n\n\tt.Run(\"when the variable-options is included in the params\", func(t *testing.T) {\n\t\tvarOpts := []*NoCodeVariableOption{\n\t\t\t{\n\t\t\t\tVariableName: \"var1\",\n\t\t\t\tVariableType: \"string\",\n\t\t\t\tOptions:      []string{\"option1\", \"option2\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tVariableName: \"my_var\",\n\t\t\t\tVariableType: \"string\",\n\t\t\t\tOptions:      []string{\"my_option1\", \"my_option2\"},\n\t\t\t},\n\t\t}\n\t\tnoCodeModule, noCodeModuleCleanup := createNoCodeRegistryModule(t, client, orgTest.Name, registryModuleTest, varOpts)\n\t\tdefer noCodeModuleCleanup()\n\n\t\tncm, err := client.RegistryNoCodeModules.Read(ctx, noCodeModule.ID, &RegistryNoCodeModuleReadOptions{\n\t\t\tInclude: []RegistryNoCodeModuleIncludeOpt{RegistryNoCodeIncludeVariableOptions},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, noCodeModule.ID, ncm.ID)\n\t\tassert.True(t, noCodeModule.Enabled)\n\t\tassert.Equal(t, noCodeModule.Organization.Name, ncm.Organization.Name)\n\t\tassert.Equal(t, noCodeModule.RegistryModule.ID, ncm.RegistryModule.ID)\n\n\t\tassert.Equal(t, len(varOpts), len(ncm.VariableOptions))\n\t\tfor i, opt := range varOpts {\n\t\t\tassert.Equal(t, opt.VariableName, ncm.VariableOptions[i].VariableName)\n\t\t\tassert.Equal(t, opt.VariableType, ncm.VariableOptions[i].VariableType)\n\t\t\tassert.Equal(t, opt.Options, ncm.VariableOptions[i].Options)\n\t\t}\n\t})\n\n\tt.Run(\"when the id does not exist\", func(t *testing.T) {\n\t\tncm, err := client.RegistryNoCodeModules.Read(ctx, \"non-existing\", nil)\n\t\tassert.Nil(t, ncm)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n}\n\n// TestRegistryNoCodeModuleReadVariables tests the ReadVariables method of the\n// RegistryNoCodeModules service.\n//\n// This test requires that the environment variable \"GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER\" is set\n// with the ID of an existing no-code module that has variables.\nfunc TestRegistryNoCodeModulesReadVariables(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\tr := require.New(t)\n\n\tncmID := os.Getenv(\"GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER\")\n\tif ncmID == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\tncm, err := client.RegistryNoCodeModules.Read(ctx, ncmID, nil)\n\tr.NoError(err)\n\tr.NotNil(ncm)\n\n\tt.Run(\"happy path\", func(t *testing.T) {\n\t\tvars, err := client.RegistryNoCodeModules.ReadVariables(ctx, ncm.ID, ncm.VersionPin, &RegistryNoCodeModuleReadVariablesOptions{})\n\t\tr.NoError(err)\n\t\tr.NotNil(vars)\n\t\tr.NotEmpty(vars)\n\t})\n}\n\nfunc TestRegistryNoCodeModulesUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\tdefer registryModuleTestCleanup()\n\n\tt.Run(\"update no-code registry module\", func(t *testing.T) {\n\t\tnoCodeModule, noCodeModuleCleanup := createNoCodeRegistryModule(t, client, orgTest.Name, registryModuleTest, nil)\n\t\tdefer noCodeModuleCleanup()\n\n\t\tassert.True(t, noCodeModule.Enabled)\n\n\t\toptions := RegistryNoCodeModuleUpdateOptions{\n\t\t\tRegistryModule: &RegistryModule{ID: registryModuleTest.ID},\n\t\t\tEnabled:        Bool(false),\n\t\t}\n\t\tupdated, err := client.RegistryNoCodeModules.Update(ctx, noCodeModule.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, updated.Enabled)\n\t})\n\tt.Run(\"no changes when no options are set\", func(t *testing.T) {\n\t\tnoCodeModule, noCodeModuleCleanup := createNoCodeRegistryModule(t, client, orgTest.Name, registryModuleTest, nil)\n\t\tdefer noCodeModuleCleanup()\n\n\t\toptions := RegistryNoCodeModuleUpdateOptions{\n\t\t\tRegistryModule: &RegistryModule{ID: registryModuleTest.ID},\n\t\t}\n\t\tupdated, err := client.RegistryNoCodeModules.Update(ctx, noCodeModule.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, *noCodeModule, *updated)\n\t})\n}\n\nfunc TestRegistryNoCodeModulesDelete(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tregistryModuleTest, registryModuleTestCleanup := createRegistryModule(t, client, orgTest, PrivateRegistry)\n\tdefer registryModuleTestCleanup()\n\n\tt.Run(\"with valid ID\", func(t *testing.T) {\n\t\tnoCodeModule, _ := createNoCodeRegistryModule(t, client, orgTest.Name, registryModuleTest, nil)\n\n\t\terr := client.RegistryNoCodeModules.Delete(ctx, noCodeModule.ID)\n\t\trequire.NoError(t, err)\n\n\t\trm, err := client.RegistryNoCodeModules.Read(ctx, noCodeModule.ID, nil)\n\t\tassert.Nil(t, rm)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"without an ID\", func(t *testing.T) {\n\t\terr := client.RegistryNoCodeModules.Delete(ctx, \"\")\n\t\tassert.EqualError(t, err, ErrInvalidModuleID.Error())\n\t})\n\n\tt.Run(\"with an invalid ID\", func(t *testing.T) {\n\t\terr := client.RegistryNoCodeModules.Delete(ctx, \"invalid\")\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n\nfunc createNoCodeRegistryModule(t *testing.T, client *Client, orgName string, rm *RegistryModule, variables []*NoCodeVariableOption) (*RegistryNoCodeModule, func()) {\n\toptions := RegistryNoCodeModuleCreateOptions{\n\t\tRegistryModule:  rm,\n\t\tVariableOptions: variables,\n\t}\n\n\tctx := context.Background()\n\n\tncm, err := client.RegistryNoCodeModules.Create(ctx, orgName, options)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, ncm)\n\treturn ncm, func() {\n\t\tif err := client.RegistryNoCodeModules.Delete(ctx, ncm.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying no-code registry module! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"NoCode Module: %s\\nError: %s\", ncm.ID, err)\n\t\t}\n\t}\n}\n\nfunc TestRegistryNoCodeModulesCreateWorkspace(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\tr := require.New(t)\n\n\t// create an org that will be deleted later. the wskp will live here\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\torg, err := client.Organizations.Read(ctx, orgTest.Name)\n\tr.NoError(err)\n\tr.NotNil(org)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\ttoken, cleanupToken := createOAuthToken(t, client, org)\n\tdefer cleanupToken()\n\n\trmOpts := RegistryModuleCreateWithVCSConnectionOptions{\n\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\tOrganizationName:  String(org.Name),\n\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\tTags:              Bool(true),\n\t\t\tOAuthTokenID:      String(token.ID),\n\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t},\n\t}\n\n\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, rmOpts)\n\tr.NoError(err)\n\n\t// 1. create the registry module\n\t// 2. create the no-code module, with the registry module\n\t// 3. use the ID to create the workspace\n\tncm, err := client.RegistryNoCodeModules.Create(ctx, org.Name, RegistryNoCodeModuleCreateOptions{\n\t\tRegistryModule:  rm,\n\t\tEnabled:         Bool(true),\n\t\tVariableOptions: nil,\n\t})\n\tr.NoError(err)\n\tr.NotNil(ncm)\n\n\t// We sleep for 10 seconds to let the module finish getting ready\n\ttime.Sleep(time.Second * 10)\n\n\tt.Run(\"test creating a workspace via a no-code module\", func(t *testing.T) {\n\t\twn := fmt.Sprintf(\"foo-%s\", randomString(t))\n\t\tsn := \"my-app\"\n\t\tsu := \"http://my-app.com\"\n\t\tw, err := client.RegistryNoCodeModules.CreateWorkspace(\n\t\t\tctx,\n\t\t\tncm.ID,\n\t\t\t&RegistryNoCodeModuleCreateWorkspaceOptions{\n\t\t\t\tName:          wn,\n\t\t\t\tSourceName:    String(sn),\n\t\t\t\tSourceURL:     String(su),\n\t\t\t\tExecutionMode: String(\"remote\"),\n\t\t\t},\n\t\t)\n\t\tr.NoError(err)\n\t\tr.Equal(wn, w.Name)\n\t\tr.Equal(sn, w.SourceName)\n\t\tr.Equal(su, w.SourceURL)\n\t\tr.Equal(\"remote\", w.ExecutionMode)\n\t})\n\n\tt.Run(\"fail to create a workspace with a bad module ID\", func(t *testing.T) {\n\t\twn := fmt.Sprintf(\"foo-%s\", randomString(t))\n\t\t_, err = client.RegistryNoCodeModules.CreateWorkspace(\n\t\t\tctx,\n\t\t\t\"codeno-abc123XYZ\",\n\t\t\t&RegistryNoCodeModuleCreateWorkspaceOptions{\n\t\t\t\tName: wn,\n\t\t\t},\n\t\t)\n\t\tr.Error(err)\n\t})\n}\n\nfunc TestRegistryNoCodeModuleWorkspaceUpgrade(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\tr := require.New(t)\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\torg, err := client.Organizations.Read(ctx, orgTest.Name)\n\tr.NoError(err)\n\tr.NotNil(org)\n\n\tgithubIdentifier := os.Getenv(\"GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER\")\n\tif githubIdentifier == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER before running this test\")\n\t}\n\n\ttoken, cleanupToken := createOAuthToken(t, client, org)\n\tdefer cleanupToken()\n\n\trmOpts := RegistryModuleCreateWithVCSConnectionOptions{\n\t\tVCSRepo: &RegistryModuleVCSRepoOptions{\n\t\t\tOrganizationName:  String(org.Name),\n\t\t\tIdentifier:        String(githubIdentifier),\n\t\t\tTags:              Bool(true),\n\t\t\tOAuthTokenID:      String(token.ID),\n\t\t\tDisplayIdentifier: String(githubIdentifier),\n\t\t},\n\t\tInitialVersion: String(\"1.0.0\"),\n\t}\n\n\t// create the module\n\trm, err := client.RegistryModules.CreateWithVCSConnection(ctx, rmOpts)\n\tr.NoError(err)\n\n\t// create the no-code module\n\tncm, err := client.RegistryNoCodeModules.Create(ctx, org.Name, RegistryNoCodeModuleCreateOptions{\n\t\tRegistryModule:  rm,\n\t\tEnabled:         Bool(true),\n\t\tVariableOptions: nil,\n\t})\n\tr.NoError(err)\n\tr.NotNil(ncm)\n\n\t// We sleep for 10 seconds to let the module finish getting ready\n\ttime.Sleep(time.Second * 10)\n\n\t// update the module's pinned version to be 1.0.0\n\t// NOTE: This is done here as an update instead of at create time, because\n\t// that results in the following error:\n\t// Validation failed: Provided version pin is not equal to latest or provided\n\t// string does not represent an existing version of the module.\n\tuncm, err := client.RegistryNoCodeModules.Update(ctx, ncm.ID, RegistryNoCodeModuleUpdateOptions{\n\t\tRegistryModule: rm,\n\t\tVersionPin:     \"1.0.0\",\n\t})\n\tr.NoError(err)\n\tr.NotNil(uncm)\n\n\t// create a workspace, which will be attempted to be updated during the test\n\twn := fmt.Sprintf(\"foo-%s\", randomString(t))\n\tsn := \"my-app\"\n\tsu := \"http://my-app.com\"\n\tw, err := client.RegistryNoCodeModules.CreateWorkspace(\n\t\tctx,\n\t\tuncm.ID,\n\t\t&RegistryNoCodeModuleCreateWorkspaceOptions{\n\t\t\tName:       wn,\n\t\t\tSourceName: String(sn),\n\t\t\tSourceURL:  String(su),\n\t\t},\n\t)\n\tr.NoError(err)\n\tr.NotNil(w)\n\n\t// update the module's pinned version\n\tuncm, err = client.RegistryNoCodeModules.Update(ctx, ncm.ID, RegistryNoCodeModuleUpdateOptions{\n\t\tVersionPin: \"1.0.1\",\n\t})\n\tr.NoError(err)\n\tr.NotNil(uncm)\n\n\tt.Run(\"test upgrading a workspace via a no-code module\", func(t *testing.T) {\n\t\twu, err := client.RegistryNoCodeModules.UpgradeWorkspace(\n\t\t\tctx,\n\t\t\tncm.ID,\n\t\t\tw.ID,\n\t\t\t&RegistryNoCodeModuleUpgradeWorkspaceOptions{},\n\t\t)\n\t\tr.NoError(err)\n\t\tr.NotNil(wu)\n\t\tr.NotEmpty(wu.Status)\n\t\tr.NotEmpty(wu.PlanURL)\n\t})\n\n\tt.Run(\"fail to upgrade workspace with invalid no-code module\", func(t *testing.T) {\n\t\t_, err = client.RegistryNoCodeModules.UpgradeWorkspace(\n\t\t\tctx,\n\t\t\tncm.ID+\"-invalid\",\n\t\t\tw.ID,\n\t\t\t&RegistryNoCodeModuleUpgradeWorkspaceOptions{},\n\t\t)\n\t\tr.Error(err)\n\t})\n\n\tt.Run(\"fail to upgrade workspace with invalid workspace ID\", func(t *testing.T) {\n\t\t_, err = client.RegistryNoCodeModules.UpgradeWorkspace(\n\t\t\tctx,\n\t\t\tncm.ID,\n\t\t\tw.ID+\"-invalid\",\n\t\t\t&RegistryNoCodeModuleUpgradeWorkspaceOptions{},\n\t\t)\n\t\tr.Error(err)\n\t})\n}\n"
  },
  {
    "path": "registry_provider.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ RegistryProviders = (*registryProviders)(nil)\n\n// RegistryProviders describes all the registry provider-related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/providers\ntype RegistryProviders interface {\n\t// List all the providers within an organization.\n\tList(ctx context.Context, organization string, options *RegistryProviderListOptions) (*RegistryProviderList, error)\n\n\t// Create a registry provider.\n\tCreate(ctx context.Context, organization string, options RegistryProviderCreateOptions) (*RegistryProvider, error)\n\n\t// Read a registry provider.\n\tRead(ctx context.Context, providerID RegistryProviderID, options *RegistryProviderReadOptions) (*RegistryProvider, error)\n\n\t// Delete a registry provider.\n\tDelete(ctx context.Context, providerID RegistryProviderID) error\n}\n\n// registryProviders implements RegistryProviders.\ntype registryProviders struct {\n\tclient *Client\n}\n\n// RegistryName represents which registry is being targeted\ntype RegistryName string\n\n// List of available registry names\nconst (\n\tPrivateRegistry RegistryName = \"private\"\n\tPublicRegistry  RegistryName = \"public\"\n)\n\n// RegistryProviderIncludeOps represents which jsonapi include can be used with registry providers\ntype RegistryProviderIncludeOps string\n\n// List of available includes\nconst (\n\tRegistryProviderVersionsInclude RegistryProviderIncludeOps = \"registry-provider-versions\"\n)\n\n// RegistryProvider represents a registry provider\ntype RegistryProvider struct {\n\tID           string                      `jsonapi:\"primary,registry-providers\"`\n\tName         string                      `jsonapi:\"attr,name\"`\n\tNamespace    string                      `jsonapi:\"attr,namespace\"`\n\tCreatedAt    string                      `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt    string                      `jsonapi:\"attr,updated-at,iso8601\"`\n\tRegistryName RegistryName                `jsonapi:\"attr,registry-name\"`\n\tPermissions  RegistryProviderPermissions `jsonapi:\"attr,permissions\"`\n\n\t// Relations\n\tOrganization             *Organization              `jsonapi:\"relation,organization\"`\n\tRegistryProviderVersions []*RegistryProviderVersion `jsonapi:\"relation,registry-provider-versions\"`\n\n\t// Links\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n}\n\ntype RegistryProviderPermissions struct {\n\tCanDelete bool `jsonapi:\"attr,can-delete\"`\n}\n\ntype RegistryProviderListOptions struct {\n\tListOptions\n\n\t// Optional: A query string to filter by registry_name\n\tRegistryName RegistryName `url:\"filter[registry_name],omitempty\"`\n\n\t// Optional: A query string to filter by organization\n\tOrganizationName string `url:\"filter[organization_name],omitempty\"`\n\n\t// Optional: A query string to do a fuzzy search\n\tSearch string `url:\"q,omitempty\"`\n\n\t// Optional: Include related jsonapi relationships\n\tInclude *[]RegistryProviderIncludeOps `url:\"include,omitempty\"`\n}\n\ntype RegistryProviderList struct {\n\t*Pagination\n\tItems []*RegistryProvider\n}\n\n// RegistryProviderID is the multi key ID for addressing a provider\ntype RegistryProviderID struct {\n\tOrganizationName string\n\tRegistryName     RegistryName\n\tNamespace        string\n\tName             string\n}\n\n// RegistryProviderCreateOptions is used when creating a registry provider\ntype RegistryProviderCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,registry-providers\"`\n\n\t// Required: The name of the registry provider\n\tName string `jsonapi:\"attr,name\"`\n\n\t// Required: The namespace of the provider. For private providers, this is the same as the organization name\n\tNamespace string `jsonapi:\"attr,namespace\"`\n\n\t// Required: Whether this is a publicly maintained provider or private. Must be either public or private.\n\tRegistryName RegistryName `jsonapi:\"attr,registry-name\"`\n}\n\ntype RegistryProviderReadOptions struct {\n\t// Optional: Include related jsonapi relationships\n\tInclude []RegistryProviderIncludeOps `url:\"include,omitempty\"`\n}\n\nfunc (r *registryProviders) List(ctx context.Context, organization string, options *RegistryProviderListOptions) (*RegistryProviderList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/registry-providers\", url.PathEscape(organization))\n\treq, err := r.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpl := &RegistryProviderList{}\n\terr = req.Do(ctx, pl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pl, nil\n}\n\nfunc (r *registryProviders) Create(ctx context.Context, organization string, options RegistryProviderCreateOptions) (*RegistryProvider, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers\",\n\t\turl.PathEscape(organization),\n\t)\n\treq, err := r.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprv := &RegistryProvider{}\n\terr = req.Do(ctx, prv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn prv, nil\n}\n\nfunc (r *registryProviders) Read(ctx context.Context, providerID RegistryProviderID, options *RegistryProviderReadOptions) (*RegistryProvider, error) {\n\tif err := providerID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers/%s/%s/%s\",\n\t\turl.PathEscape(providerID.OrganizationName),\n\t\turl.PathEscape(string(providerID.RegistryName)),\n\t\turl.PathEscape(providerID.Namespace),\n\t\turl.PathEscape(providerID.Name),\n\t)\n\treq, err := r.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprv := &RegistryProvider{}\n\terr = req.Do(ctx, prv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn prv, nil\n}\n\nfunc (r *registryProviders) Delete(ctx context.Context, providerID RegistryProviderID) error {\n\tif err := providerID.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers/%s/%s/%s\",\n\t\turl.PathEscape(providerID.OrganizationName),\n\t\turl.PathEscape(string(providerID.RegistryName)),\n\t\turl.PathEscape(providerID.Namespace),\n\t\turl.PathEscape(providerID.Name),\n\t)\n\treq, err := r.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o RegistryProviderCreateOptions) valid() error {\n\tif !validStringID(&o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\tif !validStringID(&o.Namespace) {\n\t\treturn ErrInvalidNamespace\n\t}\n\treturn nil\n}\n\nfunc (id RegistryProviderID) valid() error {\n\tif !validStringID(&id.OrganizationName) {\n\t\treturn ErrInvalidOrg\n\t}\n\tif !validStringID(&id.Name) {\n\t\treturn ErrInvalidName\n\t}\n\tif !validStringID(&id.Namespace) {\n\t\treturn ErrInvalidNamespace\n\t}\n\tif !validStringID((*string)(&id.RegistryName)) {\n\t\treturn ErrInvalidRegistryName\n\t}\n\treturn nil\n}\n\nfunc (o *RegistryProviderListOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "registry_provider_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRegistryProvidersList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"with providers\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\tcreateN := 10\n\t\tproviders := make([]*RegistryProvider, 0)\n\t\t// These providers will be destroyed when the org is cleaned up\n\t\tfor i := 0; i < createN; i++ {\n\t\t\t// Create public providers\n\t\t\tproviderTest, _ := createRegistryProvider(t, client, orgTest, PublicRegistry)\n\t\t\tproviders = append(providers, providerTest)\n\t\t}\n\t\tfor i := 0; i < createN; i++ {\n\t\t\t// Create private providers\n\t\t\tproviderTest, _ := createRegistryProvider(t, client, orgTest, PrivateRegistry)\n\t\t\tproviders = append(providers, providerTest)\n\t\t}\n\t\tproviderN := len(providers)\n\t\tpublicProviderN := createN\n\n\t\tt.Run(\"returns all providers\", func(t *testing.T) {\n\t\t\treturnedProviders, err := client.RegistryProviders.List(ctx, orgTest.Name, &RegistryProviderListOptions{\n\t\t\t\tListOptions: ListOptions{\n\t\t\t\t\tPageNumber: 0,\n\t\t\t\t\tPageSize:   providerN,\n\t\t\t\t},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.NotEmpty(t, returnedProviders.Items)\n\t\t\tassert.Equal(t, providerN, returnedProviders.TotalCount)\n\t\t\tassert.Equal(t, 1, returnedProviders.TotalPages)\n\t\t})\n\n\t\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t\t// Request a page number which is out of range. The result should\n\t\t\t// be successful, but return no results if the paging options are\n\t\t\t// properly passed along.\n\t\t\trpl, err := client.RegistryProviders.List(ctx, orgTest.Name, &RegistryProviderListOptions{\n\t\t\t\tListOptions: ListOptions{\n\t\t\t\t\tPageNumber: 999,\n\t\t\t\t\tPageSize:   100,\n\t\t\t\t},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Empty(t, rpl.Items)\n\t\t\tassert.Equal(t, 999, rpl.CurrentPage)\n\t\t\tassert.Equal(t, 20, rpl.TotalCount)\n\t\t})\n\n\t\tt.Run(\"filters on registry name\", func(t *testing.T) {\n\t\t\treturnedProviders, err := client.RegistryProviders.List(ctx, orgTest.Name, &RegistryProviderListOptions{\n\t\t\t\tRegistryName: PublicRegistry,\n\t\t\t\tListOptions: ListOptions{\n\t\t\t\t\tPageNumber: 0,\n\t\t\t\t\tPageSize:   providerN,\n\t\t\t\t},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.NotEmpty(t, returnedProviders.Items)\n\t\t\tassert.Equal(t, publicProviderN, returnedProviders.TotalCount)\n\t\t\tassert.Equal(t, 1, returnedProviders.TotalPages)\n\t\t\tfor _, rp := range returnedProviders.Items {\n\t\t\t\tfoundProvider := false\n\t\t\t\tfor _, p := range providers {\n\t\t\t\t\tif rp.ID == p.ID {\n\t\t\t\t\t\tfoundProvider = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassert.Equal(t, PublicRegistry, rp.RegistryName)\n\t\t\t\tassert.True(t, foundProvider, \"Expected to find provider %s but did not:\\nexpected:\\n%v\\nreturned\\n%v\", rp.ID, providers, returnedProviders)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"searches\", func(t *testing.T) {\n\t\t\texpectedProvider := providers[0]\n\t\t\treturnedProviders, err := client.RegistryProviders.List(ctx, orgTest.Name, &RegistryProviderListOptions{\n\t\t\t\tSearch: expectedProvider.Name,\n\t\t\t})\n\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.NotEmpty(t, returnedProviders.Items)\n\t\t\tassert.Equal(t, 1, returnedProviders.TotalCount)\n\t\t\tassert.Equal(t, 1, returnedProviders.TotalPages)\n\n\t\t\tfoundProvider := returnedProviders.Items[0]\n\t\t\tassert.Equal(t, foundProvider.ID, expectedProvider.ID, \"Expected to find provider %s but did not:\\nexpected:\\n%v\\nreturned\\n%v\", expectedProvider.ID, providers, returnedProviders)\n\t\t})\n\t})\n\n\tt.Run(\"without providers\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\tproviders, err := client.RegistryProviders.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, providers.Items)\n\t\tassert.Equal(t, 0, providers.TotalCount)\n\t\tassert.Equal(t, 0, providers.TotalPages)\n\t})\n\n\tt.Run(\"with include provider versions\", func(t *testing.T) {\n\t\tversion1, version1Cleanup := createRegistryProviderVersion(t, client, nil)\n\t\tdefer version1Cleanup()\n\n\t\tprovider := version1.RegistryProvider\n\n\t\tversion2, version2Cleanup := createRegistryProviderVersion(t, client, provider)\n\t\tdefer version2Cleanup()\n\n\t\tversions := []*RegistryProviderVersion{version1, version2}\n\n\t\toptions := RegistryProviderListOptions{\n\t\t\tInclude: &[]RegistryProviderIncludeOps{\n\t\t\t\tRegistryProviderVersionsInclude,\n\t\t\t},\n\t\t}\n\n\t\tprovidersRead, err := client.RegistryProviders.List(ctx, provider.Organization.Name, &options)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, providersRead.Items)\n\t\tproviderRead := providersRead.Items[0]\n\t\tassert.Equal(t, providerRead.ID, provider.ID)\n\t\tassert.Equal(t, len(versions), len(providerRead.RegistryProviderVersions))\n\t\tfoundVersion := &RegistryProviderVersion{}\n\t\tfor _, v := range providerRead.RegistryProviderVersions {\n\t\t\tfor i := 0; i < len(versions); i++ {\n\t\t\t\tif v.ID == versions[i].ID {\n\t\t\t\t\tfoundVersion = versions[i]\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tassert.True(t, foundVersion.ID != \"\", \"Expected to find versions: %v but did not\", versions)\n\t\t\tassert.Equal(t, v.Version, foundVersion.Version)\n\t\t}\n\t})\n}\n\nfunc TestRegistryProvidersCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tpublicProviderOptions := RegistryProviderCreateOptions{\n\t\t\tName:         \"provider_name\",\n\t\t\tNamespace:    \"public_namespace\",\n\t\t\tRegistryName: PublicRegistry,\n\t\t}\n\t\tprivateProviderOptions := RegistryProviderCreateOptions{\n\t\t\tName:         \"provider_name\",\n\t\t\tNamespace:    orgTest.Name,\n\t\t\tRegistryName: PrivateRegistry,\n\t\t}\n\n\t\tregistryOptions := []RegistryProviderCreateOptions{publicProviderOptions, privateProviderOptions}\n\n\t\tfor _, options := range registryOptions {\n\t\t\ttestName := fmt.Sprintf(\"with %s provider\", options.RegistryName)\n\t\t\tt.Run(testName, func(t *testing.T) {\n\t\t\t\tprv, err := client.RegistryProviders.Create(ctx, orgTest.Name, options)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.NotEmpty(t, prv.ID)\n\t\t\t\tassert.Equal(t, options.Name, prv.Name)\n\t\t\t\tassert.Equal(t, options.Namespace, prv.Namespace)\n\t\t\t\tassert.Equal(t, options.RegistryName, prv.RegistryName)\n\n\t\t\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\t\t\tassert.True(t, prv.Permissions.CanDelete)\n\t\t\t\t})\n\n\t\t\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\t\t\tassert.Equal(t, orgTest.Name, prv.Organization.Name)\n\t\t\t\t})\n\n\t\t\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\t\t\tassert.NotEmpty(t, prv.CreatedAt)\n\t\t\t\t\tassert.NotEmpty(t, prv.UpdatedAt)\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\tt.Run(\"without a name\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderCreateOptions{\n\t\t\t\tNamespace:    \"namespace\",\n\t\t\t\tRegistryName: PublicRegistry,\n\t\t\t}\n\t\t\trm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t\t})\n\n\t\tt.Run(\"with an invalid name\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderCreateOptions{\n\t\t\t\tName:         \"invalid name\",\n\t\t\t\tNamespace:    \"namespace\",\n\t\t\t\tRegistryName: PublicRegistry,\n\t\t\t}\n\t\t\trm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t\t})\n\n\t\tt.Run(\"without a namespace\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderCreateOptions{\n\t\t\t\tName:         \"name\",\n\t\t\t\tRegistryName: PublicRegistry,\n\t\t\t}\n\t\t\trm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrInvalidNamespace.Error())\n\t\t})\n\n\t\tt.Run(\"with an invalid namespace\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderCreateOptions{\n\t\t\t\tName:         \"name\",\n\t\t\t\tNamespace:    \"invalid namespace\",\n\t\t\t\tRegistryName: PublicRegistry,\n\t\t\t}\n\t\t\trm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrInvalidNamespace.Error())\n\t\t})\n\n\t\tt.Run(\"without a registry-name\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderCreateOptions{\n\t\t\t\tName:      \"name\",\n\t\t\t\tNamespace: \"namespace\",\n\t\t\t}\n\t\t\trm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\t// This error is returned by the API\n\t\t\tassert.EqualError(t, err, \"invalid attribute\\n\\nRegistry name can't be blank\\ninvalid attribute\\n\\nRegistry name is not included in the list\")\n\t\t})\n\n\t\tt.Run(\"with an invalid registry-name\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderCreateOptions{\n\t\t\t\tName:         \"name\",\n\t\t\t\tNamespace:    \"namespace\",\n\t\t\t\tRegistryName: \"invalid\",\n\t\t\t}\n\t\t\trm, err := client.RegistryProviders.Create(ctx, orgTest.Name, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\t// This error is returned by the API\n\t\t\tassert.EqualError(t, err, \"invalid attribute\\n\\nRegistry name is not included in the list\")\n\t\t})\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\toptions := RegistryProviderCreateOptions{\n\t\t\tName:         \"name\",\n\t\t\tNamespace:    \"namespace\",\n\t\t\tRegistryName: PublicRegistry,\n\t\t}\n\t\trm, err := client.RegistryProviders.Create(ctx, badIdentifier, options)\n\t\tassert.Nil(t, rm)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestRegistryProvidersRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttype ProviderContext struct {\n\t\tRegistryName RegistryName\n\t}\n\n\tproviderContexts := []ProviderContext{\n\t\t{\n\t\t\tRegistryName: PublicRegistry,\n\t\t},\n\t\t{\n\t\t\tRegistryName: PrivateRegistry,\n\t\t},\n\t}\n\n\tfor _, prvCtx := range providerContexts {\n\t\ttestName := fmt.Sprintf(\"with %s provider\", prvCtx.RegistryName)\n\t\tt.Run(testName, func(t *testing.T) {\n\t\t\tt.Run(\"with valid provider\", func(t *testing.T) {\n\t\t\t\tregistryProviderTest, providerTestCleanup := createRegistryProvider(t, client, orgTest, prvCtx.RegistryName)\n\t\t\t\tdefer providerTestCleanup()\n\n\t\t\t\tid := RegistryProviderID{\n\t\t\t\t\tOrganizationName: orgTest.Name,\n\t\t\t\t\tRegistryName:     registryProviderTest.RegistryName,\n\t\t\t\t\tNamespace:        registryProviderTest.Namespace,\n\t\t\t\t\tName:             registryProviderTest.Name,\n\t\t\t\t}\n\n\t\t\t\tprv, err := client.RegistryProviders.Read(ctx, id, nil)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tassert.NotEmpty(t, prv.ID)\n\t\t\t\tassert.Equal(t, registryProviderTest.Name, prv.Name)\n\t\t\t\tassert.Equal(t, registryProviderTest.Namespace, prv.Namespace)\n\t\t\t\tassert.Equal(t, registryProviderTest.RegistryName, prv.RegistryName)\n\n\t\t\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\t\t\tassert.True(t, prv.Permissions.CanDelete)\n\t\t\t\t})\n\n\t\t\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\t\t\tassert.Equal(t, orgTest.Name, prv.Organization.Name)\n\t\t\t\t})\n\n\t\t\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\t\t\tassert.NotEmpty(t, prv.CreatedAt)\n\t\t\t\t\tassert.NotEmpty(t, prv.UpdatedAt)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tt.Run(\"when the registry provider does not exist\", func(t *testing.T) {\n\t\t\t\tid := RegistryProviderID{\n\t\t\t\t\tOrganizationName: orgTest.Name,\n\t\t\t\t\tRegistryName:     prvCtx.RegistryName,\n\t\t\t\t\tNamespace:        \"nonexistent\",\n\t\t\t\t\tName:             \"nonexistent\",\n\t\t\t\t}\n\t\t\t\t_, err := client.RegistryProviders.Read(ctx, id, nil)\n\t\t\t\tassert.Error(t, err)\n\t\t\t\t// Local HCP Terraform or Terraform Enterprise will return a forbidden here when HCP Terraform or Terraform Enterprise is in development mode\n\t\t\t\t// In non development mode this returns a 404\n\t\t\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t\t\t})\n\t\t})\n\t}\n\n\tt.Run(\"populates version relationships\", func(t *testing.T) {\n\t\tversion1, version1Cleanup := createRegistryProviderVersion(t, client, nil)\n\t\tdefer version1Cleanup()\n\n\t\tprovider := version1.RegistryProvider\n\n\t\tversion2, version2Cleanup := createRegistryProviderVersion(t, client, provider)\n\t\tdefer version2Cleanup()\n\n\t\tversions := []*RegistryProviderVersion{version1, version2}\n\n\t\tid := RegistryProviderID{\n\t\t\tOrganizationName: provider.Organization.Name,\n\t\t\tRegistryName:     provider.RegistryName,\n\t\t\tNamespace:        provider.Namespace,\n\t\t\tName:             provider.Name,\n\t\t}\n\n\t\toptions := RegistryProviderReadOptions{\n\t\t\tInclude: []RegistryProviderIncludeOps{\n\t\t\t\tRegistryProviderVersionsInclude,\n\t\t\t},\n\t\t}\n\n\t\tproviderRead, err := client.RegistryProviders.Read(ctx, id, &options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, providerRead.RegistryProviderVersions)\n\n\t\tassert.Equal(t, providerRead.ID, provider.ID)\n\t\tassert.Equal(t, len(versions), len(providerRead.RegistryProviderVersions))\n\t\tfoundVersion := &RegistryProviderVersion{}\n\t\tfor _, v := range providerRead.RegistryProviderVersions {\n\t\t\tfor i := 0; i < len(versions); i++ {\n\t\t\t\tif v.ID == versions[i].ID {\n\t\t\t\t\tfoundVersion = versions[i]\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tassert.True(t, foundVersion.ID != \"\", \"Expected to find versions: %v but did not\", versions)\n\t\t\tassert.Equal(t, v.Version, foundVersion.Version)\n\t\t}\n\t})\n}\n\nfunc TestRegistryProvidersDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttype ProviderContext struct {\n\t\tRegistryName RegistryName\n\t}\n\n\tproviderContexts := []ProviderContext{\n\t\t{\n\t\t\tRegistryName: PublicRegistry,\n\t\t},\n\t\t{\n\t\t\tRegistryName: PrivateRegistry,\n\t\t},\n\t}\n\n\tfor _, prvCtx := range providerContexts {\n\t\ttestName := fmt.Sprintf(\"with %s provider\", prvCtx.RegistryName)\n\t\tt.Run(testName, func(t *testing.T) {\n\t\t\tt.Run(\"with valid provider\", func(t *testing.T) {\n\t\t\t\tregistryProviderTest, _ := createRegistryProvider(t, client, orgTest, prvCtx.RegistryName)\n\n\t\t\t\tid := RegistryProviderID{\n\t\t\t\t\tOrganizationName: orgTest.Name,\n\t\t\t\t\tRegistryName:     registryProviderTest.RegistryName,\n\t\t\t\t\tNamespace:        registryProviderTest.Namespace,\n\t\t\t\t\tName:             registryProviderTest.Name,\n\t\t\t\t}\n\n\t\t\t\terr := client.RegistryProviders.Delete(ctx, id)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tprv, err := client.RegistryProviders.Read(ctx, id, nil)\n\t\t\t\tassert.Nil(t, prv)\n\t\t\t\tassert.Error(t, err)\n\t\t\t})\n\n\t\t\tt.Run(\"when the registry provider does not exist\", func(t *testing.T) {\n\t\t\t\tid := RegistryProviderID{\n\t\t\t\t\tOrganizationName: orgTest.Name,\n\t\t\t\t\tRegistryName:     prvCtx.RegistryName,\n\t\t\t\t\tNamespace:        \"nonexistent\",\n\t\t\t\t\tName:             \"nonexistent\",\n\t\t\t\t}\n\t\t\t\terr := client.RegistryProviders.Delete(ctx, id)\n\t\t\t\tassert.Error(t, err)\n\t\t\t\t// Local HCP Terraform or Terraform Enterprise will return a forbidden here when HCP Terraform or Terraform Enterprise is in development mode\n\t\t\t\t// In non development mode this returns a 404\n\t\t\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestRegistryProvidersIDValidation(t *testing.T) {\n\tt.Parallel()\n\torgName := \"orgName\"\n\tregistryName := PublicRegistry\n\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tid := RegistryProviderID{\n\t\t\tOrganizationName: orgName,\n\t\t\tRegistryName:     registryName,\n\t\t\tNamespace:        \"namespace\",\n\t\t\tName:             \"name\",\n\t\t}\n\t\trequire.NoError(t, id.valid())\n\t})\n\n\tt.Run(\"without a name\", func(t *testing.T) {\n\t\tid := RegistryProviderID{\n\t\t\tOrganizationName: orgName,\n\t\t\tRegistryName:     registryName,\n\t\t\tNamespace:        \"namespace\",\n\t\t\tName:             \"\",\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"with an invalid name\", func(t *testing.T) {\n\t\tid := RegistryProviderID{\n\t\t\tOrganizationName: orgName,\n\t\t\tRegistryName:     registryName,\n\t\t\tNamespace:        \"namespace\",\n\t\t\tName:             badIdentifier,\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"without a namespace\", func(t *testing.T) {\n\t\tid := RegistryProviderID{\n\t\t\tOrganizationName: orgName,\n\t\t\tRegistryName:     registryName,\n\t\t\tNamespace:        \"\",\n\t\t\tName:             \"name\",\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrInvalidNamespace.Error())\n\t})\n\n\tt.Run(\"with an invalid namespace\", func(t *testing.T) {\n\t\tid := RegistryProviderID{\n\t\t\tOrganizationName: orgName,\n\t\t\tRegistryName:     registryName,\n\t\t\tNamespace:        badIdentifier,\n\t\t\tName:             \"name\",\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrInvalidNamespace.Error())\n\t})\n\n\tt.Run(\"without a registry-name\", func(t *testing.T) {\n\t\tid := RegistryProviderID{\n\t\t\tOrganizationName: orgName,\n\t\t\tRegistryName:     \"\",\n\t\t\tNamespace:        \"namespace\",\n\t\t\tName:             \"name\",\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrInvalidRegistryName.Error())\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tid := RegistryProviderID{\n\t\t\tOrganizationName: badIdentifier,\n\t\t\tRegistryName:     registryName,\n\t\t\tNamespace:        \"namespace\",\n\t\t\tName:             \"name\",\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrInvalidOrg.Error())\n\t})\n}\n"
  },
  {
    "path": "registry_provider_platform.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation\nvar _ RegistryProviderPlatforms = (*registryProviderPlatforms)(nil)\n\n// RegistryProviderPlatforms describes the registry provider platform methods supported by the Terraform Enterprise API.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/provider-versions-platforms#private-provider-versions-and-platforms-api\ntype RegistryProviderPlatforms interface {\n\t// Create a provider platform for an organization\n\tCreate(ctx context.Context, versionID RegistryProviderVersionID, options RegistryProviderPlatformCreateOptions) (*RegistryProviderPlatform, error)\n\n\t// List all provider platforms for a single version\n\tList(ctx context.Context, versionID RegistryProviderVersionID, options *RegistryProviderPlatformListOptions) (*RegistryProviderPlatformList, error)\n\n\t// Read a provider platform by ID\n\tRead(ctx context.Context, platformID RegistryProviderPlatformID) (*RegistryProviderPlatform, error)\n\n\t// Delete a provider platform\n\tDelete(ctx context.Context, platformID RegistryProviderPlatformID) error\n}\n\n// registryProviders implements RegistryProviders\ntype registryProviderPlatforms struct {\n\tclient *Client\n}\n\n// RegistryProviderPlatform represents a registry provider platform\ntype RegistryProviderPlatform struct {\n\tID                     string `jsonapi:\"primary,registry-provider-platforms\"`\n\tOS                     string `jsonapi:\"attr,os\"`\n\tArch                   string `jsonapi:\"attr,arch\"`\n\tFilename               string `jsonapi:\"attr,filename\"`\n\tShasum                 string `jsonapi:\"attr,shasum\"`\n\tProviderBinaryUploaded bool   `jsonapi:\"attr,provider-binary-uploaded\"`\n\n\t// Relations\n\tRegistryProviderVersion *RegistryProviderVersion `jsonapi:\"relation,registry-provider-version\"`\n\n\t// Links\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n}\n\n// RegistryProviderPlatformID is the multi key ID for identifying a provider platform\ntype RegistryProviderPlatformID struct {\n\tRegistryProviderVersionID\n\tOS   string\n\tArch string\n}\n\n// RegistryProviderPlatformCreateOptions represents the set of options for creating a registry provider platform\ntype RegistryProviderPlatformCreateOptions struct {\n\t// Required: A valid operating system string\n\tOS string `jsonapi:\"attr,os\"`\n\n\t// Required: A valid architecture string\n\tArch string `jsonapi:\"attr,arch\"`\n\n\t// Required: A valid shasum string\n\tShasum string `jsonapi:\"attr,shasum\"`\n\n\t// Required: A valid filename string\n\tFilename string `jsonapi:\"attr,filename\"`\n}\n\ntype RegistryProviderPlatformList struct {\n\t*Pagination\n\tItems []*RegistryProviderPlatform\n}\n\ntype RegistryProviderPlatformListOptions struct {\n\tListOptions\n}\n\n// Create a new registry provider platform\nfunc (r *registryProviderPlatforms) Create(ctx context.Context, versionID RegistryProviderVersionID, options RegistryProviderPlatformCreateOptions) (*RegistryProviderPlatform, error) {\n\tif err := versionID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// POST /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms\",\n\t\turl.PathEscape(versionID.OrganizationName),\n\t\turl.PathEscape(string(versionID.RegistryName)),\n\t\turl.PathEscape(versionID.Namespace),\n\t\turl.PathEscape(versionID.Name),\n\t\turl.PathEscape(versionID.Version),\n\t)\n\treq, err := r.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trpp := &RegistryProviderPlatform{}\n\terr = req.Do(ctx, rpp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rpp, nil\n}\n\n// List all provider platforms for a single version\nfunc (r *registryProviderPlatforms) List(ctx context.Context, versionID RegistryProviderVersionID, options *RegistryProviderPlatformListOptions) (*RegistryProviderPlatformList, error) {\n\tif err := versionID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms\",\n\t\turl.PathEscape(versionID.OrganizationName),\n\t\turl.PathEscape(string(versionID.RegistryName)),\n\t\turl.PathEscape(versionID.Namespace),\n\t\turl.PathEscape(versionID.Name),\n\t\turl.PathEscape(versionID.Version),\n\t)\n\treq, err := r.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tppl := &RegistryProviderPlatformList{}\n\terr = req.Do(ctx, ppl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ppl, nil\n}\n\n// Read is used to read an organization's example by ID\nfunc (r *registryProviderPlatforms) Read(ctx context.Context, platformID RegistryProviderPlatformID) (*RegistryProviderPlatform, error) {\n\tif err := platformID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms/:os/:arch\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms/%s/%s\",\n\t\turl.PathEscape(platformID.OrganizationName),\n\t\turl.PathEscape(string(platformID.RegistryName)),\n\t\turl.PathEscape(platformID.Namespace),\n\t\turl.PathEscape(platformID.Name),\n\t\turl.PathEscape(platformID.Version),\n\t\turl.PathEscape(platformID.OS),\n\t\turl.PathEscape(platformID.Arch),\n\t)\n\treq, err := r.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trpp := &RegistryProviderPlatform{}\n\terr = req.Do(ctx, rpp)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rpp, nil\n}\n\n// Delete a registry provider platform\nfunc (r *registryProviderPlatforms) Delete(ctx context.Context, platformID RegistryProviderPlatformID) error {\n\tif err := platformID.valid(); err != nil {\n\t\treturn err\n\t}\n\n\t// DELETE /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms/:os/:arch\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms/%s/%s\",\n\t\turl.PathEscape(platformID.OrganizationName),\n\t\turl.PathEscape(string(platformID.RegistryName)),\n\t\turl.PathEscape(platformID.Namespace),\n\t\turl.PathEscape(platformID.Name),\n\t\turl.PathEscape(platformID.Version),\n\t\turl.PathEscape(platformID.OS),\n\t\turl.PathEscape(platformID.Arch),\n\t)\n\treq, err := r.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (id RegistryProviderPlatformID) valid() error {\n\tif err := id.RegistryProviderID.valid(); err != nil {\n\t\treturn err\n\t}\n\tif !validString(&id.OS) {\n\t\treturn ErrInvalidOS\n\t}\n\tif !validString(&id.Arch) {\n\t\treturn ErrInvalidArch\n\t}\n\treturn nil\n}\n\nfunc (o RegistryProviderPlatformCreateOptions) valid() error {\n\tif !validString(&o.OS) {\n\t\treturn ErrRequiredOS\n\t}\n\tif !validString(&o.Arch) {\n\t\treturn ErrRequiredArch\n\t}\n\tif !validStringID(&o.Shasum) {\n\t\treturn ErrRequiredShasum\n\t}\n\tif !validStringID(&o.Filename) {\n\t\treturn ErrRequiredFilename\n\t}\n\treturn nil\n}\n\nfunc (o *RegistryProviderPlatformListOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "registry_provider_platform_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRegistryProviderPlatformsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tprovider, providerTestCleanup := createRegistryProvider(t, client, nil, PrivateRegistry)\n\tdefer providerTestCleanup()\n\n\tversion, versionCleanup := createRegistryProviderVersion(t, client, provider)\n\tdefer versionCleanup()\n\n\tversionID := RegistryProviderVersionID{\n\t\tRegistryProviderID: RegistryProviderID{\n\t\t\tOrganizationName: provider.Organization.Name,\n\t\t\tRegistryName:     provider.RegistryName,\n\t\t\tNamespace:        provider.Namespace,\n\t\t\tName:             provider.Name,\n\t\t},\n\t\tVersion: version.Version,\n\t}\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := RegistryProviderPlatformCreateOptions{\n\t\t\tOS:       \"linux\",\n\t\t\tArch:     \"amd64\",\n\t\t\tShasum:   \"shasum\",\n\t\t\tFilename: \"filename\",\n\t\t}\n\n\t\trpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, rpp.ID)\n\t\tassert.Equal(t, options.OS, rpp.OS)\n\t\tassert.Equal(t, options.Arch, rpp.Arch)\n\t\tassert.Equal(t, options.Shasum, rpp.Shasum)\n\t\tassert.Equal(t, options.Filename, rpp.Filename)\n\t\tassert.False(t, rpp.ProviderBinaryUploaded)\n\n\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, version.ID, rpp.RegistryProviderVersion.ID)\n\t\t})\n\n\t\tt.Run(\"attributes are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, rpp.Arch)\n\t\t\tassert.NotEmpty(t, rpp.OS)\n\t\t\tassert.NotEmpty(t, rpp.Shasum)\n\t\t\tassert.NotEmpty(t, rpp.Filename)\n\t\t})\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\tt.Run(\"without an OS\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderPlatformCreateOptions{\n\t\t\t\tOS:       \"\",\n\t\t\t\tArch:     \"amd64\",\n\t\t\t\tShasum:   \"shasum\",\n\t\t\t\tFilename: \"filename\",\n\t\t\t}\n\n\t\t\tsadRpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options)\n\n\t\t\tassert.Nil(t, sadRpp)\n\t\t\tassert.EqualError(t, err, ErrRequiredOS.Error())\n\t\t})\n\n\t\tt.Run(\"without an arch\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderPlatformCreateOptions{\n\t\t\t\tOS:       \"os\",\n\t\t\t\tArch:     \"\",\n\t\t\t\tShasum:   \"shasum\",\n\t\t\t\tFilename: \"filename\",\n\t\t\t}\n\n\t\t\tsadRpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options)\n\n\t\t\tassert.Nil(t, sadRpp)\n\t\t\tassert.EqualError(t, err, ErrRequiredArch.Error())\n\t\t})\n\n\t\tt.Run(\"without a shasum\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderPlatformCreateOptions{\n\t\t\t\tOS:       \"linux\",\n\t\t\t\tArch:     \"amd64\",\n\t\t\t\tShasum:   \"\",\n\t\t\t\tFilename: \"filename\",\n\t\t\t}\n\n\t\t\tsadRpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options)\n\n\t\t\tassert.Nil(t, sadRpp)\n\t\t\tassert.EqualError(t, err, ErrRequiredShasum.Error())\n\t\t})\n\n\t\tt.Run(\"without a filename\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderPlatformCreateOptions{\n\t\t\t\tOS:       \"linux\",\n\t\t\t\tArch:     \"amd64\",\n\t\t\t\tShasum:   \"shasum\",\n\t\t\t\tFilename: \"\",\n\t\t\t}\n\n\t\t\tsadRpp, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options)\n\n\t\t\tassert.Nil(t, sadRpp)\n\t\t\tassert.EqualError(t, err, ErrRequiredFilename.Error())\n\t\t})\n\n\t\tt.Run(\"with a public provider\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderPlatformCreateOptions{\n\t\t\t\tOS:       \"linux\",\n\t\t\t\tArch:     \"amd64\",\n\t\t\t\tShasum:   \"shasum\",\n\t\t\t\tFilename: \"filename\",\n\t\t\t}\n\n\t\t\tversionID = RegistryProviderVersionID{\n\t\t\t\tRegistryProviderID: RegistryProviderID{\n\t\t\t\t\tOrganizationName: provider.Organization.Name,\n\t\t\t\t\tRegistryName:     PublicRegistry,\n\t\t\t\t\tNamespace:        provider.Namespace,\n\t\t\t\t\tName:             provider.Name,\n\t\t\t\t},\n\t\t\t\tVersion: version.Version,\n\t\t\t}\n\n\t\t\trm, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrRequiredPrivateRegistry.Error())\n\t\t})\n\n\t\tt.Run(\"without a valid registry provider version id\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderPlatformCreateOptions{\n\t\t\t\tOS:       \"linux\",\n\t\t\t\tArch:     \"amd64\",\n\t\t\t\tShasum:   \"shasum\",\n\t\t\t\tFilename: \"filename\",\n\t\t\t}\n\n\t\t\tversionID = RegistryProviderVersionID{\n\t\t\t\tRegistryProviderID: RegistryProviderID{\n\t\t\t\t\tOrganizationName: badIdentifier,\n\t\t\t\t\tRegistryName:     provider.RegistryName,\n\t\t\t\t\tNamespace:        provider.Namespace,\n\t\t\t\t\tName:             provider.Name,\n\t\t\t\t},\n\t\t\t\tVersion: version.Version,\n\t\t\t}\n\n\t\t\trm, err := client.RegistryProviderPlatforms.Create(ctx, versionID, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t\t})\n\t})\n}\n\nfunc TestRegistryProviderPlatformsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tprovider, providerCleanup := createRegistryProvider(t, client, nil, PrivateRegistry)\n\tdefer providerCleanup()\n\n\tversion, versionCleanup := createRegistryProviderVersion(t, client, provider)\n\tdefer versionCleanup()\n\n\tversionID := RegistryProviderVersionID{\n\t\tRegistryProviderID: RegistryProviderID{\n\t\t\tOrganizationName: provider.Organization.Name,\n\t\t\tRegistryName:     provider.RegistryName,\n\t\t\tNamespace:        provider.Namespace,\n\t\t\tName:             provider.Name,\n\t\t},\n\t\tVersion: version.Version,\n\t}\n\n\tt.Run(\"with a valid version\", func(t *testing.T) {\n\t\tplatform, _ := createRegistryProviderPlatform(t, client, provider, version, \"\", \"\")\n\n\t\tplatformID := RegistryProviderPlatformID{\n\t\t\tRegistryProviderVersionID: versionID,\n\t\t\tOS:                        platform.OS,\n\t\t\tArch:                      platform.Arch,\n\t\t}\n\n\t\terr := client.RegistryProviderPlatforms.Delete(ctx, platformID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with a non-existent version\", func(t *testing.T) {\n\t\tplatformID := RegistryProviderPlatformID{\n\t\t\tRegistryProviderVersionID: versionID,\n\t\t\tOS:                        \"linux\",\n\t\t\tArch:                      \"amd64\",\n\t\t}\n\n\t\terr := client.RegistryProviderPlatforms.Delete(ctx, platformID)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestRegistryProviderPlatformsRead(t *testing.T) {\n\tt.Parallel()\n\tt.Skip()\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tprovider, providerCleanup := createRegistryProvider(t, client, nil, PrivateRegistry)\n\tdefer providerCleanup()\n\n\tproviderID := RegistryProviderID{\n\t\tOrganizationName: provider.Organization.Name,\n\t\tNamespace:        provider.Namespace,\n\t\tName:             provider.Name,\n\t\tRegistryName:     provider.RegistryName,\n\t}\n\n\tversion, versionCleanup := createRegistryProviderVersion(t, client, provider)\n\tdefer versionCleanup()\n\n\tversionID := RegistryProviderVersionID{\n\t\tRegistryProviderID: providerID,\n\t\tVersion:            version.Version,\n\t}\n\n\tplatform, platformCleanup := createRegistryProviderPlatform(t, client, provider, version, \"\", \"\")\n\tdefer platformCleanup()\n\n\tt.Run(\"with valid platform\", func(t *testing.T) {\n\t\tplatformID := RegistryProviderPlatformID{\n\t\t\tRegistryProviderVersionID: versionID,\n\t\t\tOS:                        platform.OS,\n\t\t\tArch:                      platform.Arch,\n\t\t}\n\n\t\treadPlatform, err := client.RegistryProviderPlatforms.Read(ctx, platformID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, platformID.OS, readPlatform.OS)\n\t\tassert.Equal(t, platformID.Arch, readPlatform.Arch)\n\t\tassert.Equal(t, platform.Filename, readPlatform.Filename)\n\t\tassert.Equal(t, platform.Shasum, readPlatform.Shasum)\n\t\tassert.Equal(t, platform.ProviderBinaryUploaded, readPlatform.ProviderBinaryUploaded)\n\n\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, platform.RegistryProviderVersion.ID, readPlatform.RegistryProviderVersion.ID)\n\t\t})\n\n\t\tt.Run(\"includes provider binary upload link\", func(t *testing.T) {\n\t\t\texpectedLinks := []string{\n\t\t\t\t\"provider-binary-upload\",\n\t\t\t}\n\t\t\tfor _, l := range expectedLinks {\n\t\t\t\t_, ok := readPlatform.Links[l].(string)\n\t\t\t\tassert.True(t, ok, \"Expect upload link: %s\", l)\n\t\t\t}\n\t\t})\n\t})\n\n\tt.Run(\"with non-existent os\", func(t *testing.T) {\n\t\tplatformID := RegistryProviderPlatformID{\n\t\t\tRegistryProviderVersionID: versionID,\n\t\t\tOS:                        \"DoesNotExist\",\n\t\t\tArch:                      platform.Arch,\n\t\t}\n\n\t\t_, err := client.RegistryProviderPlatforms.Read(ctx, platformID)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"with non-existent arch\", func(t *testing.T) {\n\t\tplatformID := RegistryProviderPlatformID{\n\t\t\tRegistryProviderVersionID: versionID,\n\t\t\tOS:                        platform.OS,\n\t\t\tArch:                      \"DoesNotExist\",\n\t\t}\n\n\t\t_, err := client.RegistryProviderPlatforms.Read(ctx, platformID)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestRegistryProviderPlatformsList(t *testing.T) {\n\tt.Parallel()\n\tt.Skip()\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"with platforms\", func(t *testing.T) {\n\t\tprovider, providerCleanup := createRegistryProvider(t, client, nil, PrivateRegistry)\n\t\tdefer providerCleanup()\n\n\t\tversion, versionCleanup := createRegistryProviderVersion(t, client, provider)\n\t\tdefer versionCleanup()\n\n\t\tosl := []string{\"linux\", \"darwin\", \"windows\"}\n\t\tarchl := []string{\"amd64\", \"arm64\", \"amd64\"}\n\n\t\tplatforms := make([]*RegistryProviderPlatform, 0)\n\t\tfor i, os := range osl {\n\t\t\tplatform, _ := createRegistryProviderPlatform(t, client, provider, version, os, archl[i])\n\t\t\tplatforms = append(platforms, platform)\n\t\t}\n\t\tnumPlatforms := len(platforms)\n\n\t\tproviderID := RegistryProviderID{\n\t\t\tOrganizationName: provider.Organization.Name,\n\t\t\tNamespace:        provider.Namespace,\n\t\t\tName:             provider.Name,\n\t\t\tRegistryName:     provider.RegistryName,\n\t\t}\n\t\tversionID := RegistryProviderVersionID{\n\t\t\tRegistryProviderID: providerID,\n\t\t\tVersion:            version.Version,\n\t\t}\n\n\t\tt.Run(\"with no list options\", func(t *testing.T) {\n\t\t\treturnedPlatforms, err := client.RegistryProviderPlatforms.List(ctx, versionID, nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Len(t, returnedPlatforms.Items, numPlatforms)\n\t\t\tassert.Equal(t, 1, returnedPlatforms.TotalPages)\n\t\t\tfor _, rp := range returnedPlatforms.Items {\n\t\t\t\tfoundPlatform := false\n\t\t\t\tfor _, p := range platforms {\n\t\t\t\t\tif rp.ID == p.ID {\n\t\t\t\t\t\tfoundPlatform = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassert.True(t, foundPlatform, \"Expected to find platform %s but did not:\\nexpected:\\n%v\\nreturned\\n%v\", rp.ID, platforms, returnedPlatforms)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t\treturnedPlatforms, err := client.RegistryProviderPlatforms.List(ctx, versionID, &RegistryProviderPlatformListOptions{\n\t\t\t\tListOptions: ListOptions{\n\t\t\t\t\tPageNumber: 999,\n\t\t\t\t\tPageSize:   100,\n\t\t\t\t},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Len(t, returnedPlatforms.Items, 0)\n\t\t\tassert.Equal(t, 999, returnedPlatforms.CurrentPage)\n\t\t\tassert.Equal(t, numPlatforms, returnedPlatforms.TotalCount)\n\t\t})\n\t})\n\n\tt.Run(\"without platforms\", func(t *testing.T) {\n\t\tprovider, providerCleanup := createRegistryProvider(t, client, nil, PrivateRegistry)\n\t\tdefer providerCleanup()\n\n\t\tversion, versionCleanup := createRegistryProviderVersion(t, client, provider)\n\t\tdefer versionCleanup()\n\n\t\tversionID := RegistryProviderVersionID{\n\t\t\tRegistryProviderID: RegistryProviderID{\n\t\t\t\tOrganizationName: provider.Organization.Name,\n\t\t\t\tNamespace:        provider.Namespace,\n\t\t\t\tName:             provider.Name,\n\t\t\t\tRegistryName:     provider.RegistryName,\n\t\t\t},\n\t\t\tVersion: version.Version,\n\t\t}\n\t\tplatforms, err := client.RegistryProviderPlatforms.List(ctx, versionID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, platforms.Items)\n\t\tassert.Equal(t, 0, platforms.TotalCount)\n\t\tassert.Equal(t, 0, platforms.TotalPages)\n\t})\n}\n"
  },
  {
    "path": "registry_provider_version.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ RegistryProviderVersions = (*registryProviderVersions)(nil)\n\n// RegistryProviderVersions describes the registry provider version methods that\n// the Terraform Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/provider-versions-platforms\ntype RegistryProviderVersions interface {\n\t// List all versions for a single provider.\n\tList(ctx context.Context, providerID RegistryProviderID, options *RegistryProviderVersionListOptions) (*RegistryProviderVersionList, error)\n\n\t// Create a registry provider version.\n\tCreate(ctx context.Context, providerID RegistryProviderID, options RegistryProviderVersionCreateOptions) (*RegistryProviderVersion, error)\n\n\t// Read a registry provider version.\n\tRead(ctx context.Context, versionID RegistryProviderVersionID) (*RegistryProviderVersion, error)\n\n\t// Delete a registry provider version.\n\tDelete(ctx context.Context, versionID RegistryProviderVersionID) error\n}\n\n// registryProvidersVersions implements RegistryProvidersVersions\ntype registryProviderVersions struct {\n\tclient *Client\n}\n\n// RegistryProviderVersion represents a registry provider version\ntype RegistryProviderVersion struct {\n\tID                 string                             `jsonapi:\"primary,registry-provider-versions\"`\n\tVersion            string                             `jsonapi:\"attr,version\"`\n\tCreatedAt          string                             `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt          string                             `jsonapi:\"attr,updated-at,iso8601\"`\n\tKeyID              string                             `jsonapi:\"attr,key-id\"`\n\tProtocols          []string                           `jsonapi:\"attr,protocols\"`\n\tPermissions        RegistryProviderVersionPermissions `jsonapi:\"attr,permissions\"`\n\tShasumsUploaded    bool                               `jsonapi:\"attr,shasums-uploaded\"`\n\tShasumsSigUploaded bool                               `jsonapi:\"attr,shasums-sig-uploaded\"`\n\n\t// Relations\n\tRegistryProvider          *RegistryProvider           `jsonapi:\"relation,registry-provider\"`\n\tRegistryProviderPlatforms []*RegistryProviderPlatform `jsonapi:\"relation,platforms\"`\n\n\t// Links\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n}\n\n// RegistryProviderVersionID is the multi key ID for addressing a version provider\ntype RegistryProviderVersionID struct {\n\tRegistryProviderID\n\tVersion string\n}\n\ntype RegistryProviderVersionPermissions struct {\n\tCanDelete      bool `jsonapi:\"attr,can-delete\"`\n\tCanUploadAsset bool `jsonapi:\"attr,can-upload-asset\"`\n}\n\ntype RegistryProviderVersionList struct {\n\t*Pagination\n\tItems []*RegistryProviderVersion\n}\n\ntype RegistryProviderVersionListOptions struct {\n\tListOptions\n}\n\ntype RegistryProviderVersionCreateOptions struct {\n\t// Required: A valid semver version string.\n\tVersion string `jsonapi:\"attr,version\"`\n\n\t// Required: A valid gpg-key string.\n\tKeyID string `jsonapi:\"attr,key-id\"`\n\n\t// Required: An array of Terraform provider API versions that this version supports.\n\tProtocols []string `jsonapi:\"attr,protocols\"`\n}\n\n// List registry provider versions\nfunc (r *registryProviderVersions) List(ctx context.Context, providerID RegistryProviderID, options *RegistryProviderVersionListOptions) (*RegistryProviderVersionList, error) {\n\tif err := providerID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers/%s/%s/%s/versions\",\n\t\turl.PathEscape(providerID.OrganizationName),\n\t\turl.PathEscape(string(providerID.RegistryName)),\n\t\turl.PathEscape(providerID.Namespace),\n\t\turl.PathEscape(providerID.Name),\n\t)\n\treq, err := r.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpvl := &RegistryProviderVersionList{}\n\terr = req.Do(ctx, pvl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pvl, nil\n}\n\n// Create a registry provider version\nfunc (r *registryProviderVersions) Create(ctx context.Context, providerID RegistryProviderID, options RegistryProviderVersionCreateOptions) (*RegistryProviderVersion, error) {\n\tif err := providerID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif providerID.RegistryName != PrivateRegistry {\n\t\treturn nil, ErrRequiredPrivateRegistry\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers/%s/%s/%s/versions\",\n\t\turl.PathEscape(providerID.OrganizationName),\n\t\turl.PathEscape(string(providerID.RegistryName)),\n\t\turl.PathEscape(providerID.Namespace),\n\t\turl.PathEscape(providerID.Name),\n\t)\n\treq, err := r.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprvv := &RegistryProviderVersion{}\n\terr = req.Do(ctx, prvv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn prvv, nil\n}\n\n// Read a registry provider version\nfunc (r *registryProviderVersions) Read(ctx context.Context, versionID RegistryProviderVersionID) (*RegistryProviderVersion, error) {\n\tif err := versionID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers/%s/%s/%s/versions/%s\",\n\t\turl.PathEscape(versionID.OrganizationName),\n\t\turl.PathEscape(string(versionID.RegistryName)),\n\t\turl.PathEscape(versionID.Namespace),\n\t\turl.PathEscape(versionID.Name),\n\t\turl.PathEscape(versionID.Version),\n\t)\n\treq, err := r.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprvv := &RegistryProviderVersion{}\n\terr = req.Do(ctx, prvv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn prvv, nil\n}\n\n// Delete a registry provider version\nfunc (r *registryProviderVersions) Delete(ctx context.Context, versionID RegistryProviderVersionID) error {\n\tif err := versionID.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/registry-providers/%s/%s/%s/versions/%s\",\n\t\turl.PathEscape(versionID.OrganizationName),\n\t\turl.PathEscape(string(versionID.RegistryName)),\n\t\turl.PathEscape(versionID.Namespace),\n\t\turl.PathEscape(versionID.Name),\n\t\turl.PathEscape(versionID.Version),\n\t)\n\treq, err := r.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// ShasumsUploadURL returns the upload URL to upload shasums if one is available\nfunc (v *RegistryProviderVersion) ShasumsUploadURL() (string, error) {\n\tuploadURL, ok := v.Links[\"shasums-upload\"].(string)\n\tif !ok {\n\t\treturn uploadURL, fmt.Errorf(\"the Registry Provider Version does not contain a shasums upload link\")\n\t}\n\tif uploadURL == \"\" {\n\t\treturn uploadURL, fmt.Errorf(\"the Registry Provider Version shasums upload URL is empty\")\n\t}\n\treturn uploadURL, nil\n}\n\n// ShasumsSigUploadURL returns the URL to upload a shasums sig\nfunc (v *RegistryProviderVersion) ShasumsSigUploadURL() (string, error) {\n\tuploadURL, ok := v.Links[\"shasums-sig-upload\"].(string)\n\tif !ok {\n\t\treturn uploadURL, fmt.Errorf(\"the Registry Provider Version does not contain a shasums sig upload link\")\n\t}\n\tif uploadURL == \"\" {\n\t\treturn uploadURL, fmt.Errorf(\"the Registry Provider Version shasums sig upload URL is empty\")\n\t}\n\treturn uploadURL, nil\n}\n\n// ShasumsDownloadURL returns the URL to download the shasums for the registry version\nfunc (v *RegistryProviderVersion) ShasumsDownloadURL() (string, error) {\n\tdownloadURL, ok := v.Links[\"shasums-download\"].(string)\n\tif !ok {\n\t\treturn downloadURL, fmt.Errorf(\"the Registry Provider Version does not contain a shasums download link\")\n\t}\n\tif downloadURL == \"\" {\n\t\treturn downloadURL, fmt.Errorf(\"the Registry Provider Version shasums download URL is empty\")\n\t}\n\treturn downloadURL, nil\n}\n\n// ShasumsSigDownloadURL returns the URL to download the shasums sig for the registry version\nfunc (v *RegistryProviderVersion) ShasumsSigDownloadURL() (string, error) {\n\tdownloadURL, ok := v.Links[\"shasums-sig-download\"].(string)\n\tif !ok {\n\t\treturn downloadURL, fmt.Errorf(\"the Registry Provider Version does not contain a shasums sig download link\")\n\t}\n\tif downloadURL == \"\" {\n\t\treturn downloadURL, fmt.Errorf(\"the Registry Provider Version shasums sig download URL is empty\")\n\t}\n\treturn downloadURL, nil\n}\n\nfunc (id RegistryProviderVersionID) valid() error {\n\tif !validStringID(&id.Version) {\n\t\treturn ErrInvalidVersion\n\t}\n\tif id.RegistryName != PrivateRegistry {\n\t\treturn ErrRequiredPrivateRegistry\n\t}\n\tif err := id.RegistryProviderID.valid(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (o *RegistryProviderVersionListOptions) valid() error {\n\treturn nil\n}\n\nfunc (o RegistryProviderVersionCreateOptions) valid() error {\n\tif !validStringID(&o.Version) {\n\t\treturn ErrInvalidVersion\n\t}\n\tif !validStringID(&o.KeyID) {\n\t\treturn ErrInvalidKeyID\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "registry_provider_version_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRegistryProviderVersionsIDValidation(t *testing.T) {\n\tt.Parallel()\n\tversion := \"1.0.0\"\n\tvalidRegistryProviderID := RegistryProviderID{\n\t\tOrganizationName: \"orgName\",\n\t\tRegistryName:     PrivateRegistry,\n\t\tNamespace:        \"namespace\",\n\t\tName:             \"name\",\n\t}\n\tinvalidRegistryProviderID := RegistryProviderID{\n\t\tOrganizationName: badIdentifier,\n\t\tRegistryName:     PrivateRegistry,\n\t\tNamespace:        \"namespace\",\n\t\tName:             \"name\",\n\t}\n\tpublicRegistryProviderID := RegistryProviderID{\n\t\tOrganizationName: \"orgName\",\n\t\tRegistryName:     PublicRegistry,\n\t\tNamespace:        \"namespace\",\n\t\tName:             \"name\",\n\t}\n\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tid := RegistryProviderVersionID{\n\t\t\tVersion:            version,\n\t\t\tRegistryProviderID: validRegistryProviderID,\n\t\t}\n\t\trequire.NoError(t, id.valid())\n\t})\n\n\tt.Run(\"without a version\", func(t *testing.T) {\n\t\tid := RegistryProviderVersionID{\n\t\t\tVersion:            \"\",\n\t\t\tRegistryProviderID: validRegistryProviderID,\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrInvalidVersion.Error())\n\t})\n\n\tt.Run(\"without a key-id\", func(t *testing.T) {\n\t\tid := RegistryProviderVersionID{\n\t\t\tVersion:            \"\",\n\t\t\tRegistryProviderID: validRegistryProviderID,\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrInvalidVersion.Error())\n\t})\n\n\tt.Run(\"invalid version\", func(t *testing.T) {\n\t\tt.Skip(\"This is skipped as we don't actually validate version is a valid semver - the registry does this validation\")\n\t\tid := RegistryProviderVersionID{\n\t\t\tVersion:            \"foo\",\n\t\t\tRegistryProviderID: validRegistryProviderID,\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrInvalidVersion.Error())\n\t})\n\n\tt.Run(\"invalid registry for parent provider\", func(t *testing.T) {\n\t\tid := RegistryProviderVersionID{\n\t\t\tVersion:            version,\n\t\t\tRegistryProviderID: publicRegistryProviderID,\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrRequiredPrivateRegistry.Error())\n\t})\n\n\tt.Run(\"without a valid registry provider id\", func(t *testing.T) {\n\t\t// this is a proxy for all permutations of an invalid registry provider id\n\t\t// it is assumed that validity of the registry provider id is delegated to its own valid method\n\t\tid := RegistryProviderVersionID{\n\t\t\tVersion:            version,\n\t\t\tRegistryProviderID: invalidRegistryProviderID,\n\t\t}\n\t\tassert.EqualError(t, id.valid(), ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestRegistryProviderVersionsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tproviderTest, providerTestCleanup := createRegistryProvider(t, client, nil, PrivateRegistry)\n\tdefer providerTestCleanup()\n\n\tproviderID := RegistryProviderID{\n\t\tOrganizationName: providerTest.Organization.Name,\n\t\tRegistryName:     providerTest.RegistryName,\n\t\tNamespace:        providerTest.Namespace,\n\t\tName:             providerTest.Name,\n\t}\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := RegistryProviderVersionCreateOptions{\n\t\t\tVersion: \"1.0.0\",\n\t\t\tKeyID:   \"abcdefg\",\n\t\t}\n\t\tprvv, err := client.RegistryProviderVersions.Create(ctx, providerID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, prvv.ID)\n\t\tassert.Equal(t, options.Version, prvv.Version)\n\t\tassert.Equal(t, options.KeyID, prvv.KeyID)\n\n\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, providerTest.ID, prvv.RegistryProvider.ID)\n\t\t})\n\n\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, prvv.CreatedAt)\n\t\t\tassert.NotEmpty(t, prvv.UpdatedAt)\n\t\t})\n\n\t\tt.Run(\"includes upload links\", func(t *testing.T) {\n\t\t\t_, err := prvv.ShasumsUploadURL()\n\t\t\trequire.NoError(t, err)\n\t\t\t_, err = prvv.ShasumsSigUploadURL()\n\t\t\trequire.NoError(t, err)\n\t\t\texpectedLinks := []string{\n\t\t\t\t\"shasums-upload\",\n\t\t\t\t\"shasums-sig-upload\",\n\t\t\t}\n\t\t\tfor _, l := range expectedLinks {\n\t\t\t\t_, ok := prvv.Links[l].(string)\n\t\t\t\tassert.True(t, ok, \"Expect upload link: %s\", l)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"doesn't include download links\", func(t *testing.T) {\n\t\t\t_, err := prvv.ShasumsDownloadURL()\n\t\t\tassert.Error(t, err)\n\t\t\t_, err = prvv.ShasumsSigDownloadURL()\n\t\t\tassert.Error(t, err)\n\t\t})\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\tt.Run(\"without a version\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderVersionCreateOptions{\n\t\t\t\tVersion: \"\",\n\t\t\t\tKeyID:   \"abcdefg\",\n\t\t\t}\n\t\t\trm, err := client.RegistryProviderVersions.Create(ctx, providerID, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrInvalidVersion.Error())\n\t\t})\n\n\t\tt.Run(\"without a key-id\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderVersionCreateOptions{\n\t\t\t\tVersion: \"1.0.0\",\n\t\t\t\tKeyID:   \"\",\n\t\t\t}\n\t\t\trm, err := client.RegistryProviderVersions.Create(ctx, providerID, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrInvalidKeyID.Error())\n\t\t})\n\n\t\tt.Run(\"with a public provider\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderVersionCreateOptions{\n\t\t\t\tVersion: \"1.0.0\",\n\t\t\t\tKeyID:   \"abcdefg\",\n\t\t\t}\n\t\t\tproviderID := RegistryProviderID{\n\t\t\t\tOrganizationName: providerTest.Organization.Name,\n\t\t\t\tRegistryName:     PublicRegistry,\n\t\t\t\tNamespace:        providerTest.Namespace,\n\t\t\t\tName:             providerTest.Name,\n\t\t\t}\n\t\t\trm, err := client.RegistryProviderVersions.Create(ctx, providerID, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrRequiredPrivateRegistry.Error())\n\t\t})\n\n\t\tt.Run(\"without a valid provider id\", func(t *testing.T) {\n\t\t\toptions := RegistryProviderVersionCreateOptions{\n\t\t\t\tVersion: \"1.0.0\",\n\t\t\t\tKeyID:   \"abcdefg\",\n\t\t\t}\n\t\t\tproviderID := RegistryProviderID{\n\t\t\t\tOrganizationName: badIdentifier,\n\t\t\t\tRegistryName:     providerTest.RegistryName,\n\t\t\t\tNamespace:        providerTest.Namespace,\n\t\t\t\tName:             providerTest.Name,\n\t\t\t}\n\t\t\trm, err := client.RegistryProviderVersions.Create(ctx, providerID, options)\n\t\t\tassert.Nil(t, rm)\n\t\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t\t})\n\t})\n}\n\nfunc TestRegistryProviderVersionsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"with versions\", func(t *testing.T) {\n\t\tprovider, providerCleanup := createRegistryProvider(t, client, nil, PrivateRegistry)\n\t\tdefer providerCleanup()\n\n\t\tcreateN := 10\n\t\tversions := make([]*RegistryProviderVersion, 0)\n\t\t// these providers will be destroyed when the org is cleaned up\n\t\tfor i := 0; i < createN; i++ {\n\t\t\tversion, _ := createRegistryProviderVersion(t, client, provider)\n\t\t\tversions = append(versions, version)\n\t\t}\n\t\tversionN := len(versions)\n\n\t\tproviderID := RegistryProviderID{\n\t\t\tOrganizationName: provider.Organization.Name,\n\t\t\tNamespace:        provider.Namespace,\n\t\t\tName:             provider.Name,\n\t\t\tRegistryName:     provider.RegistryName,\n\t\t}\n\n\t\tt.Run(\"returns all versions\", func(t *testing.T) {\n\t\t\treturnedVersions, err := client.RegistryProviderVersions.List(ctx, providerID, &RegistryProviderVersionListOptions{\n\t\t\t\tListOptions: ListOptions{\n\t\t\t\t\tPageNumber: 0,\n\t\t\t\t\tPageSize:   versionN,\n\t\t\t\t},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotEmpty(t, returnedVersions.Items)\n\t\t\tassert.Equal(t, versionN, returnedVersions.TotalCount)\n\t\t\tassert.Equal(t, 1, returnedVersions.TotalPages)\n\t\t\tfor _, rv := range returnedVersions.Items {\n\t\t\t\tfoundVersion := false\n\t\t\t\tfor _, v := range versions {\n\t\t\t\t\tif rv.ID == v.ID {\n\t\t\t\t\t\tfoundVersion = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassert.True(t, foundVersion, \"Expected to find version %s but did not:\\nexpected:\\n%v\\nreturned\\n%v\", rv.ID, versions, returnedVersions)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"returns pages\", func(t *testing.T) {\n\t\t\tpageN := 2\n\t\t\tpageSize := versionN / pageN\n\n\t\t\tfor page := 0; page < pageN; page++ {\n\t\t\t\ttestName := fmt.Sprintf(\"returns page %d of versions\", page)\n\t\t\t\tt.Run(testName, func(t *testing.T) {\n\t\t\t\t\treturnedVersions, err := client.RegistryProviderVersions.List(ctx, providerID, &RegistryProviderVersionListOptions{\n\t\t\t\t\t\tListOptions: ListOptions{\n\t\t\t\t\t\t\tPageNumber: page,\n\t\t\t\t\t\t\tPageSize:   pageSize,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.NotEmpty(t, returnedVersions.Items)\n\n\t\t\t\t\tassert.Equal(t, versionN, returnedVersions.TotalCount)\n\t\t\t\t\tassert.Equal(t, pageN, returnedVersions.TotalPages)\n\t\t\t\t\tassert.Equal(t, pageSize, len(returnedVersions.Items))\n\t\t\t\t\tfor _, rv := range returnedVersions.Items {\n\t\t\t\t\t\tfoundVersion := false\n\t\t\t\t\t\tfor _, v := range versions {\n\t\t\t\t\t\t\tif rv.ID == v.ID {\n\t\t\t\t\t\t\t\tfoundVersion = true\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tassert.True(t, foundVersion, \"Expected to find version %s but did not:\\nexpected:\\n%v\\nreturned\\n%v\", rv.ID, versions, returnedVersions)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t})\n\n\tt.Run(\"without versions\", func(t *testing.T) {\n\t\tprovider, providerCleanup := createRegistryProvider(t, client, nil, PrivateRegistry)\n\t\tdefer providerCleanup()\n\n\t\tproviderID := RegistryProviderID{\n\t\t\tOrganizationName: provider.Organization.Name,\n\t\t\tNamespace:        provider.Namespace,\n\t\t\tName:             provider.Name,\n\t\t\tRegistryName:     provider.RegistryName,\n\t\t}\n\n\t\tversions, err := client.RegistryProviderVersions.List(ctx, providerID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, versions.Items)\n\t\tassert.Equal(t, 0, versions.TotalCount)\n\t\tassert.Equal(t, 0, versions.TotalPages)\n\t})\n}\n\nfunc TestRegistryProviderVersionsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tprovider, providerCleanup := createRegistryProvider(t, client, nil, PrivateRegistry)\n\tdefer providerCleanup()\n\n\tt.Run(\"with valid version\", func(t *testing.T) {\n\t\tversion, _ := createRegistryProviderVersion(t, client, provider)\n\n\t\tversionID := RegistryProviderVersionID{\n\t\t\tRegistryProviderID: RegistryProviderID{\n\t\t\t\tOrganizationName: version.RegistryProvider.Organization.Name,\n\t\t\t\tRegistryName:     version.RegistryProvider.RegistryName,\n\t\t\t\tNamespace:        version.RegistryProvider.Namespace,\n\t\t\t\tName:             version.RegistryProvider.Name,\n\t\t\t},\n\t\t\tVersion: version.Version,\n\t\t}\n\n\t\terr := client.RegistryProviderVersions.Delete(ctx, versionID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with non existing version\", func(t *testing.T) {\n\t\tversionID := RegistryProviderVersionID{\n\t\t\tRegistryProviderID: RegistryProviderID{\n\t\t\t\tOrganizationName: provider.Organization.Name,\n\t\t\t\tRegistryName:     provider.RegistryName,\n\t\t\t\tNamespace:        provider.Namespace,\n\t\t\t\tName:             provider.Name,\n\t\t\t},\n\t\t\tVersion: \"1.0.0\",\n\t\t}\n\n\t\terr := client.RegistryProviderVersions.Delete(ctx, versionID)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestRegistryProviderVersionsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"with valid version\", func(t *testing.T) {\n\t\tversion, versionCleanup := createRegistryProviderVersion(t, client, nil)\n\t\tdefer versionCleanup()\n\n\t\tversionID := RegistryProviderVersionID{\n\t\t\tRegistryProviderID: RegistryProviderID{\n\t\t\t\tOrganizationName: version.RegistryProvider.Organization.Name,\n\t\t\t\tRegistryName:     version.RegistryProvider.RegistryName,\n\t\t\t\tNamespace:        version.RegistryProvider.Namespace,\n\t\t\t\tName:             version.RegistryProvider.Name,\n\t\t\t},\n\t\t\tVersion: version.Version,\n\t\t}\n\n\t\treadVersion, err := client.RegistryProviderVersions.Read(ctx, versionID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, version.ID, readVersion.ID)\n\t\tassert.Equal(t, version.Version, readVersion.Version)\n\t\tassert.Equal(t, version.KeyID, readVersion.KeyID)\n\n\t\tt.Run(\"relationships are properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, version.RegistryProvider.ID, readVersion.RegistryProvider.ID)\n\t\t})\n\n\t\tt.Run(\"timestamps are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, readVersion.CreatedAt)\n\t\t\tassert.NotEmpty(t, readVersion.UpdatedAt)\n\t\t})\n\n\t\tt.Run(\"includes upload links\", func(t *testing.T) {\n\t\t\texpectedLinks := []string{\n\t\t\t\t\"shasums-upload\",\n\t\t\t\t\"shasums-sig-upload\",\n\t\t\t}\n\t\t\tfor _, l := range expectedLinks {\n\t\t\t\t_, ok := readVersion.Links[l].(string)\n\t\t\t\tassert.True(t, ok, \"Expect upload link: %s\", l)\n\t\t\t}\n\t\t})\n\t})\n\n\tt.Run(\"with non existing version\", func(t *testing.T) {\n\t\tprovider, providerCleanup := createRegistryProvider(t, client, nil, PrivateRegistry)\n\t\tdefer providerCleanup()\n\n\t\tversionID := RegistryProviderVersionID{\n\t\t\tRegistryProviderID: RegistryProviderID{\n\t\t\t\tOrganizationName: provider.Organization.Name,\n\t\t\t\tRegistryName:     provider.RegistryName,\n\t\t\t\tNamespace:        provider.Namespace,\n\t\t\t\tName:             provider.Name,\n\t\t\t},\n\t\t\tVersion: \"1.0.0\",\n\t\t}\n\n\t\t_, err := client.RegistryProviderVersions.Read(ctx, versionID)\n\t\tassert.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "request.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n\t\"golang.org/x/time/rate\"\n)\n\n// ClientRequest encapsulates a request sent by the Client\ntype ClientRequest struct {\n\tretryableRequest *retryablehttp.Request\n\thttp             *retryablehttp.Client\n\tlimiter          *rate.Limiter\n\n\t// Header are the headers that will be sent in this request\n\tHeader http.Header\n}\n\nfunc (r ClientRequest) Do(ctx context.Context, model interface{}) error {\n\t// Wait will block until the limiter can obtain a new token\n\t// or returns an error if the given context is canceled.\n\tif r.limiter != nil {\n\t\tif err := r.limiter.Wait(ctx); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// If the caller provided a response header hook then we'll call it\n\t// once we have a response.\n\trespHeaderHook, err := contextResponseHeaderHook(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Add the context to the request.\n\treqWithCxt := r.retryableRequest.WithContext(ctx)\n\n\t// Execute the request and check the response.\n\tresp, err := r.http.Do(reqWithCxt)\n\tif resp != nil {\n\t\t// We call the callback whenever there's any sort of response,\n\t\t// even if it's returned in conjunction with an error.\n\t\trespHeaderHook(resp.StatusCode, resp.Header)\n\t}\n\tif err != nil {\n\t\t// If we got an error, and the context has been canceled,\n\t\t// the context's error is probably more useful.\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tdefault:\n\t\t\treturn err\n\t\t}\n\t}\n\tdefer resp.Body.Close() //nolint:errcheck\n\n\t// Basic response checking.\n\tif err := checkResponseCode(resp); err != nil {\n\t\treturn err\n\t}\n\n\t// Return here if decoding the response isn't needed.\n\tif model == nil {\n\t\treturn nil\n\t}\n\n\t// If v implements io.Writer, write the raw response body.\n\tif w, ok := model.(io.Writer); ok {\n\t\t_, err := io.Copy(w, resp.Body)\n\t\treturn err\n\t}\n\n\treturn unmarshalResponse(resp.Body, model)\n}\n\n// DoJSON is similar to Do except that it should be used when a plain JSON response is expected\n// as opposed to json-api.\nfunc (r *ClientRequest) DoJSON(ctx context.Context, model any) error {\n\t// Wait will block until the limiter can obtain a new token\n\t// or returns an error if the given context is canceled.\n\tif r.limiter != nil {\n\t\tif err := r.limiter.Wait(ctx); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Add the context to the request.\n\tcontextReq := r.retryableRequest.WithContext(ctx)\n\n\t// If the caller provided a response header hook then we'll call it\n\t// once we have a response.\n\trespHeaderHook, err := contextResponseHeaderHook(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Execute the request and check the response.\n\tresp, err := r.http.Do(contextReq)\n\tif resp != nil {\n\t\t// We call the callback whenever there's any sort of response,\n\t\t// even if it's returned in conjunction with an error.\n\t\trespHeaderHook(resp.StatusCode, resp.Header)\n\t}\n\tif err != nil {\n\t\t// If we got an error, and the context has been canceled,\n\t\t// the context's error is probably more useful.\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tdefault:\n\t\t\treturn err\n\t\t}\n\t}\n\tdefer resp.Body.Close() //nolint:errcheck\n\n\tif resp.StatusCode < 200 || resp.StatusCode >= 400 {\n\t\treturn fmt.Errorf(\"error HTTP response: %d\", resp.StatusCode)\n\t} else if resp.StatusCode == 304 {\n\t\t// Got a \"Not Modified\" response, but we can't return a model because there is no response body.\n\t\t// This is necessary to support the IPRanges endpoint, which has the peculiar behavior\n\t\t// of not returning content but allowing a 304 response by optionally sending an\n\t\t// If-Modified-Since header.\n\t\treturn nil\n\t}\n\n\t// Return here if decoding the response isn't needed.\n\tif model == nil {\n\t\treturn nil\n\t}\n\n\t// If v implements io.Writer, write the raw response body.\n\tif w, ok := model.(io.Writer); ok {\n\t\t_, err := io.Copy(w, resp.Body)\n\t\treturn err\n\t}\n\n\treturn json.NewDecoder(resp.Body).Decode(model)\n}\n\n// DoRaw exposes the underlying io.ReadCloser for the response body.\n// The caller is responsible for closing the ReadCloser and unmarshaling the\n// results.\nfunc (r *ClientRequest) DoRaw(ctx context.Context) (io.ReadCloser, error) {\n\t// Wait will block until the limiter can obtain a new token\n\t// or returns an error if the given context is canceled.\n\tif r.limiter != nil {\n\t\tif err := r.limiter.Wait(ctx); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// Add the context to the request.\n\tcontextReq := r.retryableRequest.WithContext(ctx)\n\n\t// If the caller provided a response header hook then we'll call it\n\t// once we have a response.\n\trespHeaderHook, err := contextResponseHeaderHook(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Execute the request and check the response.\n\tresp, err := r.http.Do(contextReq)\n\tif resp != nil {\n\t\t// We call the callback whenever there's any sort of response,\n\t\t// even if it's returned in conjunction with an error.\n\t\trespHeaderHook(resp.StatusCode, resp.Header)\n\t}\n\tif err != nil {\n\t\t// If we got an error, and the context has been canceled,\n\t\t// the context's error is probably more useful.\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, ctx.Err()\n\t\tdefault:\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif resp.StatusCode < 200 || resp.StatusCode >= 400 {\n\t\t// Close the body here since we won't be returning it to the caller.\n\t\tresp.Body.Close() //nolint:errcheck\n\t\treturn nil, fmt.Errorf(\"error HTTP response: %d\", resp.StatusCode)\n\t} else if resp.StatusCode == 304 {\n\t\t// Got a \"Not Modified\" response, but we can't return a model because there is no response body.\n\t\t// This is necessary to support the IPRanges endpoint, which has the peculiar behavior\n\t\t// of not returning content but allowing a 304 response by optionally sending an\n\t\t// If-Modified-Since header.\n\t\treturn nil, nil\n\t}\n\n\treturn resp.Body, nil\n}\n"
  },
  {
    "path": "request_hooks.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// ContextWithResponseHeaderHook returns a context that will, if passed to\n// [ClientRequest.Do] or to any of the wrapper methods that call it, arrange\n// for the given callback to be called with the headers from the raw HTTP\n// response.\n//\n// This is intended for allowing callers to respond to out-of-band metadata\n// such as cache-control-related headers, rate limiting headers, etc. Hooks\n// must not modify the given [http.Header] or otherwise attempt to change how\n// the response is handled by [ClientRequest.Do].\n//\n// If the given context already has a response header hook then the returned\n// context will call both the existing hook and the newly-provided one, with\n// the newer being called first.\nfunc ContextWithResponseHeaderHook(parentCtx context.Context, cb func(status int, header http.Header)) context.Context {\n\t// If the given context already has a notification callback then we'll\n\t// arrange to notify both the previous and the new one. This is not\n\t// a super efficient way to achieve that but we expect it to be rare\n\t// for there to be more than one or two hooks associated with a particular\n\t// request, so it's not warranted to optimize this further.\n\texistingI := parentCtx.Value(contextResponseHeaderHookKey)\n\tfinalCb := cb\n\tif existingI != nil {\n\t\texisting, ok := existingI.(func(int, http.Header))\n\t\t// This explicit check-and-panic is redundant but required by our linter.\n\t\tif !ok {\n\t\t\tpanic(fmt.Sprintf(\"context has response header hook of invalid type %T\", existingI))\n\t\t}\n\t\tfinalCb = func(status int, header http.Header) {\n\t\t\tcb(status, header)\n\t\t\texisting(status, header)\n\t\t}\n\t}\n\treturn context.WithValue(parentCtx, contextResponseHeaderHookKey, finalCb)\n}\n\nfunc contextResponseHeaderHook(ctx context.Context) (func(int, http.Header), error) {\n\tcbI := ctx.Value(contextResponseHeaderHookKey)\n\tif cbI == nil {\n\t\t// Stub callback that does absolutely nothing, then.\n\t\treturn func(int, http.Header) {}, nil\n\t}\n\n\tcb, ok := cbI.(func(int, http.Header))\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"context has response header hook of invalid type %T\", cbI)\n\t}\n\n\treturn cb, nil\n}\n\n// contextResponseHeaderHookKey is the type of the internal key used to store\n// the callback for [ContextWithResponseHeaderHook] inside a [context.Context]\n// object.\ntype contextResponseHeaderHookKeyType struct{}\n\n// contextResponseHeaderHookKey is the internal key used to store the callback\n// for [ContextWithResponseHeaderHook] inside a [context.Context] object.\nvar contextResponseHeaderHookKey contextResponseHeaderHookKeyType\n"
  },
  {
    "path": "request_hooks_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestContextWithResponseHeaderHook(t *testing.T) {\n\tt.Parallel()\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"x-thingy\", \"boop\")\n\t\tw.WriteHeader(http.StatusNoContent)\n\t}))\n\tdefer server.Close()\n\n\tcfg := &Config{\n\t\tAddress:  server.URL,\n\t\tBasePath: \"/anything\",\n\t\tToken:    \"placeholder\",\n\t}\n\tclient, err := NewClient(cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcalled := false\n\tvar gotStatus int\n\tvar gotHeader http.Header\n\tctx := ContextWithResponseHeaderHook(context.Background(), func(status int, header http.Header) {\n\t\tcalled = true\n\t\tgotStatus = status\n\t\tgotHeader = header\n\t})\n\n\treq, err := client.NewRequest(\"GET\", \"boop\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = req.Do(ctx, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !called {\n\t\tt.Fatal(\"hook was not called\")\n\t}\n\tif got, want := gotStatus, http.StatusNoContent; got != want {\n\t\tt.Fatalf(\"wrong response status: got %d, want %d\", got, want)\n\t}\n\tif got, want := gotHeader.Get(\"x-thingy\"), \"boop\"; got != want {\n\t\tt.Fatalf(\"wrong value for x-thingy field: got %q, want %q\", got, want)\n\t}\n}\n"
  },
  {
    "path": "request_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype fixtureBody struct {\n\tID     string `json:\"id\"`\n\tName   string `json:\"name\"`\n\tMethod string `json:\"method\"`\n}\n\nfunc newTestRequest(r *retryablehttp.Request) ClientRequest {\n\theader := make(http.Header)\n\theader.Add(\"TestHeader\", \"test-header-value\")\n\n\treturn ClientRequest{\n\t\tretryableRequest: r,\n\t\thttp:             retryablehttp.NewClient(),\n\t\tHeader:           header,\n\t}\n}\n\nfunc TestClientRequest_DoJSON(t *testing.T) {\n\tt.Parallel()\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfakeBody := map[string]any{\n\t\t\t\"id\":     \"example\",\n\t\t\t\"name\":   \"fixture\",\n\t\t\t\"method\": r.Method,\n\t\t}\n\t\tfakeBodyRaw, err := json.Marshal(fakeBody)\n\t\trequire.NoError(t, err)\n\n\t\tif strings.HasSuffix(r.URL.String(), \"/ok_request\") {\n\t\t\tw.Header().Set(\"content-type\", \"application/json\")\n\t\t\tw.Header().Set(\"content-length\", strconv.FormatInt(int64(len(fakeBodyRaw)), 10))\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t_, err = w.Write(fakeBodyRaw)\n\t\t\trequire.NoError(t, err)\n\t\t} else if strings.HasSuffix(r.URL.String(), \"/bad_request\") {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t} else if strings.HasSuffix(r.URL.String(), \"/created_request\") {\n\t\t\tw.WriteHeader(http.StatusCreated)\n\t\t} else if strings.HasSuffix(r.URL.String(), \"/not_modified_request\") {\n\t\t\tw.WriteHeader(http.StatusNotModified)\n\t\t} else {\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t}\n\t}))\n\tt.Cleanup(func() {\n\t\ttestServer.Close()\n\t})\n\n\tt.Run(\"Success 200 responses\", func(t *testing.T) {\n\t\tr, err := retryablehttp.NewRequest(\"PUT\", fmt.Sprintf(\"%s/ok_request\", testServer.URL), nil)\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\n\t\trequest := newTestRequest(r)\n\t\tputResponseBody := &fixtureBody{}\n\t\terr = request.DoJSON(ctx, putResponseBody)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, \"example\", putResponseBody.ID)\n\t\tassert.Equal(t, \"fixture\", putResponseBody.Name)\n\t\tassert.Equal(t, \"PUT\", putResponseBody.Method)\n\t})\n\n\tt.Run(\"Success response with no body\", func(t *testing.T) {\n\t\tr, err := retryablehttp.NewRequest(\"POST\", fmt.Sprintf(\"%s/created_request\", testServer.URL), nil)\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\n\t\trequest := newTestRequest(r)\n\t\terr = request.DoJSON(ctx, nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"Not Modified response\", func(t *testing.T) {\n\t\tr, err := retryablehttp.NewRequest(\"POST\", fmt.Sprintf(\"%s/not_modified_request\", testServer.URL), nil)\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\n\t\trequest := newTestRequest(r)\n\t\tpostResponseBody := &fixtureBody{}\n\t\terr = request.DoJSON(ctx, postResponseBody)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Empty(t, postResponseBody.Method)\n\t\tassert.Empty(t, postResponseBody.ID)\n\t})\n\n\tt.Run(\"Bad 400 responses\", func(t *testing.T) {\n\t\tr, err := retryablehttp.NewRequest(\"POST\", fmt.Sprintf(\"%s/bad_request\", testServer.URL), nil)\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\n\t\trequest := newTestRequest(r)\n\t\tpostResponseBody := &fixtureBody{}\n\t\terr = request.DoJSON(ctx, postResponseBody)\n\n\t\t// body is empty (no response)\n\t\tassert.Empty(t, postResponseBody.Method)\n\t\tassert.Empty(t, postResponseBody.ID)\n\n\t\tassert.EqualError(t, err, \"error HTTP response: 400\")\n\t})\n}\n"
  },
  {
    "path": "reserved_tag_key.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ ReservedTagKeys = (*reservedTagKeys)(nil)\n\n// ReservedTagKeys describes all the reserved tag key endpoints that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/reserved-tag-keys\ntype ReservedTagKeys interface {\n\t// List all the reserved tag keys for the given organization.\n\tList(ctx context.Context, organization string, options *ReservedTagKeyListOptions) (*ReservedTagKeyList, error)\n\n\t// Create a new reserved tag key for the given organization.\n\tCreate(ctx context.Context, organization string, options ReservedTagKeyCreateOptions) (*ReservedTagKey, error)\n\n\t// Update the reserved tag key with the given ID.\n\tUpdate(ctx context.Context, reservedTagKeyID string, options ReservedTagKeyUpdateOptions) (*ReservedTagKey, error)\n\n\t// Delete the reserved tag key with the given ID.\n\tDelete(ctx context.Context, reservedTagKeyID string) error\n}\n\n// reservedTagKeys implements ReservedTagKeys.\ntype reservedTagKeys struct {\n\tclient *Client\n}\n\n// ReservedTagKeyList represents a list of reserved tag keys.\ntype ReservedTagKeyList struct {\n\t*Pagination\n\tItems []*ReservedTagKey\n}\n\n// ReservedTagKey represents a Terraform Enterprise reserved tag key.\ntype ReservedTagKey struct {\n\tID               string    `jsonapi:\"primary,reserved-tag-keys\"`\n\tKey              string    `jsonapi:\"attr,key\"`\n\tDisableOverrides bool      `jsonapi:\"attr,disable-overrides\"`\n\tCreatedAt        time.Time `jsonapi:\"attr,created_at,iso8601\"`\n\tUpdatedAt        time.Time `jsonapi:\"attr,updated_at,iso8601\"`\n}\n\n// ReservedTagKeyListOptions represents the options for listing reserved tag\n// keys.\ntype ReservedTagKeyListOptions struct {\n\tListOptions\n}\n\n// List all the reserved tag keys for the given organization.\nfunc (s *reservedTagKeys) List(ctx context.Context, organization string, options *ReservedTagKeyListOptions) (*ReservedTagKeyList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/reserved-tag-keys\", url.PathEscape(organization))\n\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trl := &ReservedTagKeyList{}\n\terr = req.Do(ctx, rl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rl, nil\n}\n\n// ReservedTagKeyCreateOptions represents the options for creating a\n// reserved tag key.\ntype ReservedTagKeyCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,reserved-tag-keys\"`\n\n\t// Required: The reserved tag key's key string.\n\tKey string `jsonapi:\"attr,key\"`\n\n\t// Optional: When true, project tag bindings that match this reserved tag key can not\n\t// be overridden at the workspace level.\n\tDisableOverrides *bool `jsonapi:\"attr,disable-overrides,omitempty\"`\n}\n\nfunc (o ReservedTagKeyCreateOptions) valid() error {\n\tif !validString(&o.Key) {\n\t\treturn ErrRequiredKey\n\t}\n\treturn nil\n}\n\n// Create a reserved tag key.\nfunc (s *reservedTagKeys) Create(ctx context.Context, organization string, options ReservedTagKeyCreateOptions) (*ReservedTagKey, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/reserved-tag-keys\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &ReservedTagKey{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r, nil\n}\n\n// ReservedTagKeyUpdateOptions represents the options for updating a\n// reserved tag key.\ntype ReservedTagKeyUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,reserved-tag-keys\"`\n\n\t// Optional: The reserved tag key's key string.\n\tKey *string `jsonapi:\"attr,key,omitempty\"`\n\n\t// Optional: When true, project tag bindings that match this reserved tag key can not\n\t// be overridden at the workspace level.\n\tDisableOverrides *bool `jsonapi:\"attr,disable-overrides,omitempty\"`\n}\n\n// Update the reserved tag key with the given ID.\nfunc (s *reservedTagKeys) Update(ctx context.Context, reservedTagKeyID string, options ReservedTagKeyUpdateOptions) (*ReservedTagKey, error) {\n\tif !validStringID(&reservedTagKeyID) {\n\t\treturn nil, ErrInvalidReservedTagKeyID\n\t}\n\n\tu := fmt.Sprintf(\"reserved-tag-keys/%s\", url.PathEscape(reservedTagKeyID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &ReservedTagKey{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r, nil\n}\n\n// Delete the reserved tag key with the given ID.\nfunc (s *reservedTagKeys) Delete(ctx context.Context, reservedTagKeyID string) error {\n\tif !validStringID(&reservedTagKeyID) {\n\t\treturn ErrInvalidReservedTagKeyID\n\t}\n\n\tu := fmt.Sprintf(\"reserved-tag-keys/%s\", url.PathEscape(reservedTagKeyID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "reserved_tag_key_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReservedTagKeysList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trtkTest1, rtkTestCleanup := createReservedTagKey(t, client, orgTest,\n\t\tReservedTagKeyCreateOptions{\n\t\t\tKey: randomString(t),\n\t\t})\n\n\tdefer rtkTestCleanup()\n\n\trtkTest2, rtkTestCleanup := createReservedTagKey(t, client, orgTest,\n\t\tReservedTagKeyCreateOptions{\n\t\t\tKey: randomString(t),\n\t\t})\n\tdefer rtkTestCleanup()\n\n\trtks, err := client.ReservedTagKeys.List(ctx, orgTest.Name, nil)\n\trequire.NoError(t, err)\n\trequire.Len(t, rtks.Items, 2)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tpl, err := client.ReservedTagKeys.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, pl.Items, rtkTest1)\n\n\t\tassert.Equal(t, 1, pl.CurrentPage)\n\t\tassert.Equal(t, 2, pl.TotalCount)\n\t})\n\n\tt.Run(\"with pagination list options\", func(t *testing.T) {\n\t\trtks, err := client.ReservedTagKeys.List(ctx, orgTest.Name, &ReservedTagKeyListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, rtks.Items, rtkTest1)\n\t\tassert.Contains(t, rtks.Items, rtkTest2)\n\t\tassert.Equal(t, 2, len(rtks.Items))\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tpl, err := client.ReservedTagKeys.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, pl)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestReservedTagKeysCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := ReservedTagKeyCreateOptions{\n\t\t\tKey:              randomString(t),\n\t\t\tDisableOverrides: Bool(true),\n\t\t}\n\n\t\trtk, err := client.ReservedTagKeys.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\trtks, err := client.ReservedTagKeys.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, rtks.Items, 1)\n\n\t\tassert.NotEmpty(t, rtk.ID)\n\t\tassert.Equal(t, options.Key, rtk.Key)\n\t\tassert.Equal(t, *options.DisableOverrides, rtk.DisableOverrides)\n\t})\n\n\tt.Run(\"when key has already been taken\", func(t *testing.T) {\n\t\trtkExisting, rtkTestCleanup := createReservedTagKey(t, client, orgTest, ReservedTagKeyCreateOptions{\n\t\t\tKey:              randomString(t),\n\t\t\tDisableOverrides: Bool(true),\n\t\t})\n\t\tt.Cleanup(rtkTestCleanup)\n\n\t\trtks, err := client.ReservedTagKeys.List(ctx, orgTest.Name, nil)\n\t\tassert.NoError(t, err)\n\t\tassert.Len(t, rtks.Items, 2)\n\n\t\trtk, err := client.ReservedTagKeys.Create(ctx, orgTest.Name, ReservedTagKeyCreateOptions{\n\t\t\tKey: rtkExisting.Key,\n\t\t})\n\t\tassert.Nil(t, rtk)\n\t\tassert.Contains(t, err.Error(), \"invalid attribute\\n\\nKey has already been taken\")\n\t})\n\n\tt.Run(\"when options is missing key\", func(t *testing.T) {\n\t\tw, err := client.ReservedTagKeys.Create(ctx, orgTest.Name, ReservedTagKeyCreateOptions{\n\t\t\tDisableOverrides: Bool(true),\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, \"key is required\")\n\t})\n\n\tt.Run(\"when options has an invalid key\", func(t *testing.T) {\n\t\trtk, err := client.ReservedTagKeys.Create(ctx, orgTest.Name, ReservedTagKeyCreateOptions{\n\t\t\tKey: badIdentifier,\n\t\t})\n\t\tassert.Nil(t, rtk)\n\t\tassert.Contains(t, err.Error(), \"invalid attribute\\n\\nKey is invalid\")\n\t})\n\n\tt.Run(\"when options has an invalid organization\", func(t *testing.T) {\n\t\trtk, err := client.ReservedTagKeys.Create(ctx, badIdentifier, ReservedTagKeyCreateOptions{\n\t\t\tKey: randomString(t),\n\t\t})\n\t\tassert.Nil(t, rtk)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when organization does not exist\", func(t *testing.T) {\n\t\trtk, err := client.ReservedTagKeys.Create(ctx, \"nonexistent\", ReservedTagKeyCreateOptions{\n\t\t\tKey: randomString(t),\n\t\t})\n\t\tassert.Nil(t, rtk)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n\nfunc TestReservedTagKeysUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\trtkExisting, rtkTestCleanup := createReservedTagKey(t, client, orgTest, ReservedTagKeyCreateOptions{\n\t\tKey:              randomString(t),\n\t\tDisableOverrides: Bool(true),\n\t})\n\tt.Cleanup(rtkTestCleanup)\n\n\trtks, err := client.ReservedTagKeys.List(ctx, orgTest.Name, nil)\n\trequire.NoError(t, err)\n\trequire.Len(t, rtks.Items, 1)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\trtkAfter, err := client.ReservedTagKeys.Update(ctx, rtkExisting.ID, ReservedTagKeyUpdateOptions{\n\t\t\tKey:              String(randomString(t)),\n\t\t\tDisableOverrides: Bool(false),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, rtkExisting.ID, rtkAfter.ID)\n\t\tassert.NotEqual(t, rtkExisting.Key, rtkAfter.Key)\n\t\tassert.NotEqual(t, rtkExisting.DisableOverrides, rtkAfter.DisableOverrides)\n\t})\n\n\tt.Run(\"when updating with invalid key\", func(t *testing.T) {\n\t\trtkAfter, err := client.ReservedTagKeys.Update(ctx, rtkExisting.ID, ReservedTagKeyUpdateOptions{\n\t\t\tKey: String(badIdentifier),\n\t\t})\n\n\t\tassert.Error(t, err)\n\t\tassert.Nil(t, rtkAfter)\n\t\tassert.Contains(t, err.Error(), \"invalid attribute\\n\\nKey is invalid\")\n\t})\n\n\tt.Run(\"when key has already been taken\", func(t *testing.T) {\n\t\trtkOther, rtkTestCleanup := createReservedTagKey(t, client, orgTest, ReservedTagKeyCreateOptions{\n\t\t\tKey:              randomString(t),\n\t\t\tDisableOverrides: Bool(true),\n\t\t})\n\t\tt.Cleanup(rtkTestCleanup)\n\n\t\trtks, err := client.ReservedTagKeys.List(ctx, orgTest.Name, nil)\n\t\tassert.NoError(t, err)\n\t\tassert.Len(t, rtks.Items, 2)\n\n\t\trtkAfter, err := client.ReservedTagKeys.Update(ctx, rtkExisting.ID, ReservedTagKeyUpdateOptions{\n\t\t\tKey: String(rtkOther.Key),\n\t\t})\n\t\trequire.Error(t, err)\n\t\tassert.Nil(t, rtkAfter)\n\t\tassert.Contains(t, err.Error(), \"invalid attribute\\n\\nKey has already been taken\")\n\t})\n\n\tt.Run(\"without a valid reserved tag key ID\", func(t *testing.T) {\n\t\trtkAfter, err := client.ReservedTagKeys.Update(ctx, badIdentifier, ReservedTagKeyUpdateOptions{\n\t\t\tKey: String(randomString(t)),\n\t\t})\n\t\tassert.Nil(t, rtkAfter)\n\t\tassert.EqualError(t, err, ErrInvalidReservedTagKeyID.Error())\n\t})\n\n\tt.Run(\"when the reserved tag key does not exist\", func(t *testing.T) {\n\t\trtkAfter, err := client.ReservedTagKeys.Update(ctx, \"nonexistent\", ReservedTagKeyUpdateOptions{\n\t\t\tKey: String(randomString(t)),\n\t\t})\n\t\tassert.Nil(t, rtkAfter)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n\nfunc TestReservedTagKeysDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trtkBefore, rtkTestCleanup := createReservedTagKey(t, client, orgTest, ReservedTagKeyCreateOptions{\n\t\tKey:              randomString(t),\n\t\tDisableOverrides: Bool(true),\n\t})\n\tt.Cleanup(rtkTestCleanup)\n\n\trtks, err := client.ReservedTagKeys.List(ctx, orgTest.Name, nil)\n\tassert.NoError(t, err)\n\tassert.Len(t, rtks.Items, 1)\n\n\tt.Run(\"when the request is valid\", func(t *testing.T) {\n\t\terr := client.ReservedTagKeys.Delete(ctx, rtkBefore.ID)\n\t\trequire.NoError(t, err)\n\n\t\trtks, err = client.ReservedTagKeys.List(ctx, orgTest.Name, nil)\n\t\tassert.NoError(t, err)\n\t\tassert.Len(t, rtks.Items, 0)\n\t})\n\n\tt.Run(\"when the reserved tag key does not exist\", func(t *testing.T) {\n\t\terr := client.ReservedTagKeys.Delete(ctx, \"nonexistent\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the reserved tag key ID is invalid\", func(t *testing.T) {\n\t\terr := client.ReservedTagKeys.Delete(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidReservedTagKeyID.Error())\n\t})\n}\n\nfunc createReservedTagKey(t *testing.T, client *Client, org *Organization, opts ReservedTagKeyCreateOptions) (*ReservedTagKey, func()) {\n\tt.Helper()\n\n\trtk, err := client.ReservedTagKeys.Create(context.Background(), org.Name, opts)\n\trequire.NoError(t, err)\n\n\tcleanup := func() {\n\t\terr := client.ReservedTagKeys.Delete(context.Background(), rtk.ID)\n\t\tif err != nil && err == ErrResourceNotFound {\n\t\t\t// It's already been deleted\n\t\t\treturn\n\t\t}\n\n\t\trequire.NoError(t, err)\n\t}\n\n\treturn rtk, cleanup\n}\n"
  },
  {
    "path": "run.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Runs = (*runs)(nil)\n\n// Runs describes all the run related methods that the Terraform Enterprise\n// API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run\ntype Runs interface {\n\t// List all the runs of the given workspace.\n\tList(ctx context.Context, workspaceID string, options *RunListOptions) (*RunList, error)\n\n\t// List all the runs of the given organization.\n\tListForOrganization(ctx context.Context, organization string, options *RunListForOrganizationOptions) (*OrganizationRunList, error)\n\n\t// Create a new run with the given options.\n\tCreate(ctx context.Context, options RunCreateOptions) (*Run, error)\n\n\t// Read a run by its ID.\n\tRead(ctx context.Context, runID string) (*Run, error)\n\n\t// ReadWithOptions reads a run by its ID using the options supplied\n\tReadWithOptions(ctx context.Context, runID string, options *RunReadOptions) (*Run, error)\n\n\t// Apply a run by its ID.\n\tApply(ctx context.Context, runID string, options RunApplyOptions) error\n\n\t// Cancel a run by its ID.\n\tCancel(ctx context.Context, runID string, options RunCancelOptions) error\n\n\t// Force-cancel a run by its ID.\n\tForceCancel(ctx context.Context, runID string, options RunForceCancelOptions) error\n\n\t// Force execute a run by its ID.\n\tForceExecute(ctx context.Context, runID string) error\n\n\t// Discard a run by its ID.\n\tDiscard(ctx context.Context, runID string, options RunDiscardOptions) error\n}\n\n// runs implements Runs.\ntype runs struct {\n\tclient *Client\n}\n\n// RunStatus represents a run state.\ntype RunStatus string\n\n// List all available run statuses.\nconst (\n\tRunApplied                  RunStatus = \"applied\"\n\tRunApplying                 RunStatus = \"applying\"\n\tRunApplyQueued              RunStatus = \"apply_queued\"\n\tRunCanceled                 RunStatus = \"canceled\"\n\tRunConfirmed                RunStatus = \"confirmed\"\n\tRunCostEstimated            RunStatus = \"cost_estimated\"\n\tRunCostEstimating           RunStatus = \"cost_estimating\"\n\tRunDiscarded                RunStatus = \"discarded\"\n\tRunErrored                  RunStatus = \"errored\"\n\tRunFetching                 RunStatus = \"fetching\"\n\tRunFetchingCompleted        RunStatus = \"fetching_completed\"\n\tRunPending                  RunStatus = \"pending\"\n\tRunPlanned                  RunStatus = \"planned\"\n\tRunPlannedAndFinished       RunStatus = \"planned_and_finished\"\n\tRunPlannedAndSaved          RunStatus = \"planned_and_saved\"\n\tRunPlanning                 RunStatus = \"planning\"\n\tRunPlanQueued               RunStatus = \"plan_queued\"\n\tRunPolicyChecked            RunStatus = \"policy_checked\"\n\tRunPolicyChecking           RunStatus = \"policy_checking\"\n\tRunPolicyOverride           RunStatus = \"policy_override\"\n\tRunPolicySoftFailed         RunStatus = \"policy_soft_failed\"\n\tRunPostPlanAwaitingDecision RunStatus = \"post_plan_awaiting_decision\"\n\tRunPostPlanCompleted        RunStatus = \"post_plan_completed\"\n\tRunPostPlanRunning          RunStatus = \"post_plan_running\"\n\tRunPreApplyRunning          RunStatus = \"pre_apply_running\"\n\tRunPreApplyCompleted        RunStatus = \"pre_apply_completed\"\n\tRunPrePlanCompleted         RunStatus = \"pre_plan_completed\"\n\tRunPrePlanRunning           RunStatus = \"pre_plan_running\"\n\tRunQueuing                  RunStatus = \"queuing\"\n\tRunQueuingApply             RunStatus = \"queuing_apply\"\n)\n\n// RunSource represents a source type of a run.\ntype RunSource string\n\n// List all available run sources.\nconst (\n\tRunSourceAPI                  RunSource = \"tfe-api\"\n\tRunSourceConfigurationVersion RunSource = \"tfe-configuration-version\"\n\tRunSourceUI                   RunSource = \"tfe-ui\"\n)\n\n// RunOperation represents an operation type of run.\ntype RunOperation string\n\n// List all available run operations.\nconst (\n\tRunOperationPlanApply   RunOperation = \"plan_and_apply\"\n\tRunOperationPlanOnly    RunOperation = \"plan_only\"\n\tRunOperationRefreshOnly RunOperation = \"refresh_only\"\n\tRunOperationDestroy     RunOperation = \"destroy\"\n\tRunOperationEmptyApply  RunOperation = \"empty_apply\"\n\tRunOperationSavePlan    RunOperation = \"save_plan\"\n)\n\n// RunList represents a list of runs.\ntype RunList struct {\n\t*Pagination\n\tItems []*Run\n}\n\n// OrganizationRunList represents a list of runs across an organization. It\n// differs from the RunList in that it does not include a TotalCount of records\n// in the pagination details\ntype OrganizationRunList struct {\n\t*PaginationNextPrev\n\tItems []*Run\n}\n\n// Run represents a Terraform Enterprise run.\ntype Run struct {\n\tID                     string               `jsonapi:\"primary,runs\"`\n\tActions                *RunActions          `jsonapi:\"attr,actions\"`\n\tAutoApply              bool                 `jsonapi:\"attr,auto-apply,omitempty\"`\n\tAllowConfigGeneration  *bool                `jsonapi:\"attr,allow-config-generation,omitempty\"`\n\tAllowEmptyApply        bool                 `jsonapi:\"attr,allow-empty-apply\"`\n\tCanceledAt             time.Time            `jsonapi:\"attr,canceled-at,iso8601\"`\n\tCreatedAt              time.Time            `jsonapi:\"attr,created-at,iso8601\"`\n\tForceCancelAvailableAt time.Time            `jsonapi:\"attr,force-cancel-available-at,iso8601\"`\n\tHasChanges             bool                 `jsonapi:\"attr,has-changes\"`\n\tIsDestroy              bool                 `jsonapi:\"attr,is-destroy\"`\n\tInvokeActionAddrs      []string             `jsonapi:\"attr,invoke-action-addrs,omitempty\"`\n\tMessage                string               `jsonapi:\"attr,message\"`\n\tPermissions            *RunPermissions      `jsonapi:\"attr,permissions\"`\n\tPolicyPaths            []string             `jsonapi:\"attr,policy-paths,omitempty\"`\n\tPositionInQueue        int                  `jsonapi:\"attr,position-in-queue\"`\n\tPlanOnly               bool                 `jsonapi:\"attr,plan-only\"`\n\tRefresh                bool                 `jsonapi:\"attr,refresh\"`\n\tRefreshOnly            bool                 `jsonapi:\"attr,refresh-only\"`\n\tReplaceAddrs           []string             `jsonapi:\"attr,replace-addrs,omitempty\"`\n\tSavePlan               bool                 `jsonapi:\"attr,save-plan,omitempty\"`\n\tSource                 RunSource            `jsonapi:\"attr,source\"`\n\tStatus                 RunStatus            `jsonapi:\"attr,status\"`\n\tStatusTimestamps       *RunStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\tTargetAddrs            []string             `jsonapi:\"attr,target-addrs,omitempty\"`\n\tTerraformVersion       string               `jsonapi:\"attr,terraform-version\"`\n\tTriggerReason          string               `jsonapi:\"attr,trigger-reason\"`\n\tVariables              []*RunVariableAttr   `jsonapi:\"attr,variables\"`\n\n\t// Relations\n\tApply                *Apply                `jsonapi:\"relation,apply\"`\n\tConfigurationVersion *ConfigurationVersion `jsonapi:\"relation,configuration-version\"`\n\tCostEstimate         *CostEstimate         `jsonapi:\"relation,cost-estimate\"`\n\tCreatedBy            *User                 `jsonapi:\"relation,created-by\"`\n\tConfirmedBy          *User                 `jsonapi:\"relation,confirmed-by\"`\n\tPlan                 *Plan                 `jsonapi:\"relation,plan\"`\n\tPolicyChecks         []*PolicyCheck        `jsonapi:\"relation,policy-checks\"`\n\tRunEvents            []*RunEvent           `jsonapi:\"relation,run-events\"`\n\tTaskStages           []*TaskStage          `jsonapi:\"relation,task-stages,omitempty\"`\n\tWorkspace            *Workspace            `jsonapi:\"relation,workspace\"`\n\tComments             []*Comment            `jsonapi:\"relation,comments\"`\n}\n\n// RunActions represents the run actions.\ntype RunActions struct {\n\tIsCancelable      bool `jsonapi:\"attr,is-cancelable\"`\n\tIsConfirmable     bool `jsonapi:\"attr,is-confirmable\"`\n\tIsDiscardable     bool `jsonapi:\"attr,is-discardable\"`\n\tIsForceCancelable bool `jsonapi:\"attr,is-force-cancelable\"`\n}\n\n// RunPermissions represents the run permissions.\ntype RunPermissions struct {\n\tCanApply        bool `jsonapi:\"attr,can-apply\"`\n\tCanCancel       bool `jsonapi:\"attr,can-cancel\"`\n\tCanDiscard      bool `jsonapi:\"attr,can-discard\"`\n\tCanForceCancel  bool `jsonapi:\"attr,can-force-cancel\"`\n\tCanForceExecute bool `jsonapi:\"attr,can-force-execute\"`\n}\n\n// RunStatusTimestamps holds the timestamps for individual run statuses.\ntype RunStatusTimestamps struct {\n\tAppliedAt            time.Time `jsonapi:\"attr,applied-at,rfc3339\"`\n\tApplyingAt           time.Time `jsonapi:\"attr,applying-at,rfc3339\"`\n\tApplyQueuedAt        time.Time `jsonapi:\"attr,apply-queued-at,rfc3339\"`\n\tCanceledAt           time.Time `jsonapi:\"attr,canceled-at,rfc3339\"`\n\tConfirmedAt          time.Time `jsonapi:\"attr,confirmed-at,rfc3339\"`\n\tCostEstimatedAt      time.Time `jsonapi:\"attr,cost-estimated-at,rfc3339\"`\n\tCostEstimatingAt     time.Time `jsonapi:\"attr,cost-estimating-at,rfc3339\"`\n\tDiscardedAt          time.Time `jsonapi:\"attr,discarded-at,rfc3339\"`\n\tErroredAt            time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tFetchedAt            time.Time `jsonapi:\"attr,fetched-at,rfc3339\"`\n\tFetchingAt           time.Time `jsonapi:\"attr,fetching-at,rfc3339\"`\n\tForceCanceledAt      time.Time `jsonapi:\"attr,force-canceled-at,rfc3339\"`\n\tPlannedAndFinishedAt time.Time `jsonapi:\"attr,planned-and-finished-at,rfc3339\"`\n\tPlannedAndSavedAt    time.Time `jsonapi:\"attr,planned-and-saved-at,rfc3339\"`\n\tPlannedAt            time.Time `jsonapi:\"attr,planned-at,rfc3339\"`\n\tPlanningAt           time.Time `jsonapi:\"attr,planning-at,rfc3339\"`\n\tPlanQueueableAt      time.Time `jsonapi:\"attr,plan-queueable-at,rfc3339\"`\n\tPlanQueuedAt         time.Time `jsonapi:\"attr,plan-queued-at,rfc3339\"`\n\tPolicyCheckedAt      time.Time `jsonapi:\"attr,policy-checked-at,rfc3339\"`\n\tPolicySoftFailedAt   time.Time `jsonapi:\"attr,policy-soft-failed-at,rfc3339\"`\n\tPostPlanCompletedAt  time.Time `jsonapi:\"attr,post-plan-completed-at,rfc3339\"`\n\tPostPlanRunningAt    time.Time `jsonapi:\"attr,post-plan-running-at,rfc3339\"`\n\tPrePlanCompletedAt   time.Time `jsonapi:\"attr,pre-plan-completed-at,rfc3339\"`\n\tPrePlanRunningAt     time.Time `jsonapi:\"attr,pre-plan-running-at,rfc3339\"`\n\tQueuingAt            time.Time `jsonapi:\"attr,queuing-at,rfc3339\"`\n}\n\n// RunIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#available-related-resources\ntype RunIncludeOpt string\n\nconst (\n\tRunPlan             RunIncludeOpt = \"plan\"\n\tRunApply            RunIncludeOpt = \"apply\"\n\tRunCreatedBy        RunIncludeOpt = \"created_by\"\n\tRunCostEstimate     RunIncludeOpt = \"cost_estimate\"\n\tRunConfigVer        RunIncludeOpt = \"configuration_version\"\n\tRunConfigVerIngress RunIncludeOpt = \"configuration_version.ingress_attributes\"\n\tRunWorkspace        RunIncludeOpt = \"workspace\"\n\tRunTaskStages       RunIncludeOpt = \"task_stages\"\n)\n\n// RunListOptions represents the options for listing runs.\ntype RunListOptions struct {\n\tListOptions\n\n\t// Optional: Searches runs that matches the supplied VCS username.\n\tUser string `url:\"search[user],omitempty\"`\n\n\t// Optional: Searches runs that matches the supplied commit sha.\n\tCommit string `url:\"search[commit],omitempty\"`\n\n\t// Optional: Searches runs that matches the supplied VCS username, commit sha, run_id, and run message.\n\t// The presence of search[commit] or search[user] takes priority over this parameter and will be omitted.\n\tSearch string `url:\"search[basic],omitempty\"`\n\n\t// Optional: Comma-separated list of acceptable run statuses.\n\t// Options are listed at https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#run-states,\n\t// or as constants with the RunStatus string type.\n\tStatus string `url:\"filter[status],omitempty\"`\n\n\t// Optional: Comma-separated list of acceptable run sources.\n\t// Options are listed at https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#run-sources,\n\t// or as constants with the RunSource string type.\n\tSource string `url:\"filter[source],omitempty\"`\n\n\t// Optional: Comma-separated list of acceptable run operation types.\n\t// Options are listed at https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#run-operations,\n\t// or as constants with the RunOperation string type.\n\tOperation string `url:\"filter[operation],omitempty\"`\n\n\t// Optional: A list of relations to include. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#available-related-resources\n\tInclude []RunIncludeOpt `url:\"include,omitempty\"`\n}\n\n// RunListForOrganizationOptions represents the options for listing runs for an organization.\ntype RunListForOrganizationOptions struct {\n\tListOptions\n\n\t// Optional: Searches runs that matches the supplied VCS username.\n\tUser string `url:\"search[user],omitempty\"`\n\n\t// Optional: Searches runs that matches the supplied commit sha.\n\tCommit string `url:\"search[commit],omitempty\"`\n\n\t// Optional: Searches for runs that match the VCS username, commit sha, run_id, or run message your specify.\n\t// The presence of search[commit] or search[user] takes priority over this parameter and will be omitted.\n\tBasic string `url:\"search[basic],omitempty\"`\n\n\t// Optional: Comma-separated list of acceptable run statuses.\n\t// Options are listed at https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#run-states,\n\t// or as constants with the RunStatus string type.\n\tStatus string `url:\"filter[status],omitempty\"`\n\n\t// Optional: Comma-separated list of acceptable run sources.\n\t// Options are listed at https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#run-sources,\n\t// or as constants with the RunSource string type.\n\tSource string `url:\"filter[source],omitempty\"`\n\n\t// Optional: Comma-separated list of acceptable run operation types.\n\t// Options are listed at https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#run-operations,\n\t// or as constants with the RunOperation string type.\n\tOperation string `url:\"filter[operation],omitempty\"`\n\n\t// Optional: Comma-separated list of agent pool names.\n\tAgentPoolNames string `url:\"filter[agent_pool_names],omitempty\"`\n\n\t// Optional: Comma-separated list of run status groups.\n\tStatusGroup string `url:\"filter[status_group],omitempty\"`\n\n\t// Optional: Comma-separated list of run timeframe.\n\tTimeframe string `url:\"filter[timeframe],omitempty\"`\n\n\t// Optional: Comma-separated list of workspace names. The result lists runs that belong to one of the workspaces your specify.\n\tWorkspaceNames string `url:\"filter[workspace_names],omitempty\"`\n\n\t// Optional: A list of relations to include. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#available-related-resources\n\tInclude []RunIncludeOpt `url:\"include,omitempty\"`\n}\n\n// RunReadOptions represents the options for reading a run.\ntype RunReadOptions struct {\n\t// Optional: A list of relations to include. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#available-related-resources\n\tInclude []RunIncludeOpt `url:\"include,omitempty\"`\n}\n\n// RunCreateOptions represents the options for creating a new run.\ntype RunCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,runs\"`\n\n\t// AllowConfigGeneration specifies whether generated resource configuration may be created as a side\n\t// effect of an import block in this run. Setting this does not mean that configuration _will_ be generated,\n\t// only that it can be.\n\tAllowConfigGeneration *bool `jsonapi:\"attr,allow-config-generation,omitempty\"`\n\n\t// AllowEmptyApply specifies whether Terraform can apply the run even when the plan contains no changes.\n\t// Often used to upgrade state after upgrading a workspace to a new terraform version.\n\tAllowEmptyApply *bool `jsonapi:\"attr,allow-empty-apply,omitempty\"`\n\n\t// TerraformVersion specifies the Terraform version to use in this run.\n\t// Only valid for plan-only runs; must be a valid Terraform version available to the organization.\n\tTerraformVersion *string `jsonapi:\"attr,terraform-version,omitempty\"`\n\n\t// PlanOnly specifies if this is a speculative, plan-only run that Terraform cannot apply.\n\t// Often used in conjunction with terraform-version in order to test whether an upgrade would succeed.\n\tPlanOnly *bool `jsonapi:\"attr,plan-only,omitempty\"`\n\n\t// Specifies if this plan is a destroy plan, which will destroy all\n\t// provisioned resources.\n\tIsDestroy *bool `jsonapi:\"attr,is-destroy,omitempty\"`\n\n\t// Refresh determines if the run should\n\t// update the state prior to checking for differences\n\tRefresh *bool `jsonapi:\"attr,refresh,omitempty\"`\n\n\t// RefreshOnly determines whether the run should ignore config changes\n\t// and refresh the state only\n\tRefreshOnly *bool `jsonapi:\"attr,refresh-only,omitempty\"`\n\n\t// SavePlan determines whether this should be a saved-plan run. Saved-plan\n\t// runs perform their plan and checks immediately, but won't lock the\n\t// workspace and become its current run until they are confirmed for apply.\n\tSavePlan *bool `jsonapi:\"attr,save-plan,omitempty\"`\n\n\t// Specifies the message to be associated with this run.\n\tMessage *string `jsonapi:\"attr,message,omitempty\"`\n\n\t// Specifies the configuration version to use for this run. If the\n\t// configuration version object is omitted, the run will be created using the\n\t// workspace's latest configuration version.\n\tConfigurationVersion *ConfigurationVersion `jsonapi:\"relation,configuration-version\"`\n\n\t// Specifies the workspace where the run will be executed.\n\tWorkspace *Workspace `jsonapi:\"relation,workspace\"`\n\n\t// If non-empty, requests that Terraform should create a plan including\n\t// actions only for the given objects (specified using resource address\n\t// syntax) and the objects they depend on.\n\t//\n\t// This capability is provided for exceptional circumstances only, such as\n\t// recovering from mistakes or working around existing Terraform\n\t// limitations. Terraform will generally mention the -target command line\n\t// option in its error messages describing situations where setting this\n\t// argument may be appropriate. This argument should not be used as part\n\t// of routine workflow and Terraform will emit warnings reminding about\n\t// this whenever this property is set.\n\tTargetAddrs []string `jsonapi:\"attr,target-addrs,omitempty\"`\n\n\t// If non-empty, requests that Terraform create a plan that replaces\n\t// (destroys and then re-creates) the objects specified by the given\n\t// resource addresses.\n\tReplaceAddrs []string `jsonapi:\"attr,replace-addrs,omitempty\"`\n\n\t// PolicyPaths is a list of relative directory paths that point to policy\n\t// configuration files.\n\t//\n\t// **Note: This field is in BETA and subject to change.**\n\tPolicyPaths []string `jsonapi:\"attr,policy-paths,omitempty\"`\n\n\t// AutoApply determines if the run should be applied automatically without\n\t// user confirmation. It defaults to the Workspace.AutoApply setting.\n\tAutoApply *bool `jsonapi:\"attr,auto-apply,omitempty\"`\n\n\t// Variables allows you to specify terraform input variables for\n\t// a particular run, prioritized over variables defined on the workspace.\n\tVariables []*RunVariable `jsonapi:\"attr,variables,omitempty\"`\n\n\t// Action Addresses to invoke.\n\tInvokeActionAddrs []string `jsonapi:\"attr,invoke-action-addrs,omitempty\"`\n}\n\n// RunApplyOptions represents the options for applying a run.\ntype RunApplyOptions struct {\n\t// An optional comment about the run.\n\tComment *string `json:\"comment,omitempty\"`\n}\n\n// RunCancelOptions represents the options for canceling a run.\ntype RunCancelOptions struct {\n\t// An optional explanation for why the run was canceled.\n\tComment *string `json:\"comment,omitempty\"`\n}\n\ntype RunVariableAttr struct {\n\tKey   string `jsonapi:\"attr,key\"`\n\tValue string `jsonapi:\"attr,value\"`\n}\n\n// RunVariableAttr represents a variable that can be applied to a run. All values must be expressed as an HCL literal\n// in the same syntax you would use when writing terraform code. See https://developer.hashicorp.com/terraform/language/expressions/types#types\n// for more details.\ntype RunVariable struct {\n\tKey   string `json:\"key\"`\n\tValue string `json:\"value\"`\n}\n\n// RunForceCancelOptions represents the options for force-canceling a run.\ntype RunForceCancelOptions struct {\n\t// An optional comment explaining the reason for the force-cancel.\n\tComment *string `json:\"comment,omitempty\"`\n}\n\n// RunDiscardOptions represents the options for discarding a run.\ntype RunDiscardOptions struct {\n\t// An optional explanation for why the run was discarded.\n\tComment *string `json:\"comment,omitempty\"`\n}\n\n// List all the runs of the given workspace.\nfunc (s *runs) List(ctx context.Context, workspaceID string, options *RunListOptions) (*RunList, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/runs\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trl := &RunList{}\n\terr = req.Do(ctx, rl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rl, nil\n}\n\n// List all the runs of the given workspace.\nfunc (s *runs) ListForOrganization(ctx context.Context, organization string, options *RunListForOrganizationOptions) (*OrganizationRunList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/runs\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trl := &OrganizationRunList{}\n\terr = req.Do(ctx, rl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rl, nil\n}\n\n// Create a new run with the given options.\nfunc (s *runs) Create(ctx context.Context, options RunCreateOptions) (*Run, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", \"runs\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &Run{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r, nil\n}\n\n// Read a run by its ID.\nfunc (s *runs) Read(ctx context.Context, runID string) (*Run, error) {\n\treturn s.ReadWithOptions(ctx, runID, nil)\n}\n\n// Read a run by its ID with the given options.\nfunc (s *runs) ReadWithOptions(ctx context.Context, runID string, options *RunReadOptions) (*Run, error) {\n\tif !validStringID(&runID) {\n\t\treturn nil, ErrInvalidRunID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s\", url.PathEscape(runID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &Run{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r, nil\n}\n\n// Apply a run by its ID.\nfunc (s *runs) Apply(ctx context.Context, runID string, options RunApplyOptions) error {\n\tif !validStringID(&runID) {\n\t\treturn ErrInvalidRunID\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s/actions/apply\", url.PathEscape(runID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Cancel a run by its ID.\nfunc (s *runs) Cancel(ctx context.Context, runID string, options RunCancelOptions) error {\n\tif !validStringID(&runID) {\n\t\treturn ErrInvalidRunID\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s/actions/cancel\", url.PathEscape(runID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// ForceCancel is used to forcefully cancel a run by its ID.\nfunc (s *runs) ForceCancel(ctx context.Context, runID string, options RunForceCancelOptions) error {\n\tif !validStringID(&runID) {\n\t\treturn ErrInvalidRunID\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s/actions/force-cancel\", url.PathEscape(runID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// ForceExecute is used to forcefully execute a run by its ID.\n//\n// Note: While useful at times, force-executing a run circumvents the typical\n// workflow of applying runs using HCP Terraform. It is not intended for\n// regular use. If you find yourself using it frequently, please reach out to\n// HashiCorp Support for help in developing an alternative approach.\nfunc (s *runs) ForceExecute(ctx context.Context, runID string) error {\n\tif !validStringID(&runID) {\n\t\treturn ErrInvalidRunID\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s/actions/force-execute\", url.PathEscape(runID))\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Discard a run by its ID.\nfunc (s *runs) Discard(ctx context.Context, runID string, options RunDiscardOptions) error {\n\tif !validStringID(&runID) {\n\t\treturn ErrInvalidRunID\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s/actions/discard\", url.PathEscape(runID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o RunCreateOptions) valid() error {\n\tif o.Workspace == nil {\n\t\treturn ErrRequiredWorkspace\n\t}\n\n\tif validString(o.TerraformVersion) && (o.PlanOnly == nil || !*o.PlanOnly) {\n\t\treturn ErrTerraformVersionValidForPlanOnly\n\t}\n\n\treturn nil\n}\n\nfunc (o *RunReadOptions) valid() error {\n\treturn nil\n}\n\nfunc (o *RunListOptions) valid() error {\n\treturn nil\n}\n\nfunc (o *RunListForOrganizationOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "run_event.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ RunEvents = (*runEvents)(nil)\n\n// RunEvents describes all the run events that the Terraform Enterprise\n// API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run\ntype RunEvents interface {\n\t// List all the runs events of the given run.\n\tList(ctx context.Context, runID string, options *RunEventListOptions) (*RunEventList, error)\n\n\t// Read a run event by its ID.\n\tRead(ctx context.Context, runEventID string) (*RunEvent, error)\n\n\t// ReadWithOptions reads a run event by its ID using the options supplied\n\tReadWithOptions(ctx context.Context, runEventID string, options *RunEventReadOptions) (*RunEvent, error)\n}\n\n// runEvents implements RunEvents.\ntype runEvents struct {\n\tclient *Client\n}\n\n// RunEventList represents a list of run events.\ntype RunEventList struct {\n\t// Pagination is not supported by the API\n\t*Pagination\n\tItems []*RunEvent\n}\n\n// RunEvent represents a Terraform Enterprise run event.\ntype RunEvent struct {\n\tID          string    `jsonapi:\"primary,run-events\"`\n\tAction      string    `jsonapi:\"attr,action\"`\n\tCreatedAt   time.Time `jsonapi:\"attr,created-at,iso8601\"`\n\tDescription string    `jsonapi:\"attr,description\"`\n\n\t// Relations - Note that `target` is not supported yet\n\tActor   *User    `jsonapi:\"relation,actor\"`\n\tComment *Comment `jsonapi:\"relation,comment\"`\n}\n\n// RunEventIncludeOpt represents the available options for include query params.\ntype RunEventIncludeOpt string\n\nconst (\n\tRunEventComment RunEventIncludeOpt = \"comment\"\n\tRunEventActor   RunEventIncludeOpt = \"actor\"\n)\n\n// RunEventListOptions represents the options for listing run events.\ntype RunEventListOptions struct {\n\t// Optional: A list of relations to include. See available resources:\n\tInclude []RunEventIncludeOpt `url:\"include,omitempty\"`\n}\n\n// RunEventReadOptions represents the options for reading a run event.\ntype RunEventReadOptions struct {\n\t// Optional: A list of relations to include. See available resources:\n\tInclude []RunEventIncludeOpt `url:\"include,omitempty\"`\n}\n\n// List all the run events of the given run.\nfunc (s *runEvents) List(ctx context.Context, runID string, options *RunEventListOptions) (*RunEventList, error) {\n\tif !validStringID(&runID) {\n\t\treturn nil, ErrInvalidRunID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s/run-events\", url.PathEscape(runID))\n\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trl := &RunEventList{}\n\terr = req.Do(ctx, rl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rl, nil\n}\n\n// Read a run by its ID.\nfunc (s *runEvents) Read(ctx context.Context, runEventID string) (*RunEvent, error) {\n\treturn s.ReadWithOptions(ctx, runEventID, nil)\n}\n\n// ReadWithOptions reads a run by its ID with the given options.\nfunc (s *runEvents) ReadWithOptions(ctx context.Context, runEventID string, options *RunEventReadOptions) (*RunEvent, error) {\n\tif !validStringID(&runEventID) {\n\t\treturn nil, ErrInvalidRunEventID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"run-events/%s\", url.PathEscape(runEventID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &RunEvent{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r, nil\n}\n\nfunc (o *RunEventReadOptions) valid() error {\n\treturn nil\n}\n\nfunc (o *RunEventListOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "run_event_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRunEventsList_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, _ := createWorkspace(t, client, orgTest)\n\trTest, _ := createRun(t, client, wTest)\n\tcommentText := \"Test comment\"\n\t_, err := client.Comments.Create(ctx, rTest.ID, CommentCreateOptions{\n\t\tBody: commentText,\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\trl, err := client.RunEvents.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, rl.Items)\n\t\t// Find the comment that was added\n\t\tvar commentEvent *RunEvent = nil\n\t\tfor _, event := range rl.Items {\n\t\t\tif event.Action == \"commented\" {\n\t\t\t\tcommentEvent = event\n\t\t\t}\n\t\t}\n\t\tassert.NotNil(t, commentEvent)\n\t\t// We didn't include any resources so these should be empty\n\t\tassert.Empty(t, commentEvent.Actor.Username)\n\t\tassert.Empty(t, commentEvent.Comment.Body)\n\t})\n\n\tt.Run(\"with all includes\", func(t *testing.T) {\n\t\trl, err := client.RunEvents.List(ctx, rTest.ID, &RunEventListOptions{\n\t\t\tInclude: []RunEventIncludeOpt{RunEventActor, RunEventComment},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// Find the comment that was added\n\t\tvar commentEvent *RunEvent = nil\n\t\tfor _, event := range rl.Items {\n\t\t\tif event.Action == \"commented\" {\n\t\t\t\tcommentEvent = event\n\t\t\t}\n\t\t}\n\t\trequire.NotNil(t, commentEvent)\n\n\t\t// Assert that the include resources are included\n\t\trequire.NotNil(t, commentEvent.Actor)\n\t\tassert.NotEmpty(t, commentEvent.Actor.Username)\n\t\trequire.NotNil(t, commentEvent.Comment)\n\t\tassert.Equal(t, commentEvent.Comment.Body, commentText)\n\t})\n\n\tt.Run(\"without a valid run ID\", func(t *testing.T) {\n\t\trl, err := client.RunEvents.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, rl)\n\t\tassert.EqualError(t, err, ErrInvalidRunID.Error())\n\t})\n}\n\nfunc TestRunEventsRead_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, _ := createWorkspace(t, client, orgTest)\n\trTest, _ := createRun(t, client, wTest)\n\tcommentText := \"Test comment\"\n\t_, err := client.Comments.Create(ctx, rTest.ID, CommentCreateOptions{\n\t\tBody: commentText,\n\t})\n\trequire.NoError(t, err)\n\n\trl, err := client.RunEvents.List(ctx, rTest.ID, nil)\n\trequire.NoError(t, err)\n\t// Find the comment that was added\n\tvar commentEvent *RunEvent = nil\n\tfor _, event := range rl.Items {\n\t\tif event.Action == \"commented\" {\n\t\t\tcommentEvent = event\n\t\t}\n\t}\n\tassert.NotNil(t, commentEvent)\n\n\tt.Run(\"without read options\", func(t *testing.T) {\n\t\tre, err := client.RunEvents.Read(ctx, commentEvent.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// We didn't include any resources so these should be empty\n\t\tassert.Empty(t, re.Actor.Username)\n\t\tassert.Empty(t, re.Comment.Body)\n\t})\n\n\tt.Run(\"with all includes\", func(t *testing.T) {\n\t\tre, err := client.RunEvents.ReadWithOptions(ctx, commentEvent.ID, &RunEventReadOptions{\n\t\t\tInclude: []RunEventIncludeOpt{RunEventActor, RunEventComment},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// Assert that the include resources are included\n\t\trequire.NotNil(t, re.Actor)\n\t\tassert.NotEmpty(t, re.Actor.Username)\n\t\trequire.NotNil(t, re.Comment)\n\t\tassert.Equal(t, re.Comment.Body, commentText)\n\t})\n\n\tt.Run(\"without a valid run event ID\", func(t *testing.T) {\n\t\trl, err := client.RunEvents.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, rl)\n\t\tassert.EqualError(t, err, ErrInvalidRunEventID.Error())\n\t})\n}\n"
  },
  {
    "path": "run_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRunsList_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, _ := createWorkspace(t, client, orgTest)\n\trTest1, _ := createRun(t, client, wTest)\n\trTest2, _ := createRun(t, client, wTest)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\trl, err := client.Runs.List(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tfound := []string{}\n\t\tfor _, r := range rl.Items {\n\t\t\tfound = append(found, r.ID)\n\t\t}\n\n\t\tassert.Contains(t, found, rTest1.ID)\n\t\tassert.Contains(t, found, rTest2.ID)\n\t\tassert.Equal(t, 1, rl.CurrentPage)\n\t\tassert.Equal(t, 2, rl.TotalCount)\n\t})\n\n\tt.Run(\"without list options and include as nil\", func(t *testing.T) {\n\t\trl, err := client.Runs.List(ctx, wTest.ID, &RunListOptions{\n\t\t\tInclude: []RunIncludeOpt{},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, rl.Items)\n\n\t\tfound := []string{}\n\t\tfor _, r := range rl.Items {\n\t\t\tfound = append(found, r.ID)\n\t\t}\n\n\t\tassert.Contains(t, found, rTest1.ID)\n\t\tassert.Contains(t, found, rTest2.ID)\n\t\tassert.Equal(t, 1, rl.CurrentPage)\n\t\tassert.Equal(t, 2, rl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tt.Skip(\"paging not supported yet in API\")\n\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\trl, err := client.Runs.List(ctx, wTest.ID, &RunListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, rl.Items)\n\t\tassert.Equal(t, 999, rl.CurrentPage)\n\t\tassert.Equal(t, 2, rl.TotalCount)\n\t})\n\n\tt.Run(\"with workspace included\", func(t *testing.T) {\n\t\trl, err := client.Runs.List(ctx, wTest.ID, &RunListOptions{\n\t\t\tInclude: []RunIncludeOpt{RunWorkspace},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, rl.Items)\n\t\trequire.NotNil(t, rl.Items[0].Workspace)\n\t\tassert.NotEmpty(t, rl.Items[0].Workspace.Name)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\trl, err := client.Runs.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, rl)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestRunsListQueryParams_RunDependent(t *testing.T) {\n\ttype testCase struct {\n\t\toptions     *RunListOptions\n\t\tdescription string\n\t\tassertion   func(tc testCase, rl *RunList, err error)\n\t}\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tworkspaceTest, _ := createWorkspace(t, client, orgTest)\n\tcreatePlannedRun(t, client, workspaceTest)\n\tcreateRun(t, client, workspaceTest)\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tdescription: \"with status query parameter\",\n\t\t\toptions:     &RunListOptions{Status: string(RunPending), Include: []RunIncludeOpt{RunWorkspace}},\n\t\t\tassertion: func(tc testCase, rl *RunList, err error) {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, 1, len(rl.Items))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"with source query parameter\",\n\t\t\toptions:     &RunListOptions{Source: string(RunSourceAPI), Include: []RunIncludeOpt{RunWorkspace}},\n\t\t\tassertion: func(tc testCase, rl *RunList, err error) {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, 2, len(rl.Items))\n\t\t\t\tassert.Equal(t, rl.Items[0].Source, RunSourceAPI)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"with operation of plan_only parameter\",\n\t\t\toptions:     &RunListOptions{Operation: string(RunOperationPlanOnly), Include: []RunIncludeOpt{RunWorkspace}},\n\t\t\tassertion: func(tc testCase, rl *RunList, err error) {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, 0, len(rl.Items))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"with mismatch user & commit parameter\",\n\t\t\toptions:     &RunListOptions{User: randomString(t), Commit: randomString(t), Include: []RunIncludeOpt{RunWorkspace}},\n\t\t\tassertion: func(tc testCase, rl *RunList, err error) {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, 0, len(rl.Items))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"with operation of save_plan parameter\",\n\t\t\toptions:     &RunListOptions{Operation: string(RunOperationSavePlan), Include: []RunIncludeOpt{RunWorkspace}},\n\t\t\tassertion: func(tc testCase, rl *RunList, err error) {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, 0, len(rl.Items))\n\t\t\t},\n\t\t},\n\t}\n\n\tbetaTestCases := []testCase{}\n\n\tfor _, testCase := range testCases {\n\t\tt.Run(testCase.description, func(t *testing.T) {\n\t\t\truns, err := client.Runs.List(ctx, workspaceTest.ID, testCase.options)\n\t\t\ttestCase.assertion(testCase, runs, err)\n\t\t})\n\t}\n\n\tfor _, testCase := range betaTestCases {\n\t\tt.Run(testCase.description, func(t *testing.T) {\n\t\t\tskipUnlessBeta(t)\n\t\t\truns, err := client.Runs.List(ctx, workspaceTest.ID, testCase.options)\n\t\t\ttestCase.assertion(testCase, runs, err)\n\t\t})\n\t}\n}\n\nfunc TestRunsCreate_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\tcvTest, _ := createUploadedConfigurationVersion(t, client, wTest)\n\n\tt.Run(\"without a configuration version\", func(t *testing.T) {\n\t\toptions := RunCreateOptions{\n\t\t\tWorkspace: wTest,\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, r.ID)\n\t\tassert.NotNil(t, r.CreatedAt)\n\t\tassert.NotNil(t, r.Source)\n\t\trequire.NotNil(t, r.StatusTimestamps)\n\t\tassert.NotZero(t, r.StatusTimestamps.PlanQueueableAt)\n\t})\n\n\tt.Run(\"with a configuration version\", func(t *testing.T) {\n\t\toptions := RunCreateOptions{\n\t\t\tConfigurationVersion: cvTest,\n\t\t\tWorkspace:            wTest,\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, r.ConfigurationVersion)\n\t\tassert.Equal(t, cvTest.ID, r.ConfigurationVersion.ID)\n\t})\n\n\tt.Run(\"with allow empty apply\", func(t *testing.T) {\n\t\toptions := RunCreateOptions{\n\t\t\tWorkspace:       wTest,\n\t\t\tAllowEmptyApply: Bool(true),\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, true, r.AllowEmptyApply)\n\t})\n\n\tt.Run(\"with save-plan\", func(t *testing.T) {\n\t\toptions := RunCreateOptions{\n\t\t\tWorkspace: wTest,\n\t\t\tSavePlan:  Bool(true),\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, true, r.SavePlan)\n\t})\n\n\tt.Run(\"with terraform version and plan only\", func(t *testing.T) {\n\t\toptions := RunCreateOptions{\n\t\t\tWorkspace:        wTest,\n\t\t\tTerraformVersion: String(\"1.0.0\"),\n\t\t}\n\t\t_, err := client.Runs.Create(ctx, options)\n\t\trequire.ErrorIs(t, err, ErrTerraformVersionValidForPlanOnly)\n\n\t\toptions.PlanOnly = Bool(true)\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, true, r.PlanOnly)\n\t\tassert.Equal(t, \"1.0.0\", r.TerraformVersion)\n\t})\n\n\tt.Run(\"refresh defaults to true if not set as a create option\", func(t *testing.T) {\n\t\toptions := RunCreateOptions{\n\t\t\tWorkspace: wTest,\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, true, r.Refresh)\n\t})\n\n\tt.Run(\"with refresh-only requested\", func(t *testing.T) {\n\t\t// TODO: remove this skip after the release of Terraform 0.15.4\n\t\tt.Skip(\"Skipping this test until -refresh-only is released in the Terraform CLI\")\n\n\t\toptions := RunCreateOptions{\n\t\t\tWorkspace:   wTest,\n\t\t\tRefreshOnly: Bool(true),\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, true, r.RefreshOnly)\n\t})\n\n\tt.Run(\"with auto-apply requested\", func(t *testing.T) {\n\t\t// ensure the worksapce auto-apply is false so it does not default to that.\n\t\tassert.Equal(t, false, wTest.AutoApply)\n\n\t\toptions := RunCreateOptions{\n\t\t\tWorkspace: wTest,\n\t\t\tAutoApply: Bool(true),\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, true, r.AutoApply)\n\t})\n\n\tt.Run(\"without auto-apply, defaulting to workspace autoapply\", func(t *testing.T) {\n\t\toptions := RunCreateOptions{\n\t\t\tWorkspace: wTest,\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, wTest.AutoApply, r.AutoApply)\n\t})\n\n\tt.Run(\"without a workspace\", func(t *testing.T) {\n\t\tr, err := client.Runs.Create(ctx, RunCreateOptions{})\n\t\tassert.Nil(t, r)\n\t\tassert.Equal(t, err, ErrRequiredWorkspace)\n\t})\n\n\tt.Run(\"with additional attributes\", func(t *testing.T) {\n\t\toptions := RunCreateOptions{\n\t\t\tMessage:      String(\"yo\"),\n\t\t\tWorkspace:    wTest,\n\t\t\tRefresh:      Bool(false),\n\t\t\tReplaceAddrs: []string{\"null_resource.example\"},\n\t\t\tTargetAddrs:  []string{\"null_resource.example\"},\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, *options.Message, r.Message)\n\t\tassert.Equal(t, *options.Refresh, r.Refresh)\n\t\tassert.Equal(t, options.ReplaceAddrs, r.ReplaceAddrs)\n\t\tassert.Equal(t, options.TargetAddrs, r.TargetAddrs)\n\t\tassert.Nil(t, r.Variables)\n\t})\n\n\tt.Run(\"with variables\", func(t *testing.T) {\n\t\tvars := []*RunVariable{\n\t\t\t{\n\t\t\t\tKey:   \"test_variable\",\n\t\t\t\tValue: \"Hello, World!\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   \"test_foo\",\n\t\t\t\tValue: \"Hello, Foo!\",\n\t\t\t},\n\t\t}\n\n\t\toptions := RunCreateOptions{\n\t\t\tMessage:   String(\"yo\"),\n\t\t\tWorkspace: wTest,\n\t\t\tVariables: vars,\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, r.Variables)\n\t\tassert.Equal(t, len(vars), len(r.Variables))\n\n\t\tfor _, v := range r.Variables {\n\t\t\tswitch v.Key {\n\t\t\tcase \"test_foo\":\n\t\t\t\tassert.Equal(t, v.Value, \"Hello, Foo!\")\n\t\t\tcase \"test_variable\":\n\t\t\t\tassert.Equal(t, v.Value, \"Hello, World!\")\n\t\t\tdefault:\n\t\t\t\tt.Fatalf(\"Unexpected variable key: %s\", v.Key)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"with policy paths\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\topts := RunCreateOptions{\n\t\t\tMessage:     String(\"creating with policy paths\"),\n\t\t\tWorkspace:   wTest,\n\t\t\tPolicyPaths: []string{\"./path/to/dir1\", \"./path/to/dir2\"},\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, r.PolicyPaths)\n\n\t\tassert.Len(t, r.PolicyPaths, 2)\n\t\tassert.Contains(t, r.PolicyPaths, \"./path/to/dir1\")\n\t\tassert.Contains(t, r.PolicyPaths, \"./path/to/dir2\")\n\t})\n\n\tt.Run(\"with action invocations\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\topts := RunCreateOptions{\n\t\t\tMessage:           String(\"creating with policy paths\"),\n\t\t\tWorkspace:         wTest,\n\t\t\tInvokeActionAddrs: []string{\"actions.foo.bar\"},\n\t\t}\n\n\t\tr, err := client.Runs.Create(ctx, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, r.InvokeActionAddrs)\n\n\t\tassert.Len(t, r.InvokeActionAddrs, 1)\n\t\tassert.Contains(t, r.InvokeActionAddrs, \"actions.foo.bar\")\n\t})\n}\n\nfunc TestRunsRead_CostEstimate_RunDependent(t *testing.T) {\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\trTest, rTestCleanup := createCostEstimatedRun(t, client, nil)\n\tdefer rTestCleanup()\n\n\tt.Run(\"when the run exists\", func(t *testing.T) {\n\t\tr, err := client.Runs.Read(ctx, rTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, rTest, r)\n\t})\n\n\tt.Run(\"when the run does not exist\", func(t *testing.T) {\n\t\tr, err := client.Runs.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, r)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid run ID\", func(t *testing.T) {\n\t\tr, err := client.Runs.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, r)\n\t\tassert.EqualError(t, err, ErrInvalidRunID.Error())\n\t})\n}\n\nfunc TestRunsReadWithOptions_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\trTest, rTestCleanup := createRun(t, client, nil)\n\tdefer rTestCleanup()\n\n\tt.Run(\"when the run exists\", func(t *testing.T) {\n\t\tcurOpts := &RunReadOptions{\n\t\t\tInclude: []RunIncludeOpt{RunCreatedBy},\n\t\t}\n\n\t\tr, err := client.Runs.ReadWithOptions(ctx, rTest.ID, curOpts)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, r.CreatedBy)\n\t\tassert.NotEmpty(t, r.CreatedBy.Username)\n\t})\n}\n\nfunc TestRunsReadWithPolicyPaths(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wTestCleanup)\n\n\t_, cvCleanup := createUploadedConfigurationVersion(t, client, wTest)\n\tt.Cleanup(cvCleanup)\n\n\tr, err := client.Runs.Create(ctx, RunCreateOptions{\n\t\tWorkspace:   wTest,\n\t\tPolicyPaths: []string{\"./foo\"},\n\t})\n\trequire.NoError(t, err)\n\n\tr, err = client.Runs.Read(ctx, r.ID)\n\trequire.NoError(t, err)\n\n\trequire.NotEmpty(t, r.PolicyPaths)\n\tassert.Contains(t, r.PolicyPaths, \"./foo\")\n}\n\nfunc TestRunsConfirmedBy_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"with apply\", func(t *testing.T) {\n\t\trTest, rTestCleanup := createRunApply(t, client, nil)\n\t\tt.Cleanup(rTestCleanup)\n\n\t\tr, err := client.Runs.Read(ctx, rTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotNil(t, r.ConfirmedBy)\n\t\tassert.NotZero(t, r.ConfirmedBy.ID)\n\t})\n\n\tt.Run(\"without apply\", func(t *testing.T) {\n\t\trTest, rTestCleanup := createPlannedRun(t, client, nil)\n\t\tt.Cleanup(rTestCleanup)\n\n\t\tr, err := client.Runs.Read(ctx, rTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, rTest, r)\n\n\t\tassert.Nil(t, r.ConfirmedBy)\n\t})\n}\n\nfunc TestRunsCanceledAt_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wTestCleanup)\n\n\t// We need to create 2 runs here. The first run will automatically\n\t// be planned so that one cannot be cancelled. The second one will\n\t// be pending until the first one is confirmed or discarded, so we\n\t// can cancel that one.\n\tcreateRun(t, client, wTest)\n\trTest, _ := createRun(t, client, wTest)\n\n\tt.Run(\"when the run is not canceled\", func(t *testing.T) {\n\t\tr, err := client.Runs.Read(ctx, rTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Empty(t, r.CanceledAt)\n\t})\n\n\tt.Run(\"when the run is canceled\", func(t *testing.T) {\n\t\terr := client.Runs.Cancel(ctx, rTest.ID, RunCancelOptions{})\n\t\trequire.NoError(t, err)\n\n\t\tfor i := 1; ; i++ {\n\t\t\t// Refresh the view of the run\n\t\t\trTest, err = client.Runs.Read(ctx, rTest.ID)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Check if the timestamp is present.\n\t\t\tif !rTest.ForceCancelAvailableAt.IsZero() {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif i > 30 {\n\t\t\t\tt.Fatal(\"Timeout waiting for run to be canceled\")\n\t\t\t}\n\n\t\t\ttime.Sleep(time.Second)\n\t\t}\n\n\t\tr, err := client.Runs.Read(ctx, rTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, r.CanceledAt)\n\t})\n}\n\nfunc TestRunsRunEvents(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\t_, cvCleanup := createUploadedConfigurationVersion(t, client, wTest)\n\tt.Cleanup(cvCleanup)\n\n\toptions := RunCreateOptions{\n\t\tWorkspace: wTest,\n\t}\n\n\tr, err := client.Runs.Create(ctx, options)\n\trequire.NoError(t, err)\n\n\tassert.NotEmpty(t, r.RunEvents)\n}\n\nfunc TestRunsTriggerReason(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\t_, cvCleanup := createUploadedConfigurationVersion(t, client, wTest)\n\tt.Cleanup(cvCleanup)\n\n\toptions := RunCreateOptions{\n\t\tWorkspace: wTest,\n\t}\n\n\tr, err := client.Runs.Create(ctx, options)\n\trequire.NoError(t, err)\n\n\tassert.NotNil(t, r.TriggerReason)\n}\n\nfunc TestRunsApply_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\twTest, _ := createWorkspace(t, client, orgTest)\n\n\trTest, _ := createPlannedRun(t, client, wTest)\n\n\tt.Run(\"when the run exists\", func(t *testing.T) {\n\t\terr := client.Runs.Apply(ctx, rTest.ID, RunApplyOptions{\n\t\t\tComment: String(\"Hello, Earl\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tr, err := client.Runs.Read(ctx, rTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, r.Comments, 1)\n\n\t\tc, err := client.Comments.Read(ctx, r.Comments[0].ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"Hello, Earl\", c.Body)\n\t})\n\n\tt.Run(\"when the run does not exist\", func(t *testing.T) {\n\t\terr := client.Runs.Apply(ctx, \"nonexisting\", RunApplyOptions{})\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid run ID\", func(t *testing.T) {\n\t\terr := client.Runs.Apply(ctx, badIdentifier, RunApplyOptions{})\n\t\tassert.EqualError(t, err, ErrInvalidRunID.Error())\n\t})\n}\n\nfunc TestRunsCancel_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wTestCleanup)\n\n\t// We need to create 2 runs here. The first run will automatically\n\t// be planned so that one cannot be cancelled. The second one will\n\t// be pending until the first one is confirmed or discarded, so we\n\t// can cancel that one.\n\tcreateRun(t, client, wTest)\n\trTest, _ := createRun(t, client, wTest)\n\n\tt.Run(\"when the run exists\", func(t *testing.T) {\n\t\terr := client.Runs.Cancel(ctx, rTest.ID, RunCancelOptions{})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the run does not exist\", func(t *testing.T) {\n\t\terr := client.Runs.Cancel(ctx, \"nonexisting\", RunCancelOptions{})\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid run ID\", func(t *testing.T) {\n\t\terr := client.Runs.Cancel(ctx, badIdentifier, RunCancelOptions{})\n\t\tassert.EqualError(t, err, ErrInvalidRunID.Error())\n\t})\n}\n\nfunc TestRunsForceCancel_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\t// We need to create 2 runs here. The first run will automatically\n\t// be planned so that one cannot be cancelled. The second one will\n\t// be pending until the first one is confirmed or discarded, so we\n\t// can cancel that one.\n\tcreateRun(t, client, wTest)\n\trTest, _ := createRun(t, client, wTest)\n\n\tt.Run(\"run is not force-cancelable\", func(t *testing.T) {\n\t\tassert.False(t, rTest.Actions.IsForceCancelable)\n\t})\n\n\tt.Run(\"user is allowed to force-cancel\", func(t *testing.T) {\n\t\tassert.True(t, rTest.Permissions.CanForceCancel)\n\t})\n\n\tt.Run(\"after a normal cancel\", func(t *testing.T) {\n\t\t// Request the normal cancel\n\t\terr := client.Runs.Cancel(ctx, rTest.ID, RunCancelOptions{})\n\t\trequire.NoError(t, err)\n\n\t\trTest, err := retryPatientlyIf(\n\t\t\tfunc() (any, error) {\n\t\t\t\treturn client.Runs.Read(ctx, rTest.ID)\n\t\t\t},\n\t\t\tfunc(r *Run) bool {\n\t\t\t\treturn r.ForceCancelAvailableAt.IsZero()\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tt.Run(\"force-cancel-available-at timestamp is present\", func(t *testing.T) {\n\t\t\tassert.True(t, rTest.ForceCancelAvailableAt.After(time.Now()))\n\t\t})\n\n\t\t// This test case is minimal because a force-cancel is not needed in\n\t\t// any normal circumstance. Only if Terraform encounters unexpected\n\t\t// errors or behaves abnormally should this functionality be required.\n\t\t// Force-cancel only becomes available if a normal cancel is performed\n\t\t// first, and the desired canceled state is not reached within a pre-\n\t\t// determined amount of time (see\n\t\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#forcefully-cancel-a-run).\n\t})\n\n\tt.Run(\"when the run does not exist\", func(t *testing.T) {\n\t\terr := client.Runs.ForceCancel(ctx, \"nonexisting\", RunForceCancelOptions{})\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid run ID\", func(t *testing.T) {\n\t\terr := client.Runs.ForceCancel(ctx, badIdentifier, RunForceCancelOptions{})\n\t\tassert.EqualError(t, err, ErrInvalidRunID.Error())\n\t})\n}\n\nfunc TestRunsForceExecute_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\t// We need to create 2 runs here:\n\t// - The first run will automatically be planned so that the second\n\t//   run can't be executed.\n\t// - The second run will be pending until the first run is confirmed or\n\t//   discarded, so we will force execute this run.\n\trToCancel, _ := createPlannedRun(t, client, wTest)\n\trTest, _ := createRunWaitForStatus(t, client, wTest, RunPending)\n\n\tt.Run(\"a successful force-execute\", func(t *testing.T) {\n\t\t// Verify the user has permission to force-execute the run\n\t\tassert.True(t, rTest.Permissions.CanForceExecute)\n\n\t\terr := client.Runs.ForceExecute(ctx, rTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\ttimeout := 2 * time.Minute\n\t\tctxPollRunForceExecute, cancel := context.WithTimeout(ctx, timeout)\n\t\tdefer cancel()\n\n\t\t// Verify the second run has a status that is an applyable status\n\t\trTest = pollRunStatus(t,\n\t\t\tclient,\n\t\t\tctxPollRunForceExecute,\n\t\t\trTest,\n\t\t\tapplyableStatuses(rTest))\n\t\tif rTest.Status == RunErrored {\n\t\t\tfatalDumpRunLog(t, client, ctx, rTest)\n\t\t}\n\n\t\t// Refresh the view of the first run\n\t\trToCancel, err = client.Runs.Read(ctx, rToCancel.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify the first run was discarded\n\t\tassert.Equal(t, RunDiscarded, rToCancel.Status)\n\t})\n\n\tt.Run(\"when the run does not exist\", func(t *testing.T) {\n\t\terr := client.Runs.ForceExecute(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid run ID\", func(t *testing.T) {\n\t\terr := client.Runs.ForceExecute(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidRunID.Error())\n\t})\n}\n\nfunc TestRunsDiscard_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\trTest, _ := createPlannedRun(t, client, wTest)\n\n\tt.Run(\"when the run exists\", func(t *testing.T) {\n\t\terr := client.Runs.Discard(ctx, rTest.ID, RunDiscardOptions{})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the run does not exist\", func(t *testing.T) {\n\t\terr := client.Runs.Discard(ctx, \"nonexisting\", RunDiscardOptions{})\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid run ID\", func(t *testing.T) {\n\t\terr := client.Runs.Discard(ctx, badIdentifier, RunDiscardOptions{})\n\t\tassert.EqualError(t, err, ErrInvalidRunID.Error())\n\t})\n}\n\nfunc TestRun_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"runs\",\n\t\t\t\"id\":   \"1\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"created-at\":  \"2018-03-02T23:42:06.651Z\",\n\t\t\t\t\"has-changes\": true,\n\t\t\t\t\"is-destroy\":  false,\n\t\t\t\t\"message\":     \"run message\",\n\t\t\t\t\"actions\": map[string]interface{}{\n\t\t\t\t\t\"is-cancelable\":       true,\n\t\t\t\t\t\"is-confirmable\":      true,\n\t\t\t\t\t\"is-discardable\":      true,\n\t\t\t\t\t\"is-force-cancelable\": true,\n\t\t\t\t},\n\t\t\t\t\"permissions\": map[string]interface{}{\n\t\t\t\t\t\"can-apply\":         true,\n\t\t\t\t\t\"can-cancel\":        true,\n\t\t\t\t\t\"can-discard\":       true,\n\t\t\t\t\t\"can-force-cancel\":  true,\n\t\t\t\t\t\"can-force-execute\": true,\n\t\t\t\t},\n\t\t\t\t\"status-timestamps\": map[string]string{\n\t\t\t\t\t\"plan-queued-at\": \"2020-03-16T23:15:59+00:00\",\n\t\t\t\t\t\"errored-at\":     \"2019-03-16T23:23:59+00:00\",\n\t\t\t\t},\n\t\t\t\t\"variables\": []map[string]string{{\"key\": \"a-key\", \"value\": \"\\\"a-value\\\"\"}},\n\t\t\t},\n\t\t},\n\t}\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\trun := &Run{}\n\terr = unmarshalResponse(responseBody, run)\n\trequire.NoError(t, err)\n\n\tplanQueuedParsedTime, err := time.Parse(time.RFC3339, \"2020-03-16T23:15:59+00:00\")\n\trequire.NoError(t, err)\n\terroredParsedTime, err := time.Parse(time.RFC3339, \"2019-03-16T23:23:59+00:00\")\n\trequire.NoError(t, err)\n\n\tiso8601TimeFormat := \"2006-01-02T15:04:05Z\"\n\tparsedTime, err := time.Parse(iso8601TimeFormat, \"2018-03-02T23:42:06.651Z\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, run.ID, \"1\")\n\tassert.Equal(t, run.CreatedAt, parsedTime)\n\tassert.Equal(t, run.HasChanges, true)\n\tassert.Equal(t, run.IsDestroy, false)\n\tassert.Equal(t, run.Message, \"run message\")\n\tassert.Equal(t, run.Actions.IsConfirmable, true)\n\tassert.Equal(t, run.Actions.IsCancelable, true)\n\tassert.Equal(t, run.Actions.IsDiscardable, true)\n\tassert.Equal(t, run.Actions.IsForceCancelable, true)\n\tassert.Equal(t, run.Permissions.CanApply, true)\n\tassert.Equal(t, run.Permissions.CanCancel, true)\n\tassert.Equal(t, run.Permissions.CanDiscard, true)\n\tassert.Equal(t, run.Permissions.CanForceExecute, true)\n\tassert.Equal(t, run.Permissions.CanForceCancel, true)\n\tassert.Equal(t, run.StatusTimestamps.PlanQueuedAt, planQueuedParsedTime)\n\tassert.Equal(t, run.StatusTimestamps.ErroredAt, erroredParsedTime)\n\n\trequire.NotEmpty(t, run.Variables)\n\tassert.Equal(t, run.Variables[0].Key, \"a-key\")\n\tassert.Equal(t, run.Variables[0].Value, \"\\\"a-value\\\"\")\n}\n\nfunc TestRunCreateOptions_Marshal(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\topts := RunCreateOptions{\n\t\tWorkspace: wTest,\n\t\tVariables: []*RunVariable{\n\t\t\t{\n\t\t\t\tKey:   \"test_variable\",\n\t\t\t\tValue: \"Hello, World!\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   \"test_foo\",\n\t\t\t\tValue: \"Hello, Foo!\",\n\t\t\t},\n\t\t},\n\t}\n\n\treqBody, err := serializeRequestBody(&opts)\n\trequire.NoError(t, err)\n\treq, err := retryablehttp.NewRequest(\"POST\", \"url\", reqBody)\n\trequire.NoError(t, err)\n\tbodyBytes, err := req.BodyBytes()\n\trequire.NoError(t, err)\n\n\texpectedBody := fmt.Sprintf(`{\"data\":{\"type\":\"runs\",\"attributes\":{\"variables\":[{\"key\":\"test_variable\",\"value\":\"Hello, World!\"},{\"key\":\"test_foo\",\"value\":\"Hello, Foo!\"}]},\"relationships\":{\"configuration-version\":{\"data\":null},\"workspace\":{\"data\":{\"type\":\"workspaces\",\"id\":\"%s\"}}}}}\n`, wTest.ID)\n\n\tassert.Equal(t, string(bodyBytes), expectedBody)\n}\n\nfunc TestRunsListForOrganization_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tapTest, _ := createAgentPool(t, client, orgTest)\n\n\twTest, _ := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\tName:          String(randomString(t)),\n\t\tExecutionMode: String(\"agent\"),\n\t\tAgentPoolID:   &apTest.ID,\n\t})\n\trTest1, _ := createRun(t, client, wTest)\n\trTest2, _ := createRun(t, client, wTest)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\trl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tfound := []string{}\n\t\tfor _, r := range rl.Items {\n\t\t\tfound = append(found, r.ID)\n\t\t}\n\n\t\tassert.Contains(t, found, rTest1.ID)\n\t\tassert.Contains(t, found, rTest2.ID)\n\t\tassert.Equal(t, 1, rl.CurrentPage)\n\t\tassert.Empty(t, rl.NextPage)\n\t})\n\n\tt.Run(\"without list options and include as nil\", func(t *testing.T) {\n\t\trl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, &RunListForOrganizationOptions{\n\t\t\tInclude: []RunIncludeOpt{},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, rl.Items)\n\n\t\tfound := []string{}\n\t\tfor _, r := range rl.Items {\n\t\t\tfound = append(found, r.ID)\n\t\t}\n\n\t\tassert.Contains(t, found, rTest1.ID)\n\t\tassert.Contains(t, found, rTest2.ID)\n\t\tassert.Equal(t, 1, rl.CurrentPage)\n\t\tassert.Empty(t, rl.NextPage)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// Request a page number that is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\trl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, &RunListForOrganizationOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, rl.Items)\n\t\tassert.Equal(t, 999, rl.CurrentPage)\n\t\tassert.Empty(t, rl.NextPage)\n\t})\n\n\tt.Run(\"with workspace included\", func(t *testing.T) {\n\t\trl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, &RunListForOrganizationOptions{\n\t\t\tInclude: []RunIncludeOpt{RunWorkspace},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, rl.Items)\n\t\trequire.NotNil(t, rl.Items[0].Workspace)\n\t\tassert.NotEmpty(t, rl.Items[0].Workspace.Name)\n\t})\n\n\tt.Run(\"without a valid organization name\", func(t *testing.T) {\n\t\trl, err := client.Runs.ListForOrganization(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, rl)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"with filter by agent pool\", func(t *testing.T) {\n\t\trl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, &RunListForOrganizationOptions{\n\t\t\tAgentPoolNames: apTest.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tfound := make([]string, len(rl.Items))\n\t\tfor i, r := range rl.Items {\n\t\t\tfound[i] = r.ID\n\t\t}\n\n\t\tassert.Contains(t, found, rTest1.ID)\n\t\tassert.Contains(t, found, rTest2.ID)\n\t\tassert.Equal(t, 1, rl.CurrentPage)\n\t\tassert.Empty(t, rl.NextPage)\n\t})\n\n\tt.Run(\"with filter by workspace\", func(t *testing.T) {\n\t\trl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, &RunListForOrganizationOptions{\n\t\t\tWorkspaceNames: wTest.Name,\n\t\t\tInclude:        []RunIncludeOpt{RunWorkspace},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tfound := make([]string, len(rl.Items))\n\t\tfor i, r := range rl.Items {\n\t\t\tfound[i] = r.ID\n\t\t}\n\n\t\tassert.Contains(t, found, rTest1.ID)\n\t\tassert.Contains(t, found, rTest2.ID)\n\t\trequire.NotNil(t, rl.Items[0].Workspace)\n\t\tassert.NotEmpty(t, rl.Items[0].Workspace.Name)\n\t\trequire.NotNil(t, rl.Items[1].Workspace)\n\t\tassert.NotEmpty(t, rl.Items[1].Workspace.Name)\n\t\tassert.Equal(t, 1, rl.CurrentPage)\n\t\tassert.Empty(t, rl.NextPage)\n\t})\n}\n"
  },
  {
    "path": "run_task.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation\nvar _ RunTasks = (*runTasks)(nil)\n\n// RunTasks represents all the run task related methods in the context of an organization\n// that the HCP Terraform and Terraform Enterprise API supports.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run-tasks/run-tasks#run-tasks-api\ntype RunTasks interface {\n\t// Create a run task for an organization\n\tCreate(ctx context.Context, organization string, options RunTaskCreateOptions) (*RunTask, error)\n\n\t// List all run tasks for an organization\n\tList(ctx context.Context, organization string, options *RunTaskListOptions) (*RunTaskList, error)\n\n\t// Read an organization's run task by ID\n\tRead(ctx context.Context, runTaskID string) (*RunTask, error)\n\n\t// Read an organization's run task by ID with given options\n\tReadWithOptions(ctx context.Context, runTaskID string, options *RunTaskReadOptions) (*RunTask, error)\n\n\t// Update a run task for an organization\n\tUpdate(ctx context.Context, runTaskID string, options RunTaskUpdateOptions) (*RunTask, error)\n\n\t// Delete an organization's run task\n\tDelete(ctx context.Context, runTaskID string) error\n\n\t// Attach a run task to an organization's workspace\n\tAttachToWorkspace(ctx context.Context, workspaceID string, runTaskID string, enforcementLevel TaskEnforcementLevel) (*WorkspaceRunTask, error)\n}\n\n// runTasks implements RunTasks\ntype runTasks struct {\n\tclient *Client\n}\n\n// RunTask represents a HCP Terraform or Terraform Enterprise run task\ntype RunTask struct {\n\tID          string         `jsonapi:\"primary,tasks\"`\n\tName        string         `jsonapi:\"attr,name\"`\n\tURL         string         `jsonapi:\"attr,url\"`\n\tDescription string         `jsonapi:\"attr,description\"`\n\tCategory    string         `jsonapi:\"attr,category\"`\n\tHMACKey     *string        `jsonapi:\"attr,hmac-key,omitempty\"`\n\tEnabled     bool           `jsonapi:\"attr,enabled\"`\n\tGlobal      *GlobalRunTask `jsonapi:\"attr,global-configuration,omitempty\"`\n\n\tAgentPool         *AgentPool          `jsonapi:\"relation,agent-pool\"`\n\tOrganization      *Organization       `jsonapi:\"relation,organization\"`\n\tWorkspaceRunTasks []*WorkspaceRunTask `jsonapi:\"relation,workspace-tasks\"`\n}\n\n// GlobalRunTask represents the global configuration of a HCP Terraform or Terraform Enterprise run task\ntype GlobalRunTask struct {\n\tEnabled          bool                 `jsonapi:\"attr,enabled\"`\n\tStages           []Stage              `jsonapi:\"attr,stages\"`\n\tEnforcementLevel TaskEnforcementLevel `jsonapi:\"attr,enforcement-level\"`\n}\n\n// RunTaskList represents a list of run tasks\ntype RunTaskList struct {\n\t*Pagination\n\tItems []*RunTask\n}\n\n// RunTaskIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run-tasks/run-tasks#list-run-tasks\ntype RunTaskIncludeOpt string\n\nconst (\n\tRunTaskWorkspaceTasks RunTaskIncludeOpt = \"workspace_tasks\"\n\tRunTaskWorkspace      RunTaskIncludeOpt = \"workspace_tasks.workspace\"\n)\n\n// RunTaskListOptions represents the set of options for listing run tasks\ntype RunTaskListOptions struct {\n\tListOptions\n\t// Optional: A list of relations to include with a run task. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run-tasks/run-tasks#list-run-tasks\n\tInclude []RunTaskIncludeOpt `url:\"include,omitempty\"`\n}\n\n// RunTaskReadOptions represents the set of options for reading a run task\ntype RunTaskReadOptions struct {\n\t// Optional: A list of relations to include with a run task. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run-tasks/run-tasks#list-run-tasks\n\tInclude []RunTaskIncludeOpt `url:\"include,omitempty\"`\n}\n\n// GlobalRunTask represents the optional global configuration of a HCP Terraform or Terraform Enterprise run task\ntype GlobalRunTaskOptions struct {\n\tEnabled          *bool                 `jsonapi:\"attr,enabled,omitempty\"`\n\tStages           *[]Stage              `jsonapi:\"attr,stages,omitempty\"`\n\tEnforcementLevel *TaskEnforcementLevel `jsonapi:\"attr,enforcement-level,omitempty\"`\n}\n\n// RunTaskCreateOptions represents the set of options for creating a run task\ntype RunTaskCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,tasks\"`\n\n\t// Required: The name of the run task\n\tName string `jsonapi:\"attr,name\"`\n\n\t// Required: The URL to send a run task payload\n\tURL string `jsonapi:\"attr,url\"`\n\n\t// Optional: Description of the task\n\tDescription *string `jsonapi:\"attr,description\"`\n\n\t// Required: Must be \"task\"\n\tCategory string `jsonapi:\"attr,category\"`\n\n\t// Optional: An HMAC key to verify the run task\n\tHMACKey *string `jsonapi:\"attr,hmac-key,omitempty\"`\n\n\t// Optional: Whether the task should be enabled\n\tEnabled *bool `jsonapi:\"attr,enabled,omitempty\"`\n\n\t// Optional: Whether the task contains global configuration\n\tGlobal *GlobalRunTaskOptions `jsonapi:\"attr,global-configuration,omitempty\"`\n\n\t// Optional: Whether the task will be executed using an Agent Pool\n\t// Requires the PrivateRunTasks entitlement\n\tAgentPool *AgentPool `jsonapi:\"relation,agent-pool,omitempty\"`\n}\n\n// RunTaskUpdateOptions represents the set of options for updating an organization's run task\ntype RunTaskUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,tasks\"`\n\n\t// Optional: The name of the run task, defaults to previous value\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: The URL to send a run task payload, defaults to previous value\n\tURL *string `jsonapi:\"attr,url,omitempty\"`\n\n\t// Optional: An optional description of the task\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Optional: Must be \"task\", defaults to \"task\"\n\tCategory *string `jsonapi:\"attr,category,omitempty\"`\n\n\t// Optional: An HMAC key to verify the run task\n\tHMACKey *string `jsonapi:\"attr,hmac-key,omitempty\"`\n\n\t// Optional: Whether the task should be enabled\n\tEnabled *bool `jsonapi:\"attr,enabled,omitempty\"`\n\n\t// Optional: Whether the task contains global configuration\n\tGlobal *GlobalRunTaskOptions `jsonapi:\"attr,global-configuration,omitempty\"`\n\n\t// Optional: Whether the task will be executed using an Agent Pool\n\t// Requires the PrivateRunTasks entitlement\n\tAgentPool *AgentPool `jsonapi:\"relation,agent-pool,omitempty\"`\n}\n\n// Create is used to create a new run task for an organization\nfunc (s *runTasks) Create(ctx context.Context, organization string, options RunTaskCreateOptions) (*RunTask, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/tasks\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &internalRunTask{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r.ToRunTask(), nil\n}\n\n// List all the run tasks for an organization\nfunc (s *runTasks) List(ctx context.Context, organization string, options *RunTaskListOptions) (*RunTaskList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/tasks\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trl := &internalRunTaskList{}\n\terr = req.Do(ctx, rl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rl.ToRunTaskList(), nil\n}\n\n// Read is used to read an organization's run task by ID\nfunc (s *runTasks) Read(ctx context.Context, runTaskID string) (*RunTask, error) {\n\treturn s.ReadWithOptions(ctx, runTaskID, nil)\n}\n\n// Read is used to read an organization's run task by ID with options\nfunc (s *runTasks) ReadWithOptions(ctx context.Context, runTaskID string, options *RunTaskReadOptions) (*RunTask, error) {\n\tif !validStringID(&runTaskID) {\n\t\treturn nil, ErrInvalidRunTaskID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"tasks/%s\", url.PathEscape(runTaskID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &internalRunTask{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r.ToRunTask(), nil\n}\n\n// Update an existing run task for an organization by ID\nfunc (s *runTasks) Update(ctx context.Context, runTaskID string, options RunTaskUpdateOptions) (*RunTask, error) {\n\tif !validStringID(&runTaskID) {\n\t\treturn nil, ErrInvalidRunTaskID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"tasks/%s\", url.PathEscape(runTaskID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &internalRunTask{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r.ToRunTask(), nil\n}\n\n// Delete an existing run task for an organization by ID\nfunc (s *runTasks) Delete(ctx context.Context, runTaskID string) error {\n\tif !validStringID(&runTaskID) {\n\t\treturn ErrInvalidRunTaskID\n\t}\n\n\tu := fmt.Sprintf(\"tasks/%s\", runTaskID)\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// AttachToWorkspace is a convenient method to attach a run task to a workspace. See: WorkspaceRunTasks.Create()\nfunc (s *runTasks) AttachToWorkspace(ctx context.Context, workspaceID, runTaskID string, enforcement TaskEnforcementLevel) (*WorkspaceRunTask, error) {\n\treturn s.client.WorkspaceRunTasks.Create(ctx, workspaceID, WorkspaceRunTaskCreateOptions{\n\t\tEnforcementLevel: enforcement,\n\t\tRunTask:          &RunTask{ID: runTaskID},\n\t})\n}\n\nfunc (o *RunTaskCreateOptions) valid() error {\n\tif !validString(&o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\n\tif !validString(&o.URL) {\n\t\treturn ErrInvalidRunTaskURL\n\t}\n\n\tif o.Category != \"task\" {\n\t\treturn ErrInvalidRunTaskCategory\n\t}\n\n\treturn nil\n}\n\nfunc (o *RunTaskUpdateOptions) valid() error {\n\tif o.Name != nil && !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\n\tif o.URL != nil && !validString(o.URL) {\n\t\treturn ErrInvalidRunTaskURL\n\t}\n\n\tif o.Category != nil && *o.Category != \"task\" {\n\t\treturn ErrInvalidRunTaskCategory\n\t}\n\n\treturn nil\n}\n\nfunc (o *RunTaskListOptions) valid() error {\n\treturn nil\n}\n\nfunc (o *RunTaskReadOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "run_task_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRunTasksCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tif v, err := hasGlobalRunTasks(client, orgTest.Name); err != nil {\n\t\tt.Fatalf(\"Could not retrieve the entitlements for the test organization.: %s\", err)\n\t} else if !v {\n\t\tt.Fatal(\"The test organization requires the global-run-tasks entitlement but is not entitled.\")\n\t\treturn\n\t}\n\n\trunTaskServerURL := os.Getenv(\"TFC_RUN_TASK_URL\")\n\tif runTaskServerURL == \"\" {\n\t\tt.Error(\"Cannot create a run task with an empty URL. You must set TFC_RUN_TASK_URL for run task related tests.\")\n\t}\n\n\trunTaskName := \"tst-runtask-\" + randomString(t)\n\trunTaskDescription := \"A Run Task Description\"\n\tglobalEnabled := true\n\tglobalStages := []Stage{\n\t\tPostPlan,\n\t\tPrePlan,\n\t}\n\tglobalEnforce := Mandatory\n\n\tt.Run(\"with an agent pool\", func(t *testing.T) {\n\t\t// We can only test if the org, supports private run tasks. For now this isn't\n\t\t// a fatal error and we just skip the test.\n\t\tif v, err := hasPrivateRunTasks(client, orgTest.Name); err != nil {\n\t\t\tt.Fatalf(\"Could not retrieve the entitlements for the test organization.: %s\", err)\n\t\t} else if !v {\n\t\t\tt.Skip(\"The test organization requires the private-run-tasks entitlement but is not entitled.\")\n\t\t\treturn\n\t\t}\n\n\t\t// Unfortunately when we create a Run Task it automatically verifies that the URL by sending a test payload. But\n\t\t// this means with an agent pool, we need an agent pool to exist, and an agent created with request forwarding enabled.\n\t\t// This is too much to create for this one test suite. So instead, we really only need to assert that; when the options include an\n\t\t// agent pool, then we expect HCP Terraform to process the agent pool. So, if we send it a nonsense agent pool ID, then we\n\t\t// expect an error to be returned saying that the ID was nonsense.\n\t\t_, err := client.RunTasks.Create(ctx, orgTest.Name, RunTaskCreateOptions{\n\t\t\tName:        runTaskName,\n\t\t\tURL:         runTaskServerURL,\n\t\t\tDescription: &runTaskDescription,\n\t\t\tCategory:    \"task\",\n\t\t\tAgentPool: &AgentPool{\n\t\t\t\tID: \"apool-this-pool-id-will-never-exist-so-we-expect-http-error-response\",\n\t\t\t},\n\t\t})\n\t\trequire.ErrorContains(t, err, \"The provided agent pool does not exist\")\n\t})\n\n\tt.Run(\"add run task to organization\", func(t *testing.T) {\n\t\tr, err := client.RunTasks.Create(ctx, orgTest.Name, RunTaskCreateOptions{\n\t\t\tName:        runTaskName,\n\t\t\tURL:         runTaskServerURL,\n\t\t\tDescription: &runTaskDescription,\n\t\t\tCategory:    \"task\",\n\t\t\tEnabled:     Bool(true),\n\t\t\tGlobal: &GlobalRunTaskOptions{\n\t\t\t\tEnabled:          &globalEnabled,\n\t\t\t\tStages:           &globalStages,\n\t\t\t\tEnforcementLevel: &globalEnforce,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, r.ID)\n\t\tassert.Equal(t, r.Name, runTaskName)\n\t\tassert.Equal(t, r.URL, runTaskServerURL)\n\t\tassert.Equal(t, r.Category, \"task\")\n\t\tassert.Equal(t, r.Description, runTaskDescription)\n\t\tassert.NotNil(t, r.Global)\n\t\tassert.Equal(t, globalEnabled, r.Global.Enabled)\n\t\tassert.Equal(t, globalEnforce, r.Global.EnforcementLevel)\n\t\tassert.Equal(t, globalStages, r.Global.Stages)\n\n\t\tt.Run(\"ensure org is deserialized properly\", func(t *testing.T) {\n\t\t\tassert.Equal(t, r.Organization.Name, orgTest.Name)\n\t\t})\n\t})\n}\n\nfunc TestRunTasksCreateWithoutGlobalEntitlement(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tnewSubscriptionUpdater(orgTest).WithTrialPlan().Update(t)\n\n\tif v, err := hasGlobalRunTasks(client, orgTest.Name); err != nil {\n\t\tt.Fatalf(\"Could not retrieve the entitlements for the test organization.: %s\", err)\n\t} else if v {\n\t\tt.Fatal(\"The test organization should not have the global-run-tasks entitlement but it does.\")\n\t\treturn\n\t}\n\n\trunTaskServerURL := os.Getenv(\"TFC_RUN_TASK_URL\")\n\tif runTaskServerURL == \"\" {\n\t\tt.Error(\"Cannot create a run task with an empty URL. You must set TFC_RUN_TASK_URL for run task related tests.\")\n\t}\n\n\trunTaskName := \"tst-runtask-\" + randomString(t)\n\trunTaskDescription := \"A Run Task Description\"\n\tglobalStages := []Stage{\n\t\tPostPlan,\n\t\tPrePlan,\n\t}\n\tglobalEnforce := Mandatory\n\n\tt.Run(\"add run task to organization\", func(t *testing.T) {\n\t\tr, err := client.RunTasks.Create(ctx, orgTest.Name, RunTaskCreateOptions{\n\t\t\tName:        runTaskName,\n\t\t\tURL:         runTaskServerURL,\n\t\t\tDescription: &runTaskDescription,\n\t\t\tCategory:    \"task\",\n\t\t\t// Even though we pass in these global parameters,\n\t\t\t// they should be ignored and not throw an API error\n\t\t\tGlobal: &GlobalRunTaskOptions{\n\t\t\t\tEnabled:          Bool(true),\n\t\t\t\tStages:           &globalStages,\n\t\t\t\tEnforcementLevel: &globalEnforce,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, r.ID)\n\t\tassert.Nil(t, r.Global)\n\t})\n}\n\nfunc TestRunTasksList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\t_, runTaskTest1Cleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTest1Cleanup()\n\n\t_, runTaskTest2Cleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTest2Cleanup()\n\n\tt.Run(\"with no params\", func(t *testing.T) {\n\t\trunTaskList, err := client.RunTasks.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, runTaskList.Items)\n\t\tassert.NotEmpty(t, runTaskList.Items[0].ID)\n\t\tassert.NotEmpty(t, runTaskList.Items[0].URL)\n\t\tassert.NotEmpty(t, runTaskList.Items[1].ID)\n\t\tassert.NotEmpty(t, runTaskList.Items[1].URL)\n\t})\n}\n\nfunc TestRunTasksRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\tt.Run(\"by ID\", func(t *testing.T) {\n\t\tr, err := client.RunTasks.Read(ctx, runTaskTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, runTaskTest.ID, r.ID)\n\t\tassert.Equal(t, runTaskTest.URL, r.URL)\n\t\tassert.Equal(t, runTaskTest.Category, r.Category)\n\t\tassert.Equal(t, runTaskTest.Description, r.Description)\n\t\tassert.Equal(t, runTaskTest.HMACKey, r.HMACKey)\n\t\tassert.Equal(t, runTaskTest.Enabled, r.Enabled)\n\t})\n\n\tt.Run(\"with options\", func(t *testing.T) {\n\t\twkTest1, wkTest1Cleanup := createWorkspace(t, client, orgTest)\n\t\tdefer wkTest1Cleanup()\n\n\t\twkTest2, wkTest2Cleanup := createWorkspace(t, client, orgTest)\n\t\tdefer wkTest2Cleanup()\n\n\t\t_, wrTest1Cleanup := createWorkspaceRunTask(t, client, wkTest1, runTaskTest)\n\t\tdefer wrTest1Cleanup()\n\n\t\t_, wrTest2Cleanup := createWorkspaceRunTask(t, client, wkTest2, runTaskTest)\n\t\tdefer wrTest2Cleanup()\n\n\t\tr, err := client.RunTasks.ReadWithOptions(ctx, runTaskTest.ID, &RunTaskReadOptions{\n\t\t\tInclude: []RunTaskIncludeOpt{RunTaskWorkspaceTasks},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, r.WorkspaceRunTasks)\n\t\tassert.NotEmpty(t, r.WorkspaceRunTasks[0].ID)\n\t\tassert.NotEmpty(t, r.WorkspaceRunTasks[0].EnforcementLevel)\n\t\tassert.NotEmpty(t, r.WorkspaceRunTasks[1].ID)\n\t\tassert.NotEmpty(t, r.WorkspaceRunTasks[1].EnforcementLevel)\n\t})\n}\n\nfunc TestRunTasksUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\tt.Run(\"rename task\", func(t *testing.T) {\n\t\trename := runTaskTest.Name + \"-UPDATED\"\n\t\tr, err := client.RunTasks.Update(ctx, runTaskTest.ID, RunTaskUpdateOptions{\n\t\t\tName: &rename,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tr, err = client.RunTasks.Read(ctx, r.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, rename, r.Name)\n\t})\n\n\tt.Run(\"toggle enabled\", func(t *testing.T) {\n\t\trunTaskTest.Enabled = !runTaskTest.Enabled\n\t\tr, err := client.RunTasks.Update(ctx, runTaskTest.ID, RunTaskUpdateOptions{\n\t\t\tEnabled: &runTaskTest.Enabled,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tr, err = client.RunTasks.Read(ctx, r.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, runTaskTest.Enabled, r.Enabled)\n\t})\n\n\tt.Run(\"update description\", func(t *testing.T) {\n\t\tnewDescription := \"An updated task description\"\n\t\tr, err := client.RunTasks.Update(ctx, runTaskTest.ID, RunTaskUpdateOptions{\n\t\t\tDescription: &newDescription,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tr, err = client.RunTasks.Read(ctx, r.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, newDescription, r.Description)\n\t})\n\n\tt.Run(\"with an agent pool\", func(t *testing.T) {\n\t\t// We can only test if the org, supports private run tasks. For now this isn't\n\t\t// a fatal error and we just skip the test.\n\t\tif v, err := hasPrivateRunTasks(client, orgTest.Name); err != nil {\n\t\t\tt.Fatalf(\"Could not retrieve the entitlements for the test organization.: %s\", err)\n\t\t} else if !v {\n\t\t\tt.Skip(\"The test organization requires the private-run-tasks entitlement but is not entitled.\")\n\t\t\treturn\n\t\t}\n\n\t\t// Unfortunately when we update a Run Task it automatically verifies that the URL by sending a test payload. But\n\t\t// this means with an agent pool, we need an agent pool to exist, and an agent created with request forwarding enabled.\n\t\t// This is too much to create for this one test suite. So instead, we really only need to assert that; when the options include an\n\t\t// agent pool, then we expect HCP Terraform to process the agent pool. So, if we send it a nonsense agent pool ID, then we\n\t\t// expect an error to be returned saying that the ID was nonsense.\n\t\t_, err := client.RunTasks.Update(ctx, runTaskTest.ID, RunTaskUpdateOptions{\n\t\t\tAgentPool: &AgentPool{\n\t\t\t\tID: \"apool-this-pool-id-will-never-exist-so-we-expect-http-error-response\",\n\t\t\t},\n\t\t})\n\t\trequire.ErrorContains(t, err, \"The provided agent pool does not exist\")\n\t})\n}\n\nfunc TestRunTasksDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\trunTaskTest, _ := createRunTask(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.RunTasks.Delete(ctx, runTaskTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.RunTasks.Read(ctx, runTaskTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the run task does not exist\", func(t *testing.T) {\n\t\terr := client.RunTasks.Delete(ctx, runTaskTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the run task ID is invalid\", func(t *testing.T) {\n\t\terr := client.RunTasks.Delete(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidRunTaskID.Error())\n\t})\n}\n\nfunc TestRunTasksAttachToWorkspace(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\tt.Run(\"to a valid workspace\", func(t *testing.T) {\n\t\twr, err := client.RunTasks.AttachToWorkspace(ctx, wkspaceTest.ID, runTaskTest.ID, Advisory)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\terr = client.WorkspaceRunTasks.Delete(ctx, wkspaceTest.ID, wr.ID)\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\t\trequire.NotNil(t, wr.ID)\n\t})\n}\n"
  },
  {
    "path": "run_task_request.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"time\"\n)\n\n// RunTaskRequest is the payload object that TFC/E sends to the Run Task's URL.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/run-tasks/run-tasks-integration#common-properties\ntype RunTaskRequest struct {\n\tAccessToken                     string                      `json:\"access_token\"`\n\tCapabilitites                   RunTaskRequestCapabilitites `json:\"capabilitites,omitempty\"`\n\tConfigurationVersionDownloadURL string                      `json:\"configuration_version_download_url,omitempty\"`\n\tConfigurationVersionID          string                      `json:\"configuration_version_id,omitempty\"`\n\tIsSpeculative                   bool                        `json:\"is_speculative\"`\n\tOrganizationName                string                      `json:\"organization_name\"`\n\tPayloadVersion                  int                         `json:\"payload_version\"`\n\tPlanJSONAPIURL                  string                      `json:\"plan_json_api_url,omitempty\"` // Specific to post_plan, pre_apply or post_apply stage\n\tRunAppURL                       string                      `json:\"run_app_url\"`\n\tRunCreatedAt                    time.Time                   `json:\"run_created_at\"`\n\tRunCreatedBy                    string                      `json:\"run_created_by\"`\n\tRunID                           string                      `json:\"run_id\"`\n\tRunMessage                      string                      `json:\"run_message\"`\n\tStage                           string                      `json:\"stage\"`\n\tTaskResultCallbackURL           string                      `json:\"task_result_callback_url\"`\n\tTaskResultEnforcementLevel      string                      `json:\"task_result_enforcement_level\"`\n\tTaskResultID                    string                      `json:\"task_result_id\"`\n\tVcsBranch                       string                      `json:\"vcs_branch,omitempty\"`\n\tVcsCommitURL                    string                      `json:\"vcs_commit_url,omitempty\"`\n\tVcsPullRequestURL               string                      `json:\"vcs_pull_request_url,omitempty\"`\n\tVcsRepoURL                      string                      `json:\"vcs_repo_url,omitempty\"`\n\tWorkspaceAppURL                 string                      `json:\"workspace_app_url\"`\n\tWorkspaceID                     string                      `json:\"workspace_id\"`\n\tWorkspaceName                   string                      `json:\"workspace_name\"`\n\tWorkspaceWorkingDirectory       string                      `json:\"workspace_working_directory,omitempty\"`\n}\n\n// RunTaskRequestCapabilitites defines the capabilities that the caller supports.\ntype RunTaskRequestCapabilitites struct {\n\tOutcomes bool `json:\"outcomes\"`\n}\n"
  },
  {
    "path": "run_tasks_integration.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ RunTasksIntegration = (*runTaskIntegration)(nil)\n\n// RunTasksIntegration describes all the Run Tasks Integration Callback API methods.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/run-tasks/run-tasks-integration\ntype RunTasksIntegration interface {\n\t// Update sends updates to TFC/E Run Task Callback URL\n\tCallback(ctx context.Context, callbackURL string, accessToken string, options TaskResultCallbackRequestOptions) error\n}\n\n// taskResultsCallback implements RunTasksIntegration.\ntype runTaskIntegration struct {\n\tclient *Client\n}\n\n// TaskResultCallbackRequestOptions represents the TFC/E Task result callback request\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/run-tasks/run-tasks-integration#request-body-1\ntype TaskResultCallbackRequestOptions struct {\n\tType     string               `jsonapi:\"primary,task-results\"`\n\tStatus   TaskResultStatus     `jsonapi:\"attr,status\"`\n\tMessage  string               `jsonapi:\"attr,message,omitempty\"`\n\tURL      string               `jsonapi:\"attr,url,omitempty\"`\n\tOutcomes []*TaskResultOutcome `jsonapi:\"relation,outcomes,omitempty\"`\n}\n\n// TaskResultOutcome represents a detailed TFC/E run task outcome, which improves result visibility and content in the TFC/E UI.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/run-tasks/run-tasks-integration#outcomes-payload-body\ntype TaskResultOutcome struct {\n\tType        string                      `jsonapi:\"primary,task-result-outcomes\"`\n\tOutcomeID   string                      `jsonapi:\"attr,outcome-id,omitempty\"`\n\tDescription string                      `jsonapi:\"attr,description,omitempty\"`\n\tBody        string                      `jsonapi:\"attr,body,omitempty\"`\n\tURL         string                      `jsonapi:\"attr,url,omitempty\"`\n\tTags        map[string][]*TaskResultTag `jsonapi:\"attr,tags,omitempty\"`\n}\n\n// TaskResultTag can be used to enrich outcomes display list in TFC/E.\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/run-tasks/run-tasks-integration#severity-and-status-tags\ntype TaskResultTag struct {\n\tLabel string `json:\"label\"`\n\tLevel string `json:\"level,omitempty\"`\n}\n\n// Update sends updates to TFC/E Run Task Callback URL\nfunc (s *runTaskIntegration) Callback(ctx context.Context, callbackURL, accessToken string, options TaskResultCallbackRequestOptions) error {\n\tif !validString(&callbackURL) {\n\t\treturn ErrInvalidCallbackURL\n\t}\n\tif !validString(&accessToken) {\n\t\treturn ErrInvalidAccessToken\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\treq, err := s.client.NewRequest(http.MethodPatch, callbackURL, &options)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// The PATCH request must use the token supplied in the originating request (access_token) for authentication.\n\t// https://developer.hashicorp.com/terraform/enterprise/api-docs/run-tasks/run-tasks-integration#request-headers-1\n\treq.Header.Set(\"Authorization\", \"Bearer \"+accessToken)\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o *TaskResultCallbackRequestOptions) valid() error {\n\tif o.Status != TaskFailed && o.Status != TaskPassed && o.Status != TaskRunning {\n\t\treturn ErrInvalidTaskResultsCallbackStatus\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "run_tasks_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// TestRunTasksIntegration_Validate runs a series of tests that test whether various TaskResultCallbackRequestOptions objects can be considered valid or not\nfunc TestRunTasksIntegration_Validate(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"with an empty status\", func(t *testing.T) {\n\t\topts := TaskResultCallbackRequestOptions{Status: \"\"}\n\t\terr := opts.valid()\n\t\tassert.EqualError(t, err, ErrInvalidTaskResultsCallbackStatus.Error())\n\t})\n\tt.Run(\"without valid Status options\", func(t *testing.T) {\n\t\tfor _, s := range []TaskResultStatus{TaskPending, TaskErrored, \"foo\"} {\n\t\t\topts := TaskResultCallbackRequestOptions{Status: s}\n\t\t\terr := opts.valid()\n\t\t\tassert.EqualError(t, err, ErrInvalidTaskResultsCallbackStatus.Error())\n\t\t}\n\t})\n\tt.Run(\"with valid Status options\", func(t *testing.T) {\n\t\tfor _, s := range []TaskResultStatus{TaskFailed, TaskPassed, TaskRunning} {\n\t\t\topts := TaskResultCallbackRequestOptions{Status: s}\n\t\t\terr := opts.valid()\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t})\n}\n\n// TestTaskResultsCallbackRequestOptions_Marshal tests whether you can properly serialise a TaskResultCallbackRequestOptions object\n// You may find the expected body here: https://developer.hashicorp.com/terraform/enterprise/api-docs/run-tasks/run-tasks-integration#request-body-1\nfunc TestTaskResultsCallbackRequestOptions_Marshal(t *testing.T) {\n\tt.Parallel()\n\topts := TaskResultCallbackRequestOptions{\n\t\tStatus:  TaskPassed,\n\t\tMessage: \"4 passed, 0 skipped, 0 failed\",\n\t\tURL:     \"https://external.service.dev/terraform-plan-checker/run-i3Df5to9ELvibKpQ\",\n\t\tOutcomes: []*TaskResultOutcome{\n\t\t\t{\n\t\t\t\tOutcomeID:   \"PRTNR-CC-TF-127\",\n\t\t\t\tDescription: \"ST-2942:S3 Bucket will not enforce MFA login on delete requests\",\n\t\t\t\tBody:        \"# Resolution for issue ST-2942\\n\\n## Impact\\n\\nFollow instructions in the [AWS S3 docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiFactorAuthenticationDelete.html) to manually configure the MFA setting.\\n—-- Payload truncated —--\",\n\t\t\t\tURL:         \"https://external.service.dev/result/PRTNR-CC-TF-127\",\n\t\t\t\tTags: map[string][]*TaskResultTag{\n\t\t\t\t\t\"Status\": {&TaskResultTag{Label: \"Denied\", Level: \"error\"}},\n\t\t\t\t\t\"Severity\": {\n\t\t\t\t\t\t&TaskResultTag{Label: \"High\", Level: \"error\"},\n\t\t\t\t\t\t&TaskResultTag{Label: \"Recoverable\", Level: \"info\"},\n\t\t\t\t\t},\n\t\t\t\t\t\"Cost Centre\": {&TaskResultTag{Label: \"IT-OPS\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\trequire.NoError(t, opts.valid())\n\treqBody, err := serializeRequestBody(&opts)\n\trequire.NoError(t, err)\n\texpectedBody := `{\"data\":{\"type\":\"task-results\",\"attributes\":{\"message\":\"4 passed, 0 skipped, 0 failed\",\"status\":\"passed\",\"url\":\"https://external.service.dev/terraform-plan-checker/run-i3Df5to9ELvibKpQ\"},\"relationships\":{\"outcomes\":{\"data\":[{\"type\":\"task-result-outcomes\",\"attributes\":{\"body\":\"# Resolution for issue ST-2942\\n\\n## Impact\\n\\nFollow instructions in the [AWS S3 docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiFactorAuthenticationDelete.html) to manually configure the MFA setting.\\n—-- Payload truncated —--\",\"description\":\"ST-2942:S3 Bucket will not enforce MFA login on delete requests\",\"outcome-id\":\"PRTNR-CC-TF-127\",\"tags\":{\"Cost Centre\":[{\"label\":\"IT-OPS\"}],\"Severity\":[{\"label\":\"High\",\"level\":\"error\"},{\"label\":\"Recoverable\",\"level\":\"info\"}],\"Status\":[{\"label\":\"Denied\",\"level\":\"error\"}]},\"url\":\"https://external.service.dev/result/PRTNR-CC-TF-127\"}}]}}}}\n`\n\tbuf, ok := reqBody.(*bytes.Buffer)\n\trequire.True(t, ok, \"expected request body to be a bytes.Buffer\")\n\n\tassert.Equal(t, buf.String(), expectedBody)\n}\n\nfunc TestRunTasksIntegration_ValidateCallback(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"with invalid callbackURL\", func(t *testing.T) {\n\t\ttrc := runTaskIntegration{client: nil}\n\t\terr := trc.Callback(context.Background(), \"\", \"\", TaskResultCallbackRequestOptions{})\n\t\tassert.EqualError(t, err, ErrInvalidCallbackURL.Error())\n\t})\n\tt.Run(\"with invalid accessToken\", func(t *testing.T) {\n\t\ttrc := runTaskIntegration{client: nil}\n\t\terr := trc.Callback(context.Background(), \"https://app.terraform.io/foo\", \"\", TaskResultCallbackRequestOptions{})\n\t\tassert.EqualError(t, err, ErrInvalidAccessToken.Error())\n\t})\n}\n\nfunc TestRunTasksIntegration_Callback(t *testing.T) {\n\tt.Parallel()\n\tts := runTaskCallbackMockServer(t)\n\tdefer ts.Close()\n\n\tclient, err := NewClient(&Config{\n\t\tRetryServerErrors: true,\n\t\tToken:             testInitialClientToken,\n\t\tAddress:           ts.URL,\n\t})\n\trequire.NoError(t, err)\n\ttrc := runTaskIntegration{\n\t\tclient: client,\n\t}\n\treq := RunTaskRequest{\n\t\tAccessToken:           testTaskResultCallbackToken,\n\t\tTaskResultCallbackURL: ts.URL,\n\t}\n\terr = trc.Callback(context.Background(), req.TaskResultCallbackURL, req.AccessToken, TaskResultCallbackRequestOptions{Status: TaskPassed})\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "run_trigger.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ RunTriggers = (*runTriggers)(nil)\n\n// RunTriggers describes all the Run Trigger\n// related methods that the HCP Terraform API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run-triggers\ntype RunTriggers interface {\n\t// List all the run triggers within a workspace.\n\tList(ctx context.Context, workspaceID string, options *RunTriggerListOptions) (*RunTriggerList, error)\n\n\t// Create a new run trigger with the given options.\n\tCreate(ctx context.Context, workspaceID string, options RunTriggerCreateOptions) (*RunTrigger, error)\n\n\t// Read a run trigger by its ID.\n\tRead(ctx context.Context, RunTriggerID string) (*RunTrigger, error)\n\n\t// ReadWithOptions reads a run trigger by its ID using the options supplied\n\tReadWithOptions(ctx context.Context, runID string, options *RunTriggerReadOptions) (*RunTrigger, error)\n\n\t// Delete a run trigger by its ID.\n\tDelete(ctx context.Context, RunTriggerID string) error\n}\n\n// runTriggers implements RunTriggers.\ntype runTriggers struct {\n\tclient *Client\n}\n\n// RunTriggerList represents a list of Run Triggers\ntype RunTriggerList struct {\n\t*Pagination\n\tItems []*RunTrigger\n}\n\n// SourceableChoice is a choice type struct that represents the possible values\n// within a polymorphic relation. If a value is available, exactly one field\n// will be non-nil.\ntype SourceableChoice struct {\n\tWorkspace *Workspace\n}\n\n// RunTrigger represents a run trigger.\ntype RunTrigger struct {\n\tID             string    `jsonapi:\"primary,run-triggers\"`\n\tCreatedAt      time.Time `jsonapi:\"attr,created-at,iso8601\"`\n\tSourceableName string    `jsonapi:\"attr,sourceable-name\"`\n\tWorkspaceName  string    `jsonapi:\"attr,workspace-name\"`\n\t// DEPRECATED. The sourceable field is polymorphic. Use SourceableChoice instead.\n\tSourceable       *Workspace        `jsonapi:\"relation,sourceable\"`\n\tSourceableChoice *SourceableChoice `jsonapi:\"polyrelation,sourceable\"`\n\tWorkspace        *Workspace        `jsonapi:\"relation,workspace\"`\n}\n\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run-triggers#query-parameters\ntype RunTriggerFilterOp string\n\nconst (\n\tRunTriggerOutbound RunTriggerFilterOp = \"outbound\" // create runs in other workspaces.\n\tRunTriggerInbound  RunTriggerFilterOp = \"inbound\"  // create runs in the specified workspace\n)\n\n// A list of relations to include\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run-triggers#available-related-resources\ntype RunTriggerIncludeOpt string\n\nconst (\n\tRunTriggerWorkspace  RunTriggerIncludeOpt = \"workspace\"\n\tRunTriggerSourceable RunTriggerIncludeOpt = \"sourceable\"\n)\n\n// RunTriggerListOptions represents the options for listing\n// run triggers.\ntype RunTriggerListOptions struct {\n\tListOptions\n\tRunTriggerType RunTriggerFilterOp     `url:\"filter[run-trigger][type]\"` // Required\n\tInclude        []RunTriggerIncludeOpt `url:\"include,omitempty\"`         // optional\n}\n\n// RunTriggerCreateOptions represents the options for\n// creating a new run trigger.\ntype RunTriggerCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,run-triggers\"`\n\n\t// The source workspace\n\tSourceable *Workspace `jsonapi:\"relation,sourceable\"`\n}\n\n// RunTriggerCreateOptions represents the options for reading a run.\ntype RunTriggerReadOptions struct {\n\tInclude []RunTriggerIncludeOpt `url:\"include,omitempty\"` // optional`\n}\n\n// List all the run triggers associated with a workspace.\nfunc (s *runTriggers) List(ctx context.Context, workspaceID string, options *RunTriggerListOptions) (*RunTriggerList, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/run-triggers\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trtl := &RunTriggerList{}\n\terr = req.Do(ctx, rtl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i := range rtl.Items {\n\t\tbackfillDeprecatedSourceable(rtl.Items[i])\n\t}\n\n\treturn rtl, nil\n}\n\n// Create a run trigger with the given options.\nfunc (s *runTriggers) Create(ctx context.Context, workspaceID string, options RunTriggerCreateOptions) (*RunTrigger, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/run-triggers\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := &RunTrigger{}\n\terr = req.Do(ctx, rt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbackfillDeprecatedSourceable(rt)\n\n\treturn rt, nil\n}\n\n// Read a run trigger by its ID.\nfunc (s *runTriggers) Read(ctx context.Context, runTriggerID string) (*RunTrigger, error) {\n\treturn s.ReadWithOptions(ctx, runTriggerID, nil)\n}\n\n// Read a run trigger by its ID.\nfunc (s *runTriggers) ReadWithOptions(ctx context.Context, runTriggerID string, options *RunTriggerReadOptions) (*RunTrigger, error) {\n\tif !validStringID(&runTriggerID) {\n\t\treturn nil, ErrInvalidRunTriggerID\n\t}\n\n\tu := fmt.Sprintf(\"run-triggers/%s\", url.PathEscape(runTriggerID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := &RunTrigger{}\n\terr = req.Do(ctx, rt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbackfillDeprecatedSourceable(rt)\n\n\treturn rt, nil\n}\n\n// Delete a run trigger by its ID.\nfunc (s *runTriggers) Delete(ctx context.Context, runTriggerID string) error {\n\tif !validStringID(&runTriggerID) {\n\t\treturn ErrInvalidRunTriggerID\n\t}\n\n\tu := fmt.Sprintf(\"run-triggers/%s\", url.PathEscape(runTriggerID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o RunTriggerCreateOptions) valid() error {\n\tif o.Sourceable == nil {\n\t\treturn ErrRequiredSourceable\n\t}\n\treturn nil\n}\n\nfunc (o *RunTriggerListOptions) valid() error {\n\tif o == nil {\n\t\treturn ErrRequiredRunTriggerListOps\n\t}\n\n\tif err := validateRunTriggerFilterParam(o.RunTriggerType, o.Include); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc backfillDeprecatedSourceable(runTrigger *RunTrigger) {\n\tif runTrigger.Sourceable != nil || runTrigger.SourceableChoice == nil {\n\t\treturn\n\t}\n\n\trunTrigger.Sourceable = runTrigger.SourceableChoice.Workspace\n}\n\nfunc validateRunTriggerFilterParam(filterParam RunTriggerFilterOp, includeParams []RunTriggerIncludeOpt) error {\n\tswitch filterParam {\n\tcase RunTriggerOutbound, RunTriggerInbound:\n\t\t// Do nothing\n\tdefault:\n\t\treturn ErrInvalidRunTriggerType // return an error even if string is empty because this a required field\n\t}\n\n\tif len(includeParams) > 0 {\n\t\tif filterParam != RunTriggerInbound {\n\t\t\treturn ErrUnsupportedRunTriggerType // if user passes RunTriggerOutbound the platform will not return any \"include\" data\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "run_trigger_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRunTriggerList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\tsourceable1Test, sourceable1TestCleanup := createWorkspace(t, client, orgTest)\n\tdefer sourceable1TestCleanup()\n\n\tsourceable2Test, sourceable2TestCleanup := createWorkspace(t, client, orgTest)\n\tdefer sourceable2TestCleanup()\n\n\trtTest1, rtTestCleanup1 := createRunTrigger(t, client, wTest, sourceable1Test)\n\tdefer rtTestCleanup1()\n\trtTest2, rtTestCleanup2 := createRunTrigger(t, client, wTest, sourceable2Test)\n\tdefer rtTestCleanup2()\n\n\tt.Run(\"without ListOptions and with RunTriggerType\", func(t *testing.T) {\n\t\trtl, err := client.RunTriggers.List(\n\t\t\tctx,\n\t\t\twTest.ID,\n\t\t\t&RunTriggerListOptions{\n\t\t\t\tRunTriggerType: RunTriggerInbound,\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, rtl.Items, rtTest1)\n\t\tassert.Contains(t, rtl.Items, rtTest2)\n\t\tassert.Equal(t, 1, rtl.CurrentPage)\n\t\tassert.Equal(t, 2, rtl.TotalCount)\n\t})\n\n\tt.Run(\"with ListOptions and a RunTriggerType\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\trtl, err := client.RunTriggers.List(\n\t\t\tctx,\n\t\t\twTest.ID,\n\t\t\t&RunTriggerListOptions{\n\t\t\t\tListOptions: ListOptions{\n\t\t\t\t\tPageNumber: 999,\n\t\t\t\t\tPageSize:   100,\n\t\t\t\t},\n\t\t\t\tRunTriggerType: RunTriggerInbound,\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, rtl.Items)\n\t\tassert.Equal(t, 999, rtl.CurrentPage)\n\t\tassert.Equal(t, 2, rtl.TotalCount)\n\t})\n\n\tt.Run(\"without a valid workspace\", func(t *testing.T) {\n\t\trtl, err := client.RunTriggers.List(\n\t\t\tctx,\n\t\t\tbadIdentifier,\n\t\t\t&RunTriggerListOptions{\n\t\t\t\tRunTriggerType: RunTriggerInbound,\n\t\t\t},\n\t\t)\n\t\tassert.Nil(t, rtl)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n\n\tt.Run(\"without defining RunTriggerListOptions\", func(t *testing.T) {\n\t\trtl, err := client.RunTriggers.List(\n\t\t\tctx,\n\t\t\twTest.ID,\n\t\t\tnil,\n\t\t)\n\t\tassert.Nil(t, rtl)\n\t\tassert.Equal(t, err, ErrRequiredRunTriggerListOps)\n\t})\n\n\tt.Run(\"without defining RunTriggerFilterOp as a filter param\", func(t *testing.T) {\n\t\trtl, err := client.RunTriggers.List(\n\t\t\tctx,\n\t\t\twTest.ID,\n\t\t\t&RunTriggerListOptions{\n\t\t\t\tListOptions: ListOptions{\n\t\t\t\t\tPageNumber: 999,\n\t\t\t\t\tPageSize:   100,\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\t\tassert.Nil(t, rtl)\n\t\tassert.Equal(t, err, ErrInvalidRunTriggerType)\n\t})\n\n\tt.Run(\"with invalid option for runTriggerType\", func(t *testing.T) {\n\t\trtl, err := client.RunTriggers.List(\n\t\t\tctx,\n\t\t\twTest.ID,\n\t\t\t&RunTriggerListOptions{\n\t\t\t\tRunTriggerType: \"oubound\",\n\t\t\t},\n\t\t)\n\t\tassert.Nil(t, rtl)\n\t\tassert.Equal(t, err, ErrInvalidRunTriggerType)\n\t})\n\n\tt.Run(\"with sourceable include option\", func(t *testing.T) {\n\t\trtl, err := client.RunTriggers.List(\n\t\t\tctx,\n\t\t\twTest.ID,\n\t\t\t&RunTriggerListOptions{\n\t\t\t\tRunTriggerType: RunTriggerInbound,\n\t\t\t\tInclude:        []RunTriggerIncludeOpt{RunTriggerSourceable},\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, rtl.Items)\n\t\trequire.NotNil(t, rtl.Items[0].Sourceable)\n\t\tassert.NotEmpty(t, rtl.Items[0].Sourceable)\n\t\tassert.NotNil(t, rtl.Items[0].SourceableChoice.Workspace)\n\t\tassert.NotEmpty(t, rtl.Items[0].SourceableChoice.Workspace)\n\t})\n\n\tt.Run(\"with a RunTriggerType that does not return included data\", func(t *testing.T) {\n\t\t_, err := client.RunTriggers.List(\n\t\t\tctx,\n\t\t\twTest.ID,\n\t\t\t&RunTriggerListOptions{\n\t\t\t\tRunTriggerType: RunTriggerOutbound,\n\t\t\t\tInclude:        []RunTriggerIncludeOpt{RunTriggerSourceable},\n\t\t\t},\n\t\t)\n\t\tassert.Equal(t, err, ErrUnsupportedRunTriggerType)\n\t})\n}\n\nfunc TestRunTriggerCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\tsourceableTest, sourceableTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer sourceableTestCleanup()\n\n\tt.Run(\"with all required values\", func(t *testing.T) {\n\t\toptions := RunTriggerCreateOptions{\n\t\t\tSourceable: sourceableTest,\n\t\t}\n\n\t\t_, err := client.RunTriggers.Create(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without a required value\", func(t *testing.T) {\n\t\toptions := RunTriggerCreateOptions{}\n\n\t\trt, err := client.RunTriggers.Create(ctx, wTest.ID, options)\n\t\tassert.Nil(t, rt)\n\t\tassert.Equal(t, err, ErrRequiredSourceable)\n\t})\n\n\tt.Run(\"without a valid workspace\", func(t *testing.T) {\n\t\trt, err := client.RunTriggers.Create(ctx, badIdentifier, RunTriggerCreateOptions{})\n\t\tassert.Nil(t, rt)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n\n\tt.Run(\"when an error is returned from the api\", func(t *testing.T) {\n\t\t// There are many cases that would cause the server to return an error\n\t\t// on run trigger creation. This tests one of them: setting workspace\n\t\t// and sourceable to the same workspace\n\t\toptions := RunTriggerCreateOptions{\n\t\t\tSourceable: sourceableTest,\n\t\t}\n\n\t\trt, err := client.RunTriggers.Create(ctx, sourceableTest.ID, options)\n\t\tassert.Nil(t, rt)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestRunTriggerRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\tsourceableTest, sourceableTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer sourceableTestCleanup()\n\n\trtTest, rtTestCleanup := createRunTrigger(t, client, wTest, sourceableTest)\n\tdefer rtTestCleanup()\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\trt, err := client.RunTriggers.Read(ctx, rtTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, rtTest.ID, rt.ID)\n\t})\n\n\tt.Run(\"when the run trigger does not exist\", func(t *testing.T) {\n\t\t_, err := client.RunTriggers.Read(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the run trigger ID is invalid\", func(t *testing.T) {\n\t\t_, err := client.RunTriggers.Read(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidRunTriggerID)\n\t})\n}\n\nfunc TestRunTriggerReadWithOptions(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\tsourceableTest, sourceableTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer sourceableTestCleanup()\n\n\trtTest, rtTestCleanup := createRunTrigger(t, client, wTest, sourceableTest)\n\tdefer rtTestCleanup()\n\n\tt.Run(\"with include options\", func(t *testing.T) {\n\t\trt, err := client.RunTriggers.ReadWithOptions(ctx, rtTest.ID, &RunTriggerReadOptions{\n\t\t\tInclude: []RunTriggerIncludeOpt{RunTriggerSourceable, RunTriggerWorkspace},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, rtTest.ID, rt.ID)\n\t\trequire.NotNil(t, rt.Sourceable)\n\t\tassert.Equal(t, sourceableTest.ID, rt.Sourceable.ID)\n\t\trequire.NotNil(t, rt.Workspace)\n\t\tassert.Equal(t, wTest.ID, rt.Workspace.ID)\n\t})\n}\n\nfunc TestRunTriggerDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\tsourceableTest, sourceableTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer sourceableTestCleanup()\n\n\t// No need to cleanup here, as this test will delete this run trigger\n\trtTest, _ := createRunTrigger(t, client, wTest, sourceableTest)\n\n\tt.Run(\"with a valid ID\", func(t *testing.T) {\n\t\terr := client.RunTriggers.Delete(ctx, rtTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.RunTriggers.Read(ctx, rtTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the run trigger does not exist\", func(t *testing.T) {\n\t\terr := client.RunTriggers.Delete(ctx, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the run trigger ID is invalid\", func(t *testing.T) {\n\t\terr := client.RunTriggers.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidRunTriggerID)\n\t})\n}\n"
  },
  {
    "path": "scripts/generate_resource/go.mod",
    "content": "module generate_resource\n\ngo 1.17\n\nrequire github.com/iancoleman/strcase v0.2.0\n\nrequire github.com/gertd/go-pluralize v0.2.1\n"
  },
  {
    "path": "scripts/generate_resource/go.sum",
    "content": "github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA=\ngithub.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk=\ngithub.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=\ngithub.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=\n"
  },
  {
    "path": "scripts/generate_resource/main.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/gertd/go-pluralize\"\n\t\"github.com/iancoleman/strcase\"\n)\n\nvar validResourceName = regexp.MustCompile(`^[a-zA-Z_]+$`).MatchString\n\nfunc generateResourceTemplate(name string) ResourceTemplate {\n\tvar pluralName string\n\tpluralize := pluralize.NewClient()\n\n\tif pluralize.IsPlural(name) {\n\t\tpluralName = name\n\t\tname = pluralize.Singular(name)\n\t} else {\n\t\tpluralName = pluralize.Plural(name)\n\t}\n\n\tcamelName := strcase.ToCamel(name)\n\n\treturn ResourceTemplate{\n\t\tPrimaryTag:        strings.ReplaceAll(name, \"_\", \"-\"),\n\t\tName:              strings.ReplaceAll(name, \"_\", \" \"),\n\t\tPluralName:        strings.ReplaceAll(pluralName, \"_\", \" \"),\n\t\tResource:          camelName,\n\t\tResourceInterface: strcase.ToCamel(pluralName),\n\t\tResourceStruct:    strcase.ToLowerCamel(pluralName),\n\t\tResourceID:        fmt.Sprintf(\"%sID\", camelName),\n\t\tListOptions:       fmt.Sprintf(\"%sListOptions\", camelName),\n\t\tReadOptions:       fmt.Sprintf(\"%sReadOptions\", camelName),\n\t\tCreateOptions:     fmt.Sprintf(\"%sCreateOptions\", camelName),\n\t\tUpdateOptions:     fmt.Sprintf(\"%sUpdateOptions\", camelName),\n\t}\n}\n\nfunc main() {\n\tvar resourceName string\n\n\tif len(os.Args) < 2 {\n\t\tlog.Fatal(\"usage: <resource name>\")\n\t}\n\n\tif os.Args[1] == \"-h\" {\n\t\tfmt.Println(helpTemplate)\n\t\treturn\n\t} else {\n\t\tresourceName = strings.ToLower(os.Args[1])\n\t}\n\n\tif !validResourceName(resourceName) {\n\t\tlog.Fatal(\"resource name can only contain letters or underscores.\")\n\t}\n\n\tresourceTmpl := generateResourceTemplate(resourceName)\n\n\ttmp, err := template.New(\"source\").Parse(sourceTemplate)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tsourceFile, err := os.Create(\"../../\" + resourceName + \".go\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer sourceFile.Close()\n\n\tfmt.Printf(\"Generating %s.go\\n\", resourceName)\n\terr = tmp.Execute(sourceFile, resourceTmpl)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\ttmp, err = template.New(\"source\").Parse(testTemplate)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\ttestFile, err := os.Create(\"../../\" + resourceName + \"_integration_test.go\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer testFile.Close()\n\n\tfmt.Printf(\"Generating %s_integration_test.go\\n\", resourceName)\n\terr = tmp.Execute(testFile, resourceTmpl)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Printf(\"Done generating files for new resource: %s\\n\", resourceTmpl.Resource)\n}\n"
  },
  {
    "path": "scripts/generate_resource/templates.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\ntype ResourceTemplate struct {\n\t// Lower cased name of a resource, not plural\n\tName string\n\n\t// Lower cased name of a resource, plural\n\tPluralName string\n\n\t// Name of resource model\n\tResource string\n\n\t// Name of resource interface\n\tResourceInterface string\n\n\t// Name of resource struct that implements resource interface\n\tResourceStruct string\n\n\t// The resource ID\n\tResourceID string\n\n\t// Struct tag name for (un)marshalling the JSON+API resource.\n\tPrimaryTag string\n\n\tListOptions   string\n\tReadOptions   string\n\tCreateOptions string\n\tUpdateOptions string\n}\n\nconst helpTemplate = `\nThis script is used to quickly scaffold a resource in go-tfe. Simply provide a\nresource name as the first argument and it will generate standard boilerplate.\n\nNote: A resource name can only contain letters and underscores.\n\nAllowed: policy_set, Run_task, orGanizatIon\nNot Allowed: policy123, #user, my cool resource\n\nIf your resource contains multiple terms, e.g policy set, you must use an\nunderscore delimiter for each term in order to generate proper casing in your\ncode. For example, if you wanted to generate the policy set resource as\nPolicySet you would pass policy_set as your argument.\n\nExample usage: go run ./scripts/generate_resource/main.go policy_set`\n\nconst sourceTemplate = `\npackage tfe\n\nimport (\n  \"context\"\n)\n\nvar _ {{ .ResourceInterface }} = (*{{ .ResourceStruct }})(nil)\n\n// {{ .ResourceInterface }} describes all the {{ .Name }} related methods that the Terraform\n// Enterprise API supports\n//\n// TFE API docs: (TODO: ADD DOCS URL)\ntype {{ .ResourceInterface }} interface {\n  // List all {{ .PluralName }}.\n  List(ctx context.Context, options *{{ .ListOptions }}) (*{{ .Resource }}List, error)\n\n  // Create a {{ .Name }}.\n  Create(ctx context.Context, options {{ .CreateOptions }}) (*{{ .Resource }}, error)\n\n  // Read a {{ .Name }} by its ID.\n  Read(ctx context.Context, {{ .ResourceID }} string) (*{{ .Resource }}, error)\n\n  // Read a {{ .Name }} by its ID with options.\n  ReadWithOptions(ctx context.Context, {{ .ResourceID }} string, options *{{ .ReadOptions }}) (*{{ .Resource }}, error)\n\n  // Update a {{ .Name }}.\n  Update(ctx context.Context, {{ .ResourceID }} string, options {{ .UpdateOptions }}) (*{{ .Resource }}, error)\n\n  // Delete a {{ .Name }}.\n  Delete(ctx context.Context, {{ .ResourceID }} string) error\n}\n\n// {{ .ResourceStruct }} implements {{ .ResourceInterface }}\ntype {{ .ResourceStruct }} struct {\n  client *Client\n}\n\n// {{ .Resource }}List represents a list of {{ .PluralName }}\ntype {{ .Resource }}List struct {\n  *Pagination\n  Items []*{{ .Resource }}\n}\n\n// {{ .Resource }} represents a Terraform Enterprise $resource\ntype {{ .Resource }} struct {\n  ID string ` + \"`jsonapi:\\\"primary,\" + `{{ .PrimaryTag }}` + \"\\\"`\" + `\n  // Add more fields here\n}\n\n// {{ .ListOptions }} represents the options for listing {{ .PluralName }}\ntype {{ .ListOptions }} struct {\n  ListOptions\n\n  // Add more list options here\n}\n\n// {{ .CreateOptions }} represents the options for creating a {{ .Name }}\ntype {{ .CreateOptions }} struct {\n  Type string ` + \"`jsonapi:\\\"primary,\" + `{{ .PrimaryTag }}` + \"\\\"`\" + `\n  // Add more create options here\n}\n\n// {{ .ReadOptions }} represents the options for reading a {{ .Name }}\ntype {{ .ReadOptions }} struct {\n  // Add more read options here\n}\n\n// {{ .UpdateOptions }} represents the options for updating a {{ .Name }}\ntype {{ .UpdateOptions }} struct {\n  ID string ` + \"`jsonapi:\\\"primary,\" + `{{ .PrimaryTag }}` + \"\\\"`\" + `\n\n  // Add more update options here\n}\n\n// List all {{ .PluralName }}.\nfunc List(ctx context.Context, options *{{ .ListOptions }}) (*{{ .Resource }}List, error) {\n    panic(\"not yet implemented\")\n}\n\n// Create a {{ .Name }}.\nfunc Create(ctx context.Context, options {{ .CreateOptions }}) (*{{ .Resource }}, error) {\n    panic(\"not yet implemented\")\n}\n\n// Read a {{ .Name }} by its ID.\nfunc Read(ctx context.Context, {{ .ResourceID }} string) (*{{ .Resource }}, error) {\n    panic(\"not yet implemented\")\n}\n\n// Read a {{ .Name }} by its ID with options.\nfunc ReadWithOptions(ctx context.Context, {{ .ResourceID }} string, options *{{ .ReadOptions }}) (*{{ .Resource }}, error) {\n    panic(\"not yet implemented\")\n}\n\n// Update a {{ .Name }}.\nfunc Update(ctx context.Context, {{ .ResourceID }} string, options {{ .UpdateOptions }}) (*{{ .Resource }}, error) {\n    panic(\"not yet implemented\")\n}\n\n// Delete a {{ .Name }}.\nfunc Delete(ctx context.Context, {{ .ResourceID }} string) error {\n    panic(\"not yet implemented\")\n}`\n\nconst testTemplate = `package tfe\n\nimport (\n  \"context\"\n  \"testing\"\n\n  \"github.com/stretchr/testify/assert\"\n  \"github.com/stretchr/testify/require\"\n)\n\nfunc Test{{ .ResourceInterface }}List(t *testing.T) {\n   client := testClient(t)\n  ctx := context.Background()\n\n  // Create your test helper resources here\n  t.Run(\"test not yet implemented\", func(t *testing.T) {\n    require.NotNil(t, nil)\n  })\n}\n\nfunc Test{{ .ResourceInterface }}Read(t *testing.T) {\n   client := testClient(t)\n  ctx := context.Background()\n\n  // Create your test helper resources here\n  t.Run(\"test not yet implemented\", func(t *testing.T) {\n    require.NotNil(t, nil)\n  })\n}\n\nfunc Test{{ .ResourceInterface }}Create(t *testing.T) {\n   client := testClient(t)\n  ctx := context.Background()\n\n  // Create your test helper resources here\n  t.Run(\"test not yet implemented\", func(t *testing.T) {\n    require.NotNil(t, nil)\n  })\n}\n\nfunc Test{{ .ResourceInterface }}Update(t *testing.T) {\n   client := testClient(t)\n  ctx := context.Background()\n\n  // Create your test helper resources here\n  t.Run(\"test not yet implemented\", func(t *testing.T) {\n    require.NotNil(t, nil)\n  })\n}`\n"
  },
  {
    "path": "scripts/gofmtcheck.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\nif ! gofmt -l -s .; then\n    echo \"gofmt found some files that need to be formatted. You can use the command: \\`make fmt\\` to reformat code.\"\n    exit 1\nfi\n\nexit 0\n"
  },
  {
    "path": "scripts/hyok-testing.sh",
    "content": "#!/bin/bash\n\nenv=\"STAGING_ENVCHAIN\"\npairs=(\n    # HYOK Attributes testing\n    # -- Agent Pools\n    \"TestAgentPoolsRead:read_hyok_configurations_of_an_agent_pool\"\n    # -- Plans\n    \"TestPlansRead:read_hyok_encrypted_data_key_of_a_plan\"\n    \"TestPlansRead:read_sanitized_plan_of_a_plan\"\n    # -- Workspaces\n    \"TestWorkspacesCreate:create_workspace_with_hyok_enabled_set_to_false\"\n    \"TestWorkspacesCreate:create_workspace_with_hyok_enabled_set_to_true\"\n    \"TestWorkspacesRead:read_hyok_enabled_of_a_workspace\"\n    \"TestWorkspacesRead:read_hyok_encrypted_data_key_of_a_workspace\"\n    \"TestWorkspacesUpdate:update_hyok_enabled_of_a_workspace_from_false_to_false\"\n    \"TestWorkspacesUpdate:update_hyok_enabled_of_a_workspace_from_false_to_true\"\n    \"TestWorkspacesUpdate:update_hyok_enabled_of_a_workspace_from_true_to_true\"\n    \"TestWorkspacesUpdate:update_hyok_enabled_of_a_workspace_from_true_to_false\"\n    # -- Organizations\n    \"TestOrganizationsRead:read_primary_hyok_configuration_of_an_organization\"\n    \"TestOrganizationsRead:read_enforce_hyok_of_an_organization\"\n    \"TestOrganizationsUpdate:update_enforce_hyok_of_an_organization_to_true\"\n    \"TestOrganizationsUpdate:update_enforce_hyok_of_an_organization_to_false\"\n    # -- State Versions\n    \"TestStateVersionsRead:read_encrypted_state_download_url_of_a_state_version\"\n    \"TestStateVersionsRead:read_sanitized_state_download_url_of_a_state_version\"\n    \"TestStateVersionsRead:read_hyok_encrypted_data_key_of_a_state_version\"\n    \"TestStateVersionsUpload:uploading_state_using_SanitizedStateUploadURL_and_verifying_SanitizedStateDownloadURL_exists\"\n    \"TestStateVersionsUpload:SanitizedStateUploadURL_is_required_when_uploading_sanitized_state\"\n\n    # AWS OIDC Configuration testing\n    \"TestAWSOIDCConfigurationCreateDelete:with_valid_options\"\n    \"TestAWSOIDCConfigurationCreateDelete:missing_role_ARN\"\n    \"TestAWSOIDCConfigurationRead:fetch_existing_configuration\"\n    \"TestAWSOIDCConfigurationRead:fetching_non-existing_configuration\"\n    \"TestAWSOIDCConfigurationsUpdate:with_valid_options\"\n    \"TestAWSOIDCConfigurationsUpdate:missing_role_ARN\"\n\n    # Azure OIDC Configuration testing\n    \"TestAzureOIDCConfigurationCreateDelete:with_valid_options\"\n    \"TestAzureOIDCConfigurationCreateDelete:missing_client_ID\"\n    \"TestAzureOIDCConfigurationCreateDelete:missing_subscription_ID\"\n    \"TestAzureOIDCConfigurationCreateDelete:missing_tenant_ID\"\n    \"TestAzureOIDCConfigurationRead:fetch_existing_configuration\"\n    \"TestAzureOIDCConfigurationRead:fetching_non-existing_configuration\"\n    \"TestAzureOIDCConfigurationUpdate:update_all_fields\"\n    \"TestAzureOIDCConfigurationUpdate:client_ID_not_provided\"\n    \"TestAzureOIDCConfigurationUpdate:subscription_ID_not_provided\"\n    \"TestAzureOIDCConfigurationUpdate:tenant_ID_not_provided\"\n\n    # GCP OIDC Configuration testing\n    \"TestGCPOIDCConfigurationCreateDelete:with_valid_options\"\n    \"TestGCPOIDCConfigurationCreateDelete:missing_workload_provider_name\"\n    \"TestGCPOIDCConfigurationCreateDelete:missing_service_account_email\"\n    \"TestGCPOIDCConfigurationCreateDelete:missing_project_number\"\n    \"TestGCPOIDCConfigurationRead:fetch_existing_configuration\"\n    \"TestGCPOIDCConfigurationRead:fetching_non-existing_configuration\"\n    \"TestGCPOIDCConfigurationUpdate:update_all_fields\"\n    \"TestGCPOIDCConfigurationUpdate:workload_provider_name_not_provided\"\n    \"TestGCPOIDCConfigurationUpdate:service_account_email_not_provided\"\n    \"TestGCPOIDCConfigurationUpdate:project_number_not_provided\"\n\n    # Vault OIDC Configuration testing\n    \"TestVaultOIDCConfigurationCreateDelete:with_valid_options\"\n    \"TestVaultOIDCConfigurationCreateDelete:missing_address\"\n    \"TestVaultOIDCConfigurationCreateDelete:missing_role_name\"\n    \"TestVaultOIDCConfigurationRead:fetch_existing_configuration\"\n    \"TestVaultOIDCConfigurationRead:fetching_non-existing_configuration\"\n    \"TestVaultOIDCConfigurationUpdate:update_all_fields\"\n    \"TestVaultOIDCConfigurationUpdate:address_not_provided\"\n    \"TestVaultOIDCConfigurationUpdate:role_name_not_provided\"\n    \"TestVaultOIDCConfigurationUpdate:namespace_not_provided\"\n    \"TestVaultOIDCConfigurationUpdate:JWTAuthPath_not_provided\"\n    \"TestVaultOIDCConfigurationUpdate:TLSCACertificate_not_provided\"\n\n    # HYOK Customer Key Version testing\n    \"TestHYOKCustomerKeyVersionsList:with_no_list_options\"\n    \"TestHYOKCustomerKeyVersionsRead:read_an_existing_key_version\"\n\n    # HYOK Encrypted Data Key testing\n    \"TestHYOKEncryptedDataKeyRead:read_an_existing_encrypted_data_key\"\n\n    # HYOK Configurations testing\n    \"TestHYOKConfigurationCreateRevokeDelete:AWS_with_valid_options\"\n    \"TestHYOKConfigurationCreateRevokeDelete:AWS_with_missing_key_region\"\n    \"TestHYOKConfigurationCreateRevokeDelete:GCP_with_valid_options\"\n    \"TestHYOKConfigurationCreateRevokeDelete:GCP_with_missing_key_location\"\n    \"TestHYOKConfigurationCreateRevokeDelete:GCP_with_missing_key_ring_ID\"\n    \"TestHYOKConfigurationCreateRevokeDelete:Vault_with_valid_options\"\n    \"TestHYOKConfigurationCreateRevokeDelete:Azure_with_valid_options\"\n    \"TestHYOKConfigurationCreateRevokeDelete:with_missing_KEK_ID\"\n    \"TestHYOKConfigurationCreateRevokeDelete:with_missing_agent_pool\"\n    \"TestHYOKConfigurationCreateRevokeDelete:with_missing_OIDC_config\"\n    \"TestHyokConfigurationList:without_list_options\"\n    \"TestHyokConfigurationRead:AWS\"\n    \"TestHyokConfigurationRead:Azure\"\n    \"TestHyokConfigurationRead:GCP\"\n    \"TestHyokConfigurationRead:Vault\"\n    \"TestHyokConfigurationRead:fetching_non-existing_configuration\"\n    \"TestHYOKConfigurationUpdate:AWS_with_valid_options\"\n    \"TestHYOKConfigurationUpdate:GCP_with_valid_options\"\n    \"TestHYOKConfigurationUpdate:Vault_with_valid_options\"\n    \"TestHYOKConfigurationUpdate:Azure_with_valid_options\"\n)\n\nfor pair in \"${pairs[@]}\"; do\n    IFS=':' read -r parent child <<< \"$pair\"\n    result=$(envchain ${env} go test -run \"^${parent}$/^${child}$\" -v ./...)\n    status=\"\\033[33mUNKNOWN\\033[0m\" # yellow by default\n    if echo \"$result\" | grep -q \"^    --- SKIP: ${parent}/${child}\"; then\n        status=\"\\033[33mSKIP\\033[0m\" # yellow\n    elif echo \"$result\" | grep -q \"^--- PASS: ${parent}\"; then\n        status=\"\\033[32mPASS\\033[0m\" # green\n    elif echo \"$result\" | grep -q \"^--- FAIL: ${parent}\"; then\n        status=\"\\033[31mFAIL\\033[0m\" # red\n    fi\n    echo -e \"\\033[34m${parent}/${child}\\033[0m: ${status}\"\ndone"
  },
  {
    "path": "scripts/rebase-fork.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\n\nif [[ -z $1 ]]; then\n    echo \"Please specify the pull request number you want to rebase to a local branch.\"\n    echo \"Usage: ./scripts/rebase-fork.sh <pr number>\"\n    echo \"\"\n    echo \"Example: ./scripts/rebase-fork.sh 557\"\n    exit 1\nfi\n\nPR_NUMBER=$1\n\ndeclare -a req_tools=(\"gh\" \"git\" \"jq\")\nfor tool in \"${req_tools[@]}\"; do\n  if ! command -v \"${tool}\" > /dev/null; then\n    echo \"It looks like '${tool}' is not installed; please install it and run this script again.\"\n    exit 1\n  fi\ndone\n\n# Check if the PR specified is a valid number\nre='^[0-9]+$'\nif ! [[ ${PR_NUMBER} =~ ${re} ]] ; then\n   echo \"The PR you specify must be a valid integer number.\" >&2; exit 1\nfi\n\n# Check if the specified PR exists\n# We only capture stderr here and redirect stdout to /dev/null\nerrormsg=$(gh pr view ${PR_NUMBER} 2>&1 1>/dev/null)\nif [[ ! -z ${errormsg} ]]; then\n    # strip GraphQL log prefix to keep the error message clean\n    errormsg=${errormsg#\"GraphQL: \"}\n    echo \"Failed to fetch pull request #${PR_NUMBER}: ${errormsg}\"\n    exit 1\nfi\n\n# Check if the pull request we want to rebase is already closed. If so\n# exit.\nclosed=$(gh pr view ${PR_NUMBER} --json closed | jq '.closed')\nif [[ $closed = \"true\" ]]; then\n    echo \"The pull request #${PR_NUMBER} to rebase is already closed.\"\n    exit 1\nfi\n\n# Save the name of the current branch we're in so we can go back to it after\n# we are done\nCURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)\n\n# Checkout the fork PR locally\ngh pr checkout ${PR_NUMBER}\n\n# Grab the PR title and branch name\nFORK_PR_TITLE=$(gh pr view ${PR_NUMBER} --json title | jq '.title' | tr -d '\"')\nFORK_PR_BRANCH=$(gh pr view ${PR_NUMBER} --json headRefName | jq '.headRefName')\n\n# Fetch the username of the user currently authenticated with gh cli\nUSER=$(gh api user | jq -r '.login')\n\n# Name of the local branch that will be pushed upstream\nLOCAL_BRANCH=\"${USER}/$(echo ${FORK_PR_BRANCH} | tr -d '\"')\"\n\n# Fetch the PR body and write to local markdown file\ngh pr view ${PR_NUMBER} --json body | jq -r '.body' > ${PR_NUMBER}.md\n\ngit checkout -b ${LOCAL_BRANCH}\ngit commit --allow-empty -m \"Rebased ${FORK_PR_BRANCH} onto a local branch\"\ngit push -u origin ${LOCAL_BRANCH}\n\n# Finally we can automagically open a new PR using the fork PR's original title\n# and description\ngh pr create --title=\"${FORK_PR_TITLE}\" --body-file=${PR_NUMBER}.md\n\n# Cleanup\nrm ${PR_NUMBER}.md\ngit checkout ${CURRENT_BRANCH}\n\n"
  },
  {
    "path": "scripts/setup-test-envvars.sh",
    "content": "#!/usr/bin/env bash -e\n# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\n\n# setup-test-envvars.sh\n#\n# A helper script that uses envchain (https://github.com/sorah/envchain) to set environment variables for tests.\n# It's required to have TFE_ADDRESS and TFE_TOKEN set, the others are optional.\n#\ntype envchain >/dev/null 2>&1 || { echo >&2 \"Required executable 'envchain' not found - install it via 'brew install envchain'. Exiting.\"; exit 1; }\n\necho \"Set environment variables (envvars) for running tests locally\"\necho \" envchain will prompt you for values for 4 envvars\"\necho \" TFE_ADDRESS and TFE_TOKEN are required, the others are optional,\"\necho \" press 'return' to skip them\"\necho \"\"\n\nread -p \"Enter the namespace you want to use in envchain [go-tfe]: \" namespace\nnamespace=${namespace:-go-tfe}\nenvchain --set ${namespace} TFE_ADDRESS TFE_TOKEN OAUTH_CLIENT_GITHUB_TOKEN GITHUB_POLICY_SET_IDENTIFIER\necho \"Done! To see the values: envchain ${namespace} env\"\n"
  },
  {
    "path": "ssh_key.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ SSHKeys = (*sshKeys)(nil)\n\n// SSHKeys describes all the SSH key related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/ssh-keys\ntype SSHKeys interface {\n\t// List all the SSH keys for a given organization\n\tList(ctx context.Context, organization string, options *SSHKeyListOptions) (*SSHKeyList, error)\n\n\t// Create an SSH key and associate it with an organization.\n\tCreate(ctx context.Context, organization string, options SSHKeyCreateOptions) (*SSHKey, error)\n\n\t// Read an SSH key by its ID.\n\tRead(ctx context.Context, sshKeyID string) (*SSHKey, error)\n\n\t// Update an SSH key by its ID.\n\tUpdate(ctx context.Context, sshKeyID string, options SSHKeyUpdateOptions) (*SSHKey, error)\n\n\t// Delete an SSH key by its ID.\n\tDelete(ctx context.Context, sshKeyID string) error\n}\n\n// sshKeys implements SSHKeys.\ntype sshKeys struct {\n\tclient *Client\n}\n\n// SSHKeyList represents a list of SSH keys.\ntype SSHKeyList struct {\n\t*Pagination\n\tItems []*SSHKey\n}\n\n// SSHKey represents a SSH key.\ntype SSHKey struct {\n\tID   string `jsonapi:\"primary,ssh-keys\"`\n\tName string `jsonapi:\"attr,name\"`\n}\n\n// SSHKeyListOptions represents the options for listing SSH keys.\ntype SSHKeyListOptions struct {\n\tListOptions\n}\n\n// SSHKeyCreateOptions represents the options for creating an SSH key.\ntype SSHKeyCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,ssh-keys\"`\n\n\t// A name to identify the SSH key.\n\tName *string `jsonapi:\"attr,name\"`\n\n\t// The content of the SSH private key.\n\tValue *string `jsonapi:\"attr,value\"`\n}\n\n// SSHKeyUpdateOptions represents the options for updating an SSH key.\ntype SSHKeyUpdateOptions struct {\n\t// For internal use only!\n\tID string `jsonapi:\"primary,ssh-keys\"`\n\n\t// Optional: A new name to identify the SSH key.\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n}\n\n// List all the SSH keys for a given organization\nfunc (s *sshKeys) List(ctx context.Context, organization string, options *SSHKeyListOptions) (*SSHKeyList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/ssh-keys\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkl := &SSHKeyList{}\n\terr = req.Do(ctx, kl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn kl, nil\n}\n\n// Create an SSH key and associate it with an organization.\nfunc (s *sshKeys) Create(ctx context.Context, organization string, options SSHKeyCreateOptions) (*SSHKey, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/ssh-keys\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tk := &SSHKey{}\n\terr = req.Do(ctx, k)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn k, nil\n}\n\n// Read an SSH key by its ID.\nfunc (s *sshKeys) Read(ctx context.Context, sshKeyID string) (*SSHKey, error) {\n\tif !validStringID(&sshKeyID) {\n\t\treturn nil, ErrInvalidSHHKeyID\n\t}\n\n\tu := fmt.Sprintf(\"ssh-keys/%s\", url.PathEscape(sshKeyID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tk := &SSHKey{}\n\terr = req.Do(ctx, k)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn k, nil\n}\n\n// Update an SSH key by its ID.\nfunc (s *sshKeys) Update(ctx context.Context, sshKeyID string, options SSHKeyUpdateOptions) (*SSHKey, error) {\n\tif !validStringID(&sshKeyID) {\n\t\treturn nil, ErrInvalidSHHKeyID\n\t}\n\n\tu := fmt.Sprintf(\"ssh-keys/%s\", url.PathEscape(sshKeyID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tk := &SSHKey{}\n\terr = req.Do(ctx, k)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn k, nil\n}\n\n// Delete an SSH key by its ID.\nfunc (s *sshKeys) Delete(ctx context.Context, sshKeyID string) error {\n\tif !validStringID(&sshKeyID) {\n\t\treturn ErrInvalidSHHKeyID\n\t}\n\n\tu := fmt.Sprintf(\"ssh-keys/%s\", url.PathEscape(sshKeyID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o SSHKeyCreateOptions) valid() error {\n\tif !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\tif !validString(o.Value) {\n\t\treturn ErrRequiredValue\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "ssh_key_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSSHKeysList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tkTest1, kTestCleanup1 := createSSHKey(t, client, orgTest)\n\tdefer kTestCleanup1()\n\tkTest2, kTestCleanup2 := createSSHKey(t, client, orgTest)\n\tdefer kTestCleanup2()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tkl, err := client.SSHKeys.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, kl.Items, kTest1)\n\t\tassert.Contains(t, kl.Items, kTest2)\n\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\tassert.Equal(t, 1, kl.CurrentPage)\n\t\tassert.Equal(t, 2, kl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tkl, err := client.SSHKeys.List(ctx, orgTest.Name, &SSHKeyListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, kl.Items)\n\t\tassert.Equal(t, 999, kl.CurrentPage)\n\t\tassert.Equal(t, 2, kl.TotalCount)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tkl, err := client.SSHKeys.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, kl)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestSSHKeysCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := SSHKeyCreateOptions{\n\t\t\tName:  String(randomString(t)),\n\t\t\tValue: String(randomString(t)),\n\t\t}\n\n\t\tk, err := client.SSHKeys.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.SSHKeys.Read(ctx, k.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*SSHKey{\n\t\t\tk,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t}\n\t})\n\n\tt.Run(\"when options is missing name\", func(t *testing.T) {\n\t\tk, err := client.SSHKeys.Create(ctx, \"foo\", SSHKeyCreateOptions{\n\t\t\tValue: String(randomString(t)),\n\t\t})\n\t\tassert.Nil(t, k)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"when options is missing value\", func(t *testing.T) {\n\t\tk, err := client.SSHKeys.Create(ctx, \"foo\", SSHKeyCreateOptions{\n\t\t\tName: String(randomString(t)),\n\t\t})\n\t\tassert.Nil(t, k)\n\t\tassert.Equal(t, err, ErrRequiredValue)\n\t})\n\n\tt.Run(\"when options has an invalid organization\", func(t *testing.T) {\n\t\tk, err := client.SSHKeys.Create(ctx, badIdentifier, SSHKeyCreateOptions{\n\t\t\tName: String(\"foo\"),\n\t\t})\n\t\tassert.Nil(t, k)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestSSHKeysRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tkTest, kTestCleanup := createSSHKey(t, client, orgTest)\n\tdefer kTestCleanup()\n\n\tt.Run(\"when the SSH key exists\", func(t *testing.T) {\n\t\tk, err := client.SSHKeys.Read(ctx, kTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, kTest, k)\n\t})\n\n\tt.Run(\"when the SSH key does not exist\", func(t *testing.T) {\n\t\tk, err := client.SSHKeys.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, k)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without a valid SSH key ID\", func(t *testing.T) {\n\t\tk, err := client.SSHKeys.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, k)\n\t\tassert.Equal(t, err, ErrInvalidSHHKeyID)\n\t})\n}\n\nfunc TestSSHKeysUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createSSHKey(t, client, orgTest)\n\t\tdefer kTestCleanup()\n\n\t\tkAfter, err := client.SSHKeys.Update(ctx, kBefore.ID, SSHKeyUpdateOptions{\n\t\t\tName: String(randomString(t)),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.ID, kAfter.ID)\n\t\tassert.NotEqual(t, kBefore.Name, kAfter.Name)\n\t})\n\n\tt.Run(\"when updating the name\", func(t *testing.T) {\n\t\tkBefore, kTestCleanup := createSSHKey(t, client, orgTest)\n\t\tdefer kTestCleanup()\n\n\t\tkAfter, err := client.SSHKeys.Update(ctx, kBefore.ID, SSHKeyUpdateOptions{\n\t\t\tName: String(\"updated-key-name\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, kBefore.ID, kAfter.ID)\n\t\tassert.Equal(t, \"updated-key-name\", kAfter.Name)\n\t})\n\n\tt.Run(\"without a valid SSH key ID\", func(t *testing.T) {\n\t\tw, err := client.SSHKeys.Update(ctx, badIdentifier, SSHKeyUpdateOptions{})\n\t\tassert.Nil(t, w)\n\t\tassert.Equal(t, err, ErrInvalidSHHKeyID)\n\t})\n}\n\nfunc TestSSHKeysDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tkTest, _ := createSSHKey(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.SSHKeys.Delete(ctx, kTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the SSH key - it should fail.\n\t\t_, err = client.SSHKeys.Read(ctx, kTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the SSH key does not exist\", func(t *testing.T) {\n\t\terr := client.SSHKeys.Delete(ctx, kTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the SSH key ID is invalid\", func(t *testing.T) {\n\t\terr := client.SSHKeys.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidSHHKeyID)\n\t})\n}\n"
  },
  {
    "path": "stack.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Stacks describes all the stacks-related methods that the HCP Terraform API supports.\ntype Stacks interface {\n\t// List returns a list of stacks, optionally filtered by project.\n\tList(ctx context.Context, organization string, options *StackListOptions) (*StackList, error)\n\n\t// Read returns a stack by its ID.\n\tRead(ctx context.Context, stackID string) (*Stack, error)\n\n\t// Create creates a new stack.\n\tCreate(ctx context.Context, options StackCreateOptions) (*Stack, error)\n\n\t// Update updates a stack.\n\tUpdate(ctx context.Context, stackID string, options StackUpdateOptions) (*Stack, error)\n\n\t// Delete deletes a stack.\n\tDelete(ctx context.Context, stackID string) error\n\n\t// ForceDelete deletes a stack.\n\tForceDelete(ctx context.Context, stackID string) error\n\n\t// FetchLatestFromVcs updates the configuration of a stack, triggering stack preparation.\n\tFetchLatestFromVcs(ctx context.Context, stackID string) (*Stack, error)\n}\n\n// stacks implements Stacks.\ntype stacks struct {\n\tclient *Client\n}\n\nvar _ Stacks = &stacks{}\n\n// StackSortColumn represents a string that can be used to sort items when using\n// the List method.\ntype StackSortColumn string\n\nconst (\n\t// StackSortByName sorts by the name attribute.\n\tStackSortByName StackSortColumn = \"name\"\n\n\t// StackSortByUpdatedAt sorts by the updated-at attribute.\n\tStackSortByUpdatedAt StackSortColumn = \"updated-at\"\n\n\t// StackSortByNameDesc sorts by the name attribute in descending order.\n\tStackSortByNameDesc StackSortColumn = \"-name\"\n\n\t// StackSortByUpdatedAtDesc sorts by the updated-at attribute in descending order.\n\tStackSortByUpdatedAtDesc StackSortColumn = \"-updated-at\"\n)\n\n// StackList represents a list of stacks.\ntype StackList struct {\n\t*Pagination\n\tItems []*Stack\n}\n\n// StackVCSRepo represents the version control system repository for a stack.\ntype StackVCSRepo struct {\n\tIdentifier        string `jsonapi:\"attr,identifier\"`\n\tBranch            string `jsonapi:\"attr,branch,omitempty\"`\n\tGHAInstallationID string `jsonapi:\"attr,github-app-installation-id,omitempty\"`\n\tOAuthTokenID      string `jsonapi:\"attr,oauth-token-id,omitempty\"`\n}\n\n// StackVCSRepoOptions\ntype StackVCSRepoOptions struct {\n\tIdentifier        string `json:\"identifier\"`\n\tBranch            string `json:\"branch,omitempty\"`\n\tGHAInstallationID string `json:\"github-app-installation-id,omitempty\"`\n\tOAuthTokenID      string `json:\"oauth-token-id,omitempty\"`\n}\n\n// Stack represents a stack.\ntype Stack struct {\n\tID                 string        `jsonapi:\"primary,stacks\"`\n\tName               string        `jsonapi:\"attr,name\"`\n\tDescription        string        `jsonapi:\"attr,description\"`\n\tVCSRepo            *StackVCSRepo `jsonapi:\"attr,vcs-repo\"`\n\tSpeculativeEnabled bool          `jsonapi:\"attr,speculative-enabled\"`\n\tCreatedAt          time.Time     `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt          time.Time     `jsonapi:\"attr,updated-at,iso8601\"`\n\tUpstreamCount      int           `jsonapi:\"attr,upstream-count\"`\n\tDownstreamCount    int           `jsonapi:\"attr,downstream-count\"`\n\tInputsCount        int           `jsonapi:\"attr,inputs-count\"`\n\tOutputsCount       int           `jsonapi:\"attr,outputs-count\"`\n\tCreationSource     string        `jsonapi:\"attr,creation-source\"`\n\tWorkingDirectory   string        `jsonapi:\"attr,working-directory,omitempty\"`\n\tTriggerPatterns    []string      `jsonapi:\"attr,trigger-patterns,omitempty\"`\n\n\t// Relationships\n\tProject                  *Project            `jsonapi:\"relation,project\"`\n\tAgentPool                *AgentPool          `jsonapi:\"relation,agent-pool\"`\n\tLatestStackConfiguration *StackConfiguration `jsonapi:\"relation,latest-stack-configuration\"`\n}\n\n// StackConfigurationStatusTimestamps represents the timestamps for a stack configuration\ntype StackConfigurationStatusTimestamps struct {\n\tQueuedAt     *time.Time `jsonapi:\"attr,queued-at,omitempty,rfc3339\"`\n\tCompletedAt  *time.Time `jsonapi:\"attr,completed-at,omitempty,rfc3339\"`\n\tPreparingAt  *time.Time `jsonapi:\"attr,preparing-at,omitempty,rfc3339\"`\n\tEnqueueingAt *time.Time `jsonapi:\"attr,enqueueing-at,omitempty,rfc3339\"`\n\tCanceledAt   *time.Time `jsonapi:\"attr,canceled-at,omitempty,rfc3339\"`\n\tErroredAt    *time.Time `jsonapi:\"attr,errored-at,omitempty,rfc3339\"`\n}\n\n// StackComponent represents a stack component, specified by configuration\ntype StackComponent struct {\n\tName       string `json:\"name\"`\n\tCorrelator string `json:\"correlator\"`\n\tExpanded   bool   `json:\"expanded\"`\n\tRemoved    bool   `json:\"removed\"`\n}\n\n// StackConfiguration represents a stack configuration snapshot\ntype StackConfiguration struct {\n\t// Attributes\n\tID                      string                   `jsonapi:\"primary,stack-configurations\"`\n\tStatus                  StackConfigurationStatus `jsonapi:\"attr,status\"`\n\tSequenceNumber          int                      `jsonapi:\"attr,sequence-number\"`\n\tComponents              []*StackComponent        `jsonapi:\"attr,components\"`\n\tPreparingEventStreamURL string                   `jsonapi:\"attr,preparing-event-stream-url\"`\n\tCreatedAt               time.Time                `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt               time.Time                `jsonapi:\"attr,updated-at,iso8601\"`\n\tSpeculative             bool                     `jsonapi:\"attr,speculative\"`\n\n\t// Relationships\n\tStack             *Stack             `jsonapi:\"relation,stack\"`\n\tIngressAttributes *IngressAttributes `jsonapi:\"relation,ingress-attributes\"`\n}\n\n// StackIncludeOpt represents the include options for a stack.\ntype StackIncludeOpt string\n\nconst (\n\tStackIncludeOrganization             StackIncludeOpt = \"organization\"\n\tStackIncludeProject                  StackIncludeOpt = \"project\"\n\tStackIncludeLatestStackConfiguration StackIncludeOpt = \"latest_stack_configuration\"\n\tStackIncludeStackDiagnostics         StackIncludeOpt = \"latest_stack_configuration.stack_diagnostics\"\n)\n\n// StackListOptions represents the options for listing stacks.\ntype StackListOptions struct {\n\tListOptions\n\tProjectID    string          `url:\"filter[project][id],omitempty\"`\n\tSort         StackSortColumn `url:\"sort,omitempty\"`\n\tSearchByName string          `url:\"search[name],omitempty\"`\n}\n\n// StackCreateOptions represents the options for creating a stack. The project\n// relation is required.\ntype StackCreateOptions struct {\n\tType               string               `jsonapi:\"primary,stacks\"`\n\tName               string               `jsonapi:\"attr,name\"`\n\tMigration          *bool                `jsonapi:\"attr,migration,omitempty\"`\n\tSpeculativeEnabled *bool                `jsonapi:\"attr,speculative-enabled,omitempty\"`\n\tDescription        *string              `jsonapi:\"attr,description,omitempty\"`\n\tVCSRepo            *StackVCSRepoOptions `jsonapi:\"attr,vcs-repo\"`\n\tProject            *Project             `jsonapi:\"relation,project\"`\n\tAgentPool          *AgentPool           `jsonapi:\"relation,agent-pool\"`\n\tWorkingDirectory   *string              `jsonapi:\"attr,working-directory,omitempty\"`\n\tTriggerPatterns    []string             `jsonapi:\"attr,trigger-patterns\"`\n}\n\n// StackUpdateOptions represents the options for updating a stack.\ntype StackUpdateOptions struct {\n\tName               *string              `jsonapi:\"attr,name,omitempty\"`\n\tDescription        *string              `jsonapi:\"attr,description,omitempty\"`\n\tSpeculativeEnabled *bool                `jsonapi:\"attr,speculative-enabled,omitempty\"`\n\tVCSRepo            *StackVCSRepoOptions `jsonapi:\"attr,vcs-repo\"`\n\tAgentPool          *AgentPool           `jsonapi:\"relation,agent-pool\"`\n\tWorkingDirectory   *string              `jsonapi:\"attr,working-directory,omitempty\"`\n\tTriggerPatterns    []string             `jsonapi:\"attr,trigger-patterns\"`\n}\n\n// WaitForStatusResult is the data structure that is sent over the channel\n// returned by various status polling functions. For each result, either the\n// Error or the Status will be set, but not both. If the Quit field is set,\n// the channel will be closed. If the Quit field is set and the Error is\n// nil, the Status field will be set to a specified quit status.\ntype WaitForStatusResult struct {\n\tID           string\n\tStatus       string\n\tReadAttempts int\n\tError        error\n\tQuit         bool\n}\n\nconst minimumPollingIntervalMs = 3000\nconst maximumPollingIntervalMs = 5000\n\n// FetchLatestFromVcs fetches the latest configuration of a stack from VCS, triggering stack operations\nfunc (s *stacks) FetchLatestFromVcs(ctx context.Context, stackID string) (*Stack, error) {\n\treq, err := s.client.NewRequest(\"POST\", fmt.Sprintf(\"stacks/%s/fetch-latest-from-vcs\", url.PathEscape(stackID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstack := &Stack{}\n\terr = req.Do(ctx, stack)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn stack, nil\n}\n\n// List returns a list of stacks, optionally filtered by additional paameters.\nfunc (s stacks) List(ctx context.Context, organization string, options *StackListOptions) (*StackList, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"organizations/%s/stacks\", organization), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsl := &StackList{}\n\terr = req.Do(ctx, sl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sl, nil\n}\n\n// Read returns a stack by its ID.\nfunc (s stacks) Read(ctx context.Context, stackID string) (*Stack, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stacks/%s\", url.PathEscape(stackID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstack := &Stack{}\n\terr = req.Do(ctx, stack)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn stack, nil\n}\n\n// Create creates a new stack.\nfunc (s stacks) Create(ctx context.Context, options StackCreateOptions) (*Stack, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", \"stacks\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstack := &Stack{}\n\terr = req.Do(ctx, stack)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn stack, nil\n}\n\n// Update updates a stack.\nfunc (s stacks) Update(ctx context.Context, stackID string, options StackUpdateOptions) (*Stack, error) {\n\treq, err := s.client.NewRequest(\"PATCH\", fmt.Sprintf(\"stacks/%s\", url.PathEscape(stackID)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstack := &Stack{}\n\terr = req.Do(ctx, stack)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn stack, nil\n}\n\n// Delete deletes a stack.\nfunc (s stacks) Delete(ctx context.Context, stackID string) error {\n\treq, err := s.client.NewRequest(\"DELETE\", fmt.Sprintf(\"stacks/%s\", url.PathEscape(stackID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// ForceDelete deletes a stack that still has deployments.\nfunc (s stacks) ForceDelete(ctx context.Context, stackID string) error {\n\treq, err := s.client.NewRequest(\"DELETE\", fmt.Sprintf(\"stacks/%s?force=true\", url.PathEscape(stackID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (s *StackListOptions) valid() error {\n\treturn nil\n}\n\nfunc (s StackCreateOptions) valid() error {\n\tif s.Name == \"\" {\n\t\treturn ErrRequiredName\n\t}\n\n\tif s.Project.ID == \"\" {\n\t\treturn ErrRequiredProject\n\t}\n\n\treturn nil\n}\n\n// awaitPoll is a helper function that uses a callback to read a status, then\n// waits for a terminal status or an error. The callback should return the\n// current status, or an error. For each time the status changes, the channel\n// emits a new result. The id parameter should be the ID of the resource being\n// polled, which is used in the result to help identify the resource being polled.\nfunc awaitPoll(ctx context.Context, id string, reader func(ctx context.Context) (string, error), quitStatus []string) <-chan WaitForStatusResult {\n\tresultCh := make(chan WaitForStatusResult)\n\n\tmapStatus := make(map[string]struct{}, len(quitStatus))\n\tfor _, status := range quitStatus {\n\t\tmapStatus[status] = struct{}{}\n\t}\n\n\tgo func() {\n\t\tdefer close(resultCh)\n\n\t\treads := 0\n\t\tlastStatus := \"\"\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\tresultCh <- WaitForStatusResult{ID: id, Error: fmt.Errorf(\"context canceled: %w\", ctx.Err())}\n\t\t\t\treturn\n\t\t\tcase <-time.After(backoff(minimumPollingIntervalMs, maximumPollingIntervalMs, reads)):\n\t\t\t\tstatus, err := reader(ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\tresultCh <- WaitForStatusResult{ID: id, Error: err, Quit: true}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t_, terminal := mapStatus[status]\n\n\t\t\t\tif status != lastStatus {\n\t\t\t\t\tresultCh <- WaitForStatusResult{\n\t\t\t\t\t\tID:           id,\n\t\t\t\t\t\tStatus:       status,\n\t\t\t\t\t\tReadAttempts: reads + 1,\n\t\t\t\t\t\tQuit:         terminal,\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlastStatus = status\n\n\t\t\t\tif terminal {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\treads += 1\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn resultCh\n}\n"
  },
  {
    "path": "stack_configuration.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// StackConfigurations describes all the stacks configurations-related methods that the\n// HCP Terraform API supports.\ntype StackConfigurations interface {\n\t// CreateAndUpload packages and uploads the specified Terraform Stacks\n\t// configuration files in association with a Stack.\n\tCreateAndUpload(ctx context.Context, stackID string, path string, opts *CreateStackConfigurationOptions) (*StackConfiguration, error)\n\n\t// Upload a tar gzip archive to the specified stack configuration upload URL.\n\tUploadTarGzip(ctx context.Context, url string, archive io.Reader) error\n\n\t// ReadConfiguration returns a stack configuration by its ID.\n\tRead(ctx context.Context, id string) (*StackConfiguration, error)\n\n\t// ListStackConfigurations returns a list of stack configurations for a stack.\n\tList(ctx context.Context, stackID string, opts *StackConfigurationListOptions) (*StackConfigurationList, error)\n\n\t// JSONSchemas returns a byte slice of the JSON schema for the stack configuration.\n\tJSONSchemas(ctx context.Context, stackConfigurationID string) ([]byte, error)\n\n\t// AwaitCompleted generates a channel that will receive the status of the\n\t// stack configuration as it progresses, until that status is \"converged\",\n\t// \"converging\", \"errored\", \"canceled\".\n\tAwaitCompleted(ctx context.Context, stackConfigurationID string) <-chan WaitForStatusResult\n\n\t// AwaitPrepared generates a channel that will receive the status of the\n\t// stack configuration as it progresses, until that status is \"<status>\",\n\t// \"errored\", \"canceled\".\n\tAwaitStatus(ctx context.Context, stackConfigurationID string, status StackConfigurationStatus) <-chan WaitForStatusResult\n\n\t// Diagnostics returns the diagnostics for this stack configuration.\n\tDiagnostics(ctx context.Context, stackConfigurationID string) (*StackDiagnosticsList, error)\n}\n\ntype StackConfigurationStatus string\n\nconst (\n\tStackConfigurationStatusPending   StackConfigurationStatus = \"pending\"\n\tStackConfigurationStatusQueued    StackConfigurationStatus = \"queued\"\n\tStackConfigurationStatusPreparing StackConfigurationStatus = \"preparing\"\n\tStackConfigurationStatusCompleted StackConfigurationStatus = \"completed\"\n\tStackConfigurationStatusFailed    StackConfigurationStatus = \"failed\"\n)\n\nfunc (s StackConfigurationStatus) String() string {\n\treturn string(s)\n}\n\ntype stackConfigurations struct {\n\tclient *Client\n}\n\nvar _ StackConfigurations = &stackConfigurations{}\n\nfunc (s stackConfigurations) Read(ctx context.Context, id string) (*StackConfiguration, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-configurations/%s\", url.PathEscape(id)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstackConfiguration := &StackConfiguration{}\n\terr = req.Do(ctx, stackConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn stackConfiguration, nil\n}\n\n/**\n* Returns the JSON schema for the stack configuration as a byte slice.\n* The return value needs to be unmarshalled into a struct to be useful.\n* It is meant to be unmarshalled with terraform/internal/command/jsonproivder.Providers.\n */\nfunc (s stackConfigurations) JSONSchemas(ctx context.Context, stackConfigurationID string) ([]byte, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-configurations/%s/json-schemas\", url.PathEscape(stackConfigurationID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tvar raw bytes.Buffer\n\terr = req.Do(ctx, &raw)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn raw.Bytes(), nil\n}\n\n// AwaitCompleted generates a channel that will receive the status of the stack configuration as it progresses.\n// The channel will be closed when the stack configuration reaches a status indicating that or an error occurs. The\n// read will be retried dependending on the configuration of the client. When the channel is closed,\n// the last value will either be a completed status or an error.\nfunc (s stackConfigurations) AwaitCompleted(ctx context.Context, stackConfigurationID string) <-chan WaitForStatusResult {\n\treturn awaitPoll(ctx, stackConfigurationID, func(ctx context.Context) (string, error) {\n\t\tstackConfiguration, err := s.Read(ctx, stackConfigurationID)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\treturn stackConfiguration.Status.String(), nil\n\t}, []string{StackConfigurationStatusCompleted.String(), StackConfigurationStatusFailed.String()})\n}\n\n// AwaitStatus generates a channel that will receive the status of the stack configuration as it progresses.\n// The channel will be closed when the stack configuration reaches a status indicating that or an error occurs. The\n// read will be retried dependending on the configuration of the client. When the channel is closed,\n// the last value will either be the specified status, \"errored\" status, or \"canceled\" status, or an error.\nfunc (s stackConfigurations) AwaitStatus(ctx context.Context, stackConfigurationID string, status StackConfigurationStatus) <-chan WaitForStatusResult {\n\treturn awaitPoll(ctx, stackConfigurationID, func(ctx context.Context) (string, error) {\n\t\tstackConfiguration, err := s.Read(ctx, stackConfigurationID)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\treturn stackConfiguration.Status.String(), nil\n\t}, []string{status.String(), StackConfigurationStatusFailed.String()})\n}\n\n// StackConfigurationList represents a paginated list of stack configurations.\ntype StackConfigurationList struct {\n\tPagination *Pagination\n\tItems      []*StackConfiguration\n}\n\n// StackConfigurationListOptions represents the options for listing stack configurations.\ntype StackConfigurationListOptions struct {\n\tListOptions\n}\n\nfunc (s stackConfigurations) List(ctx context.Context, stackID string, options *StackConfigurationListOptions) (*StackConfigurationList, error) {\n\tif options == nil {\n\t\toptions = &StackConfigurationListOptions{}\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stacks/%s/stack-configurations\", url.PathEscape(stackID)), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresult := &StackConfigurationList{}\n\terr = req.Do(ctx, result)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, nil\n}\n\ntype CreateStackConfigurationOptions struct {\n\tSelectedDeployments []string `jsonapi:\"attr,selected-deployments,omitempty\"`\n\tSpeculativeEnabled  *bool    `jsonapi:\"attr,speculative,omitempty\"`\n}\n\n// CreateAndUpload packages and uploads the specified Terraform Stacks\n// configuration files in association with a Stack.\nfunc (s stackConfigurations) CreateAndUpload(ctx context.Context, stackID, path string, opts *CreateStackConfigurationOptions) (*StackConfiguration, error) {\n\tif opts == nil {\n\t\topts = &CreateStackConfigurationOptions{}\n\t}\n\tu := fmt.Sprintf(\"stacks/%s/stack-configurations\", url.PathEscape(stackID))\n\treq, err := s.client.NewRequest(\"POST\", u, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating stack configuration request for stack %q: %w\", stackID, err)\n\t}\n\n\tsc := &StackConfiguration{}\n\terr = req.Do(ctx, sc)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating stack configuration for stack %q: %w\", stackID, err)\n\t}\n\n\tuploadURL, err := s.pollForUploadURL(ctx, sc.ID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error retrieving upload URL for stack configuration %q: %w\", sc.ID, err)\n\t}\n\n\tbody, err := packContents(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = s.UploadTarGzip(ctx, uploadURL, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sc, nil\n}\n\n// PollForUploadURL polls for the upload URL of a stack configuration until it becomes available.\n// It makes a request every 2 seconds until the upload URL is present in the response.\n// It will timeout after 10 seconds.\nfunc (s stackConfigurations) pollForUploadURL(ctx context.Context, stackConfigurationID string) (string, error) {\n\tticker := time.NewTicker(2 * time.Second)\n\tdefer ticker.Stop()\n\n\ttimeout := time.NewTimer(15 * time.Second)\n\tdefer timeout.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn \"\", ctx.Err()\n\t\tcase <-timeout.C:\n\t\t\treturn \"\", fmt.Errorf(\"timeout waiting for upload URL for stack configuration %q\", stackConfigurationID)\n\t\tcase <-ticker.C:\n\t\t\turlReq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-configurations/%s/upload-url\", stackConfigurationID), nil)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"error creating upload URL request for stack configuration %q: %w\", stackConfigurationID, err)\n\t\t\t}\n\n\t\t\ttype UploadURLResponse struct {\n\t\t\t\tData struct {\n\t\t\t\t\tSourceUploadURL *string `json:\"source-upload-url\"`\n\t\t\t\t} `json:\"data\"`\n\t\t\t}\n\n\t\t\tuploadResp := &UploadURLResponse{}\n\t\t\terr = urlReq.DoJSON(ctx, uploadResp)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"error getting upload URL for stack configuration %q: %w\", stackConfigurationID, err)\n\t\t\t}\n\n\t\t\tif uploadResp.Data.SourceUploadURL != nil {\n\t\t\t\treturn *uploadResp.Data.SourceUploadURL, nil\n\t\t\t}\n\t\t}\n\t}\n}\n\n// UploadTarGzip is used to upload Terraform configuration files contained a tar gzip archive.\n// Any stream implementing io.Reader can be passed into this method. This method is also\n// particularly useful for tar streams created by non-default go-slug configurations.\n//\n// **Note**: This method does not validate the content being uploaded and is therefore the caller's\n// responsibility to ensure the raw content is a valid Terraform configuration.\nfunc (s stackConfigurations) UploadTarGzip(ctx context.Context, uploadURL string, archive io.Reader) error {\n\treturn s.client.doForeignPUTRequest(ctx, uploadURL, archive)\n}\n\n// Diagnostics returns the diagnostics for this stack configuration.\nfunc (s stackConfigurations) Diagnostics(ctx context.Context, stackConfigurationID string) (*StackDiagnosticsList, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-configurations/%s/stack-diagnostics\", url.PathEscape(stackConfigurationID)), nil)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdiagnostics := &StackDiagnosticsList{}\n\terr = req.Do(ctx, diagnostics)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn diagnostics, nil\n}\n"
  },
  {
    "path": "stack_configuration_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStackConfigurationList(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack-list\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\t// Trigger first stack configuration by updating configuration\n\t_, err = client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\n\t// Wait a bit and trigger second stack configuration\n\ttime.Sleep(2 * time.Second)\n\t_, err = client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\n\tlist, err := client.StackConfigurations.List(ctx, stack.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, list)\n\tassert.Equal(t, len(list.Items), 2)\n\n\t// Assert attributes for each configuration\n\tfor _, cfg := range list.Items {\n\t\trequire.NotEmpty(t, cfg.ID)\n\t\trequire.NotEmpty(t, cfg.Status)\n\t\trequire.GreaterOrEqual(t, cfg.SequenceNumber, 1)\n\n\t\trequire.NotNil(t, cfg.Stack)\n\t\trequire.NotEmpty(t, cfg.Stack.ID)\n\t}\n\n\t// Test with pagination options\n\tt.Run(\"with pagination options\", func(t *testing.T) {\n\t\toptions := &StackConfigurationListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   10,\n\t\t\t},\n\t\t}\n\n\t\tlistWithOptions, err := client.StackConfigurations.List(ctx, stack.ID, options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, listWithOptions)\n\t\tassert.GreaterOrEqual(t, len(listWithOptions.Items), 2)\n\n\t\trequire.NotNil(t, listWithOptions.Pagination)\n\t\tassert.GreaterOrEqual(t, listWithOptions.Pagination.TotalCount, 2)\n\t})\n}\n\nfunc TestStackConfigurationCreateUploadAndRead(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tProject: orgTest.DefaultProject,\n\t\tName:    \"test-stack\",\n\t})\n\trequire.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(ctx, 20*time.Second)\n\tdefer cancel()\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tfor {\n\t\t\tsc, err := client.StackConfigurations.CreateAndUpload(ctx, stack.ID, \"test-fixtures/stack-source\", &CreateStackConfigurationOptions{\n\t\t\t\tSelectedDeployments: []string{\"simple\"},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif sc != nil {\n\t\t\t\tdone <- struct{}{}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttime.Sleep(2 * time.Second)\n\t\t}\n\t}()\n\n\tselect {\n\tcase <-done:\n\t\tt.Logf(\"Created and uploaded config to stack configuration\")\n\t\treturn\n\tcase <-ctx.Done():\n\t\trequire.Fail(t, \"timed out waiting for stack configuration to be processed\")\n\t}\n}\n\nfunc TestStackConfigurationDiagnostics(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\n\t\tProject: orgTest.DefaultProject,\n\t\tName:    \"test-stack\",\n\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"ctrombley/linked-stacks-demo-network\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"diagnostics\", // This branch will produce diagnostics\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated, err = client.Stacks.Read(ctx, stackUpdated.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\tpollStackConfigurationStatus(t, ctx, client, stackUpdated.LatestStackConfiguration.ID, \"failed\")\n\n\tt.Run(\"Diagnostics with valid ID\", func(t *testing.T) {\n\t\tdiags, err := client.StackConfigurations.Diagnostics(ctx, stackUpdated.LatestStackConfiguration.ID)\n\t\tassert.NoError(t, err)\n\t\trequire.NotEmpty(t, diags.Items)\n\n\t\tdiag := diags.Items[0]\n\n\t\tassert.NotEmpty(t, diag.ID)\n\t\tassert.NotEmpty(t, diag.Severity)\n\t\tassert.NotEmpty(t, diag.Summary)\n\t\tassert.NotEmpty(t, diag.Detail)\n\t\tassert.NotEmpty(t, diag.Diags)\n\t\tassert.False(t, diag.Acknowledged)\n\t\tassert.Nil(t, diag.AcknowledgedAt)\n\t\tassert.NotZero(t, diag.CreatedAt)\n\n\t\tassert.Nil(t, diag.StackDeploymentStep)\n\t\tassert.NotNil(t, diag.StackConfiguration)\n\t\tassert.Nil(t, diag.AcknowledgedBy)\n\t})\n\n\tt.Run(\"Diagnostics with invalid ID\", func(t *testing.T) {\n\t\t_, err := client.StackConfigurations.Diagnostics(ctx, \"invalid-id\")\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "stack_configuration_summary.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\ntype StackConfigurationSummaries interface {\n\t// List lists all the stack configuration summaries for a stack.\n\tList(ctx context.Context, stackID string, options *StackConfigurationSummaryListOptions) (*StackConfigurationSummaryList, error)\n}\n\ntype stackConfigurationSummaries struct {\n\tclient *Client\n}\n\nvar _ StackConfigurationSummaries = &stackConfigurationSummaries{}\n\ntype StackConfigurationSummaryList struct {\n\t*Pagination\n\tItems []*StackConfigurationSummary\n}\n\ntype StackConfigurationSummaryListOptions struct {\n\tListOptions\n}\n\ntype StackConfigurationSummary struct {\n\tID             string `jsonapi:\"primary,stack-configuration-summaries\"`\n\tStatus         string `jsonapi:\"attr,status\"`\n\tSequenceNumber int    `jsonapi:\"attr,sequence-number\"`\n}\n\nfunc (s stackConfigurationSummaries) List(ctx context.Context, stackID string, options *StackConfigurationSummaryListOptions) (*StackConfigurationSummaryList, error) {\n\tif !validStringID(&stackID) {\n\t\treturn nil, fmt.Errorf(\"invalid stack ID: %s\", stackID)\n\t}\n\n\tif options == nil {\n\t\toptions = &StackConfigurationSummaryListOptions{}\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stacks/%s/stack-configuration-summaries\", url.PathEscape(stackID)), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tscl := &StackConfigurationSummaryList{}\n\terr = req.Do(ctx, scl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn scl, nil\n}\n"
  },
  {
    "path": "stack_configuration_summary_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStackConfigurationSummaryList(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"aa-test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\tstack2, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"bb-test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack2)\n\n\t// Trigger first stack configuration by updating configuration\n\t_, err = client.Stacks.FetchLatestFromVcs(ctx, stack2.ID)\n\trequire.NoError(t, err)\n\n\t// Wait a bit and trigger second stack configuration\n\ttime.Sleep(2 * time.Second)\n\t_, err = client.Stacks.FetchLatestFromVcs(ctx, stack2.ID)\n\trequire.NoError(t, err)\n\n\tt.Run(\"Successful empty list\", func(t *testing.T) {\n\t\tstackConfigSummaryList, err := client.StackConfigurationSummaries.List(ctx, stack.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, stackConfigSummaryList.Items, 0)\n\t})\n\n\tt.Run(\"Successful multiple config summary list\", func(t *testing.T) {\n\t\tstackConfigSummaryList, err := client.StackConfigurationSummaries.List(ctx, stack2.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, stackConfigSummaryList.Items, 2)\n\t})\n\n\tt.Run(\"Unsuccessful list\", func(t *testing.T) {\n\t\t_, err := client.StackConfigurationSummaries.List(ctx, \"\", nil)\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "stack_deployment.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\ntype StackDeployments interface {\n\t// List returns a list of stack deployments for a given stack.\n\tList(ctx context.Context, stackID string, opts *StackDeploymentListOptions) (*StackDeploymentList, error)\n}\n\ntype StackDeployment struct {\n\t// Attributes\n\tID   string `jsonapi:\"primary,stack-deployments\"`\n\tName string `jsonapi:\"attr,name\"`\n\n\t// Relationships\n\tStack               *Stack              `jsonapi:\"relation,stack\"`\n\tLatestDeploymentRun *StackDeploymentRun `jsonapi:\"relation,latest-deployment-run\"`\n}\n\ntype stackDeployments struct {\n\tclient *Client\n}\n\ntype StackDeploymentListOptions struct {\n\tListOptions\n}\n\ntype StackDeploymentList struct {\n\t*Pagination\n\tItems []*StackDeployment\n}\n\nfunc (s stackDeployments) List(ctx context.Context, stackID string, opts *StackDeploymentListOptions) (*StackDeploymentList, error) {\n\tif !validStringID(&stackID) {\n\t\treturn nil, ErrInvalidStackID\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stacks/%s/stack-deployments\", url.PathEscape(stackID)), opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar deployments StackDeploymentList\n\tif err := req.Do(ctx, &deployments); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &deployments, nil\n}\n"
  },
  {
    "path": "stack_deployment_groups.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n)\n\n// StackDeploymentGroups describes all the stack-deployment-groups related methods that the HCP Terraform API supports.\ntype StackDeploymentGroups interface {\n\t// List returns a list of Deployment Groups in a stack.\n\tList(ctx context.Context, stackConfigID string, options *StackDeploymentGroupListOptions) (*StackDeploymentGroupList, error)\n\n\t// Read retrieves a stack deployment group by its ID.\n\tRead(ctx context.Context, stackDeploymentGroupID string) (*StackDeploymentGroup, error)\n\n\t// ReadByName retrieves a stack deployment group by its Name.\n\tReadByName(ctx context.Context, stackConfigurationID, stackDeploymentName string) (*StackDeploymentGroup, error)\n\n\t// ApproveAllPlans approves all pending plans in a stack deployment group.\n\tApproveAllPlans(ctx context.Context, stackDeploymentGroupID string) error\n\n\t// Rerun re-runs all the stack deployment runs in a deployment group.\n\tRerun(ctx context.Context, stackDeploymentGroupID string, options *StackDeploymentGroupRerunOptions) error\n}\n\ntype DeploymentGroupStatus string\n\nconst (\n\tDeploymentGroupStatusPending   DeploymentGroupStatus = \"pending\"\n\tDeploymentGroupStatusDeploying DeploymentGroupStatus = \"deploying\"\n\tDeploymentGroupStatusSucceeded DeploymentGroupStatus = \"succeeded\"\n\tDeploymentGroupStatusFailed    DeploymentGroupStatus = \"failed\"\n\tDeploymentGroupStatusAbandoned DeploymentGroupStatus = \"abandoned\"\n)\n\nfunc (s DeploymentGroupStatus) String() string {\n\treturn string(s)\n}\n\n// stackDeploymentGroups implements StackDeploymentGroups.\ntype stackDeploymentGroups struct {\n\tclient *Client\n}\n\nvar _ StackDeploymentGroups = &stackDeploymentGroups{}\n\n// StackDeploymentGroup represents a stack deployment group.\ntype StackDeploymentGroup struct {\n\t// Attributes\n\tID        string                `jsonapi:\"primary,stack-deployment-groups\"`\n\tName      string                `jsonapi:\"attr,name\"`\n\tStatus    DeploymentGroupStatus `jsonapi:\"attr,status\"`\n\tCreatedAt time.Time             `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt time.Time             `jsonapi:\"attr,updated-at,iso8601\"`\n\n\t// Relationships\n\tStackConfiguration *StackConfiguration `jsonapi:\"relation,stack-configuration\"`\n}\n\n// StackDeploymentGroupList represents a list of stack deployment groups.\ntype StackDeploymentGroupList struct {\n\t*Pagination\n\tItems []*StackDeploymentGroup\n}\n\n// StackDeploymentGroupListOptions represents additional options when listing stack deployment groups.\ntype StackDeploymentGroupListOptions struct {\n\tListOptions\n}\n\n// StackDeploymentGroupRerunOptions represents options for rerunning deployments in a stack deployment group.\ntype StackDeploymentGroupRerunOptions struct {\n\t// Required query parameter: A list of deployment run IDs to rerun.\n\tDeployments []string\n}\n\n// List returns a list of Deployment Groups in a stack, optionally filtered by additional parameters.\nfunc (s stackDeploymentGroups) List(ctx context.Context, stackConfigID string, options *StackDeploymentGroupListOptions) (*StackDeploymentGroupList, error) {\n\tif !validStringID(&stackConfigID) {\n\t\treturn nil, fmt.Errorf(\"invalid stack configuration ID: %s\", stackConfigID)\n\t}\n\n\tif options == nil {\n\t\toptions = &StackDeploymentGroupListOptions{}\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-configurations/%s/stack-deployment-groups\", url.PathEscape(stackConfigID)), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsdgl := &StackDeploymentGroupList{}\n\terr = req.Do(ctx, sdgl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sdgl, nil\n}\n\n// ReadByName retrieves a stack deployment group by its Name.\nfunc (s stackDeploymentGroups) ReadByName(ctx context.Context, stackConfigurationID, stackDeploymentName string) (*StackDeploymentGroup, error) {\n\tif !validStringID(&stackConfigurationID) {\n\t\treturn nil, fmt.Errorf(\"invalid stack configuration id: %s\", stackConfigurationID)\n\t}\n\tif !validStringID(&stackDeploymentName) {\n\t\treturn nil, fmt.Errorf(\"invalid stack deployment group name: %s\", stackDeploymentName)\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-configurations/%s/stack-deployment-groups/%s\", url.PathEscape(stackConfigurationID), url.PathEscape(stackDeploymentName)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsdg := &StackDeploymentGroup{}\n\terr = req.Do(ctx, sdg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sdg, nil\n}\n\n// Read retrieves a stack deployment group by its ID.\nfunc (s stackDeploymentGroups) Read(ctx context.Context, stackDeploymentGroupID string) (*StackDeploymentGroup, error) {\n\tif !validStringID(&stackDeploymentGroupID) {\n\t\treturn nil, fmt.Errorf(\"invalid stack deployment group ID: %s\", stackDeploymentGroupID)\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-deployment-groups/%s\", url.PathEscape(stackDeploymentGroupID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsdg := &StackDeploymentGroup{}\n\terr = req.Do(ctx, sdg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sdg, nil\n}\n\n// ApproveAllPlans approves all pending plans in a stack deployment group.\nfunc (s stackDeploymentGroups) ApproveAllPlans(ctx context.Context, stackDeploymentGroupID string) error {\n\tif !validStringID(&stackDeploymentGroupID) {\n\t\treturn fmt.Errorf(\"invalid stack deployment group ID: %s\", stackDeploymentGroupID)\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", fmt.Sprintf(\"stack-deployment-groups/%s/approve-all-plans\", url.PathEscape(stackDeploymentGroupID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Rerun re-runs all the stack deployment runs in a deployment group.\nfunc (s stackDeploymentGroups) Rerun(ctx context.Context, stackDeploymentGroupID string, options *StackDeploymentGroupRerunOptions) error {\n\tif !validStringID(&stackDeploymentGroupID) {\n\t\treturn fmt.Errorf(\"invalid stack deployment group ID: %s\", stackDeploymentGroupID)\n\t}\n\n\tif options == nil || len(options.Deployments) == 0 {\n\t\treturn fmt.Errorf(\"no deployments specified for rerun\")\n\t}\n\n\tu := fmt.Sprintf(\"stack-deployment-groups/%s/rerun\", url.PathEscape(stackDeploymentGroupID))\n\n\ttype DeploymentQueryParams struct {\n\t\tDeployments string `url:\"deployments\"`\n\t}\n\n\tqp, err := decodeQueryParams(&DeploymentQueryParams{\n\t\tDeployments: strings.Join(options.Deployments, \",\"),\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\treq, err := s.client.NewRequestWithAdditionalQueryParams(\"POST\", u, nil, qp)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "stack_deployment_groups_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStackDeploymentGroupsList(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotEmpty(t, stackUpdated.LatestStackConfiguration.ID)\n\n\tt.Run(\"List with valid stack configuration ID\", func(t *testing.T) {\n\t\tsdgl, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, sdgl)\n\t\tfor _, item := range sdgl.Items {\n\t\t\tassert.NotNil(t, item.ID)\n\t\t\tassert.NotEmpty(t, item.Name)\n\t\t\tassert.NotEmpty(t, item.Status)\n\t\t\tassert.NotNil(t, item.CreatedAt)\n\t\t\tassert.NotNil(t, item.UpdatedAt)\n\t\t}\n\t\trequire.Len(t, sdgl.Items, 2)\n\t})\n\n\tt.Run(\"List with invalid stack configuration ID\", func(t *testing.T) {\n\t\t_, err := client.StackDeploymentGroups.List(ctx, \"\", nil)\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"List with pagination\", func(t *testing.T) {\n\t\toptions := &StackDeploymentGroupListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   1,\n\t\t\t},\n\t\t}\n\t\tsdgl, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, sdgl)\n\t\trequire.Len(t, sdgl.Items, 1)\n\t})\n}\n\nfunc TestStackDeploymentGroupsRead(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\tsdgl, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, sdgl)\n\trequire.Len(t, sdgl.Items, 2)\n\n\tt.Run(\"Read with valid ID\", func(t *testing.T) {\n\t\tsdgRead, err := client.StackDeploymentGroups.Read(ctx, sdgl.Items[0].ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, sdgl.Items[0].ID, sdgRead.ID)\n\t\tassert.Equal(t, sdgl.Items[0].Name, sdgRead.Name)\n\t\tassert.Equal(t, sdgl.Items[0].Status, sdgRead.Status)\n\t})\n\n\tt.Run(\"Read with invalid ID\", func(t *testing.T) {\n\t\t_, err := client.StackDeploymentGroups.Read(ctx, \"\")\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc TestStackDeploymentGroupsApproveAllPlans(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\t// Get the deployment group ID from the stack configuration\n\tdeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, deploymentGroups)\n\trequire.NotEmpty(t, deploymentGroups.Items)\n\n\tdeploymentGroupID := deploymentGroups.Items[0].ID\n\n\tt.Run(\"Approving all plans\", func(t *testing.T) {\n\t\terr := client.StackDeploymentGroups.ApproveAllPlans(ctx, deploymentGroupID)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestStackDeploymentGroupsRerun(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\tdeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, deploymentGroups)\n\trequire.NotEmpty(t, deploymentGroups.Items)\n\n\tdeploymentGroupID := deploymentGroups.Items[0].ID\n\n\tdeploymentRuns, err := client.StackDeploymentRuns.List(ctx, deploymentGroupID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, deploymentRuns)\n\trequire.NotEmpty(t, deploymentRuns.Items)\n\n\terr = client.StackDeploymentGroups.ApproveAllPlans(ctx, deploymentGroupID)\n\trequire.NoError(t, err)\n\n\tpollStackDeploymentRunStatus(t, ctx, client, deploymentRuns.Items[0].ID, \"deploying\")\n\n\tdeploymentRunIds := []string{deploymentRuns.Items[0].ID}\n\tfor _, dr := range deploymentRuns.Items {\n\t\tdeploymentRunIds = append(deploymentRunIds, dr.ID)\n\t}\n\n\tt.Run(\"No deployments specified for rerun\", func(t *testing.T) {\n\t\terr := client.StackDeploymentGroups.Rerun(ctx, deploymentGroupID, nil)\n\t\trequire.Error(t, err)\n\t\tassert.Contains(t, err.Error(), \"no deployments specified for rerun\")\n\t})\n\n\tt.Run(\"Rerun with invalid ID\", func(t *testing.T) {\n\t\terr := client.StackDeploymentGroups.Rerun(ctx, \"\", &StackDeploymentGroupRerunOptions{\n\t\t\tDeployments: deploymentRunIds,\n\t\t})\n\t\trequire.Error(t, err)\n\t\tassert.Contains(t, err.Error(), \"invalid stack deployment group ID\")\n\t})\n}\n"
  },
  {
    "path": "stack_deployment_groups_summary.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\ntype StackDeploymentGroupSummaries interface {\n\t// List lists all the stack deployment group summaries for a stack.\n\tList(ctx context.Context, configurationID string, options *StackDeploymentGroupSummaryListOptions) (*StackDeploymentGroupSummaryList, error)\n}\n\ntype stackDeploymentGroupSummaries struct {\n\tclient *Client\n}\n\nvar _ StackDeploymentGroupSummaries = &stackDeploymentGroupSummaries{}\n\ntype StackDeploymentGroupSummaryList struct {\n\t*Pagination\n\tItems []*StackDeploymentGroupSummary\n}\n\ntype StackDeploymentGroupSummaryListOptions struct {\n\tListOptions\n}\n\ntype StackDeploymentGroupStatusCounts struct {\n\tPending                     int `jsonapi:\"attr,pending\"`\n\tPreDeploying                int `jsonapi:\"attr,pre-deploying\"`\n\tPreDeployingPendingOperator int `jsonapi:\"attr,pending-operator\"`\n\tAcquiringLock               int `jsonapi:\"attr,acquiring-lock\"`\n\tDeploying                   int `jsonapi:\"attr,deploying\"`\n\tSucceeded                   int `jsonapi:\"attr,succeeded\"`\n\tFailed                      int `jsonapi:\"attr,failed\"`\n\tAbandoned                   int `jsonapi:\"attr,abandoned\"`\n}\n\ntype StackDeploymentGroupSummary struct {\n\tID string `jsonapi:\"primary,stack-deployment-group-summaries\"`\n\n\t// Attributes\n\tName         string                            `jsonapi:\"attr,name\"`\n\tStatus       string                            `jsonapi:\"attr,status\"`\n\tStatusCounts *StackDeploymentGroupStatusCounts `jsonapi:\"attr,status-counts\"`\n\n\t// Relationships\n\tStackDeploymentGroup *StackDeploymentGroup `jsonapi:\"relation,stack-deployment-group\"`\n}\n\nfunc (s stackDeploymentGroupSummaries) List(ctx context.Context, stackID string, options *StackDeploymentGroupSummaryListOptions) (*StackDeploymentGroupSummaryList, error) {\n\tif !validStringID(&stackID) {\n\t\treturn nil, fmt.Errorf(\"invalid stack ID: %s\", stackID)\n\t}\n\n\tif options == nil {\n\t\toptions = &StackDeploymentGroupSummaryListOptions{}\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-configurations/%s/stack-deployment-group-summaries\", url.PathEscape(stackID)), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tscl := &StackDeploymentGroupSummaryList{}\n\terr = req.Do(ctx, scl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn scl, nil\n}\n"
  },
  {
    "path": "stack_deployment_groups_summary_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStackDeploymentGroupSummaryList(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"aa-test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\tstack2, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"bb-test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack2)\n\n\t// Trigger first stack configuration with a fetch\n\t_, err = client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\n\tupdatedStack := pollStackDeploymentGroups(t, ctx, client, stack.ID)\n\trequire.NotNil(t, updatedStack.LatestStackConfiguration.ID)\n\n\t// Trigger second stack configuration with a fetch\n\t_, err = client.Stacks.FetchLatestFromVcs(ctx, stack2.ID)\n\trequire.NoError(t, err)\n\n\tupdatedStack2 := pollStackDeploymentGroups(t, ctx, client, stack2.ID)\n\trequire.NotNil(t, updatedStack2.LatestStackConfiguration.ID)\n\n\tt.Run(\"Successful multiple deployment group summary list\", func(t *testing.T) {\n\t\tstackConfigSummaryList, err := client.StackDeploymentGroupSummaries.List(ctx, updatedStack2.LatestStackConfiguration.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, stackConfigSummaryList.Items, 2)\n\t})\n\n\tt.Run(\"Unsuccessful list\", func(t *testing.T) {\n\t\t_, err := client.StackDeploymentGroupSummaries.List(ctx, \"\", nil)\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "stack_deployment_integration_test.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStackDeploymentsList(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"aa-test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\n\tt.Run(\"List with valid options\", func(t *testing.T) {\n\t\topts := &StackDeploymentListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   1,\n\t\t\t},\n\t\t}\n\t\tsdl, err := client.StackDeployments.List(ctx, stackUpdated.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, sdl)\n\t\trequire.Len(t, sdl.Items, 1)\n\t})\n\n\tt.Run(\"List with invalid options\", func(t *testing.T) {\n\t\topts := &StackDeploymentListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: -1,\n\t\t\t\tPageSize:   -1,\n\t\t\t},\n\t\t}\n\n\t\t_, err := client.StackDeployments.List(ctx, stackUpdated.ID, opts)\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "stack_deployment_runs.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// StackDeploymentRuns describes all the stack deployment runs-related methods that the HCP Terraform API supports.\ntype StackDeploymentRuns interface {\n\t// List returns a list of stack deployment runs for a given deployment group.\n\tList(ctx context.Context, deploymentGroupID string, options *StackDeploymentRunListOptions) (*StackDeploymentRunList, error)\n\tRead(ctx context.Context, stackDeploymentRunID string) (*StackDeploymentRun, error)\n\tReadWithOptions(ctx context.Context, stackDeploymentRunID string, options *StackDeploymentRunReadOptions) (*StackDeploymentRun, error)\n\tApproveAllPlans(ctx context.Context, deploymentRunID string) error\n\tCancel(ctx context.Context, stackDeploymentRunID string) error\n}\n\ntype DeploymentRunStatus string\n\nconst (\n\tDeploymentRunStatusPending                     DeploymentRunStatus = \"pending\"\n\tDeploymentRunStatusPreDeploying                DeploymentRunStatus = \"pre-deploying\"\n\tDeploymentRunStatusPreDeployingPendingOperator DeploymentRunStatus = \"pre-deploying-pending-operator\"\n\tDeploymentRunStatusAcquiringLock               DeploymentRunStatus = \"acquiring-lock\"\n\tDeploymentRunStatusDeploying                   DeploymentRunStatus = \"deploying\"\n\tDeploymentRunStatusDeployingPendingOperator    DeploymentRunStatus = \"deploying-pending-operator\"\n\tDeploymentRunStatusSucceeded                   DeploymentRunStatus = \"succeeded\"\n\tDeploymentRunStatusFailed                      DeploymentRunStatus = \"failed\"\n\tDeploymentRunStatusAbandoned                   DeploymentRunStatus = \"abandoned\"\n)\n\nfunc (s DeploymentRunStatus) String() string {\n\treturn string(s)\n}\n\n// stackDeploymentRuns implements StackDeploymentRuns.\ntype stackDeploymentRuns struct {\n\tclient *Client\n}\n\nvar _ StackDeploymentRuns = &stackDeploymentRuns{}\n\n// StackDeploymentRun represents a stack deployment run.\ntype StackDeploymentRun struct {\n\tID        string              `jsonapi:\"primary,stack-deployment-runs\"`\n\tStatus    DeploymentRunStatus `jsonapi:\"attr,status\"`\n\tCreatedAt time.Time           `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt time.Time           `jsonapi:\"attr,updated-at,iso8601\"`\n\n\t// Relationships\n\tStackDeploymentGroup *StackDeploymentGroup `jsonapi:\"relation,stack-deployment-group\"`\n}\n\ntype SDRIncludeOpt string\n\nconst (\n\tSDRDeploymentGroup SDRIncludeOpt = \"stack-deployment-group\"\n)\n\n// StackDeploymentRunList represents a list of stack deployment runs.\ntype StackDeploymentRunList struct {\n\t*Pagination\n\tItems []*StackDeploymentRun\n}\n\ntype StackDeploymentRunReadOptions struct {\n\t// Optional: A list of relations to include.\n\tInclude []SDRIncludeOpt `url:\"include,omitempty\"`\n}\n\n// StackDeploymentRunListOptions represents the options for listing stack deployment runs.\ntype StackDeploymentRunListOptions struct {\n\tListOptions\n\t// Optional: A list of relations to include.\n\tInclude []SDRIncludeOpt `url:\"include,omitempty\"`\n}\n\n// List returns a list of stack deployment runs for a given deployment group.\nfunc (s *stackDeploymentRuns) List(ctx context.Context, deploymentGroupID string, options *StackDeploymentRunListOptions) (*StackDeploymentRunList, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-deployment-groups/%s/stack-deployment-runs\", url.PathEscape(deploymentGroupID)), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsdrl := &StackDeploymentRunList{}\n\terr = req.Do(ctx, sdrl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sdrl, nil\n}\n\nfunc (s stackDeploymentRuns) Read(ctx context.Context, stackDeploymentRunID string) (*StackDeploymentRun, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-deployment-runs/%s\", url.PathEscape(stackDeploymentRunID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trun := StackDeploymentRun{}\n\terr = req.Do(ctx, &run)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &run, nil\n}\n\nfunc (s stackDeploymentRuns) ReadWithOptions(ctx context.Context, stackDeploymentRunID string, options *StackDeploymentRunReadOptions) (*StackDeploymentRun, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-deployment-runs/%s\", url.PathEscape(stackDeploymentRunID)), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trun := StackDeploymentRun{}\n\terr = req.Do(ctx, &run)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &run, nil\n}\n\nfunc (s stackDeploymentRuns) ApproveAllPlans(ctx context.Context, stackDeploymentRunID string) error {\n\treq, err := s.client.NewRequest(\"POST\", fmt.Sprintf(\"stack-deployment-runs/%s/approve-all-plans\", url.PathEscape(stackDeploymentRunID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (s stackDeploymentRuns) Cancel(ctx context.Context, stackDeploymentRunID string) error {\n\treq, err := s.client.NewRequest(\"POST\", fmt.Sprintf(\"stack-deployment-runs/%s/cancel\", url.PathEscape(stackDeploymentRunID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o *StackDeploymentRunReadOptions) valid() error {\n\tfor _, include := range o.Include {\n\t\tswitch include {\n\t\tcase SDRDeploymentGroup:\n\t\t\t// Valid option, do nothing.\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid include option: %s\", include)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "stack_deployment_runs_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStackDeploymentRunsList(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\t// Get the deployment group ID from the stack configuration\n\tdeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, deploymentGroups)\n\trequire.NotEmpty(t, deploymentGroups.Items)\n\tdeploymentGroupID := deploymentGroups.Items[0].ID\n\n\tt.Run(\"List without options\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\trunList, err := client.StackDeploymentRuns.List(ctx, deploymentGroupID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, runList)\n\t})\n\n\tt.Run(\"List with pagination\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\trunList, err := client.StackDeploymentRuns.List(ctx, deploymentGroupID, &StackDeploymentRunListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   10,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, runList)\n\t})\n\n\tt.Run(\"With include option\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\trunList, err := client.StackDeploymentRuns.List(ctx, deploymentGroupID, &StackDeploymentRunListOptions{\n\t\t\tInclude: []SDRIncludeOpt{\"stack-deployment-group\"},\n\t\t})\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, runList)\n\t\tfor _, run := range runList.Items {\n\t\t\tassert.NotNil(t, run.StackDeploymentGroup.ID)\n\t\t}\n\t})\n\n\tt.Run(\"With invalid include option\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t_, err := client.StackDeploymentRuns.List(ctx, deploymentGroupID, &StackDeploymentRunListOptions{\n\t\t\tInclude: []SDRIncludeOpt{\"invalid-option\"},\n\t\t})\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestStackDeploymentRunsRead(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tProject: orgTest.DefaultProject,\n\t\tName:    \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\tstackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, stackDeploymentGroups)\n\n\tsdg := stackDeploymentGroups.Items[0]\n\n\tstackDeploymentRuns, err := client.StackDeploymentRuns.List(ctx, sdg.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, stackDeploymentRuns)\n\n\tsdr := stackDeploymentRuns.Items[0]\n\n\tt.Run(\"Read with valid ID\", func(t *testing.T) {\n\t\trun, err := client.StackDeploymentRuns.Read(ctx, sdr.ID)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, run)\n\t})\n\n\tt.Run(\"Read with invalid ID\", func(t *testing.T) {\n\t\t_, err := client.StackDeploymentRuns.Read(ctx, \"\")\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"Read with options\", func(t *testing.T) {\n\t\trun, err := client.StackDeploymentRuns.ReadWithOptions(ctx, sdr.ID, &StackDeploymentRunReadOptions{\n\t\t\tInclude: []SDRIncludeOpt{\"stack-deployment-group\"},\n\t\t})\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, run)\n\t\tassert.NotNil(t, run.StackDeploymentGroup.ID)\n\t})\n\n\tt.Run(\"Read with invalid options\", func(t *testing.T) {\n\t\t_, err := client.StackDeploymentRuns.ReadWithOptions(ctx, sdr.ID, &StackDeploymentRunReadOptions{\n\t\t\tInclude: []SDRIncludeOpt{\"invalid-option\"},\n\t\t})\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestStackDeploymentRunsApproveAllPlans(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tProject: orgTest.DefaultProject,\n\t\tName:    \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\t// Get the deployment group ID from the stack configuration\n\tdeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, deploymentGroups)\n\trequire.NotEmpty(t, deploymentGroups.Items)\n\n\tdeploymentGroupID := deploymentGroups.Items[0].ID\n\n\trunList, err := client.StackDeploymentRuns.List(ctx, deploymentGroupID, nil)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, runList)\n\n\tdeploymentRunID := runList.Items[0].ID\n\n\tt.Run(\"Approve all plans\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\terr := client.StackDeploymentRuns.ApproveAllPlans(ctx, deploymentRunID)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestStackDeploymentRunsCancel(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tProject: orgTest.DefaultProject,\n\t\tName:    \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\t// Get the deployment group ID from the stack configuration\n\tconfigurationID := stackUpdated.LatestStackConfiguration.ID\n\tdeploymentGroups, err := client.StackDeploymentGroups.List(ctx, configurationID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, deploymentGroups)\n\trequire.NotEmpty(t, deploymentGroups.Items)\n\n\tdeploymentGroupID := deploymentGroups.Items[0].ID\n\n\trunList, err := client.StackDeploymentRuns.List(ctx, deploymentGroupID, nil)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, runList)\n\n\trun := runList.Items[0]\n\n\tsteps, err := client.StackDeploymentSteps.List(ctx, run.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, steps)\n\trequire.NotEmpty(t, steps.Items)\n\n\tstep := steps.Items[0]\n\n\tt.Run(\"cancel deployment run\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tpollStackDeploymentStepStatus(t, ctx, client, step.ID, \"pending_operator\")\n\n\t\terr = client.StackDeploymentRuns.Cancel(ctx, run.ID)\n\t\trequire.NoError(t, err)\n\n\t\tpollStackDeploymentStepStatus(t, ctx, client, step.ID, \"failed\")\n\t\tpollStackDeploymentRunStatus(t, ctx, client, run.ID, \"abandoned\")\n\t})\n}\n"
  },
  {
    "path": "stack_deployment_steps.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// StackDeploymentSteps describes all the stacks deployment step-related methods that the\n// HCP Terraform API supports.\ntype StackDeploymentSteps interface {\n\t// List returns the stack deployment steps for a stack deployment run.\n\tList(ctx context.Context, stackDeploymentRunID string, opts *StackDeploymentStepsListOptions) (*StackDeploymentStepList, error)\n\t// Read returns a stack deployment step by its ID.\n\tRead(ctx context.Context, stackDeploymentStepID string) (*StackDeploymentStep, error)\n\t// Advance advances the stack deployment step when in the \"pending_operator\" state.\n\tAdvance(ctx context.Context, stackDeploymentStepID string) error\n\t// Diagnostics returns the diagnostics for this stack deployment step.\n\tDiagnostics(ctx context.Context, stackConfigurationID string) (*StackDiagnosticsList, error)\n\t// Artifacts returns the artifacts for this stack deployment step.\n\t// Valid artifact names are \"plan-description\" and \"apply-description\".\n\tArtifacts(ctx context.Context, stackDeploymentStepID string, artifactType StackDeploymentStepArtifactType) (io.ReadCloser, error)\n}\n\ntype StackDeploymentStepArtifactType string\n\nconst (\n\t// StackDeploymentStepArtifactPlanDescription represents the plan description artifact type.\n\tStackDeploymentStepArtifactPlanDescription StackDeploymentStepArtifactType = \"plan-description\"\n\t// StackDeploymentStepArtifactApplyDescription represents the apply description artifact type.\n\tStackDeploymentStepArtifactApplyDescription StackDeploymentStepArtifactType = \"apply-description\"\n\t// StackDeploymentStepArtifactPlanDescription represents the plan debug log artifact type.\n\tStackDeploymentStepArtifactPlanDebugLog StackDeploymentStepArtifactType = \"plan-debug-log\"\n\t// StackDeploymentStepArtifactApplyDescription represents the apply debug log artifact type.\n\tStackDeploymentStepArtifactApplyDebugLog StackDeploymentStepArtifactType = \"apply-debug-log\"\n)\n\ntype DeploymentStepStatus string\n\nconst (\n\tDeploymentStepStatusBlocked         DeploymentStepStatus = \"blocked\"\n\tDeploymentStepStatusAbandoned       DeploymentStepStatus = \"abandoned\"\n\tDeploymentStepStatusQueued          DeploymentStepStatus = \"queued\"\n\tDeploymentStepStatusRunning         DeploymentStepStatus = \"running\"\n\tDeploymentStepStatusPendingOperator DeploymentStepStatus = \"pending-operator\"\n\tDeploymentStepStatusCompleted       DeploymentStepStatus = \"completed\"\n\tDeploymentStepStatusFailed          DeploymentStepStatus = \"failed\"\n)\n\nfunc (s DeploymentStepStatus) String() string {\n\treturn string(s)\n}\n\n// StackDeploymentStep represents a step from a stack deployment\ntype StackDeploymentStep struct {\n\t// Attributes\n\tID            string               `jsonapi:\"primary,stack-deployment-steps\"`\n\tStatus        DeploymentStepStatus `jsonapi:\"attr,status\"`\n\tOperationType string               `jsonapi:\"attr,operation-type\"`\n\tCreatedAt     time.Time            `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt     time.Time            `jsonapi:\"attr,updated-at,iso8601\"`\n\n\t// Links\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n\n\t// Relationships\n\tStackDeploymentRun *StackDeploymentRun `jsonapi:\"relation,stack-deployment-run\"`\n}\n\n// StackDeploymentStepList represents a list of stack deployment steps\ntype StackDeploymentStepList struct {\n\t*Pagination\n\tItems []*StackDeploymentStep\n}\n\ntype stackDeploymentSteps struct {\n\tclient *Client\n}\n\n// StackDeploymentStepsListOptions represents the options for listing stack\n// deployment steps.\ntype StackDeploymentStepsListOptions struct {\n\tListOptions\n}\n\nfunc (s stackDeploymentSteps) List(ctx context.Context, stackDeploymentRunID string, opts *StackDeploymentStepsListOptions) (*StackDeploymentStepList, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-deployment-runs/%s/stack-deployment-steps\", url.PathEscape(stackDeploymentRunID)), opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsteps := StackDeploymentStepList{}\n\terr = req.Do(ctx, &steps)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &steps, nil\n}\n\n// Read returns a stack deployment step by its ID.\nfunc (s stackDeploymentSteps) Read(ctx context.Context, stackDeploymentStepID string) (*StackDeploymentStep, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-deployment-steps/%s\", url.PathEscape(stackDeploymentStepID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstep := StackDeploymentStep{}\n\terr = req.Do(ctx, &step)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &step, nil\n}\n\n// Advance advances the stack deployment step when in the \"pending_operator\" state.\nfunc (s stackDeploymentSteps) Advance(ctx context.Context, stackDeploymentStepID string) error {\n\treq, err := s.client.NewRequest(\"POST\", fmt.Sprintf(\"stack-deployment-steps/%s/advance\", url.PathEscape(stackDeploymentStepID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Diagnostics returns the diagnostics for this stack deployment step.\nfunc (s stackDeploymentSteps) Diagnostics(ctx context.Context, stackDeploymentStepID string) (*StackDiagnosticsList, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-deployment-steps/%s/stack-diagnostics\", url.PathEscape(stackDeploymentStepID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdiagnostics := &StackDiagnosticsList{}\n\terr = req.Do(ctx, diagnostics)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn diagnostics, nil\n}\n\n// Artifacts returns the artifacts for this stack deployment step.\n// Valid artifact names are \"plan-description\" and \"apply-description\".\nfunc (s stackDeploymentSteps) Artifacts(ctx context.Context, stackDeploymentStepID string, artifactType StackDeploymentStepArtifactType) (io.ReadCloser, error) {\n\treq, err := s.client.NewRequestWithAdditionalQueryParams(\"GET\",\n\t\tfmt.Sprintf(\"stack-deployment-steps/%s/artifacts\", url.PathEscape(stackDeploymentStepID)),\n\t\tnil,\n\t\tmap[string][]string{\"name\": {url.PathEscape(string(artifactType))}},\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn req.DoRaw(ctx)\n}\n"
  },
  {
    "path": "stack_deployment_steps_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStackDeploymentStepsList(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tProject: orgTest.DefaultProject,\n\t\tName:    \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\tstackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, stackDeploymentGroups)\n\n\tsdg := stackDeploymentGroups.Items[0]\n\n\tstackDeploymentRuns, err := client.StackDeploymentRuns.List(ctx, sdg.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, stackDeploymentRuns)\n\n\tsdr := stackDeploymentRuns.Items[0]\n\n\tt.Run(\"List with invalid stack deployment run ID\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t_, err := client.StackDeploymentSteps.List(ctx, \"\", nil)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"List without options\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tsteps, err := client.StackDeploymentSteps.List(ctx, sdr.ID, nil)\n\t\tassert.NoError(t, err)\n\t\tassert.NotEmpty(t, steps)\n\n\t\tstep := steps.Items[0]\n\n\t\tassert.NotNil(t, step)\n\t\tassert.NotNil(t, step.ID)\n\t\tassert.NotNil(t, step.Status)\n\n\t\trequire.NotNil(t, step.StackDeploymentRun)\n\t\tassert.Equal(t, sdr.ID, step.StackDeploymentRun.ID)\n\t})\n\n\tt.Run(\"List with pagination\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tsteps, err := client.StackDeploymentSteps.List(ctx, sdr.ID, &StackDeploymentStepsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   10,\n\t\t\t},\n\t\t})\n\t\tassert.NoError(t, err)\n\t\tassert.NotEmpty(t, steps)\n\n\t\tstep := steps.Items[0]\n\n\t\tassert.NotNil(t, step)\n\t\tassert.NotNil(t, step.ID)\n\t\tassert.NotNil(t, step.Status)\n\n\t\trequire.NotNil(t, step.StackDeploymentRun)\n\t\tassert.Equal(t, sdr.ID, step.StackDeploymentRun.ID)\n\t})\n}\n\nfunc TestStackDeploymentStepsRead(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tProject: orgTest.DefaultProject,\n\t\tName:    \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\tstackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, stackDeploymentGroups)\n\n\tsdg := stackDeploymentGroups.Items[0]\n\n\tstackDeploymentRuns, err := client.StackDeploymentRuns.List(ctx, sdg.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, stackDeploymentRuns)\n\n\tsdr := stackDeploymentRuns.Items[0]\n\n\tsteps, err := client.StackDeploymentSteps.List(ctx, sdr.ID, nil)\n\tassert.NoError(t, err)\n\tassert.NotEmpty(t, steps)\n\n\tstep := steps.Items[0]\n\n\tt.Run(\"Read with valid ID\", func(t *testing.T) {\n\t\tsds, err := client.StackDeploymentSteps.Read(ctx, step.ID)\n\t\tassert.NoError(t, err)\n\t\tassert.NotEmpty(t, sds.ID)\n\t\tassert.NotEmpty(t, sds.Status)\n\t\tassert.NotEmpty(t, sds.OperationType)\n\t})\n\n\tt.Run(\"Read with invalid ID\", func(t *testing.T) {\n\t\t_, err := client.StackDeploymentSteps.Read(ctx, \"\")\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc TestStackDeploymentStepsAdvance(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tProject: orgTest.DefaultProject,\n\t\tName:    \"testing-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\tstackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, stackDeploymentGroups)\n\n\tsdg := stackDeploymentGroups.Items[0]\n\n\tstackDeploymentRuns, err := client.StackDeploymentRuns.List(ctx, sdg.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, stackDeploymentRuns)\n\n\tsdr := stackDeploymentRuns.Items[0]\n\n\tsteps, err := client.StackDeploymentSteps.List(ctx, sdr.ID, nil)\n\tassert.NoError(t, err)\n\tassert.NotEmpty(t, steps)\n\n\tstep := steps.Items[0]\n\tstep = pollStackDeploymentStepStatus(t, ctx, client, step.ID, \"pending_operator\")\n\trequire.NotNil(t, step)\n\n\tt.Run(\"Advance with valid ID\", func(t *testing.T) {\n\t\terr := client.StackDeploymentSteps.Advance(ctx, step.ID)\n\t\tassert.NoError(t, err)\n\n\t\t// Verify that the step status has changed to \"completed\"\n\t\tsds, err := client.StackDeploymentSteps.Read(ctx, step.ID)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"completed\", sds.Status)\n\t})\n\n\tt.Run(\"Advance with invalid ID\", func(t *testing.T) {\n\t\terr := client.StackDeploymentSteps.Advance(ctx, \"\")\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc pollStackDeploymentStepStatus(t *testing.T, ctx context.Context, client *Client, stackDeploymentStepID, status string) (deploymentStep *StackDeploymentStep) {\n\t// pollStackDeploymentStepStatus will poll the given stack deployment step until its status changes or the deadline is reached.\n\tctx, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute))\n\tdefer cancel()\n\n\tdeadline, _ := ctx.Deadline()\n\tt.Logf(\"Polling stack deployment step %q for change in status to %s with deadline of %s\", stackDeploymentStepID, status, deadline)\n\n\tticker := time.NewTicker(2 * time.Second)\n\tdefer ticker.Stop()\n\n\tvar err error\n\tfor finished := false; !finished; {\n\t\tt.Log(\"...\")\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatalf(\"Stack deployment step %s did not have status %q at deadline\", stackDeploymentStepID, status)\n\t\tcase <-ticker.C:\n\t\t\tdeploymentStep, err = client.StackDeploymentSteps.Read(ctx, stackDeploymentStepID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to read stack deployment step %s: %s\", stackDeploymentStepID, err)\n\t\t\t}\n\n\t\t\tt.Logf(\"Stack deployment step %s had status %q\", deploymentStep.ID, deploymentStep.Status)\n\t\t\tif deploymentStep.Status.String() == status {\n\t\t\t\tfinished = true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc TestStackDeploymentStepsDiagnosticsArtifacts(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\n\t\tProject: orgTest.DefaultProject,\n\t\tName:    \"test-stack\",\n\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\tstackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, stackDeploymentGroups)\n\n\tsdg := stackDeploymentGroups.Items[0]\n\n\tstackDeploymentRuns, err := client.StackDeploymentRuns.List(ctx, sdg.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, stackDeploymentRuns)\n\n\tsdr := stackDeploymentRuns.Items[0]\n\tsteps, err := client.StackDeploymentSteps.List(ctx, sdr.ID, nil)\n\tassert.NoError(t, err)\n\tassert.NotEmpty(t, steps)\n\n\tstep := steps.Items[0]\n\tstep = pollStackDeploymentStepStatus(t, ctx, client, step.ID, \"pending_operator\")\n\trequire.NotNil(t, step)\n\n\tt.Run(\"Diagnostics with valid ID\", func(t *testing.T) {\n\t\tsds, err := client.StackDeploymentSteps.Diagnostics(ctx, step.ID)\n\t\tassert.NoError(t, err)\n\t\tassert.NotEmpty(t, sds)\n\t})\n\n\tt.Run(\"Diagnostics with invalid ID\", func(t *testing.T) {\n\t\t_, err := client.StackDeploymentSteps.Diagnostics(ctx, \"invalid-id\")\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"Artifacts with valid artifact name (plan-description)\", func(t *testing.T) {\n\t\trawBytes, err := client.StackDeploymentSteps.Artifacts(ctx, step.ID, StackDeploymentStepArtifactPlanDescription)\n\t\tassert.NoError(t, err)\n\n\t\tb, err := io.ReadAll(rawBytes)\n\t\tassert.NoError(t, err)\n\t\tassert.NotEmpty(t, string(b))\n\t})\n\n\tt.Run(\"Artifacts with invalid artifact name\", func(t *testing.T) {\n\t\t_, err := client.StackDeploymentSteps.Artifacts(ctx, step.ID, \"invalid-artifact-name\")\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"Artifacts with invalid step ID\", func(t *testing.T) {\n\t\t_, err := client.StackDeploymentSteps.Artifacts(ctx, \"invalid-id\", StackDeploymentStepArtifactPlanDescription)\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "stack_diagnostic.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\ntype StackDiagnostics interface {\n\t// Read retrieves a stack diagnostic by its ID.\n\tRead(ctx context.Context, stackConfigurationID string) (*StackDiagnostic, error)\n\t// Acknowledge marks a diagnostic as acknowledged.\n\tAcknowledge(ctx context.Context, stackDiagnosticID string) error\n}\n\n// StackDiagnostic represents any sourcebundle.Diagnostic value. The simplest form has\n// just a severity, single line summary, and optional detail. If there is more\n// information about the source of the diagnostic, this is represented in the\n// range field.\ntype StackDiagnostic struct {\n\tID             string                    `jsonapi:\"primary,stack-diagnostics\"`\n\tSeverity       string                    `jsonapi:\"attr,severity\"`\n\tSummary        string                    `jsonapi:\"attr,summary\"`\n\tDetail         string                    `jsonapi:\"attr,detail\"`\n\tDiags          []*StackDiagnosticSummary `jsonapi:\"attr,diags\"`\n\tAcknowledged   bool                      `jsonapi:\"attr,acknowledged\"`\n\tAcknowledgedAt *time.Time                `jsonapi:\"attr,acknowledged-at,iso8601\"`\n\tCreatedAt      *time.Time                `jsonapi:\"attr,created-at,iso8601\"`\n\n\t// Relationships\n\tStackDeploymentStep *StackDeploymentStep `jsonapi:\"relation,stack-deployment-step\"`\n\tStackConfiguration  *StackConfiguration  `jsonapi:\"relation,stack-configuration\"`\n\tAcknowledgedBy      *User                `jsonapi:\"relation,acknowledged-by\"`\n}\n\ntype StackDiagnosticSummary struct {\n\tSeverity string             `jsonapi:\"attr,severity\"`\n\tSummary  string             `jsonapi:\"attr,summary\"`\n\tDetail   string             `jsonapi:\"attr,detail\"`\n\tRange    *DiagnosticRange   `jsonapi:\"attr,range\"`\n\tOrigin   string             `jsonapi:\"attr,origin\"`\n\tSnippet  *DiagnosticSnippet `jsonapi:\"attr,snippet\"`\n}\n\ntype DiagnosticSnippet struct {\n\tCode                 string   `jsonapi:\"attr,code\"`\n\tValues               []string `jsonapi:\"attr,values\"`\n\tContext              *string  `jsonapi:\"attr,context\"`\n\tStartLine            int      `jsonapi:\"attr,start_line\"`\n\tHighlightEndOffset   int      `jsonapi:\"attr,highlight_end_offset\"`\n\tHighlightStartOffset int      `jsonapi:\"attr,highlight_start_offset\"`\n}\n\ntype stackDiagnostics struct {\n\tclient *Client\n}\n\ntype StackDiagnosticsList struct {\n\tItems []*StackDiagnostic\n}\n\n// DiagnosticPos represents a position in the source code.\ntype DiagnosticPos struct {\n\t// Line is a one-based count for the line in the indicated file.\n\tLine int `jsonapi:\"attr,line\"`\n\n\t// Column is a one-based count of Unicode characters from the start of the line.\n\tColumn int `jsonapi:\"attr,column\"`\n\n\t// Byte is a zero-based offset into the indicated file.\n\tByte int `jsonapi:\"attr,byte\"`\n}\n\n// DiagnosticRange represents the filename and position of the diagnostic\n// subject. This defines the range of the source to be highlighted in the\n// output. Note that the snippet may include additional surrounding source code\n// if the diagnostic has a context range.\n//\n// The stacks-specific source field represents the full source bundle address\n// of the file, while the filename field is the sub path relative to its\n// enclosing package. This represents an attempt to be somewhat backwards\n// compatible with the existing Terraform JSON diagnostic format, where\n// filename is root module relative.\n//\n// The Start position is inclusive, and the End position is exclusive. Exact\n// positions are intended for highlighting for human interpretation only and\n// are subject to change.\ntype DiagnosticRange struct {\n\tFilename string        `jsonapi:\"attr,filename\"`\n\tSource   string        `jsonapi:\"attr,source\"`\n\tStart    DiagnosticPos `jsonapi:\"attr,start\"`\n\tEnd      DiagnosticPos `jsonapi:\"attr,end\"`\n}\n\n// Read retrieves a stack diagnostic by its ID.\nfunc (s stackDiagnostics) Read(ctx context.Context, stackDiagnosticID string) (*StackDiagnostic, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-diagnostics/%s\", url.PathEscape(stackDiagnosticID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar diagnostics StackDiagnostic\n\tif err := req.Do(ctx, &diagnostics); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &diagnostics, nil\n}\n\n// Acknowledge marks a diagnostic as acknowledged.\nfunc (s stackDiagnostics) Acknowledge(ctx context.Context, stackDiagnosticID string) error {\n\treq, err := s.client.NewRequest(\"POST\", fmt.Sprintf(\"stack-diagnostics/%s/acknowledge\", url.PathEscape(stackDiagnosticID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdiagnostic := StackDiagnostic{}\n\tif err := req.Do(ctx, &diagnostic); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "stack_diagnostic_integration_test.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStackDiagnosticsReadAcknowledge(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"cc-test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"ctrombley/linked-stacks-demo-network\",\n\t\t\tBranch:       \"diagnostics\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated, err = client.Stacks.Read(ctx, stackUpdated.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\tpollStackConfigurationStatus(t, ctx, client, stackUpdated.LatestStackConfiguration.ID, \"failed\")\n\n\tdiags, err := client.StackConfigurations.Diagnostics(ctx, stackUpdated.LatestStackConfiguration.ID)\n\tassert.NoError(t, err)\n\trequire.NotEmpty(t, diags.Items)\n\n\tdiag := diags.Items[0]\n\n\tt.Run(\"Read with valid ID\", func(t *testing.T) {\n\t\tdiag, err := client.StackDiagnostics.Read(ctx, diag.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, diag)\n\n\t\tassert.NotEmpty(t, diag.ID)\n\t\tassert.NotEmpty(t, diag.Severity)\n\t\tassert.NotEmpty(t, diag.Summary)\n\t\tassert.NotEmpty(t, diag.Detail)\n\t\tassert.NotEmpty(t, diag.Diags)\n\n\t\tfor _, d := range diag.Diags {\n\t\t\tassert.NotEmpty(t, d.Detail)\n\t\t\tassert.NotEmpty(t, d.Severity)\n\t\t\tassert.NotEmpty(t, d.Summary)\n\t\t\tassert.Empty(t, d.Origin)\n\n\t\t\trequire.NotNil(t, d.Range)\n\t\t\tassert.NotEmpty(t, d.Range.Filename)\n\t\t\tassert.NotEmpty(t, d.Range.Source)\n\n\t\t\trequire.NotNil(t, d.Range.Start)\n\t\t\tassert.NotZero(t, d.Range.Start.Line)\n\t\t\tassert.NotZero(t, d.Range.Start.Column)\n\t\t\tassert.NotZero(t, d.Range.Start.Byte)\n\n\t\t\trequire.NotNil(t, d.Range.End)\n\t\t\tassert.NotZero(t, d.Range.End.Line)\n\t\t\tassert.NotZero(t, d.Range.End.Column)\n\t\t\tassert.NotZero(t, d.Range.End.Byte)\n\n\t\t\trequire.NotNil(t, d.Snippet)\n\t\t\tassert.NotEmpty(t, d.Snippet.Code)\n\t\t\tassert.Empty(t, d.Snippet.Values)\n\t\t\tassert.Nil(t, d.Snippet.Context)\n\t\t\tassert.Zero(t, d.Snippet.HighlightStartOffset)\n\t\t\tassert.NotZero(t, d.Snippet.HighlightEndOffset)\n\t\t}\n\n\t\tassert.False(t, diag.Acknowledged)\n\t\tassert.Nil(t, diag.AcknowledgedAt)\n\t\tassert.NotZero(t, diag.CreatedAt)\n\n\t\tassert.Nil(t, diag.StackDeploymentStep)\n\t\tassert.NotNil(t, diag.StackConfiguration)\n\t\tassert.Nil(t, diag.AcknowledgedBy)\n\t})\n\n\tt.Run(\"Read with invalid ID\", func(t *testing.T) {\n\t\t_, err := client.StackDiagnostics.Read(ctx, \"\")\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"Acknowledge with valid ID\", func(t *testing.T) {\n\t\terr := client.StackDiagnostics.Acknowledge(ctx, diag.ID)\n\t\trequire.NoError(t, err)\n\n\t\tdiag, err := client.StackDiagnostics.Read(ctx, diag.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, diag)\n\t\tassert.True(t, diag.Acknowledged)\n\t\tassert.NotNil(t, diag.AcknowledgedAt)\n\t\tassert.NotNil(t, diag.AcknowledgedBy)\n\t})\n\n\tt.Run(\"Acknowledge with invalid ID\", func(t *testing.T) {\n\t\terr := client.StackDiagnostics.Acknowledge(ctx, \"\")\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "stack_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStackCreateAndList(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tproject2, err := client.Projects.Create(ctx, orgTest.Name, ProjectCreateOptions{\n\t\tName: \"test-project-2\",\n\t})\n\trequire.NoError(t, err)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack1, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"aa-test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t\tMigration:          Bool(true),\n\t\tSpeculativeEnabled: Bool(true),\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack1)\n\trequire.True(t, stack1.SpeculativeEnabled)\n\n\tstack2, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"zz-test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: project2.ID,\n\t\t},\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack2)\n\trequire.False(t, stack2.SpeculativeEnabled)\n\n\tt.Run(\"List without options\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tstackList, err := client.Stacks.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, stackList.Items, 2)\n\t\tassert.Equal(t, stack1.CreationSource, \"migration-api\")\n\t\tassert.Equal(t, stack2.CreationSource, \"api\")\n\t})\n\n\tt.Run(\"List with project filter\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tstackList, err := client.Stacks.List(ctx, orgTest.Name, &StackListOptions{\n\t\t\tProjectID: project2.ID,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, stackList.Items, 1)\n\t\tassert.Equal(t, stack2.ID, stackList.Items[0].ID)\n\t})\n\n\tt.Run(\"List with name filter\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tstackList, err := client.Stacks.List(ctx, orgTest.Name, &StackListOptions{\n\t\t\tSearchByName: \"zz\",\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, stackList.Items, 1)\n\t\tassert.Equal(t, stack2.ID, stackList.Items[0].ID)\n\t})\n\n\tt.Run(\"List with sort options\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// By name ASC\n\t\tstackList, err := client.Stacks.List(ctx, orgTest.Name, &StackListOptions{\n\t\t\tSort: StackSortByName,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, stackList.Items, 2)\n\t\tassert.Equal(t, stack1.ID, stackList.Items[0].ID)\n\n\t\t// By name DESC\n\t\tstackList, err = client.Stacks.List(ctx, orgTest.Name, &StackListOptions{\n\t\t\tSort: StackSortByNameDesc,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, stackList.Items, 2)\n\t\tassert.Equal(t, stack2.ID, stackList.Items[0].ID)\n\t})\n\n\tt.Run(\"List with pagination\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tstackList, err := client.Stacks.List(ctx, orgTest.Name, &StackListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 1,\n\t\t\t\tPageSize:   1,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, stackList.Items, 1)\n\t\tassert.Equal(t, 2, stackList.TotalPages)\n\t\tassert.Equal(t, 2, stackList.TotalCount)\n\t})\n}\n\nfunc TestStackReadUpdateDelete(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tinitialPool, err := client.AgentPools.Create(ctx, orgTest.Name, AgentPoolCreateOptions{\n\t\tName: String(\"initial-test-pool\"),\n\t})\n\trequire.NoError(t, err)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t\tAgentPool:        initialPool,\n\t\tWorkingDirectory: String(\"envs\"),\n\t\tTriggerPatterns:  []string{\"/**/*\"},\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\trequire.NotEmpty(t, stack.VCSRepo.Identifier)\n\trequire.NotEmpty(t, stack.VCSRepo.OAuthTokenID)\n\trequire.NotEmpty(t, stack.VCSRepo.Branch)\n\trequire.False(t, stack.SpeculativeEnabled)\n\n\tstackRead, err := client.Stacks.Read(ctx, stack.ID)\n\n\trequire.NoError(t, err)\n\trequire.Equal(t, stack.VCSRepo.Identifier, stackRead.VCSRepo.Identifier)\n\trequire.Equal(t, stack.VCSRepo.OAuthTokenID, stackRead.VCSRepo.OAuthTokenID)\n\trequire.Equal(t, stack.VCSRepo.Branch, stackRead.VCSRepo.Branch)\n\trequire.Equal(t, stack.AgentPool.ID, stackRead.AgentPool.ID)\n\tassert.Equal(t, stack, stackRead)\n\tassert.Equal(t, stackRead.WorkingDirectory, \"envs\")\n\tassert.Equal(t, stackRead.TriggerPatterns, []string{\"/**/*\"})\n\tassert.False(t, stackRead.SpeculativeEnabled)\n\n\tupdatedPool, err := client.AgentPools.Create(ctx, orgTest.Name, AgentPoolCreateOptions{\n\t\tName: String(\"updated-test-pool\"),\n\t})\n\trequire.NoError(t, err)\n\n\tstackUpdated, err := client.Stacks.Update(ctx, stack.ID, StackUpdateOptions{\n\t\tDescription: String(\"updated description\"),\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t\tAgentPool:          updatedPool,\n\t\tSpeculativeEnabled: Bool(true),\n\t\tWorkingDirectory:   String(\"\"),\n\t\tTriggerPatterns:    []string{\"\"},\n\t})\n\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"updated description\", stackUpdated.Description)\n\trequire.Equal(t, updatedPool.ID, stackUpdated.AgentPool.ID)\n\trequire.True(t, stackUpdated.SpeculativeEnabled)\n\n\tstackUpdatedConfig, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.Equal(t, stack.Name, stackUpdatedConfig.Name)\n\trequire.Equal(t, stackUpdated.WorkingDirectory, \"\")\n\trequire.Equal(t, stackUpdated.TriggerPatterns, []string{\"\"})\n\n\terr = client.Stacks.Delete(ctx, stack.ID)\n\trequire.NoError(t, err)\n\n\tstackReadAfterDelete, err := client.Stacks.Read(ctx, stack.ID)\n\trequire.ErrorIs(t, err, ErrResourceNotFound)\n\trequire.Nil(t, stackReadAfterDelete)\n}\n\nfunc TestStackRemoveVCSBacking(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\trequire.NotEmpty(t, stack.VCSRepo.Identifier)\n\trequire.NotEmpty(t, stack.VCSRepo.OAuthTokenID)\n\trequire.NotEmpty(t, stack.VCSRepo.Branch)\n\n\tstackRead, err := client.Stacks.Read(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.Equal(t, stack.VCSRepo.Identifier, stackRead.VCSRepo.Identifier)\n\trequire.Equal(t, stack.VCSRepo.OAuthTokenID, stackRead.VCSRepo.OAuthTokenID)\n\trequire.Equal(t, stack.VCSRepo.Branch, stackRead.VCSRepo.Branch)\n\n\tassert.Equal(t, stack, stackRead)\n\n\tstackUpdated, err := client.Stacks.Update(ctx, stack.ID, StackUpdateOptions{\n\t\tVCSRepo: nil,\n\t})\n\n\trequire.NoError(t, err)\n\trequire.Nil(t, stackUpdated.VCSRepo)\n}\n\nfunc TestStackReadUpdateForceDelete(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\trequire.NotEmpty(t, stack.VCSRepo.Identifier)\n\trequire.NotEmpty(t, stack.VCSRepo.OAuthTokenID)\n\trequire.NotEmpty(t, stack.VCSRepo.Branch)\n\n\tstackRead, err := client.Stacks.Read(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.Equal(t, stack.VCSRepo.Identifier, stackRead.VCSRepo.Identifier)\n\trequire.Equal(t, stack.VCSRepo.OAuthTokenID, stackRead.VCSRepo.OAuthTokenID)\n\trequire.Equal(t, stack.VCSRepo.Branch, stackRead.VCSRepo.Branch)\n\n\tassert.Equal(t, stack, stackRead)\n\n\tstackUpdated, err := client.Stacks.Update(ctx, stack.ID, StackUpdateOptions{\n\t\tDescription: String(\"updated description\"),\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t\tBranch:       \"main\",\n\t\t},\n\t})\n\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"updated description\", stackUpdated.Description)\n\n\tstackUpdatedConfig, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.Equal(t, stack.Name, stackUpdatedConfig.Name)\n\n\terr = client.Stacks.ForceDelete(ctx, stack.ID)\n\trequire.NoError(t, err)\n\n\tstackReadAfterDelete, err := client.Stacks.Read(ctx, stack.ID)\n\trequire.ErrorIs(t, err, ErrResourceNotFound)\n\trequire.Nil(t, stackReadAfterDelete)\n}\n\nfunc pollStackDeploymentGroups(t *testing.T, ctx context.Context, client *Client, stackID string) (stack *Stack) {\n\tt.Helper()\n\n\t// pollStackDeployments will poll the given stack until it has deployments or the deadline is reached.\n\tctx, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute))\n\tdefer cancel()\n\n\tdeadline, _ := ctx.Deadline()\n\tt.Logf(\"Polling stack %q for deployment groups with deadline of %s\", stackID, deadline)\n\n\tticker := time.NewTicker(2 * time.Second)\n\tdefer ticker.Stop()\n\n\tfor finished := false; !finished; {\n\t\tt.Log(\"...\")\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatalf(\"Stack %q had no deployment groups at deadline\", stackID)\n\t\tcase <-ticker.C:\n\t\t\tvar err error\n\t\t\tstack, err = client.Stacks.Read(ctx, stackID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to read stack %q: %s\", stackID, err)\n\t\t\t}\n\t\t\tgroups, err := client.StackDeploymentGroups.List(ctx, stack.LatestStackConfiguration.ID, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to read deployment groups %q: %s\", stackID, err)\n\t\t\t}\n\n\t\t\tt.Logf(\"Stack %q had %d deployment groups\", stack.ID, groups.TotalCount)\n\t\t\tif groups.TotalCount > 0 {\n\t\t\t\tfinished = true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn stack\n}\n\nfunc pollStackDeploymentGroupStatus(t *testing.T, ctx context.Context, client *Client, configurationID, status string) {\n\t// pollStackDeploymentGroupStatus will poll the given stack until its deployment groups\n\t// all match the given status, or the deadline is reached.\n\tctx, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute))\n\tdefer cancel()\n\n\tdeadline, _ := ctx.Deadline()\n\tt.Logf(\"Polling configuration %q for deployments with deadline of %s\", configurationID, deadline)\n\n\tticker := time.NewTicker(2 * time.Second)\n\tdefer ticker.Stop()\n\n\tfor finished := false; !finished; {\n\t\tt.Log(\"...\")\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatalf(\"Stack deployment groups for config %s did not have status %q at deadline\", configurationID, status)\n\t\tcase <-ticker.C:\n\t\t\tvar err error\n\t\t\tsummaries, err := client.StackDeploymentGroupSummaries.List(ctx, configurationID, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to read stack deployment groups for config %s: %s\", configurationID, err)\n\t\t\t}\n\n\t\t\tfor _, group := range summaries.Items {\n\t\t\t\tt.Logf(\"Stack deployment group %s for config %s had status %q\", group.ID, configurationID, group.Status)\n\t\t\t\tif group.Status == status {\n\t\t\t\t\tfinished = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc pollStackDeploymentRunStatus(t *testing.T, ctx context.Context, client *Client, deploymentRunID, status string) {\n\t// pollStackDeploymentRunStatus will poll the given stack until the targeted\n\t// deployment run matches the given status, or the deadline is reached.\n\tctx, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute))\n\tdefer cancel()\n\n\tdeadline, _ := ctx.Deadline()\n\tt.Logf(\"Polling deployment run %q for status %s with deadline of %s\", deploymentRunID, status, deadline)\n\n\tticker := time.NewTicker(2 * time.Second)\n\tdefer ticker.Stop()\n\n\tfor finished := false; !finished; {\n\t\tt.Log(\"...\")\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatalf(\"Stack deployment run %s did not have status %q at deadline\", deploymentRunID, status)\n\t\tcase <-ticker.C:\n\t\t\tvar err error\n\t\t\tdeploymentRun, err := client.StackDeploymentRuns.Read(ctx, deploymentRunID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to read stack deployment run %s: %s\", deploymentRunID, err)\n\t\t\t}\n\n\t\t\tt.Logf(\"Stack deployment run %s had status %q\", deploymentRunID, deploymentRun.Status)\n\t\t\tif deploymentRun.Status.String() == status {\n\t\t\t\tfinished = true\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc pollStackConfigurationStatus(t *testing.T, ctx context.Context, client *Client, stackConfigID, status string) (stackConfig *StackConfiguration) {\n\t// pollStackDeployments will poll the given stack until it has deployments or the deadline is reached.\n\tctx, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute))\n\tdefer cancel()\n\n\tdeadline, _ := ctx.Deadline()\n\tt.Logf(\"Polling stack configuration %q for status %q with deadline of %s\", stackConfigID, status, deadline)\n\n\tticker := time.NewTicker(2 * time.Second)\n\tdefer ticker.Stop()\n\n\tvar err error\n\tfor finished := false; !finished; {\n\t\tt.Log(\"...\")\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatalf(\"Stack configuration %q did not have status %q at deadline\", stackConfigID, status)\n\t\tcase <-ticker.C:\n\t\t\tstackConfig, err = client.StackConfigurations.Read(ctx, stackConfigID)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to read stack configuration %q: %s\", stackConfigID, err)\n\t\t\t}\n\n\t\t\tt.Logf(\"Stack configuration %q had status %q\", stackConfigID, stackConfig.Status)\n\t\t\tif stackConfig.Status.String() == status {\n\t\t\t\tfinished = true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc pollNewStackConfiguration(t *testing.T, ctx context.Context, client *Client, stackID string) (stackConfig *StackConfiguration) {\n\t// pollNewStackConfiguration can be used after a new configuration is fetched\n\t// to ensure a non-nil configuration is returned.\n\tctx, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute))\n\tdefer cancel()\n\n\tdeadline, _ := ctx.Deadline()\n\tt.Logf(\"Polling stack %s for new stack configuration with deadline of %s\", stackID, deadline)\n\n\tticker := time.NewTicker(2 * time.Second)\n\tdefer ticker.Stop()\n\n\tfor finished := false; !finished; {\n\t\tt.Log(\"...\")\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tt.Fatalf(\"Stack %q had no new configuration at deadline\", stackID)\n\t\tcase <-ticker.C:\n\t\t\tstackConfigs, err := client.StackConfigurations.List(ctx, stackID, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to list stack configurations for stack %q: %s\", stackID, err)\n\t\t\t}\n\n\t\t\tif len(stackConfigs.Items) > 0 {\n\t\t\t\tstackConfig = stackConfigs.Items[0]\n\t\t\t\tfinished = true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "stack_state.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n)\n\n// StackState describes all the stack state-related methods that the\n// HCP Terraform API supports.\ntype StackStates interface {\n\t// List returns the stack states for a stack.\n\tList(ctx context.Context, stackID string, opts *StackStateListOptions) (*StackStateList, error)\n\t// Read returns a stack state by its ID.\n\tRead(ctx context.Context, stackStateID string) (*StackState, error)\n\t// Description returns the state description for the given stack state.\n\t// The description is returned as an io.ReadCloser and should be closed and\n\t// unmarshaled by the caller.\n\tDescription(ctx context.Context, stackStateID string) (io.ReadCloser, error)\n}\n\n// StackState represents a stack state.\ntype StackState struct {\n\t// Attributes\n\tID                    string            `jsonapi:\"primary,stack-states\"`\n\tGeneration            int               `jsonapi:\"attr,generation\"`\n\tStatus                string            `jsonapi:\"attr,status\"`\n\tDeployment            string            `jsonapi:\"attr,deployment\"`\n\tComponents            []*StackComponent `jsonapi:\"attr,components\"`\n\tIsCurrent             bool              `jsonapi:\"attr,is-current\"`\n\tResourceInstanceCount int               `jsonapi:\"attr,resource-instance-count\"`\n\n\t// Relationships\n\tStack              *Stack              `jsonapi:\"relation,stack\"`\n\tStackDeploymentRun *StackDeploymentRun `jsonapi:\"relation,stack-deployment-run\"`\n\n\t// Links\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n}\n\n// StackStateList represents a list of stack states.\ntype StackStateList struct {\n\t*Pagination\n\tItems []*StackState\n}\n\ntype stackStates struct {\n\tclient *Client\n}\n\n// StackStateListOptions represents the options for listing stack states.\ntype StackStateListOptions struct {\n\tListOptions\n}\n\n// List returns the stack states for a stack.\nfunc (s stackStates) List(ctx context.Context, stackID string, opts *StackStateListOptions) (*StackStateList, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stacks/%s/stack-states\", url.PathEscape(stackID)), opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstates := StackStateList{}\n\tif err := req.Do(ctx, &states); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &states, nil\n}\n\n// Read returns a stack state by its ID.\nfunc (s stackStates) Read(ctx context.Context, stackStateID string) (*StackState, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-states/%s\", url.PathEscape(stackStateID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstate := StackState{}\n\tif err := req.Do(ctx, &state); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &state, nil\n}\n\n// Description returns the state description for the given stack state.\nfunc (s stackStates) Description(ctx context.Context, stackStateID string) (io.ReadCloser, error) {\n\treq, err := s.client.NewRequest(\"GET\", fmt.Sprintf(\"stack-states/%s/description\", url.PathEscape(stackStateID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn req.DoRaw(ctx)\n}\n"
  },
  {
    "path": "stack_state_integration_test.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStackStateListReadDescription(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\tstack, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"aa-test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack)\n\tstack2, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"bb-test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stack2)\n\n\tstackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, stackUpdated)\n\n\tstackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID)\n\trequire.NotNil(t, stackUpdated.LatestStackConfiguration)\n\n\t// Get the deployment group ID from the stack configuration\n\tdeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, deploymentGroups)\n\trequire.NotEmpty(t, deploymentGroups.Items)\n\n\tfor _, dg := range deploymentGroups.Items {\n\t\terr = client.StackDeploymentGroups.ApproveAllPlans(ctx, dg.ID)\n\t\trequire.NoError(t, err)\n\t}\n\n\tpollStackDeploymentGroupStatus(t, ctx, client, stackUpdated.LatestStackConfiguration.ID, \"succeeded\")\n\n\tt.Run(\"List with valid ID\", func(t *testing.T) {\n\t\tstates, err := client.StackStates.List(ctx, stackUpdated.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, states)\n\t\trequire.NotEmpty(t, states.Items)\n\t})\n\n\tt.Run(\"List with invalid ID\", func(t *testing.T) {\n\t\t_, err := client.StackStates.List(ctx, \"invalid-id\", nil)\n\t\trequire.Error(t, err)\n\t})\n\n\tstates, err := client.StackStates.List(ctx, stackUpdated.ID, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, states)\n\trequire.NotEmpty(t, states.Items)\n\n\tstate := states.Items[0]\n\n\tt.Run(\"Read with valid ID\", func(t *testing.T) {\n\t\tstate, err := client.StackStates.Read(ctx, state.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, state)\n\n\t\tassert.NotEmpty(t, state.ID)\n\n\t\t// Assert attribute presence\n\t\tassert.NotZero(t, state.Generation)\n\t\tassert.NotEmpty(t, state.Status)\n\t\tassert.NotEmpty(t, state.Deployment)\n\t\tassert.NotNil(t, state.Components)\n\t\tassert.True(t, state.IsCurrent)\n\t\tassert.NotZero(t, state.ResourceInstanceCount)\n\n\t\t// Assert relationship presence\n\t\tassert.NotNil(t, state.Stack)\n\t\tassert.NotEmpty(t, state.Stack.ID)\n\t\tassert.NotNil(t, state.StackDeploymentRun)\n\t\tassert.NotEmpty(t, state.StackDeploymentRun)\n\n\t\t// Assert link presence\n\t\tassert.NotEmpty(t, state.Links)\n\t\t// Description link\n\t\tdescription, ok := state.Links[\"description\"].(string)\n\t\trequire.True(t, ok)\n\t\tassert.NotEmpty(t, description)\n\t})\n\n\tt.Run(\"Read with invalid ID\", func(t *testing.T) {\n\t\t_, err := client.StackStates.Read(ctx, \"invalid-id\")\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"Description with valid ID\", func(t *testing.T) {\n\t\trawBytes, err := client.StackStates.Description(ctx, state.ID)\n\t\trequire.NoError(t, err)\n\t\tdefer rawBytes.Close()\n\n\t\tb, err := io.ReadAll(rawBytes)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, string(b))\n\t})\n\n\tt.Run(\"Description with invalid ID\", func(t *testing.T) {\n\t\t_, err := client.StackStates.Description(ctx, \"invalid-id\")\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "state_version.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"golang.org/x/sync/errgroup\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ StateVersions = (*stateVersions)(nil)\n\n// StateVersionStatus are available state version status values\ntype StateVersionStatus string\n\n// Available state version statuses.\nconst (\n\tStateVersionPending   StateVersionStatus = \"pending\"\n\tStateVersionFinalized StateVersionStatus = \"finalized\"\n\tStateVersionDiscarded StateVersionStatus = \"discarded\"\n)\n\n// StateVersions describes all the state version related methods that\n// the Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/state-versions\ntype StateVersions interface {\n\t// List all the state versions for a given workspace.\n\tList(ctx context.Context, options *StateVersionListOptions) (*StateVersionList, error)\n\n\t// Create a new state version for the given workspace.\n\tCreate(ctx context.Context, workspaceID string, options StateVersionCreateOptions) (*StateVersion, error)\n\n\t// Upload creates a new state version but uploads the state content directly to the object store.\n\t// This is a more resilient form of Create and is the recommended approach to creating state versions.\n\tUpload(ctx context.Context, workspaceID string, options StateVersionUploadOptions) (*StateVersion, error)\n\n\t// UploadSanitizedState uploads a sanitized version of the state to the provided sanitized state upload url.\n\t// The SanitizedStateUploadURL cannot be empty.\n\tUploadSanitizedState(ctx context.Context, sanitizedStateUploadURL *string, sanitizedState []byte) error\n\n\t// Read a state version by its ID.\n\tRead(ctx context.Context, svID string) (*StateVersion, error)\n\n\t// ReadWithOptions reads a state version by its ID using the options supplied\n\tReadWithOptions(ctx context.Context, svID string, options *StateVersionReadOptions) (*StateVersion, error)\n\n\t// ReadCurrent reads the latest available state from the given workspace.\n\tReadCurrent(ctx context.Context, workspaceID string) (*StateVersion, error)\n\n\t// ReadCurrentWithOptions reads the latest available state from the given workspace using the options supplied\n\tReadCurrentWithOptions(ctx context.Context, workspaceID string, options *StateVersionCurrentOptions) (*StateVersion, error)\n\n\t// Download retrieves the actual stored state of a state version\n\tDownload(ctx context.Context, url string) ([]byte, error)\n\n\t// ListOutputs retrieves all the outputs of a state version by its ID. IMPORTANT: HCP Terraform might\n\t// process outputs asynchronously. When consuming outputs or other async StateVersion fields, be sure to\n\t// wait for ResourcesProcessed to become `true` before assuming they are empty.\n\tListOutputs(ctx context.Context, svID string, options *StateVersionOutputsListOptions) (*StateVersionOutputsList, error)\n\n\t// SoftDeleteBackingData soft deletes the state version's backing data\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tSoftDeleteBackingData(ctx context.Context, svID string) error\n\n\t// RestoreBackingData restores a soft deleted state version's backing data\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tRestoreBackingData(ctx context.Context, svID string) error\n\n\t// PermanentlyDeleteBackingData permanently deletes a soft deleted state version's backing data\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tPermanentlyDeleteBackingData(ctx context.Context, svID string) error\n}\n\n// stateVersions implements StateVersions.\ntype stateVersions struct {\n\tclient *Client\n}\n\n// StateVersionList represents a list of state versions.\ntype StateVersionList struct {\n\t*Pagination\n\tItems []*StateVersion\n}\n\n// StateVersion represents a Terraform Enterprise state version.\ntype StateVersion struct {\n\tID                        string             `jsonapi:\"primary,state-versions\"`\n\tCreatedAt                 time.Time          `jsonapi:\"attr,created-at,iso8601\"`\n\tDownloadURL               string             `jsonapi:\"attr,hosted-state-download-url\"`\n\tUploadURL                 string             `jsonapi:\"attr,hosted-state-upload-url\"`\n\tStatus                    StateVersionStatus `jsonapi:\"attr,status\"`\n\tJSONUploadURL             string             `jsonapi:\"attr,hosted-json-state-upload-url\"`\n\tJSONDownloadURL           string             `jsonapi:\"attr,hosted-json-state-download-url\"`\n\tSerial                    int64              `jsonapi:\"attr,serial\"`\n\tSize                      int64              `jsonapi:\"attr,size\"`\n\tVCSCommitSHA              string             `jsonapi:\"attr,vcs-commit-sha\"`\n\tVCSCommitURL              string             `jsonapi:\"attr,vcs-commit-url\"`\n\tBillableRUMCount          *uint32            `jsonapi:\"attr,billable-rum-count\"`\n\tEncryptedStateDownloadURL *string            `jsonapi:\"attr,encrypted-state-download-url,omitempty\"`\n\tSanitizedStateUploadURL   *string            `jsonapi:\"attr,sanitized-state-upload-url,omitempty\"`\n\tSanitizedStateDownloadURL *string            `jsonapi:\"attr,sanitized-state-download-url,omitempty\"`\n\n\t// Whether HCP Terraform has finished populating any StateVersion fields that required async processing.\n\t// If `false`, some fields may appear empty even if they should actually contain data; see comments on\n\t// individual fields for details.\n\tResourcesProcessed bool `jsonapi:\"attr,resources-processed\"`\n\tStateVersion       int  `jsonapi:\"attr,state-version\"`\n\t// Populated asynchronously.\n\tTerraformVersion string `jsonapi:\"attr,terraform-version\"`\n\t// Populated asynchronously.\n\tModules *StateVersionModules `jsonapi:\"attr,modules\"`\n\t// Populated asynchronously.\n\tProviders *StateVersionProviders `jsonapi:\"attr,providers\"`\n\t// Populated asynchronously.\n\tResources []*StateVersionResources `jsonapi:\"attr,resources\"`\n\n\t// Relations\n\tRun                  *Run                  `jsonapi:\"relation,run\"`\n\tOutputs              []*StateVersionOutput `jsonapi:\"relation,outputs\"`\n\tHYOKEncryptedDataKey *HYOKEncryptedDataKey `jsonapi:\"relation,hyok-encrypted-data-key,omitempty\"`\n}\n\n// StateVersionOutputsList represents a list of StateVersionOutput items.\ntype StateVersionOutputsList struct {\n\t*Pagination\n\tItems []*StateVersionOutput\n}\n\n// StateVersionListOptions represents the options for listing state versions.\ntype StateVersionListOptions struct {\n\tListOptions\n\tOrganization string `url:\"filter[organization][name]\"`\n\tWorkspace    string `url:\"filter[workspace][name]\"`\n}\n\n// StateVersionIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/state-versions#available-related-resources\ntype StateVersionIncludeOpt string\n\nconst (\n\tSVcreatedby               StateVersionIncludeOpt = \"created_by\"\n\tSVrun                     StateVersionIncludeOpt = \"run\"\n\tSVrunCreatedBy            StateVersionIncludeOpt = \"run.created_by\"\n\tSVrunConfigurationVersion StateVersionIncludeOpt = \"run.configuration_version\"\n\tSVoutputs                 StateVersionIncludeOpt = \"outputs\"\n)\n\n// StateVersionReadOptions represents the options for reading state version.\ntype StateVersionReadOptions struct {\n\t// Optional: A list of relations to include. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/state-versions#available-related-resources\n\tInclude []StateVersionIncludeOpt `url:\"include,omitempty\"`\n}\n\n// StateVersionOutputsListOptions represents the options for listing state\n// version outputs.\ntype StateVersionOutputsListOptions struct {\n\tListOptions\n}\n\n// StateVersionCurrentOptions represents the options for reading the current state version.\ntype StateVersionCurrentOptions struct {\n\t// Optional: A list of relations to include. See available resources:\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/state-versions#available-related-resources\n\tInclude []StateVersionIncludeOpt `url:\"include,omitempty\"`\n}\n\n// StateVersionCreateOptions represents the options for creating a state version.\ntype StateVersionCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,state-versions\"`\n\n\t// Optional: The lineage of the state.\n\tLineage *string `jsonapi:\"attr,lineage,omitempty\"`\n\n\t// Required: The MD5 hash of the state version.\n\tMD5 *string `jsonapi:\"attr,md5\"`\n\n\t// Required: The serial of the state.\n\tSerial *int64 `jsonapi:\"attr,serial\"`\n\n\t// Optional: The base64 encoded state.\n\tState *string `jsonapi:\"attr,state,omitempty\"`\n\n\t// Optional: Force can be set to skip certain validations. Wrong use\n\t// of this flag can cause data loss, so USE WITH CAUTION!\n\tForce *bool `jsonapi:\"attr,force,omitempty\"`\n\n\t// Optional: Specifies the run to associate the state with.\n\tRun *Run `jsonapi:\"relation,run,omitempty\"`\n\n\t// Optional: The external, json representation of state data, base64 encoded.\n\t// https://developer.hashicorp.com/terraform/internals/json-format#state-representation\n\t// Supplying this state representation can provide more details to the platform\n\t// about the current terraform state.\n\tJSONState *string `jsonapi:\"attr,json-state,omitempty\"`\n\t// Optional: The external, json representation of state outputs, base64 encoded. Supplying this field\n\t// will provide more detailed output type information to TFE.\n\t// For more information on the contents of this field: https://developer.hashicorp.com/terraform/internals/json-format#values-representation\n\t// about the current terraform state.\n\tJSONStateOutputs *string `jsonapi:\"attr,json-state-outputs,omitempty\"`\n}\n\ntype StateVersionUploadOptions struct {\n\tStateVersionCreateOptions\n\n\tRawState     []byte\n\tRawJSONState []byte\n}\n\ntype StateVersionModules struct {\n\tRoot StateVersionModuleRoot `jsonapi:\"attr,root\"`\n}\n\ntype StateVersionModuleRoot struct {\n\tNullResource         int `jsonapi:\"attr,null-resource\"`\n\tTerraformRemoteState int `jsonapi:\"attr,data.terraform-remote-state\"`\n}\n\ntype StateVersionProviders struct {\n\tData ProviderData `jsonapi:\"attr,provider[map]string\"`\n}\n\ntype ProviderData struct {\n\tNullResource         int `json:\"null-resource\"`\n\tTerraformRemoteState int `json:\"data.terraform-remote-state\"`\n}\n\ntype StateVersionResources struct {\n\tName     string `jsonapi:\"attr,name\"`\n\tCount    int    `jsonapi:\"attr,count\"`\n\tType     string `jsonapi:\"attr,type\"`\n\tModule   string `jsonapi:\"attr,module\"`\n\tProvider string `jsonapi:\"attr,provider\"`\n}\n\n// List all the state versions for a given workspace.\nfunc (s *stateVersions) List(ctx context.Context, options *StateVersionListOptions) (*StateVersionList, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", \"state-versions\", options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsvl := &StateVersionList{}\n\terr = req.Do(ctx, svl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn svl, nil\n}\n\n// Create a new state version for the given workspace.\nfunc (s *stateVersions) Create(ctx context.Context, workspaceID string, options StateVersionCreateOptions) (*StateVersion, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/state-versions\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsv := &StateVersion{}\n\terr = req.Do(ctx, sv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sv, nil\n}\n\n// Upload creates a new state version but uploads the state content directly to the object store.\n// This is a more resilient form of Create and is the recommended approach to creating state versions.\nfunc (s *stateVersions) Upload(ctx context.Context, workspaceID string, options StateVersionUploadOptions) (*StateVersion, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tsv, err := s.Create(ctx, workspaceID, options.StateVersionCreateOptions)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"param is missing or the value is empty: state\") {\n\t\t\treturn nil, ErrStateVersionUploadNotSupported\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tg, _ := errgroup.WithContext(ctx)\n\tg.Go(func() error {\n\t\treturn s.client.doForeignPUTRequest(ctx, sv.UploadURL, bytes.NewReader(options.RawState))\n\t})\n\tif options.RawJSONState != nil {\n\t\tg.Go(func() error {\n\t\t\treturn s.client.doForeignPUTRequest(ctx, sv.JSONUploadURL, bytes.NewReader(options.RawJSONState))\n\t\t})\n\t}\n\n\tif err := g.Wait(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Re-read the state version to get the updated status, if available\n\treturn s.Read(ctx, sv.ID)\n}\n\n// UploadSanitizedState uploads a sanitized version of the state to the provided sanitized state upload url.\n// The SanitizedStateUploadURL cannot be empty.\nfunc (s *stateVersions) UploadSanitizedState(ctx context.Context, sanitizedStateUploadURL *string, sanitizedState []byte) error {\n\tif sanitizedStateUploadURL == nil {\n\t\treturn ErrSanitizedStateUploadURLMissing\n\t}\n\n\treturn s.client.doForeignPUTRequest(ctx, *sanitizedStateUploadURL, bytes.NewReader(sanitizedState))\n}\n\n// Read a state version by its ID.\nfunc (s *stateVersions) ReadWithOptions(ctx context.Context, svID string, options *StateVersionReadOptions) (*StateVersion, error) {\n\tif !validStringID(&svID) {\n\t\treturn nil, ErrInvalidStateVerID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"state-versions/%s\", url.PathEscape(svID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsv := &StateVersion{}\n\terr = req.Do(ctx, sv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sv, nil\n}\n\n// Read a state version by its ID.\nfunc (s *stateVersions) Read(ctx context.Context, svID string) (*StateVersion, error) {\n\treturn s.ReadWithOptions(ctx, svID, nil)\n}\n\n// ReadCurrentWithOptions reads the latest available state from the given workspace using the options supplied.\nfunc (s *stateVersions) ReadCurrentWithOptions(ctx context.Context, workspaceID string, options *StateVersionCurrentOptions) (*StateVersion, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/current-state-version\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsv := &StateVersion{}\n\terr = req.Do(ctx, sv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sv, nil\n}\n\n// ReadCurrent reads the latest available state from the given workspace.\nfunc (s *stateVersions) ReadCurrent(ctx context.Context, workspaceID string) (*StateVersion, error) {\n\treturn s.ReadCurrentWithOptions(ctx, workspaceID, nil)\n}\n\n// Download retrieves the actual stored state of a state version\nfunc (s *stateVersions) Download(ctx context.Context, u string) ([]byte, error) {\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tvar buf bytes.Buffer\n\terr = req.Do(ctx, &buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\n// ListOutputs retrieves all the outputs of a state version by its ID. IMPORTANT: HCP Terraform might\n// process outputs asynchronously. When consuming outputs or other async StateVersion fields, be sure to\n// wait for ResourcesProcessed to become `true` before assuming they are empty.\nfunc (s *stateVersions) ListOutputs(ctx context.Context, svID string, options *StateVersionOutputsListOptions) (*StateVersionOutputsList, error) {\n\tif !validStringID(&svID) {\n\t\treturn nil, ErrInvalidStateVerID\n\t}\n\n\tu := fmt.Sprintf(\"state-versions/%s/outputs\", url.PathEscape(svID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsv := &StateVersionOutputsList{}\n\terr = req.Do(ctx, sv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sv, nil\n}\n\nfunc (s *stateVersions) SoftDeleteBackingData(ctx context.Context, svID string) error {\n\treturn s.manageBackingData(ctx, svID, \"soft_delete_backing_data\")\n}\n\nfunc (s *stateVersions) RestoreBackingData(ctx context.Context, svID string) error {\n\treturn s.manageBackingData(ctx, svID, \"restore_backing_data\")\n}\n\nfunc (s *stateVersions) PermanentlyDeleteBackingData(ctx context.Context, svID string) error {\n\treturn s.manageBackingData(ctx, svID, \"permanently_delete_backing_data\")\n}\n\nfunc (s *stateVersions) manageBackingData(ctx context.Context, svID, action string) error {\n\tif !validStringID(&svID) {\n\t\treturn ErrInvalidStateVerID\n\t}\n\n\tu := fmt.Sprintf(\"state-versions/%s/actions/%s\", svID, action)\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// check that StateVersionListOptions fields had valid values\nfunc (o *StateVersionListOptions) valid() error {\n\tif o == nil {\n\t\treturn ErrRequiredStateVerListOps\n\t}\n\tif !validString(&o.Organization) {\n\t\treturn ErrRequiredOrg\n\t}\n\tif !validString(&o.Workspace) {\n\t\treturn ErrRequiredWorkspace\n\t}\n\treturn nil\n}\n\nfunc (o StateVersionCreateOptions) valid() error {\n\tif !validString(o.MD5) {\n\t\treturn ErrRequiredM5\n\t}\n\tif o.Serial == nil {\n\t\treturn ErrRequiredSerial\n\t}\n\treturn nil\n}\n\nfunc (o StateVersionUploadOptions) valid() error {\n\tif err := o.StateVersionCreateOptions.valid(); err != nil {\n\t\treturn err\n\t}\n\tif o.State != nil || o.JSONState != nil {\n\t\treturn ErrStateMustBeOmitted\n\t}\n\tif o.RawState == nil {\n\t\treturn ErrRequiredRawState\n\t}\n\treturn nil\n}\n\nfunc (o *StateVersionReadOptions) valid() error {\n\treturn nil\n}\nfunc (o *StateVersionCurrentOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "state_version_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"crypto/md5\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc containsStateVersion(versions []*StateVersion, item *StateVersion) bool {\n\tfor _, sv := range versions {\n\t\tif sv.ID == item.ID {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc TestStateVersionsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tsvTest1, svTestCleanup1 := createStateVersion(t, client, 0, wTest)\n\tt.Cleanup(svTestCleanup1)\n\tsvTest2, svTestCleanup2 := createStateVersion(t, client, 1, wTest)\n\tt.Cleanup(svTestCleanup2)\n\n\tt.Run(\"without StateVersionListOptions\", func(t *testing.T) {\n\t\tsvl, err := client.StateVersions.List(ctx, nil)\n\t\tassert.Nil(t, svl)\n\t\tassert.Equal(t, err, ErrRequiredStateVerListOps)\n\t})\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\toptions := &StateVersionListOptions{\n\t\t\tOrganization: orgTest.Name,\n\t\t\tWorkspace:    wTest.Name,\n\t\t}\n\n\t\tsvl, err := client.StateVersions.List(ctx, options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, svl.Items)\n\n\t\tassert.True(t, containsStateVersion(svl.Items, svTest1), fmt.Sprintf(\"State Versions did not contain %s\", svTest1.ID))\n\t\tassert.True(t, containsStateVersion(svl.Items, svTest2), fmt.Sprintf(\"State Versions did not contain %s\", svTest2.ID))\n\n\t\tassert.Equal(t, 1, svl.CurrentPage)\n\t\tassert.Equal(t, 2, svl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\toptions := &StateVersionListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t\tOrganization: orgTest.Name,\n\t\t\tWorkspace:    wTest.Name,\n\t\t}\n\n\t\tsvl, err := client.StateVersions.List(ctx, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, svl.Items)\n\t\tassert.Equal(t, 999, svl.CurrentPage)\n\t\tassert.Equal(t, 2, svl.TotalCount)\n\t})\n\n\tt.Run(\"without an organization\", func(t *testing.T) {\n\t\toptions := &StateVersionListOptions{\n\t\t\tWorkspace: wTest.Name,\n\t\t}\n\n\t\tsvl, err := client.StateVersions.List(ctx, options)\n\t\tassert.Nil(t, svl)\n\t\tassert.Equal(t, err, ErrRequiredOrg)\n\t})\n\n\tt.Run(\"without a workspace\", func(t *testing.T) {\n\t\toptions := &StateVersionListOptions{\n\t\t\tOrganization: orgTest.Name,\n\t\t}\n\n\t\tsvl, err := client.StateVersions.List(ctx, options)\n\t\tassert.Nil(t, svl)\n\t\tassert.Equal(t, err, ErrRequiredWorkspace)\n\t})\n}\n\nfunc TestStateVersionsUpload(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wTestCleanup)\n\n\tstate, err := os.ReadFile(\"test-fixtures/state-version/terraform.tfstate\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tjsonState, err := os.ReadFile(\"test-fixtures/json-state/state.json\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tjsonStateOutputs, err := os.ReadFile(\"test-fixtures/json-state-outputs/everything.json\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Run(\"can create finalized state versions\", func(t *testing.T) {\n\t\tctx := context.Background()\n\t\t_, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\t\trequire.NoError(t, err)\n\n\t\tsv, err := client.StateVersions.Upload(ctx, wTest.ID, StateVersionUploadOptions{\n\t\t\tStateVersionCreateOptions: StateVersionCreateOptions{\n\t\t\t\tLineage:          String(\"741c4949-60b9-5bb1-5bf8-b14f4bb14af3\"),\n\t\t\t\tMD5:              String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\t\tSerial:           Int64(1),\n\t\t\t\tJSONStateOutputs: String(base64.StdEncoding.EncodeToString(jsonStateOutputs)),\n\t\t\t},\n\t\t\tRawState:     state,\n\t\t\tRawJSONState: jsonState,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.Workspaces.Unlock(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// HCP Terraform does some async processing on state versions, so we must await it\n\t\t// lest we flake. Should take well less than a minute tho.\n\t\ttimeout := time.Minute / 2\n\n\t\tctxPollSVReady, cancelPollSVReady := context.WithTimeout(ctx, timeout)\n\t\tdefer cancelPollSVReady()\n\n\t\tsv = pollStateVersionStatus(t, client, ctxPollSVReady, sv, []StateVersionStatus{StateVersionFinalized})\n\n\t\tassert.NotEmpty(t, sv.DownloadURL)\n\t\tassert.Equal(t, StateVersionFinalized, sv.Status)\n\t})\n\tt.Run(\"cannot provide base64 state parameter when uploading\", func(t *testing.T) {\n\t\tctx := context.Background()\n\t\t_, err = client.StateVersions.Upload(ctx, wTest.ID, StateVersionUploadOptions{\n\t\t\tStateVersionCreateOptions: StateVersionCreateOptions{\n\t\t\t\tLineage:          String(\"741c4949-60b9-5bb1-5bf8-b14f4bb14af3\"),\n\t\t\t\tMD5:              String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\t\tSerial:           Int64(1),\n\t\t\t\tState:            String(base64.StdEncoding.EncodeToString(state)),\n\t\t\t\tJSONStateOutputs: String(base64.StdEncoding.EncodeToString(jsonStateOutputs)),\n\t\t\t},\n\t\t\tRawState:     state,\n\t\t\tRawJSONState: jsonState,\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrStateMustBeOmitted)\n\t})\n\n\tt.Run(\"RawState parameter is required when uploading\", func(t *testing.T) {\n\t\tctx := context.Background()\n\t\t_, err = client.StateVersions.Upload(ctx, wTest.ID, StateVersionUploadOptions{\n\t\t\tStateVersionCreateOptions: StateVersionCreateOptions{\n\t\t\t\tLineage:          String(\"741c4949-60b9-5bb1-5bf8-b14f4bb14af3\"),\n\t\t\t\tMD5:              String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\t\tSerial:           Int64(1),\n\t\t\t\tJSONStateOutputs: String(base64.StdEncoding.EncodeToString(jsonStateOutputs)),\n\t\t\t},\n\t\t\tRawJSONState: jsonState,\n\t\t})\n\t\trequire.ErrorIs(t, err, ErrRequiredRawState)\n\t})\n\n\tt.Run(\"uploading state using SanitizedStateUploadURL and verifying SanitizedStateDownloadURL exists\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\thyokWorkspaceName := os.Getenv(\"HYOK_WORKSPACE_NAME\")\n\t\tif hyokWorkspaceName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_WORKSPACE_NAME before running this test!\")\n\t\t}\n\n\t\tw, err := client.Workspaces.Read(context.Background(), hyokOrganizationName, hyokWorkspaceName)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tctx := context.Background()\n\t\t_, err = client.Workspaces.Lock(ctx, w.ID, WorkspaceLockOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tsv, err := client.StateVersions.Create(ctx, w.ID, StateVersionCreateOptions{\n\t\t\tLineage: String(\"741c4949-60b9-5bb1-5bf8-b14f4bb14af3\"),\n\t\t\tMD5:     String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tSerial:  Int64(1),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\terr = client.StateVersions.UploadSanitizedState(ctx, sv.SanitizedStateUploadURL, jsonState)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view of the configuration version.\n\t\tsv, err = retryPatientlyIf(\n\t\t\tfunc() (any, error) { return client.StateVersions.Read(ctx, sv.ID) },\n\t\t\tfunc(sv *StateVersion) bool { return sv.DownloadURL == \"\" },\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, sv.SanitizedStateDownloadURL)\n\t\tassert.Empty(t, sv.SanitizedStateUploadURL)\n\n\t\t_, err = client.Workspaces.ForceUnlock(ctx, w.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\n\tt.Run(\"SanitizedStateUploadURL is required when uploading sanitized state\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\tctx := context.Background()\n\t\t_, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tsv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{\n\t\t\tLineage: String(\"741c4949-60b9-5bb1-5bf8-b14f4bb14af3\"),\n\t\t\tMD5:     String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tSerial:  Int64(1),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\terr = client.StateVersions.UploadSanitizedState(ctx, sv.SanitizedStateUploadURL, state)\n\t\trequire.Error(t, err, ErrSanitizedStateUploadURLMissing)\n\n\t\t// Workspaces must be force-unlocked when there is a pending state version\n\t\t_, err = client.Workspaces.ForceUnlock(ctx, wTest.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestStateVersionsCreate_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wTestCleanup)\n\n\tstate, err := os.ReadFile(\"test-fixtures/state-version/terraform.tfstate\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tjsonState, err := os.ReadFile(\"test-fixtures/json-state/state.json\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tjsonStateOutputs, err := os.ReadFile(\"test-fixtures/json-state-outputs/everything.json\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Run(\"can create pending state versions\", func(t *testing.T) {\n\t\tctx := context.Background()\n\t\t_, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t_, err = client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{\n\t\t\tLineage: String(\"741c4949-60b9-5bb1-5bf8-b14f4bb14af3\"),\n\t\t\tMD5:     String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tSerial:  Int64(1),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// Workspaces must be force-unlocked when there is a pending state version\n\t\t_, err = client.Workspaces.ForceUnlock(ctx, wTest.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tctx := context.Background()\n\t\t_, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tsv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{\n\t\t\tLineage: String(\"741c4949-60b9-5bb1-5bf8-b14f4bb14af3\"),\n\t\t\tMD5:     String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tSerial:  Int64(1),\n\t\t\tState:   String(base64.StdEncoding.EncodeToString(state)),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view of the configuration version.\n\t\trefreshed, err := retryPatientlyIf(\n\t\t\tfunc() (any, error) { return client.StateVersions.Read(ctx, sv.ID) },\n\t\t\tfunc(sv *StateVersion) bool { return sv.DownloadURL == \"\" },\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.Workspaces.Unlock(ctx, wTest.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tfor _, item := range []*StateVersion{\n\t\t\tsv,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, int64(1), item.Serial)\n\t\t\tassert.NotEmpty(t, item.CreatedAt)\n\t\t}\n\n\t\tassert.NotEmpty(t, refreshed.DownloadURL)\n\t})\n\n\tt.Run(\"with external state representation\", func(t *testing.T) {\n\t\tctx := context.Background()\n\t\t_, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tsv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{\n\t\t\tLineage:          String(\"741c4949-60b9-5bb1-5bf8-b14f4bb14af3\"),\n\t\t\tMD5:              String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tSerial:           Int64(1),\n\t\t\tState:            String(base64.StdEncoding.EncodeToString(state)),\n\t\t\tJSONState:        String(base64.StdEncoding.EncodeToString(jsonState)),\n\t\t\tJSONStateOutputs: String(base64.StdEncoding.EncodeToString(jsonStateOutputs)),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view of the configuration version.\n\t\trefreshed, err := retryPatientlyIf(\n\t\t\tfunc() (any, error) { return client.StateVersions.Read(ctx, sv.ID) },\n\t\t\tfunc(sv *StateVersion) bool { return sv.DownloadURL == \"\" },\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.Workspaces.Unlock(ctx, wTest.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// TODO: check state outputs for the ones we sent in JSONStateOutputs\n\n\t\tfor _, item := range []*StateVersion{\n\t\t\tsv,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, int64(1), item.Serial)\n\t\t\tassert.NotEmpty(t, item.CreatedAt)\n\t\t}\n\n\t\tassert.NotEmpty(t, refreshed.DownloadURL)\n\t})\n\n\tt.Run(\"with the force flag set\", func(t *testing.T) {\n\t\tctx := context.Background()\n\t\t_, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t_, err = client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{\n\t\t\tLineage: String(\"741c4949-60b9-5bb1-5bf8-b14f4bb14af3\"),\n\t\t\tMD5:     String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tSerial:  Int64(1),\n\t\t\tState:   String(base64.StdEncoding.EncodeToString(state)),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tsv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{\n\t\t\tLineage: String(\"821c4747-a0b9-3bd1-8bf3-c14f4bb14be7\"),\n\t\t\tMD5:     String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tSerial:  Int64(2),\n\t\t\tState:   String(base64.StdEncoding.EncodeToString(state)),\n\t\t\tForce:   Bool(true),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view of the configuration version.\n\t\trefreshed, err := retryPatientlyIf(\n\t\t\tfunc() (any, error) { return client.StateVersions.Read(ctx, sv.ID) },\n\t\t\tfunc(sv *StateVersion) bool { return sv.DownloadURL == \"\" },\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.Workspaces.Unlock(ctx, wTest.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tfor _, item := range []*StateVersion{\n\t\t\tsv,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, int64(2), item.Serial)\n\t\t\tassert.NotEmpty(t, item.CreatedAt)\n\t\t}\n\n\t\tassert.NotEmpty(t, refreshed.DownloadURL)\n\t})\n\n\tt.Run(\"with a run to associate with\", func(t *testing.T) {\n\t\tt.Skip(\"This can only be tested with the run specific token\")\n\n\t\trTest, rTestCleanup := createRun(t, client, wTest)\n\t\tt.Cleanup(rTestCleanup)\n\n\t\tctx := context.Background()\n\t\tsv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{\n\t\t\tMD5:    String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tSerial: Int64(0),\n\t\t\tState:  String(base64.StdEncoding.EncodeToString(state)),\n\t\t\tRun:    rTest,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, sv.Run)\n\n\t\t// Get a refreshed view of the configuration version.\n\t\trefreshed, err := retryPatientlyIf(\n\t\t\tfunc() (any, error) { return client.StateVersions.Read(ctx, sv.ID) },\n\t\t\tfunc(sv *StateVersion) bool { return sv.DownloadURL == \"\" },\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, refreshed.Run)\n\n\t\tfor _, item := range []*StateVersion{\n\t\t\tsv,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, int64(0), item.Serial)\n\t\t\tassert.NotEmpty(t, item.CreatedAt)\n\t\t\tassert.NotEmpty(t, item.DownloadURL)\n\t\t\tassert.Equal(t, rTest.ID, item.Run.ID)\n\t\t}\n\t})\n\n\tt.Run(\"without md5 hash\", func(t *testing.T) {\n\t\tsv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{\n\t\t\tSerial: Int64(0),\n\t\t\tState:  String(base64.StdEncoding.EncodeToString(state)),\n\t\t})\n\t\tassert.Nil(t, sv)\n\t\tassert.Equal(t, err, ErrRequiredM5)\n\t})\n\n\tt.Run(\"without serial\", func(t *testing.T) {\n\t\tsv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{\n\t\t\tMD5:   String(fmt.Sprintf(\"%x\", md5.Sum(state))),\n\t\t\tState: String(base64.StdEncoding.EncodeToString(state)),\n\t\t})\n\t\tassert.Nil(t, sv)\n\t\tassert.Equal(t, err, ErrRequiredSerial)\n\t})\n\n\tt.Run(\"with invalid workspace id\", func(t *testing.T) {\n\t\tsv, err := client.StateVersions.Create(ctx, badIdentifier, StateVersionCreateOptions{})\n\t\tassert.Nil(t, sv)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestStateVersionsRead(t *testing.T) {\n\tt.Parallel()\n\tt.Skip(\"Skipping due to persistent failures - see TF-31172\")\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tsvTest, svTestCleanup := createStateVersion(t, client, 0, nil)\n\tt.Cleanup(svTestCleanup)\n\n\tt.Run(\"when the state version exists\", func(t *testing.T) {\n\t\tvar sv *StateVersion\n\t\tvar ok bool\n\t\tsv, err := client.StateVersions.Read(ctx, svTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tif !sv.ResourcesProcessed {\n\t\t\tsvRetry, err := retryPatiently(func() (interface{}, error) {\n\t\t\t\tsvTest, err := client.StateVersions.Read(ctx, svTest.ID)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tif !svTest.ResourcesProcessed || svTest.BillableRUMCount == nil || *svTest.BillableRUMCount == 0 {\n\t\t\t\t\treturn nil, fmt.Errorf(\"resources not processed %v / %d\", svTest.ResourcesProcessed, svTest.BillableRUMCount)\n\t\t\t\t}\n\n\t\t\t\treturn svTest, nil\n\t\t\t})\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"error retrying state version read, err=%s\", err)\n\t\t\t}\n\n\t\t\trequire.NotNil(t, svRetry, \"timed out waiting for resources to finish processing\")\n\n\t\t\tsv, ok = svRetry.(*StateVersion)\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"Expected sv to be type *StateVersion, got %T\", sv)\n\t\t\t}\n\t\t}\n\n\t\tassert.NotEmpty(t, sv.DownloadURL)\n\t\tassert.NotEmpty(t, sv.StateVersion)\n\t\tassert.NotEmpty(t, sv.TerraformVersion)\n\t\tassert.NotEmpty(t, sv.Outputs)\n\n\t\trequire.NotNil(t, sv.BillableRUMCount)\n\t\tassert.Greater(t, *sv.BillableRUMCount, uint32(0))\n\t\tassert.Greater(t, sv.Size, int64(0))\n\t})\n\n\tt.Run(\"when the state version does not exist\", func(t *testing.T) {\n\t\tsv, err := client.StateVersions.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, sv)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"with invalid state version id\", func(t *testing.T) {\n\t\tsv, err := client.StateVersions.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, sv)\n\t\tassert.Equal(t, err, ErrInvalidStateVerID)\n\t})\n\n\tt.Run(\"read encrypted state download url of a state version\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\thyokStateVersionID := os.Getenv(\"HYOK_STATE_VERSION_ID\")\n\t\tif hyokStateVersionID == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_STATE_VERSION_ID before running this test!\")\n\t\t}\n\n\t\tsv, err := client.StateVersions.Read(ctx, hyokStateVersionID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, sv.EncryptedStateDownloadURL)\n\t})\n\n\tt.Run(\"read sanitized state download url of a state version\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\thyokStateVersionID := os.Getenv(\"HYOK_STATE_VERSION_ID\")\n\t\tif hyokStateVersionID == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_STATE_VERSION_ID before running this test!\")\n\t\t}\n\n\t\tsv, err := client.StateVersions.Read(ctx, hyokStateVersionID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, sv.SanitizedStateDownloadURL)\n\t})\n\n\tt.Run(\"read hyok encrypted data key of a state version\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\thyokStateVersionID := os.Getenv(\"HYOK_STATE_VERSION_ID\")\n\t\tif hyokStateVersionID == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_STATE_VERSION_ID before running this test!\")\n\t\t}\n\n\t\tsv, err := client.StateVersions.Read(ctx, hyokStateVersionID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, sv.HYOKEncryptedDataKey)\n\t})\n}\n\nfunc TestStateVersionsReadWithOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tsvTest, svTestCleanup := createStateVersion(t, client, 0, nil)\n\tt.Cleanup(svTestCleanup)\n\n\t// give HCP Terraform some time to process the statefile and extract the outputs.\n\twaitForSVOutputs(t, client, svTest.ID)\n\n\tt.Run(\"when the state version exists\", func(t *testing.T) {\n\t\tcurOpts := &StateVersionReadOptions{\n\t\t\tInclude: []StateVersionIncludeOpt{SVoutputs},\n\t\t}\n\n\t\tsv, err := client.StateVersions.ReadWithOptions(ctx, svTest.ID, curOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, sv.Outputs)\n\t})\n}\n\nfunc TestStateVersionsCurrent(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest1, wTest1Cleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wTest1Cleanup)\n\n\twTest2, wTest2Cleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wTest2Cleanup)\n\n\tsvTest, svTestCleanup := createStateVersion(t, client, 0, wTest1)\n\tt.Cleanup(svTestCleanup)\n\n\tt.Run(\"when a state version exists\", func(t *testing.T) {\n\t\tsv, err := client.StateVersions.ReadCurrent(ctx, wTest1.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, stateVersion := range []*StateVersion{svTest, sv} {\n\t\t\t// Don't compare the DownloadURL because it will be generated twice\n\t\t\t// in this test - once at creation of the configuration version, and\n\t\t\t// again during the GET.\n\t\t\tstateVersion.DownloadURL = \"\"\n\n\t\t\t// outputs, providers are populated only once the state has been parsed by HCP Terraform\n\t\t\t// which can cause the tests to fail if it doesn't happen fast enough.\n\t\t\tstateVersion.Outputs = nil\n\t\t\tstateVersion.Providers = nil\n\t\t}\n\n\t\tassert.Equal(t, svTest.ID, sv.ID)\n\t})\n\n\tt.Run(\"when a state version does not exist\", func(t *testing.T) {\n\t\tsv, err := client.StateVersions.ReadCurrent(ctx, wTest2.ID)\n\t\tassert.Nil(t, sv)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"with invalid workspace id\", func(t *testing.T) {\n\t\tsv, err := client.StateVersions.ReadCurrent(ctx, badIdentifier)\n\t\tassert.Nil(t, sv)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestStateVersionsCurrentWithOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest1, wTest1Cleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wTest1Cleanup)\n\n\tsvTest, svTestCleanup := createStateVersion(t, client, 0, wTest1)\n\tt.Cleanup(svTestCleanup)\n\n\t// give HCP Terraform some time to process the statefile and extract the outputs.\n\twaitForSVOutputs(t, client, svTest.ID)\n\n\tt.Run(\"when the state version exists\", func(t *testing.T) {\n\t\tcurOpts := &StateVersionCurrentOptions{\n\t\t\tInclude: []StateVersionIncludeOpt{SVoutputs},\n\t\t}\n\n\t\tsv, err := client.StateVersions.ReadCurrentWithOptions(ctx, wTest1.ID, curOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, sv.Outputs)\n\t})\n}\n\nfunc TestStateVersionsDownload(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tsvTest, svTestCleanup := createStateVersion(t, client, 0, nil)\n\tt.Cleanup(svTestCleanup)\n\n\tstateTest, err := os.ReadFile(\"test-fixtures/state-version/terraform.tfstate\")\n\trequire.NoError(t, err)\n\n\tt.Run(\"when the state version exists\", func(t *testing.T) {\n\t\tstate, err := client.StateVersions.Download(ctx, svTest.DownloadURL)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, stateTest, state)\n\t})\n\n\tt.Run(\"with an invalid url\", func(t *testing.T) {\n\t\tstate, err := client.StateVersions.Download(ctx, badIdentifier)\n\t\tassert.Nil(t, state)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n}\n\nfunc TestStateVersionOutputs(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest1, wTest1Cleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wTest1Cleanup)\n\n\tsv, svTestCleanup := createStateVersion(t, client, 0, wTest1)\n\tt.Cleanup(svTestCleanup)\n\n\t// give HCP Terraform some time to process the statefile and extract the outputs.\n\twaitForSVOutputs(t, client, sv.ID)\n\n\tt.Run(\"when the state version exists\", func(t *testing.T) {\n\t\toutputs, err := client.StateVersions.ListOutputs(ctx, sv.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, outputs.Items)\n\n\t\tvalues := map[string]interface{}{}\n\t\tfor _, op := range outputs.Items {\n\t\t\tvalues[op.Name] = op.Value\n\t\t}\n\n\t\ttestOutputString, ok := values[\"test_output_string\"].(string)\n\t\trequire.True(t, ok)\n\n\t\ttestOutputNumber, ok := values[\"test_output_number\"].(float64)\n\t\trequire.True(t, ok)\n\n\t\ttestOutputBool, ok := values[\"test_output_bool\"].(bool)\n\t\trequire.True(t, ok)\n\n\t\ttestOutputListString, ok := values[\"test_output_list_string\"].([]interface{})\n\t\trequire.True(t, ok)\n\n\t\ttestOutputTupleNumber, ok := values[\"test_output_tuple_number\"].([]interface{})\n\t\trequire.True(t, ok)\n\n\t\ttestOutputTupleString, ok := values[\"test_output_tuple_string\"].([]interface{})\n\t\trequire.True(t, ok)\n\n\t\ttestOutputObject, ok := values[\"test_output_object\"].(map[string]interface{})\n\t\trequire.True(t, ok)\n\n\t\t// These asserts are based off of the values in\n\t\t// test-fixtures/state-version/terraform.tfstate\n\t\tassert.Equal(t, \"9023256633839603543\", testOutputString)\n\t\tassert.Equal(t, float64(5), testOutputNumber)\n\t\tassert.Equal(t, true, testOutputBool)\n\t\tassert.Equal(t, []interface{}{\"us-west-1a\"}, testOutputListString)\n\t\tassert.Equal(t, []interface{}{float64(1), float64(2)}, testOutputTupleNumber)\n\t\tassert.Equal(t, []interface{}{\"one\", \"two\"}, testOutputTupleString)\n\t\tassert.Equal(t, map[string]interface{}{\"foo\": \"bar\"}, testOutputObject)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\toptions := &StateVersionOutputsListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t}\n\t\toutputs, err := client.StateVersions.ListOutputs(ctx, sv.ID, options)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, outputs.Items)\n\t\tassert.Equal(t, 999, outputs.CurrentPage)\n\n\t\t// Based on fixture test-fixtures/state-version/terraform.tfstate\n\t\tassert.Equal(t, 7, outputs.TotalCount)\n\t})\n\n\tt.Run(\"when the state version does not exist\", func(t *testing.T) {\n\t\toutputs, err := client.StateVersions.ListOutputs(ctx, \"sv-999999999\", nil)\n\t\tassert.Nil(t, outputs)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestStateVersions_ManageBackingData(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tworkspace, workspaceCleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(workspaceCleanup)\n\n\tnonCurrentStateVersion, svTestCleanup := createStateVersion(t, client, 0, workspace)\n\tt.Cleanup(svTestCleanup)\n\n\t_, svTestCleanup = createStateVersion(t, client, 0, workspace)\n\tt.Cleanup(svTestCleanup)\n\n\tt.Run(\"soft delete backing data\", func(t *testing.T) {\n\t\terr := client.StateVersions.SoftDeleteBackingData(ctx, nonCurrentStateVersion.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.StateVersions.Download(ctx, nonCurrentStateVersion.DownloadURL)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"restore backing data\", func(t *testing.T) {\n\t\terr := client.StateVersions.RestoreBackingData(ctx, nonCurrentStateVersion.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.StateVersions.Download(ctx, nonCurrentStateVersion.DownloadURL)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"permanently delete backing data\", func(t *testing.T) {\n\t\terr := client.StateVersions.SoftDeleteBackingData(ctx, nonCurrentStateVersion.ID)\n\t\trequire.NoError(t, err)\n\n\t\terr = client.StateVersions.PermanentlyDeleteBackingData(ctx, nonCurrentStateVersion.ID)\n\t\trequire.NoError(t, err)\n\n\t\terr = client.StateVersions.RestoreBackingData(ctx, nonCurrentStateVersion.ID)\n\t\trequire.ErrorContainsf(t, err, \"transition not allowed\", \"Restore backing data should fail\")\n\n\t\t_, err = client.StateVersions.Download(ctx, nonCurrentStateVersion.DownloadURL)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n}\n"
  },
  {
    "path": "state_version_output.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ StateVersionOutputs = (*stateVersionOutputs)(nil)\n\n// State version outputs are the output values from a Terraform state file.\n// They include the name and value of the output, as well as a sensitive boolean\n// if the value should be hidden by default in UIs.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/state-version-outputs\ntype StateVersionOutputs interface {\n\tRead(ctx context.Context, outputID string) (*StateVersionOutput, error)\n\tReadCurrent(ctx context.Context, workspaceID string) (*StateVersionOutputsList, error)\n}\n\n// stateVersionOutputs implements StateVersionOutputs.\ntype stateVersionOutputs struct {\n\tclient *Client\n}\n\n// StateVersionOutput represents a State Version Outputs\ntype StateVersionOutput struct {\n\tID        string      `jsonapi:\"primary,state-version-outputs\"`\n\tName      string      `jsonapi:\"attr,name\"`\n\tSensitive bool        `jsonapi:\"attr,sensitive\"`\n\tType      string      `jsonapi:\"attr,type\"`\n\tValue     interface{} `jsonapi:\"attr,value\"`\n\t// BETA: This field is experimental and not universally present in all versions of TFE/Terraform\n\tDetailedType interface{} `jsonapi:\"attr,detailed-type\"`\n}\n\n// ReadCurrent reads the current state version outputs for the specified workspace\nfunc (s *stateVersionOutputs) ReadCurrent(ctx context.Context, workspaceID string) (*StateVersionOutputsList, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/current-state-version-outputs\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tso := &StateVersionOutputsList{}\n\terr = req.Do(ctx, so)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn so, nil\n}\n\n// Read a State Version Output\nfunc (s *stateVersionOutputs) Read(ctx context.Context, outputID string) (*StateVersionOutput, error) {\n\tif !validStringID(&outputID) {\n\t\treturn nil, ErrInvalidOutputID\n\t}\n\n\tu := fmt.Sprintf(\"state-version-outputs/%s\", url.PathEscape(outputID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tso := &StateVersionOutput{}\n\terr = req.Do(ctx, so)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn so, nil\n}\n"
  },
  {
    "path": "state_version_output_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStateVersionOutputsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest1, wTest1Cleanup := createWorkspace(t, client, nil)\n\tdefer wTest1Cleanup()\n\n\tsvTest, svTestCleanup := createStateVersion(t, client, 0, wTest1)\n\tdefer svTestCleanup()\n\n\t// give HCP Terraform some time to process the statefile and extract the outputs.\n\twaitForSVOutputs(t, client, svTest.ID)\n\n\tcurOpts := &StateVersionCurrentOptions{\n\t\tInclude: []StateVersionIncludeOpt{SVoutputs},\n\t}\n\n\tsv, err := client.StateVersions.ReadCurrentWithOptions(ctx, wTest1.ID, curOpts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trequire.NotEmpty(t, sv.Outputs)\n\trequire.NotNil(t, sv.Outputs[0])\n\n\toutput := sv.Outputs[0]\n\n\tt.Run(\"Read by ID\", func(t *testing.T) {\n\t\tt.Run(\"when a state output exists\", func(t *testing.T) {\n\t\t\tso, err := client.StateVersionOutputs.Read(ctx, output.ID)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.Equal(t, so.ID, output.ID)\n\t\t\tassert.Equal(t, so.Name, output.Name)\n\t\t\tassert.Equal(t, so.Value, output.Value)\n\t\t})\n\n\t\tt.Run(\"when a state output does not exist\", func(t *testing.T) {\n\t\t\tso, err := client.StateVersionOutputs.Read(ctx, \"wsout-J2zM24JPAAAAAAAA\")\n\t\t\tassert.Nil(t, so)\n\t\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t\t})\n\t})\n\n\tt.Run(\"Read current workspace outputs\", func(t *testing.T) {\n\t\tso, err := client.StateVersionOutputs.ReadCurrent(ctx, wTest1.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, so.Items)\n\t})\n\n\tt.Run(\"Sensitive secrets are null\", func(t *testing.T) {\n\t\tso, err := client.StateVersionOutputs.ReadCurrent(ctx, wTest1.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, so.Items)\n\n\t\tvar found *StateVersionOutput = nil\n\t\tfor _, s := range so.Items {\n\t\t\tif s.Name == \"test_output_string\" {\n\t\t\t\tfound = s\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tassert.NotNil(t, found)\n\t\tassert.True(t, found.Sensitive)\n\t\tassert.Nil(t, found.Value)\n\t})\n}\n"
  },
  {
    "path": "subscription_updater_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n)\n\ntype featureSet struct {\n\tID string `jsonapi:\"primary,feature-sets\"`\n}\n\ntype featureSetList struct {\n\tItems []*featureSet\n\t*Pagination\n}\n\ntype featureSetListOptions struct {\n\tQ string `url:\"q,omitempty\"`\n}\n\ntype retryableFn func() (any, error)\n\ntype updateFeatureSetOptions struct {\n\tType                          string     `jsonapi:\"primary,subscription\"`\n\tRunsCeiling                   *int       `jsonapi:\"attr,runs-ceiling,omitempty\"`\n\tAgentsCeiling                 *int       `jsonapi:\"attr,agents-ceiling,omitempty\"`\n\tContractStartAt               *time.Time `jsonapi:\"attr,contract-start-at,iso8601,omitempty\"`\n\tEndAt                         *time.Time `jsonapi:\"attr,end-at,iso8601,omitempty\"`\n\tContractUserLimit             *int       `jsonapi:\"attr,contract-user-limit,omitempty\"`\n\tContractApplyLimit            *int       `jsonapi:\"attr,contract-apply-limit,omitempty\"`\n\tContractManagedResourcesLimit *int       `jsonapi:\"attr,contract-managed-resources-limit,omitempty\"`\n\n\tFeatureSet *featureSet `jsonapi:\"relation,feature-set\"`\n}\n\ntype organizationSubscriptionUpdater struct {\n\torganization *Organization\n\tplanName     string\n\tupdateOpts   updateFeatureSetOptions\n}\n\nfunc newSubscriptionUpdater(organization *Organization) *organizationSubscriptionUpdater {\n\treturn &organizationSubscriptionUpdater{\n\t\torganization: organization,\n\t\tupdateOpts:   updateFeatureSetOptions{},\n\t}\n}\n\nfunc (b *organizationSubscriptionUpdater) WithBusinessPlan() *organizationSubscriptionUpdater {\n\tb.planName = \"Business\"\n\n\tceiling := 10\n\tagentsCeiling := 10\n\tstart := time.Now()\n\tend := time.Now().AddDate(1, 0, 0) // 1 year from now\n\tuserLimit := 1000\n\tapplyLimit := 5000\n\n\tb.updateOpts.RunsCeiling = &ceiling\n\tb.updateOpts.AgentsCeiling = &agentsCeiling\n\tb.updateOpts.ContractStartAt = &start\n\tb.updateOpts.EndAt = &end\n\tb.updateOpts.ContractUserLimit = &userLimit\n\tb.updateOpts.ContractApplyLimit = &applyLimit\n\treturn b\n}\n\nfunc (b *organizationSubscriptionUpdater) WithTrialPlan() *organizationSubscriptionUpdater {\n\tb.planName = \"Trial\"\n\tceiling := 1\n\tb.updateOpts.RunsCeiling = &ceiling\n\treturn b\n}\n\nfunc (b *organizationSubscriptionUpdater) WithStandardEntitlementPlan() *organizationSubscriptionUpdater {\n\tb.planName = \"Standard (entitlement)\"\n\n\tstart := time.Now()\n\tend := time.Now().AddDate(1, 0, 0) // 1 year from now\n\tceiling := 1\n\tmanagedResourcesLimit := 1000\n\n\tb.updateOpts.ContractStartAt = &start\n\tb.updateOpts.EndAt = &end\n\tb.updateOpts.RunsCeiling = &ceiling\n\tb.updateOpts.ContractManagedResourcesLimit = &managedResourcesLimit\n\treturn b\n}\n\n// Attempts to change an organization's subscription to a different plan. Requires a user token with admin access.\nfunc (b *organizationSubscriptionUpdater) Update(t *testing.T) {\n\tif enterpriseEnabled() {\n\t\tt.Skip(\"Cannot upgrade an organization's subscription when enterprise is enabled. Set ENABLE_TFE=0 to run.\")\n\t}\n\n\tif b.planName == \"\" {\n\t\tt.Fatal(\"organizationSubscriptionUpdater requires a plan\")\n\t\treturn\n\t}\n\n\tadminClient := testAdminClient(t, provisionLicensesAdmin)\n\treq, err := adminClient.NewRequest(\"GET\", \"admin/feature-sets\", featureSetListOptions{\n\t\tQ: b.planName,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tfsl := &featureSetList{}\n\terr = req.Do(context.Background(), fsl)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to enumerate feature sets: %v\", err)\n\t\treturn\n\t} else if len(fsl.Items) == 0 {\n\t\tt.Fatalf(\"feature set response was empty\")\n\t\treturn\n\t}\n\n\tb.updateOpts.FeatureSet = fsl.Items[0]\n\n\tu := fmt.Sprintf(\"admin/organizations/%s/subscription\", url.PathEscape(b.organization.Name))\n\treq, err = adminClient.NewRequest(\"POST\", u, &b.updateOpts)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create request: %v\", err)\n\t\treturn\n\t}\n\n\terr = req.Do(context.Background(), nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to upgrade subscription: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "tag.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"fmt\"\n)\n\ntype TagList struct {\n\t*Pagination\n\tItems []*Tag\n}\n\n// Tag is owned by an organization and applied to workspaces. Used for grouping and search.\ntype Tag struct {\n\tID   string `jsonapi:\"primary,tags\"`\n\tName string `jsonapi:\"attr,name,omitempty\"`\n}\n\ntype TagBinding struct {\n\tID    string `jsonapi:\"primary,tag-bindings\"`\n\tKey   string `jsonapi:\"attr,key\"`\n\tValue string `jsonapi:\"attr,value,omitempty\"`\n}\n\ntype EffectiveTagBinding struct {\n\tID    string                 `jsonapi:\"primary,effective-tag-bindings\"`\n\tKey   string                 `jsonapi:\"attr,key\"`\n\tValue string                 `jsonapi:\"attr,value,omitempty\"`\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n}\n\nfunc encodeTagFiltersAsParams(filters []*TagBinding) map[string][]string {\n\tif len(filters) == 0 {\n\t\treturn nil\n\t}\n\n\tvar tagFilter = make(map[string][]string, len(filters))\n\tfor index, tag := range filters {\n\t\ttagFilter[fmt.Sprintf(\"filter[tagged][%d][key]\", index)] = []string{tag.Key}\n\t\ttagFilter[fmt.Sprintf(\"filter[tagged][%d][value]\", index)] = []string{tag.Value}\n\t}\n\n\treturn tagFilter\n}\n"
  },
  {
    "path": "task_result.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation\nvar _ TaskResults = (*taskResults)(nil)\n\n// TaskResults describes all the task result related methods that the HCP Terraform or Terraform Enterprise API supports.\ntype TaskResults interface {\n\t// Read a task result by ID\n\tRead(ctx context.Context, taskResultID string) (*TaskResult, error)\n}\n\n// taskResults implements TaskResults\ntype taskResults struct {\n\tclient *Client\n}\n\n// TaskResultStatus is an enum that represents all possible statuses for a task result\ntype TaskResultStatus string\n\nconst (\n\tTaskPassed      TaskResultStatus = \"passed\"\n\tTaskFailed      TaskResultStatus = \"failed\"\n\tTaskPending     TaskResultStatus = \"pending\"\n\tTaskRunning     TaskResultStatus = \"running\"\n\tTaskUnreachable TaskResultStatus = \"unreachable\"\n\tTaskErrored     TaskResultStatus = \"errored\"\n)\n\n// TaskEnforcementLevel is an enum that describes the enforcement levels for a run task\ntype TaskEnforcementLevel string\n\nconst (\n\tAdvisory  TaskEnforcementLevel = \"advisory\"\n\tMandatory TaskEnforcementLevel = \"mandatory\"\n)\n\n// TaskResultStatusTimestamps represents the set of timestamps recorded for a task result\ntype TaskResultStatusTimestamps struct {\n\tErroredAt  time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tRunningAt  time.Time `jsonapi:\"attr,running-at,rfc3339\"`\n\tCanceledAt time.Time `jsonapi:\"attr,canceled-at,rfc3339\"`\n\tFailedAt   time.Time `jsonapi:\"attr,failed-at,rfc3339\"`\n\tPassedAt   time.Time `jsonapi:\"attr,passed-at,rfc3339\"`\n}\n\n// TaskResult represents the result of a HCP Terraform or Terraform Enterprise run task\ntype TaskResult struct {\n\tID                            string                     `jsonapi:\"primary,task-results\"`\n\tStatus                        TaskResultStatus           `jsonapi:\"attr,status\"`\n\tMessage                       string                     `jsonapi:\"attr,message\"`\n\tStatusTimestamps              TaskResultStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\tURL                           string                     `jsonapi:\"attr,url\"`\n\tCreatedAt                     time.Time                  `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt                     time.Time                  `jsonapi:\"attr,updated-at,iso8601\"`\n\tTaskID                        string                     `jsonapi:\"attr,task-id\"`\n\tTaskName                      string                     `jsonapi:\"attr,task-name\"`\n\tTaskURL                       string                     `jsonapi:\"attr,task-url\"`\n\tWorkspaceTaskID               string                     `jsonapi:\"attr,workspace-task-id\"`\n\tWorkspaceTaskEnforcementLevel TaskEnforcementLevel       `jsonapi:\"attr,workspace-task-enforcement-level\"`\n\tAgentPoolID                   *string                    `jsonapi:\"attr,agent-pool-id,omitempty\"`\n\n\t// The task stage this result belongs to\n\tTaskStage *TaskStage `jsonapi:\"relation,task_stage\"`\n}\n\n// Read a task result by ID\nfunc (t *taskResults) Read(ctx context.Context, taskResultID string) (*TaskResult, error) {\n\tif !validStringID(&taskResultID) {\n\t\treturn nil, ErrInvalidTaskResultID\n\t}\n\n\tu := fmt.Sprintf(\"task-results/%s\", taskResultID)\n\treq, err := t.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &TaskResult{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r, nil\n}\n"
  },
  {
    "path": "task_stages.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n)\n\n// Compile-time proof of interface  implementation\nvar _ TaskStages = (*taskStages)(nil)\n\n// TaskStages describes all the task stage related methods that the HCP Terraform and Terraform Enterprise API\n// supports.\ntype TaskStages interface {\n\t// Read a task stage by ID\n\tRead(ctx context.Context, taskStageID string, options *TaskStageReadOptions) (*TaskStage, error)\n\n\t// List all task stages for a given run\n\tList(ctx context.Context, runID string, options *TaskStageListOptions) (*TaskStageList, error)\n\n\t// **Note: This function is still in BETA and subject to change.**\n\t// Override a task stage for a given run\n\tOverride(ctx context.Context, taskStageID string, options TaskStageOverrideOptions) (*TaskStage, error)\n}\n\n// taskStages implements TaskStages\ntype taskStages struct {\n\tclient *Client\n}\n\n// Stage is an enum that represents the possible run stages for run tasks\ntype Stage string\n\nconst (\n\tPrePlan   Stage = \"pre_plan\"\n\tPostPlan  Stage = \"post_plan\"\n\tPreApply  Stage = \"pre_apply\"\n\tPostApply Stage = \"post_apply\"\n)\n\n// TaskStageStatus is an enum that represents all possible statuses for a task stage\ntype TaskStageStatus string\n\nconst (\n\tTaskStagePending          TaskStageStatus = \"pending\"\n\tTaskStageRunning          TaskStageStatus = \"running\"\n\tTaskStagePassed           TaskStageStatus = \"passed\"\n\tTaskStageFailed           TaskStageStatus = \"failed\"\n\tTaskStageAwaitingOverride TaskStageStatus = \"awaiting_override\"\n\tTaskStageCanceled         TaskStageStatus = \"canceled\"\n\tTaskStageErrored          TaskStageStatus = \"errored\"\n\tTaskStageUnreachable      TaskStageStatus = \"unreachable\"\n)\n\n// Permissions represents the permission types for overridding a task stage\ntype Permissions struct {\n\tCanOverridePolicy *bool `jsonapi:\"attr,can-override-policy\"`\n\tCanOverrideTasks  *bool `jsonapi:\"attr,can-override-tasks\"`\n\tCanOverride       *bool `jsonapi:\"attr,can-override\"`\n}\n\n// Actions represents a task stage actions\ntype Actions struct {\n\tIsOverridable *bool `jsonapi:\"attr,is-overridable\"`\n}\n\n// TaskStage represents a HCP Terraform or Terraform Enterprise run's stage where run tasks can occur\ntype TaskStage struct {\n\tID               string                    `jsonapi:\"primary,task-stages\"`\n\tStage            Stage                     `jsonapi:\"attr,stage\"`\n\tStatus           TaskStageStatus           `jsonapi:\"attr,status\"`\n\tStatusTimestamps TaskStageStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\tCreatedAt        time.Time                 `jsonapi:\"attr,created-at,iso8601\"`\n\tUpdatedAt        time.Time                 `jsonapi:\"attr,updated-at,iso8601\"`\n\tPermissions      *Permissions              `jsonapi:\"attr,permissions\"`\n\tActions          *Actions                  `jsonapi:\"attr,actions\"`\n\n\tRun               *Run                `jsonapi:\"relation,run\"`\n\tTaskResults       []*TaskResult       `jsonapi:\"relation,task-results\"`\n\tPolicyEvaluations []*PolicyEvaluation `jsonapi:\"relation,policy-evaluations\"`\n}\n\n// TaskStageOverrideOptions represents the options for overriding a TaskStage.\ntype TaskStageOverrideOptions struct {\n\t// An optional explanation for why the stage was overridden\n\tComment *string `json:\"comment,omitempty\"`\n}\n\n// TaskStageList represents a list of task stages\ntype TaskStageList struct {\n\t*Pagination\n\tItems []*TaskStage\n}\n\n// TaskStageStatusTimestamps represents the set of timestamps recorded for a task stage\ntype TaskStageStatusTimestamps struct {\n\tErroredAt  time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tRunningAt  time.Time `jsonapi:\"attr,running-at,rfc3339\"`\n\tCanceledAt time.Time `jsonapi:\"attr,canceled-at,rfc3339\"`\n\tFailedAt   time.Time `jsonapi:\"attr,failed-at,rfc3339\"`\n\tPassedAt   time.Time `jsonapi:\"attr,passed-at,rfc3339\"`\n}\n\n// TaskStageIncludeOpt represents the available options for include query params.\ntype TaskStageIncludeOpt string\n\nconst TaskStageTaskResults TaskStageIncludeOpt = \"task_results\"\n\n// **Note: This field is still in BETA and subject to change.**\nconst PolicyEvaluationsTaskResults TaskStageIncludeOpt = \"policy_evaluations\"\n\n// TaskStageReadOptions represents the set of options when reading a task stage\ntype TaskStageReadOptions struct {\n\t// Optional: A list of relations to include.\n\tInclude []TaskStageIncludeOpt `url:\"include,omitempty\"`\n}\n\n// TaskStageListOptions represents the options for listing task stages for a run\ntype TaskStageListOptions struct {\n\tListOptions\n}\n\n// Read a task stage by ID\nfunc (s *taskStages) Read(ctx context.Context, taskStageID string, options *TaskStageReadOptions) (*TaskStage, error) {\n\tif !validStringID(&taskStageID) {\n\t\treturn nil, ErrInvalidTaskStageID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"task-stages/%s\", taskStageID)\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt := &TaskStage{}\n\terr = req.Do(ctx, t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn t, nil\n}\n\n// List task stages for a run\nfunc (s *taskStages) List(ctx context.Context, runID string, options *TaskStageListOptions) (*TaskStageList, error) {\n\tif !validStringID(&runID) {\n\t\treturn nil, ErrInvalidRunID\n\t}\n\n\tu := fmt.Sprintf(\"runs/%s/task-stages\", runID)\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttlist := &TaskStageList{}\n\n\terr = req.Do(ctx, tlist)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tlist, nil\n}\n\n// **Note: This function is still in BETA and subject to change.**\n// Override a task stages for a run\nfunc (s *taskStages) Override(ctx context.Context, taskStageID string, options TaskStageOverrideOptions) (*TaskStage, error) {\n\tif !validStringID(&taskStageID) {\n\t\treturn nil, ErrInvalidTaskStageID\n\t}\n\n\tu := fmt.Sprintf(\"task-stages/%s/actions/override\", taskStageID)\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt := &TaskStage{}\n\terr = req.Do(ctx, t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn t, nil\n}\n\nfunc (o *TaskStageReadOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "task_stages_integration_beta_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTaskStagesRead_Beta_RunDependent(t *testing.T) {\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\toptions := PolicyCreateOptions{\n\t\tDescription: String(\"A sample policy\"),\n\t\tKind:        OPA,\n\t\tQuery:       String(\"data.example.rule\"),\n\t\tEnforce: []*EnforcementOptions{\n\t\t\t{\n\t\t\t\tPath: String(\".rego\"),\n\t\t\t\tMode: EnforcementMode(EnforcementAdvisory),\n\t\t\t},\n\t\t},\n\t}\n\tpolicyTest, policyTestCleanup := createUploadedPolicyWithOptions(t, client, true, orgTest, options)\n\tdefer policyTestCleanup()\n\n\tpolicySet := []*Policy{policyTest}\n\t_, psTestCleanup1 := createPolicySet(t, client, orgTest, policySet, []*Workspace{wkspaceTest}, nil, nil, OPA)\n\tdefer psTestCleanup1()\n\n\twrTaskTest, wrTaskTestCleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest)\n\tdefer wrTaskTestCleanup()\n\n\trTest, rTestCleanup := createRun(t, client, wkspaceTest)\n\tdefer rTestCleanup()\n\n\tr, err := client.Runs.ReadWithOptions(ctx, rTest.ID, &RunReadOptions{\n\t\tInclude: []RunIncludeOpt{RunTaskStages},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, r.TaskStages)\n\trequire.NotNil(t, r.TaskStages[0])\n\n\tt.Run(\"without read options\", func(t *testing.T) {\n\t\ttaskStage, err := client.TaskStages.Read(ctx, r.TaskStages[0].ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, taskStage.ID)\n\t\tassert.NotEmpty(t, taskStage.Stage)\n\t\tassert.NotNil(t, taskStage.StatusTimestamps.ErroredAt)\n\t\tassert.NotNil(t, taskStage.StatusTimestamps.RunningAt)\n\t\tassert.NotNil(t, taskStage.CreatedAt)\n\t\tassert.NotNil(t, taskStage.UpdatedAt)\n\t\tassert.NotNil(t, taskStage.Run)\n\t\tassert.NotNil(t, taskStage.TaskResults)\n\n\t\t// so this bit is interesting, if the relation is not specified in the include\n\t\t// param, the fields of the struct will be zeroed out, minus the ID\n\t\tassert.NotEmpty(t, taskStage.TaskResults[0].ID)\n\t\tassert.Empty(t, taskStage.TaskResults[0].Status)\n\t\tassert.Empty(t, taskStage.TaskResults[0].Message)\n\n\t\tassert.NotEmpty(t, taskStage.PolicyEvaluations[0].ID)\n\t})\n\n\tt.Run(\"with include param task_results\", func(t *testing.T) {\n\t\ttaskStage, err := client.TaskStages.Read(ctx, r.TaskStages[0].ID, &TaskStageReadOptions{\n\t\t\tInclude: []TaskStageIncludeOpt{TaskStageTaskResults, PolicyEvaluationsTaskResults},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, taskStage.TaskResults)\n\t\trequire.NotNil(t, taskStage.TaskResults[0])\n\t\trequire.NotEmpty(t, taskStage.PolicyEvaluations)\n\t\trequire.NotNil(t, taskStage.PolicyEvaluations[0])\n\n\t\tt.Run(\"task results are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, taskStage.TaskResults[0].ID)\n\t\t\tassert.NotEmpty(t, taskStage.TaskResults[0].Status)\n\t\t\tassert.NotEmpty(t, taskStage.TaskResults[0].CreatedAt)\n\t\t\tassert.Equal(t, wrTaskTest.ID, taskStage.TaskResults[0].WorkspaceTaskID)\n\t\t\tassert.Equal(t, runTaskTest.Name, taskStage.TaskResults[0].TaskName)\n\t\t})\n\n\t\tt.Run(\"policy evaluations are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, taskStage.PolicyEvaluations[0].ID)\n\t\t\tassert.NotEmpty(t, taskStage.PolicyEvaluations[0].Status)\n\t\t\tassert.NotEmpty(t, taskStage.PolicyEvaluations[0].CreatedAt)\n\t\t\tassert.Equal(t, OPA, taskStage.PolicyEvaluations[0].PolicyKind)\n\t\t\tassert.NotEmpty(t, taskStage.PolicyEvaluations[0].UpdatedAt)\n\t\t\tassert.NotNil(t, taskStage.PolicyEvaluations[0].ResultCount)\n\t\t})\n\t})\n}\n\nfunc TestTaskStagesList_Beta_RunDependent(t *testing.T) {\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\trunTaskTest2, runTaskTest2Cleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTest2Cleanup()\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\toptions := PolicyCreateOptions{\n\t\tDescription: String(\"A sample policy\"),\n\t\tKind:        OPA,\n\t\tQuery:       String(\"data.example.rule\"),\n\t\tEnforce: []*EnforcementOptions{\n\t\t\t{\n\t\t\t\tPath: String(\".rego\"),\n\t\t\t\tMode: EnforcementMode(EnforcementAdvisory),\n\t\t\t},\n\t\t},\n\t}\n\tpolicyTest, policyTestCleanup := createUploadedPolicyWithOptions(t, client, true, orgTest, options)\n\tdefer policyTestCleanup()\n\n\tpolicyTest2, policyTestCleanup2 := createUploadedPolicyWithOptions(t, client, true, orgTest, options)\n\tdefer policyTestCleanup2()\n\n\tpolicySet := []*Policy{policyTest, policyTest2}\n\t_, psTestCleanup1 := createPolicySet(t, client, orgTest, policySet, []*Workspace{wkspaceTest}, nil, nil, OPA)\n\tdefer psTestCleanup1()\n\n\tpolicySet2 := []*Policy{policyTest2}\n\t_, psTestCleanup2 := createPolicySet(t, client, orgTest, policySet2, []*Workspace{wkspaceTest}, nil, nil, OPA)\n\tdefer psTestCleanup2()\n\n\t_, wrTaskTestCleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest)\n\tdefer wrTaskTestCleanup()\n\n\t_, wrTaskTest2Cleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest2)\n\tdefer wrTaskTest2Cleanup()\n\n\trTest, rTestCleanup := createRun(t, client, wkspaceTest)\n\tdefer rTestCleanup()\n\n\tt.Run(\"with no params\", func(t *testing.T) {\n\t\ttaskStageList, err := client.TaskStages.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, taskStageList.Items)\n\t\tassert.NotEmpty(t, taskStageList.Items[0].ID)\n\t\tassert.Equal(t, 2, len(taskStageList.Items[0].TaskResults))\n\t\tassert.Equal(t, 1, len(taskStageList.Items[0].PolicyEvaluations))\n\t})\n}\n\nfunc TestTaskStageOverride_Beta_RunDependent(t *testing.T) {\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tt.Run(\"when the policy failed\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\toptions := PolicyCreateOptions{\n\t\t\tDescription: String(\"A sample policy\"),\n\t\t\tKind:        OPA,\n\t\t\tQuery:       String(\"data.example.rule\"),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tMode: EnforcementMode(EnforcementMandatory),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tpTest, pTestCleanup := createUploadedPolicyWithOptions(t, client, false, orgTest, options)\n\t\tdefer pTestCleanup()\n\n\t\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\t\tdefer wTestCleanup()\n\t\topts := PolicySetCreateOptions{\n\t\t\tKind:        OPA,\n\t\t\tOverridable: Bool(true),\n\t\t}\n\t\tcreatePolicySetWithOptions(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, nil, nil, opts)\n\t\trTest, tTestCleanup := createRunWaitForStatus(t, client, wTest, RunPostPlanAwaitingDecision)\n\t\tdefer tTestCleanup()\n\n\t\ttaskStageList, err := client.TaskStages.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, taskStageList.Items)\n\t\tassert.NotEmpty(t, taskStageList.Items[0].ID)\n\t\tassert.Equal(t, TaskStageAwaitingOverride, taskStageList.Items[0].Status)\n\t\tassert.Equal(t, 1, len(taskStageList.Items[0].PolicyEvaluations))\n\n\t\t_, err = client.TaskStages.Override(ctx, taskStageList.Items[0].ID, TaskStageOverrideOptions{})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the policy failed with options\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\toptions := PolicyCreateOptions{\n\t\t\tDescription: String(\"A sample policy\"),\n\t\t\tKind:        OPA,\n\t\t\tQuery:       String(\"data.example.rule\"),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tMode: EnforcementMode(EnforcementMandatory),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tpTest, pTestCleanup := createUploadedPolicyWithOptions(t, client, false, orgTest, options)\n\t\tdefer pTestCleanup()\n\n\t\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\t\tdefer wTestCleanup()\n\t\topts := PolicySetCreateOptions{\n\t\t\tKind:        OPA,\n\t\t\tOverridable: Bool(true),\n\t\t}\n\t\tcreatePolicySetWithOptions(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, nil, nil, opts)\n\t\trTest, tTestCleanup := createRunWaitForStatus(t, client, wTest, RunPostPlanAwaitingDecision)\n\t\tdefer tTestCleanup()\n\n\t\ttaskStageList, err := client.TaskStages.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, taskStageList.Items)\n\t\tassert.NotEmpty(t, taskStageList.Items[0].ID)\n\t\tassert.Equal(t, TaskStageAwaitingOverride, taskStageList.Items[0].Status)\n\t\tassert.Equal(t, 1, len(taskStageList.Items[0].PolicyEvaluations))\n\n\t\ttaskStageOverrideOptions := TaskStageOverrideOptions{\n\t\t\tComment: String(\"test comment\"),\n\t\t}\n\t\t_, err = client.TaskStages.Override(ctx, taskStageList.Items[0].ID, taskStageOverrideOptions)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when the policy passed\", func(t *testing.T) {\n\t\torgTest, orgTestCleanup := createOrganization(t, client)\n\t\tdefer orgTestCleanup()\n\n\t\toptions := PolicyCreateOptions{\n\t\t\tDescription: String(\"A sample policy\"),\n\t\t\tKind:        OPA,\n\t\t\tQuery:       String(\"data.example.rule\"),\n\t\t\tEnforce: []*EnforcementOptions{\n\t\t\t\t{\n\t\t\t\t\tMode: EnforcementMode(EnforcementMandatory),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tpTest, pTestCleanup := createUploadedPolicyWithOptions(t, client, true, orgTest, options)\n\t\tdefer pTestCleanup()\n\t\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\t\tdefer wTestCleanup()\n\t\topts := PolicySetCreateOptions{\n\t\t\tKind:        OPA,\n\t\t\tOverridable: Bool(true),\n\t\t}\n\t\tcreatePolicySetWithOptions(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest}, nil, nil, opts)\n\t\trTest, tTestCleanup := createPlannedRun(t, client, wTest)\n\t\tdefer tTestCleanup()\n\n\t\ttaskStageList, err := client.TaskStages.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, taskStageList.Items)\n\t\tassert.NotEmpty(t, taskStageList.Items[0].ID)\n\t\tassert.Equal(t, TaskStagePassed, taskStageList.Items[0].Status)\n\t\tassert.Equal(t, 1, len(taskStageList.Items[0].PolicyEvaluations))\n\n\t\t_, err = client.TaskStages.Override(ctx, taskStageList.Items[0].ID, TaskStageOverrideOptions{})\n\t\tassert.Errorf(t, err, \"transition not allowed\")\n\t})\n}\n"
  },
  {
    "path": "task_stages_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTaskStagesRead_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\twrTaskTest, wrTaskTestCleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest)\n\tdefer wrTaskTestCleanup()\n\n\trTest, rTestCleanup := createRun(t, client, wkspaceTest)\n\tdefer rTestCleanup()\n\n\tr, err := client.Runs.ReadWithOptions(ctx, rTest.ID, &RunReadOptions{\n\t\tInclude: []RunIncludeOpt{RunTaskStages},\n\t})\n\trequire.NoError(t, err)\n\trequire.NotEmpty(t, r.TaskStages)\n\trequire.NotNil(t, r.TaskStages[0])\n\n\tt.Run(\"without read options\", func(t *testing.T) {\n\t\ttaskStage, err := client.TaskStages.Read(ctx, r.TaskStages[0].ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, taskStage.ID)\n\t\tassert.NotEmpty(t, taskStage.Stage)\n\t\tassert.NotNil(t, taskStage.StatusTimestamps.ErroredAt)\n\t\tassert.NotNil(t, taskStage.StatusTimestamps.RunningAt)\n\t\tassert.NotNil(t, taskStage.CreatedAt)\n\t\tassert.NotNil(t, taskStage.UpdatedAt)\n\t\tassert.NotNil(t, taskStage.Run)\n\t\tassert.NotNil(t, taskStage.TaskResults)\n\n\t\t// so this bit is interesting, if the relation is not specified in the include\n\t\t// param, the fields of the struct will be zeroed out, minus the ID\n\t\tassert.NotEmpty(t, taskStage.TaskResults[0].ID)\n\t\tassert.Empty(t, taskStage.TaskResults[0].Status)\n\t\tassert.Empty(t, taskStage.TaskResults[0].Message)\n\t})\n\n\tt.Run(\"with include param task_results\", func(t *testing.T) {\n\t\ttaskStage, err := client.TaskStages.Read(ctx, r.TaskStages[0].ID, &TaskStageReadOptions{\n\t\t\tInclude: []TaskStageIncludeOpt{TaskStageTaskResults},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, taskStage.TaskResults)\n\t\trequire.NotNil(t, taskStage.TaskResults[0])\n\n\t\tt.Run(\"task results are properly decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, taskStage.TaskResults[0].ID)\n\t\t\tassert.NotEmpty(t, taskStage.TaskResults[0].Status)\n\t\t\tassert.NotEmpty(t, taskStage.TaskResults[0].CreatedAt)\n\t\t\tassert.Equal(t, wrTaskTest.ID, taskStage.TaskResults[0].WorkspaceTaskID)\n\t\t\tassert.Equal(t, runTaskTest.Name, taskStage.TaskResults[0].TaskName)\n\t\t})\n\t})\n}\n\nfunc TestTaskStagesList_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\trunTaskTest2, runTaskTest2Cleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTest2Cleanup()\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\t_, wrTaskTestCleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest)\n\tdefer wrTaskTestCleanup()\n\n\t_, wrTaskTest2Cleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest2)\n\tdefer wrTaskTest2Cleanup()\n\n\trTest, rTestCleanup := createRun(t, client, wkspaceTest)\n\tdefer rTestCleanup()\n\n\tt.Run(\"with no params\", func(t *testing.T) {\n\t\ttaskStageList, err := client.TaskStages.List(ctx, rTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotEmpty(t, taskStageList.Items)\n\t\tassert.NotEmpty(t, taskStageList.Items[0].ID)\n\t\tassert.Equal(t, 2, len(taskStageList.Items[0].TaskResults))\n\t})\n}\n"
  },
  {
    "path": "team.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Teams = (*teams)(nil)\n\n// Teams describes all the team related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/teams\ntype Teams interface {\n\t// List all the teams of the given organization.\n\tList(ctx context.Context, organization string, options *TeamListOptions) (*TeamList, error)\n\n\t// Create a new team with the given options.\n\tCreate(ctx context.Context, organization string, options TeamCreateOptions) (*Team, error)\n\n\t// Read a team by its ID.\n\tRead(ctx context.Context, teamID string) (*Team, error)\n\n\t// Update a team by its ID.\n\tUpdate(ctx context.Context, teamID string, options TeamUpdateOptions) (*Team, error)\n\n\t// Delete a team by its ID.\n\tDelete(ctx context.Context, teamID string) error\n}\n\n// teams implements Teams.\ntype teams struct {\n\tclient *Client\n}\n\n// TeamList represents a list of teams.\ntype TeamList struct {\n\t*Pagination\n\tItems []*Team\n}\n\n// Team represents a Terraform Enterprise team.\ntype Team struct {\n\tID                 string              `jsonapi:\"primary,teams\"`\n\tIsUnified          bool                `jsonapi:\"attr,is-unified\"`\n\tName               string              `jsonapi:\"attr,name\"`\n\tOrganizationAccess *OrganizationAccess `jsonapi:\"attr,organization-access\"`\n\tVisibility         string              `jsonapi:\"attr,visibility\"`\n\tPermissions        *TeamPermissions    `jsonapi:\"attr,permissions\"`\n\tUserCount          int                 `jsonapi:\"attr,users-count\"`\n\tSSOTeamID          string              `jsonapi:\"attr,sso-team-id\"`\n\t// AllowMemberTokenManagement is false for TFE versions older than v202408\n\tAllowMemberTokenManagement bool `jsonapi:\"attr,allow-member-token-management\"`\n\n\t// Relations\n\tUsers                   []*User                   `jsonapi:\"relation,users\"`\n\tOrganizationMemberships []*OrganizationMembership `jsonapi:\"relation,organization-memberships\"`\n}\n\n// OrganizationAccess represents the team's permissions on its organization\ntype OrganizationAccess struct {\n\tManagePolicies        bool `jsonapi:\"attr,manage-policies\"`\n\tManagePolicyOverrides bool `jsonapi:\"attr,manage-policy-overrides\"`\n\t// **Note: This API is still in BETA and subject to change.**\n\tDelegatePolicyOverrides  bool `jsonapi:\"attr,delegate-policy-overrides\"`\n\tManageWorkspaces         bool `jsonapi:\"attr,manage-workspaces\"`\n\tManageVCSSettings        bool `jsonapi:\"attr,manage-vcs-settings\"`\n\tManageProviders          bool `jsonapi:\"attr,manage-providers\"`\n\tManageModules            bool `jsonapi:\"attr,manage-modules\"`\n\tManageRunTasks           bool `jsonapi:\"attr,manage-run-tasks\"`\n\tManageProjects           bool `jsonapi:\"attr,manage-projects\"`\n\tReadWorkspaces           bool `jsonapi:\"attr,read-workspaces\"`\n\tReadProjects             bool `jsonapi:\"attr,read-projects\"`\n\tManageMembership         bool `jsonapi:\"attr,manage-membership\"`\n\tManageTeams              bool `jsonapi:\"attr,manage-teams\"`\n\tManageOrganizationAccess bool `jsonapi:\"attr,manage-organization-access\"`\n\tAccessSecretTeams        bool `jsonapi:\"attr,access-secret-teams\"`\n\tManageAgentPools         bool `jsonapi:\"attr,manage-agent-pools\"`\n}\n\n// TeamPermissions represents the current user's permissions on the team.\ntype TeamPermissions struct {\n\tCanDestroy          bool `jsonapi:\"attr,can-destroy\"`\n\tCanUpdateMembership bool `jsonapi:\"attr,can-update-membership\"`\n}\n\n// TeamIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/teams#available-related-resources\ntype TeamIncludeOpt string\n\nconst (\n\tTeamUsers                   TeamIncludeOpt = \"users\"\n\tTeamOrganizationMemberships TeamIncludeOpt = \"organization-memberships\"\n)\n\n// TeamListOptions represents the options for listing teams.\ntype TeamListOptions struct {\n\tListOptions\n\t// Optional: A list of relations to include.\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/teams#available-related-resources\n\tInclude []TeamIncludeOpt `url:\"include,omitempty\"`\n\n\t// Optional: A list of team names to filter by.\n\tNames []string `url:\"filter[names],omitempty\"`\n\n\t// Optional: A query string to search teams by names.\n\tQuery string `url:\"q,omitempty\"`\n}\n\n// TeamCreateOptions represents the options for creating a team.\ntype TeamCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,teams\"`\n\n\t// Name of the team.\n\tName *string `jsonapi:\"attr,name\"`\n\n\t// Optional: Unique Identifier to control team membership via SAML\n\tSSOTeamID *string `jsonapi:\"attr,sso-team-id,omitempty\"`\n\n\t// The team's organization access\n\tOrganizationAccess *OrganizationAccessOptions `jsonapi:\"attr,organization-access,omitempty\"`\n\n\t// The team's visibility (\"secret\", \"organization\")\n\tVisibility *string `jsonapi:\"attr,visibility,omitempty\"`\n\n\t// Optional: Used by Owners and users with \"Manage Teams\" permissions to control whether team members can manage team tokens\n\tAllowMemberTokenManagement *bool `jsonapi:\"attr,allow-member-token-management,omitempty\"`\n}\n\n// TeamUpdateOptions represents the options for updating a team.\ntype TeamUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,teams\"`\n\n\t// Optional: New name for the team\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: Unique Identifier to control team membership via SAML\n\tSSOTeamID *string `jsonapi:\"attr,sso-team-id,omitempty\"`\n\n\t// Optional: The team's organization access\n\tOrganizationAccess *OrganizationAccessOptions `jsonapi:\"attr,organization-access,omitempty\"`\n\n\t// Optional: The team's visibility (\"secret\", \"organization\")\n\tVisibility *string `jsonapi:\"attr,visibility,omitempty\"`\n\n\t// Optional: Used by Owners and users with \"Manage Teams\" permissions to control whether team members can manage team tokens\n\tAllowMemberTokenManagement *bool `jsonapi:\"attr,allow-member-token-management,omitempty\"`\n}\n\n// OrganizationAccessOptions represents the organization access options of a team.\ntype OrganizationAccessOptions struct {\n\tManagePolicies        *bool `json:\"manage-policies,omitempty\"`\n\tManagePolicyOverrides *bool `json:\"manage-policy-overrides,omitempty\"`\n\t// **Note: This API is still in BETA and subject to change.**\n\tDelegatePolicyOverrides  *bool `json:\"delegate-policy-overrides,omitempty\"`\n\tManageWorkspaces         *bool `json:\"manage-workspaces,omitempty\"`\n\tManageVCSSettings        *bool `json:\"manage-vcs-settings,omitempty\"`\n\tManageProviders          *bool `json:\"manage-providers,omitempty\"`\n\tManageModules            *bool `json:\"manage-modules,omitempty\"`\n\tManageRunTasks           *bool `json:\"manage-run-tasks,omitempty\"`\n\tManageProjects           *bool `json:\"manage-projects,omitempty\"`\n\tReadWorkspaces           *bool `json:\"read-workspaces,omitempty\"`\n\tReadProjects             *bool `json:\"read-projects,omitempty\"`\n\tManageMembership         *bool `json:\"manage-membership,omitempty\"`\n\tManageTeams              *bool `json:\"manage-teams,omitempty\"`\n\tManageOrganizationAccess *bool `json:\"manage-organization-access,omitempty\"`\n\tAccessSecretTeams        *bool `json:\"access-secret-teams,omitempty\"`\n\tManageAgentPools         *bool `json:\"manage-agent-pools,omitempty\"`\n}\n\n// List all the teams of the given organization.\nfunc (s *teams) List(ctx context.Context, organization string, options *TeamListOptions) (*TeamList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\tu := fmt.Sprintf(\"organizations/%s/teams\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttl := &TeamList{}\n\terr = req.Do(ctx, tl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tl, nil\n}\n\n// Create a new team with the given options.\nfunc (s *teams) Create(ctx context.Context, organization string, options TeamCreateOptions) (*Team, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/teams\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt := &Team{}\n\terr = req.Do(ctx, t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn t, nil\n}\n\n// Read a single team by its ID.\nfunc (s *teams) Read(ctx context.Context, teamID string) (*Team, error) {\n\tif !validStringID(&teamID) {\n\t\treturn nil, ErrInvalidTeamID\n\t}\n\n\tu := fmt.Sprintf(\"teams/%s\", url.PathEscape(teamID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt := &Team{}\n\terr = req.Do(ctx, t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn t, nil\n}\n\n// Update a team by its ID.\nfunc (s *teams) Update(ctx context.Context, teamID string, options TeamUpdateOptions) (*Team, error) {\n\tif !validStringID(&teamID) {\n\t\treturn nil, ErrInvalidTeamID\n\t}\n\n\tu := fmt.Sprintf(\"teams/%s\", url.PathEscape(teamID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt := &Team{}\n\terr = req.Do(ctx, t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn t, nil\n}\n\n// Delete a team by its ID.\nfunc (s *teams) Delete(ctx context.Context, teamID string) error {\n\tif !validStringID(&teamID) {\n\t\treturn ErrInvalidTeamID\n\t}\n\n\tu := fmt.Sprintf(\"teams/%s\", url.PathEscape(teamID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o TeamCreateOptions) valid() error {\n\tif !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\treturn nil\n}\n\nfunc (o *TeamListOptions) valid() error {\n\tif o == nil {\n\t\treturn nil // nothing to validate\n\t}\n\n\tif err := validateTeamNames(o.Names); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc validateTeamNames(names []string) error {\n\tfor _, name := range names {\n\t\tif name == \"\" {\n\t\t\treturn ErrEmptyTeamName\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "team_access.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ TeamAccesses = (*teamAccesses)(nil)\n\n// TeamAccesses describes all the team access related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/team-access\ntype TeamAccesses interface {\n\t// List all the team accesses for a given workspace.\n\tList(ctx context.Context, options *TeamAccessListOptions) (*TeamAccessList, error)\n\n\t// Add team access for a workspace.\n\tAdd(ctx context.Context, options TeamAccessAddOptions) (*TeamAccess, error)\n\n\t// Read a team access by its ID.\n\tRead(ctx context.Context, teamAccessID string) (*TeamAccess, error)\n\n\t// Update a team access by its ID.\n\tUpdate(ctx context.Context, teamAccessID string, options TeamAccessUpdateOptions) (*TeamAccess, error)\n\n\t// Remove team access from a workspace.\n\tRemove(ctx context.Context, teamAccessID string) error\n}\n\n// teamAccesses implements TeamAccesses.\ntype teamAccesses struct {\n\tclient *Client\n}\n\n// AccessType represents a team access type.\ntype AccessType string\n\nconst (\n\tAccessAdmin  AccessType = \"admin\"\n\tAccessPlan   AccessType = \"plan\"\n\tAccessRead   AccessType = \"read\"\n\tAccessWrite  AccessType = \"write\"\n\tAccessCustom AccessType = \"custom\"\n)\n\n// RunsPermissionType represents the permissiontype to a workspace's runs.\ntype RunsPermissionType string\n\nconst (\n\tRunsPermissionRead  RunsPermissionType = \"read\"\n\tRunsPermissionPlan  RunsPermissionType = \"plan\"\n\tRunsPermissionApply RunsPermissionType = \"apply\"\n)\n\n// VariablesPermissionType represents the permissiontype to a workspace's variables.\ntype VariablesPermissionType string\n\nconst (\n\tVariablesPermissionNone  VariablesPermissionType = \"none\"\n\tVariablesPermissionRead  VariablesPermissionType = \"read\"\n\tVariablesPermissionWrite VariablesPermissionType = \"write\"\n)\n\n// StateVersionsPermissionType represents the permissiontype to a workspace's state versions.\ntype StateVersionsPermissionType string\n\nconst (\n\tStateVersionsPermissionNone        StateVersionsPermissionType = \"none\"\n\tStateVersionsPermissionReadOutputs StateVersionsPermissionType = \"read-outputs\"\n\tStateVersionsPermissionRead        StateVersionsPermissionType = \"read\"\n\tStateVersionsPermissionWrite       StateVersionsPermissionType = \"write\"\n)\n\n// SentinelMocksPermissionType represents the permissiontype to a workspace's Sentinel mocks.\ntype SentinelMocksPermissionType string\n\nconst (\n\tSentinelMocksPermissionNone SentinelMocksPermissionType = \"none\"\n\tSentinelMocksPermissionRead SentinelMocksPermissionType = \"read\"\n)\n\n// TeamAccessList represents a list of team accesses.\ntype TeamAccessList struct {\n\t*Pagination\n\tItems []*TeamAccess\n}\n\n// TeamAccess represents the workspace access for a team.\ntype TeamAccess struct {\n\tID               string                      `jsonapi:\"primary,team-workspaces\"`\n\tAccess           AccessType                  `jsonapi:\"attr,access\"`\n\tRuns             RunsPermissionType          `jsonapi:\"attr,runs\"`\n\tVariables        VariablesPermissionType     `jsonapi:\"attr,variables\"`\n\tStateVersions    StateVersionsPermissionType `jsonapi:\"attr,state-versions\"`\n\tSentinelMocks    SentinelMocksPermissionType `jsonapi:\"attr,sentinel-mocks\"`\n\tWorkspaceLocking bool                        `jsonapi:\"attr,workspace-locking\"`\n\tRunTasks         bool                        `jsonapi:\"attr,run-tasks\"`\n\t// **Note: This API is still in BETA and subject to change.**\n\tPolicyOverrides bool `jsonapi:\"attr,policy-overrides\"`\n\n\t// Relations\n\tTeam      *Team      `jsonapi:\"relation,team\"`\n\tWorkspace *Workspace `jsonapi:\"relation,workspace\"`\n}\n\n// TeamAccessListOptions represents the options for listing team accesses.\ntype TeamAccessListOptions struct {\n\tListOptions\n\tWorkspaceID string `url:\"filter[workspace][id]\"`\n}\n\n// TeamAccessAddOptions represents the options for adding team access.\ntype TeamAccessAddOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,team-workspaces\"`\n\n\t// The type of access to grant.\n\tAccess *AccessType `jsonapi:\"attr,access\"`\n\n\t// Custom workspace access permissions. These can only be edited when Access is 'custom'; otherwise, they are\n\t// read-only and reflect the Access level's implicit permissions.\n\tRuns             *RunsPermissionType          `jsonapi:\"attr,runs,omitempty\"`\n\tVariables        *VariablesPermissionType     `jsonapi:\"attr,variables,omitempty\"`\n\tStateVersions    *StateVersionsPermissionType `jsonapi:\"attr,state-versions,omitempty\"`\n\tSentinelMocks    *SentinelMocksPermissionType `jsonapi:\"attr,sentinel-mocks,omitempty\"`\n\tWorkspaceLocking *bool                        `jsonapi:\"attr,workspace-locking,omitempty\"`\n\tRunTasks         *bool                        `jsonapi:\"attr,run-tasks,omitempty\"`\n\t// **Note: This API is still in BETA and subject to change.**\n\tPolicyOverrides *bool `jsonapi:\"attr,policy-overrides,omitempty\"`\n\n\t// The team to add to the workspace\n\tTeam *Team `jsonapi:\"relation,team\"`\n\n\t// The workspace to which the team is to be added.\n\tWorkspace *Workspace `jsonapi:\"relation,workspace\"`\n}\n\n// TeamAccessUpdateOptions represents the options for updating team access.\ntype TeamAccessUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,team-workspaces\"`\n\n\t// The type of access to grant.\n\tAccess *AccessType `jsonapi:\"attr,access,omitempty\"`\n\n\t// Custom workspace access permissions. These can only be edited when Access is 'custom'; otherwise, they are\n\t// read-only and reflect the Access level's implicit permissions.\n\tRuns             *RunsPermissionType          `jsonapi:\"attr,runs,omitempty\"`\n\tVariables        *VariablesPermissionType     `jsonapi:\"attr,variables,omitempty\"`\n\tStateVersions    *StateVersionsPermissionType `jsonapi:\"attr,state-versions,omitempty\"`\n\tSentinelMocks    *SentinelMocksPermissionType `jsonapi:\"attr,sentinel-mocks,omitempty\"`\n\tWorkspaceLocking *bool                        `jsonapi:\"attr,workspace-locking,omitempty\"`\n\tRunTasks         *bool                        `jsonapi:\"attr,run-tasks,omitempty\"`\n\t// **Note: This API is still in BETA and subject to change.**\n\tPolicyOverrides *bool `jsonapi:\"attr,policy-overrides,omitempty\"`\n}\n\n// List all the team accesses for a given workspace.\nfunc (s *teamAccesses) List(ctx context.Context, options *TeamAccessListOptions) (*TeamAccessList, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", \"team-workspaces\", options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttal := &TeamAccessList{}\n\terr = req.Do(ctx, tal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tal, nil\n}\n\n// Add team access for a workspace.\nfunc (s *teamAccesses) Add(ctx context.Context, options TeamAccessAddOptions) (*TeamAccess, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", \"team-workspaces\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tta := &TeamAccess{}\n\terr = req.Do(ctx, ta)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ta, nil\n}\n\n// Read a team access by its ID.\nfunc (s *teamAccesses) Read(ctx context.Context, teamAccessID string) (*TeamAccess, error) {\n\tif !validStringID(&teamAccessID) {\n\t\treturn nil, ErrInvalidAccessTeamID\n\t}\n\n\tu := fmt.Sprintf(\"team-workspaces/%s\", url.PathEscape(teamAccessID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tta := &TeamAccess{}\n\terr = req.Do(ctx, ta)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ta, nil\n}\n\n// Update team access for a workspace\nfunc (s *teamAccesses) Update(ctx context.Context, teamAccessID string, options TeamAccessUpdateOptions) (*TeamAccess, error) {\n\tif !validStringID(&teamAccessID) {\n\t\treturn nil, ErrInvalidAccessTeamID\n\t}\n\n\tu := fmt.Sprintf(\"team-workspaces/%s\", url.PathEscape(teamAccessID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tta := &TeamAccess{}\n\terr = req.Do(ctx, ta)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ta, err\n}\n\n// Remove team access from a workspace.\nfunc (s *teamAccesses) Remove(ctx context.Context, teamAccessID string) error {\n\tif !validStringID(&teamAccessID) {\n\t\treturn ErrInvalidAccessTeamID\n\t}\n\n\tu := fmt.Sprintf(\"team-workspaces/%s\", url.PathEscape(teamAccessID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o *TeamAccessListOptions) valid() error {\n\tif o == nil {\n\t\treturn ErrRequiredTeamAccessListOps\n\t}\n\tif !validString(&o.WorkspaceID) {\n\t\treturn ErrRequiredWorkspaceID\n\t}\n\tif !validStringID(&o.WorkspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\n\treturn nil\n}\n\nfunc (o TeamAccessAddOptions) valid() error {\n\tif o.Access == nil {\n\t\treturn ErrRequiredAccess\n\t}\n\tif o.Team == nil {\n\t\treturn ErrRequiredTeam\n\t}\n\tif o.Workspace == nil {\n\t\treturn ErrRequiredWorkspace\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "team_access_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTeamAccessesList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\ttmTest1, tmTest1Cleanup := createTeam(t, client, orgTest)\n\tdefer tmTest1Cleanup()\n\ttmTest2, tmTest2Cleanup := createTeam(t, client, orgTest)\n\tdefer tmTest2Cleanup()\n\n\ttaTest1, taTest1Cleanup := createTeamAccess(t, client, tmTest1, wTest, orgTest)\n\tdefer taTest1Cleanup()\n\ttaTest2, taTest2Cleanup := createTeamAccess(t, client, tmTest2, wTest, orgTest)\n\tdefer taTest2Cleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\ttal, err := client.TeamAccess.List(ctx, &TeamAccessListOptions{\n\t\t\tWorkspaceID: wTest.ID,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, tal.Items, taTest1)\n\t\tassert.Contains(t, tal.Items, taTest2)\n\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\tassert.Equal(t, 1, tal.CurrentPage)\n\t\tassert.Equal(t, 2, tal.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\ttal, err := client.TeamAccess.List(ctx, &TeamAccessListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, tal.Items)\n\t\tassert.Equal(t, 999, tal.CurrentPage)\n\t\tassert.Equal(t, 2, tal.TotalCount)\n\t})\n\n\tt.Run(\"without TeamAccessListOptions\", func(t *testing.T) {\n\t\ttal, err := client.TeamAccess.List(ctx, nil)\n\t\tassert.Nil(t, tal)\n\t\tassert.Equal(t, err, ErrRequiredTeamAccessListOps)\n\t})\n\n\tt.Run(\"without WorkspaceID options\", func(t *testing.T) {\n\t\ttal, err := client.TeamAccess.List(ctx, &TeamAccessListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 2,\n\t\t\t\tPageSize:   25,\n\t\t\t},\n\t\t})\n\t\tassert.Nil(t, tal)\n\t\tassert.Equal(t, err, ErrRequiredWorkspaceID)\n\t})\n\n\tt.Run(\"without a valid workspaceID\", func(t *testing.T) {\n\t\ttal, err := client.TeamAccess.List(ctx, &TeamAccessListOptions{\n\t\t\tWorkspaceID: badIdentifier,\n\t\t})\n\t\tassert.Nil(t, tal)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestTeamAccessesAdd(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := TeamAccessAddOptions{\n\t\t\tAccess:    Access(AccessAdmin),\n\t\t\tTeam:      tmTest,\n\t\t\tWorkspace: wTest,\n\t\t}\n\n\t\tta, err := client.TeamAccess.Add(ctx, options)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\terr := client.TeamAccess.Remove(ctx, ta.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error removing team access (%s): %s\", ta.ID, err)\n\t\t\t}\n\t\t}()\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.TeamAccess.Read(ctx, ta.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*TeamAccess{\n\t\t\tta,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Access, item.Access)\n\t\t}\n\t})\n\n\tt.Run(\"with valid custom options\", func(t *testing.T) {\n\t\toptions := TeamAccessAddOptions{\n\t\t\tAccess:        Access(AccessCustom),\n\t\t\tRuns:          RunsPermission(RunsPermissionRead),\n\t\t\tStateVersions: StateVersionsPermission(StateVersionsPermissionNone),\n\t\t\tTeam:          tmTest,\n\t\t\tWorkspace:     wTest,\n\t\t}\n\n\t\tta, err := client.TeamAccess.Add(ctx, options)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\terr := client.TeamAccess.Remove(ctx, ta.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error removing team access (%s): %s\", ta.ID, err)\n\t\t\t}\n\t\t}()\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.TeamAccess.Read(ctx, ta.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*TeamAccess{\n\t\t\tta,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Access, item.Access)\n\t\t}\n\t})\n\n\tt.Run(\"with invalid custom options\", func(t *testing.T) {\n\t\toptions := TeamAccessAddOptions{\n\t\t\tAccess:    Access(AccessRead),\n\t\t\tRuns:      RunsPermission(RunsPermissionApply),\n\t\t\tTeam:      tmTest,\n\t\t\tWorkspace: wTest,\n\t\t}\n\n\t\t_, err := client.TeamAccess.Add(ctx, options)\n\n\t\tassert.EqualError(t, err, \"invalid attribute\\n\\nRuns is read-only when access level is 'read'; use the 'custom' access level to set this attribute.\")\n\t})\n\n\tt.Run(\"when the team already has access\", func(t *testing.T) {\n\t\t_, taTestCleanup := createTeamAccess(t, client, tmTest, wTest, nil)\n\t\tdefer taTestCleanup()\n\n\t\toptions := TeamAccessAddOptions{\n\t\t\tAccess:    Access(AccessAdmin),\n\t\t\tTeam:      tmTest,\n\t\t\tWorkspace: wTest,\n\t\t}\n\n\t\t_, err := client.TeamAccess.Add(ctx, options)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"when options is missing access\", func(t *testing.T) {\n\t\tta, err := client.TeamAccess.Add(ctx, TeamAccessAddOptions{\n\t\t\tTeam:      tmTest,\n\t\t\tWorkspace: wTest,\n\t\t})\n\t\tassert.Nil(t, ta)\n\t\tassert.Equal(t, err, ErrRequiredAccess)\n\t})\n\n\tt.Run(\"when options is missing team\", func(t *testing.T) {\n\t\tta, err := client.TeamAccess.Add(ctx, TeamAccessAddOptions{\n\t\t\tAccess:    Access(AccessAdmin),\n\t\t\tWorkspace: wTest,\n\t\t})\n\t\tassert.Nil(t, ta)\n\t\tassert.Equal(t, err, ErrRequiredTeam)\n\t})\n\n\tt.Run(\"when options is missing workspace\", func(t *testing.T) {\n\t\tta, err := client.TeamAccess.Add(ctx, TeamAccessAddOptions{\n\t\t\tAccess: Access(AccessAdmin),\n\t\t\tTeam:   tmTest,\n\t\t})\n\t\tassert.Nil(t, ta)\n\t\tassert.Equal(t, err, ErrRequiredWorkspace)\n\t})\n}\n\nfunc TestTeamAccessesRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\ttaTest, taTestCleanup := createTeamAccess(t, client, tmTest, wTest, orgTest)\n\tdefer taTestCleanup()\n\n\tt.Run(\"when the team access exists\", func(t *testing.T) {\n\t\tta, err := client.TeamAccess.Read(ctx, taTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, AccessAdmin, ta.Access)\n\n\t\tt.Run(\"permission attributes are decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, RunsPermissionApply, ta.Runs)\n\t\t\tassert.Equal(t, VariablesPermissionWrite, ta.Variables)\n\t\t\tassert.Equal(t, StateVersionsPermissionWrite, ta.StateVersions)\n\t\t\tassert.Equal(t, SentinelMocksPermissionRead, ta.SentinelMocks)\n\t\t\tassert.Equal(t, true, ta.WorkspaceLocking)\n\t\t})\n\n\t\tt.Run(\"team relationship is decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, ta.Team)\n\t\t})\n\n\t\tt.Run(\"workspace relationship is decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, ta.Workspace)\n\t\t})\n\t})\n\n\tt.Run(\"when the team access does not exist\", func(t *testing.T) {\n\t\tta, err := client.TeamAccess.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, ta)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without a valid team access ID\", func(t *testing.T) {\n\t\tta, err := client.TeamAccess.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, ta)\n\t\tassert.Equal(t, err, ErrInvalidAccessTeamID)\n\t})\n}\n\nfunc TestTeamAccessesUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\ttaTest, taTestCleanup := createTeamAccess(t, client, tmTest, wTest, orgTest)\n\tdefer taTestCleanup()\n\n\tt.Run(\"with valid attributes\", func(t *testing.T) {\n\t\toptions := TeamAccessUpdateOptions{\n\t\t\tAccess: Access(AccessCustom),\n\t\t\tRuns:   RunsPermission(RunsPermissionPlan),\n\t\t}\n\n\t\tta, err := client.TeamAccess.Update(ctx, taTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, ta.Access, AccessCustom)\n\t\tassert.Equal(t, ta.Runs, RunsPermissionPlan)\n\t})\n}\n\nfunc TestTeamAccessesRemove(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\ttaTest, _ := createTeamAccess(t, client, tmTest, nil, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.TeamAccess.Remove(ctx, taTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the workspace - it should fail.\n\t\t_, err = client.TeamAccess.Read(ctx, taTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the team access does not exist\", func(t *testing.T) {\n\t\terr := client.TeamAccess.Remove(ctx, taTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the team access ID is invalid\", func(t *testing.T) {\n\t\terr := client.TeamAccess.Remove(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidAccessTeamID)\n\t})\n}\n\nfunc TestTeamAccessesReadRunTasks(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\ttaTest, taTestCleanup := createTeamAccess(t, client, tmTest, wTest, orgTest)\n\tdefer taTestCleanup()\n\n\tt.Run(\"when the team access exists\", func(t *testing.T) {\n\t\tta, err := client.TeamAccess.Read(ctx, taTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, AccessAdmin, ta.Access)\n\n\t\tt.Run(\"permission attributes are decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, true, ta.RunTasks)\n\t\t})\n\t})\n}\n\nfunc TestTeamAccessesUpdateRunTasks(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\ttaTest, taTestCleanup := createTeamAccess(t, client, tmTest, wTest, orgTest)\n\tdefer taTestCleanup()\n\n\tt.Run(\"with valid attributes\", func(t *testing.T) {\n\t\tnewAccess := !taTest.RunTasks\n\t\toptions := TeamAccessUpdateOptions{\n\t\t\tAccess:   Access(AccessCustom),\n\t\t\tRunTasks: &newAccess,\n\t\t}\n\n\t\tta, err := client.TeamAccess.Update(ctx, taTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, AccessCustom, ta.Access)\n\t\tassert.Equal(t, newAccess, ta.RunTasks)\n\t})\n}\n\nfunc TestTeamAccessesAddPolicyOverrides(t *testing.T) {\n\tskipUnlessBeta(t)\n\tt.Parallel()\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tt.Run(\"with valid custom options\", func(t *testing.T) {\n\t\toptions := TeamAccessAddOptions{\n\t\t\tAccess:          Access(AccessCustom),\n\t\t\tPolicyOverrides: Bool(true),\n\t\t\tTeam:            tmTest,\n\t\t\tWorkspace:       wTest,\n\t\t}\n\n\t\tta, err := client.TeamAccess.Add(ctx, options)\n\t\tdefer func() {\n\t\t\terr := client.TeamAccess.Remove(ctx, ta.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error removing team access (%s): %s\", ta.ID, err)\n\t\t\t}\n\t\t}()\n\n\t\trequire.NoError(t, err)\n\n\t\trefreshed, err := client.TeamAccess.Read(ctx, ta.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*TeamAccess{\n\t\t\tta,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Access, item.Access)\n\t\t\tassert.Equal(t, true, item.PolicyOverrides)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "team_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"testing\"\n\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTeamsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest1, tmTest1Cleanup := createTeam(t, client, orgTest)\n\tdefer tmTest1Cleanup()\n\ttmTest2, tmTest2Cleanup := createTeam(t, client, orgTest)\n\tdefer tmTest2Cleanup()\n\ttmTest3, tmTest3Cleanup := createTeam(t, client, orgTest)\n\tdefer tmTest3Cleanup()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\ttl, err := client.Teams.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, tl.Items, tmTest1)\n\t\tassert.Contains(t, tl.Items, tmTest2)\n\t\tassert.Contains(t, tl.Items, tmTest3)\n\n\t\tassert.Equal(t, 1, tl.CurrentPage)\n\t\tassert.Equal(t, 4, tl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\ttl, err := client.Teams.List(ctx, orgTest.Name, &TeamListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, tl.Items)\n\t\tassert.Equal(t, 999, tl.CurrentPage)\n\t\tassert.Equal(t, 4, tl.TotalCount)\n\n\t\ttl, err = client.Teams.List(ctx, orgTest.Name, &TeamListOptions{\n\t\t\tNames: []string{tmTest2.Name, tmTest3.Name},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, len(tl.Items), 2)\n\t\tassert.Contains(t, tl.Items, tmTest2)\n\t\tassert.Contains(t, tl.Items, tmTest3)\n\n\t\ttl, err = client.Teams.List(ctx, orgTest.Name, &TeamListOptions{\n\t\t\tQuery: tmTest1.Name[:len(tmTest1.Name)-2],\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, len(tl.Items), 1)\n\t\tassert.Equal(t, 1, tl.TotalCount)\n\t\tassert.Contains(t, tl.Items, tmTest1)\n\n\t\tt.Run(\"with invalid names query param\", func(t *testing.T) {\n\t\t\t// should return an error because we've included an empty string\n\t\t\ttl, err = client.Teams.List(ctx, orgTest.Name, &TeamListOptions{\n\t\t\t\tNames: []string{tmTest2.Name, \"\"},\n\t\t\t})\n\t\t\tassert.Equal(t, err, ErrEmptyTeamName)\n\t\t})\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\ttl, err := client.Teams.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, tl)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestTeamsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := TeamCreateOptions{\n\t\t\tName: String(\"foo\"),\n\t\t}\n\n\t\ttm, err := client.Teams.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Teams.Read(ctx, tm.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Team{\n\t\t\ttm,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t}\n\t})\n\n\tt.Run(\"with beta delegate-policy-overrides\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\toptions := TeamCreateOptions{\n\t\t\tName: String(\"delegate-policy-overrides\"),\n\t\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\t\tDelegatePolicyOverrides: Bool(true),\n\t\t\t},\n\t\t}\n\n\t\tteam, err := client.Teams.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\terr := client.Teams.Delete(ctx, team.ID)\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\n\t\trefreshed, err := client.Teams.Read(ctx, team.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Team{\n\t\t\tteam,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, *options.OrganizationAccess.DelegatePolicyOverrides, item.OrganizationAccess.DelegatePolicyOverrides)\n\t\t}\n\t})\n\n\tt.Run(\"with sso-team-id\", func(t *testing.T) {\n\t\toptions := TeamCreateOptions{\n\t\t\tName:      String(\"rockettes\"),\n\t\t\tSSOTeamID: String(\"7dddb675-73e0-4858-a8ad-0e597064301b\"),\n\t\t}\n\t\tteam, err := client.Teams.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Name, team.Name)\n\t\tassert.NotNil(t, team.SSOTeamID)\n\t\tassert.Equal(t, *options.SSOTeamID, team.SSOTeamID)\n\t})\n\n\tt.Run(\"when options is missing name\", func(t *testing.T) {\n\t\ttm, err := client.Teams.Create(ctx, \"foo\", TeamCreateOptions{})\n\t\tassert.Nil(t, tm)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"when options has an invalid organization\", func(t *testing.T) {\n\t\ttm, err := client.Teams.Create(ctx, badIdentifier, TeamCreateOptions{\n\t\t\tName: String(\"foo\"),\n\t\t})\n\t\tassert.Nil(t, tm)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestTeamsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\topts := TeamCreateOptions{\n\t\tName:      String(randomString(t)),\n\t\tSSOTeamID: String(randomString(t)),\n\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\tManagePolicies: Bool(true),\n\t\t},\n\t\tAllowMemberTokenManagement: Bool(true),\n\t}\n\tssoTeam, err := client.Teams.Create(ctx, orgTest.Name, opts)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\terr := client.Teams.Delete(ctx, ssoTeam.ID)\n\t\trequire.NoError(t, err)\n\t}()\n\n\tt.Run(\"when the team exists\", func(t *testing.T) {\n\t\ttm, err := client.Teams.Read(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, tmTest.ID, tm.ID)\n\t\tassert.Equal(t, tmTest.Name, tm.Name)\n\n\t\tt.Run(\"visibility is returned\", func(t *testing.T) {\n\t\t\tassert.Equal(t, \"secret\", tm.Visibility)\n\t\t})\n\n\t\tt.Run(\"permissions are properly decoded\", func(t *testing.T) {\n\t\t\tassert.True(t, tm.Permissions.CanDestroy)\n\t\t})\n\n\t\tt.Run(\"organization access is properly decoded\", func(t *testing.T) {\n\t\t\tassert.Equal(t, tm.OrganizationAccess.ManagePolicies, *opts.OrganizationAccess.ManagePolicies)\n\t\t})\n\n\t\tt.Run(\"SSO team id is returned\", func(t *testing.T) {\n\t\t\tassert.NotNil(t, ssoTeam.SSOTeamID)\n\t\t\tassert.Equal(t, *opts.SSOTeamID, ssoTeam.SSOTeamID)\n\t\t})\n\n\t\tt.Run(\"allow member token management is returned\", func(t *testing.T) {\n\t\t\tassert.Equal(t, *opts.AllowMemberTokenManagement, tm.AllowMemberTokenManagement)\n\t\t})\n\t})\n\n\tt.Run(\"when the team does not exist\", func(t *testing.T) {\n\t\ttm, err := client.Teams.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, tm)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without a valid team ID\", func(t *testing.T) {\n\t\ttm, err := client.Teams.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, tm)\n\t\tassert.Equal(t, err, ErrInvalidTeamID)\n\t})\n}\n\nfunc TestTeamsUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := TeamUpdateOptions{\n\t\t\tName: String(\"foo bar\"),\n\t\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\t\tManagePolicies:        Bool(false),\n\t\t\t\tManageVCSSettings:     Bool(true),\n\t\t\t\tManagePolicyOverrides: Bool(true),\n\t\t\t\tManageProviders:       Bool(true),\n\t\t\t\tManageModules:         Bool(false),\n\t\t\t},\n\t\t\tVisibility:                 String(\"organization\"),\n\t\t\tAllowMemberTokenManagement: Bool(true),\n\t\t}\n\n\t\ttm, err := client.Teams.Update(ctx, tmTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\trefreshed, err := client.Teams.Read(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Team{\n\t\t\ttm,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t,\n\t\t\t\t*options.Visibility,\n\t\t\t\titem.Visibility,\n\t\t\t)\n\t\t\tassert.Equal(t,\n\t\t\t\t*options.AllowMemberTokenManagement,\n\t\t\t\titem.AllowMemberTokenManagement,\n\t\t\t)\n\t\t\tassert.Equal(t,\n\t\t\t\t*options.OrganizationAccess.ManagePolicies,\n\t\t\t\titem.OrganizationAccess.ManagePolicies,\n\t\t\t)\n\t\t\tassert.Equal(t,\n\t\t\t\t*options.OrganizationAccess.ManageVCSSettings,\n\t\t\t\titem.OrganizationAccess.ManageVCSSettings,\n\t\t\t)\n\t\t\tassert.Equal(t,\n\t\t\t\t*options.OrganizationAccess.ManagePolicyOverrides,\n\t\t\t\titem.OrganizationAccess.ManagePolicyOverrides,\n\t\t\t)\n\t\t\tassert.Equal(t,\n\t\t\t\t*options.OrganizationAccess.ManageProviders,\n\t\t\t\titem.OrganizationAccess.ManageProviders,\n\t\t\t)\n\t\t\tassert.Equal(t,\n\t\t\t\t*options.OrganizationAccess.ManageModules,\n\t\t\t\titem.OrganizationAccess.ManageModules,\n\t\t\t)\n\t\t}\n\t})\n\n\tt.Run(\"with beta delegate policy overrides\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\tteam, err := client.Teams.Create(ctx, orgTest.Name, TeamCreateOptions{\n\t\t\tName: String(randomString(t)),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\terr := client.Teams.Delete(ctx, team.ID)\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\n\t\tupdated, err := client.Teams.Update(ctx, team.ID, TeamUpdateOptions{\n\t\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\t\tDelegatePolicyOverrides: Bool(true),\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trefreshed, err := client.Teams.Read(ctx, team.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.True(t, updated.OrganizationAccess.DelegatePolicyOverrides)\n\t\tassert.True(t, refreshed.OrganizationAccess.DelegatePolicyOverrides)\n\t})\n\n\tt.Run(\"when the team does not exist\", func(t *testing.T) {\n\t\ttm, err := client.Teams.Update(ctx, \"nonexisting\", TeamUpdateOptions{\n\t\t\tName: String(\"foo bar\"),\n\t\t})\n\t\tassert.Nil(t, tm)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without a valid team ID\", func(t *testing.T) {\n\t\ttm, err := client.Teams.Update(ctx, badIdentifier, TeamUpdateOptions{})\n\t\tassert.Nil(t, tm)\n\t\tassert.Equal(t, err, ErrInvalidTeamID)\n\t})\n}\n\nfunc TestTeamsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, _ := createTeam(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.Teams.Delete(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the workspace - it should fail.\n\t\t_, err = client.Teams.Read(ctx, tmTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without valid team ID\", func(t *testing.T) {\n\t\terr := client.Teams.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidTeamID)\n\t})\n}\n\nfunc TestTeam_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"teams\",\n\t\t\t\"id\":   \"1\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"name\": \"team hashi\",\n\t\t\t\t\"organization-access\": map[string]interface{}{\n\t\t\t\t\t\"manage-policies\":     true,\n\t\t\t\t\t\"manage-workspaces\":   true,\n\t\t\t\t\t\"manage-vcs-settings\": true,\n\t\t\t\t\t\"manage-projects\":     true,\n\t\t\t\t\t\"read-workspaces\":     true,\n\t\t\t\t\t\"read-projects\":       true,\n\t\t\t\t},\n\t\t\t\t\"permissions\": map[string]interface{}{\n\t\t\t\t\t\"can-destroy\":           true,\n\t\t\t\t\t\"can-update-membership\": true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\tteam := &Team{}\n\terr = unmarshalResponse(responseBody, team)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, team.ID, \"1\")\n\tassert.Equal(t, team.Name, \"team hashi\")\n\tassert.Empty(t, team.SSOTeamID)\n\tassert.Equal(t, team.OrganizationAccess.ManageWorkspaces, true)\n\tassert.Equal(t, team.OrganizationAccess.ManageVCSSettings, true)\n\tassert.Equal(t, team.OrganizationAccess.ManagePolicies, true)\n\tassert.Equal(t, team.OrganizationAccess.ManageProjects, true)\n\tassert.Equal(t, team.OrganizationAccess.ReadWorkspaces, true)\n\tassert.Equal(t, team.OrganizationAccess.ReadProjects, true)\n\tassert.Equal(t, team.Permissions.CanDestroy, true)\n\tassert.Equal(t, team.Permissions.CanUpdateMembership, true)\n}\n\nfunc TestTeamCreateOptions_Marshal(t *testing.T) {\n\tt.Parallel()\n\topts := TeamCreateOptions{\n\t\tName:                       String(\"team name\"),\n\t\tVisibility:                 String(\"organization\"),\n\t\tAllowMemberTokenManagement: Bool(true),\n\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\tManagePolicies: Bool(true),\n\t\t},\n\t}\n\n\treqBody, err := serializeRequestBody(&opts)\n\trequire.NoError(t, err)\n\treq, err := retryablehttp.NewRequest(\"POST\", \"url\", reqBody)\n\trequire.NoError(t, err)\n\tbodyBytes, err := req.BodyBytes()\n\trequire.NoError(t, err)\n\n\texpectedBody := `{\"data\":{\"type\":\"teams\",\"attributes\":{\"allow-member-token-management\":true,\"name\":\"team name\",\"organization-access\":{\"manage-policies\":true},\"visibility\":\"organization\"}}}\n`\n\tassert.Equal(t, expectedBody, string(bodyBytes))\n}\n\nfunc TestTeamsUpdateRunTasks(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tskipIfEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := TeamUpdateOptions{\n\t\t\tName: String(\"foo bar\"),\n\t\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\t\tManageRunTasks: Bool(true),\n\t\t\t},\n\t\t\tVisibility: String(\"organization\"),\n\t\t}\n\n\t\ttm, err := client.Teams.Update(ctx, tmTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\trefreshed, err := client.Teams.Read(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Team{\n\t\t\ttm,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t,\n\t\t\t\t*options.OrganizationAccess.ManageRunTasks,\n\t\t\t\titem.OrganizationAccess.ManageRunTasks,\n\t\t\t)\n\t\t}\n\t})\n}\n\nfunc TestTeamsUpdateManageProjects(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := TeamUpdateOptions{\n\t\t\tName: String(\"foo bar\"),\n\t\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\t\t// **Note: ManageProjects requires ManageWorkspaces field to be set and subject to change later.**\n\t\t\t\tManageWorkspaces: Bool(true),\n\t\t\t\tManageProjects:   Bool(true),\n\t\t\t},\n\t\t\tVisibility: String(\"organization\"),\n\t\t}\n\n\t\ttm, err := client.Teams.Update(ctx, tmTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\trefreshed, err := client.Teams.Read(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Team{\n\t\t\ttm,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t,\n\t\t\t\t*options.OrganizationAccess.ManageProjects,\n\t\t\t\titem.OrganizationAccess.ManageProjects,\n\t\t\t)\n\t\t}\n\t})\n}\n\nfunc TestTeamsUpdateManageManageMembership(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tteamRead, err := client.Teams.Read(ctx, tmTest.ID)\n\trequire.NoError(t, err)\n\tassert.False(t, teamRead.OrganizationAccess.ManageMembership, \"manage membership is false by default\")\n\n\toriginalTeamAccess := teamRead.OrganizationAccess\n\n\toptions := TeamUpdateOptions{\n\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\tManageMembership: Bool(true),\n\t\t},\n\t}\n\n\ttm, err := client.Teams.Update(ctx, tmTest.ID, options)\n\trequire.NoError(t, err)\n\tassert.True(t, tm.OrganizationAccess.ManageMembership)\n\n\trefreshed, err := client.Teams.Read(ctx, tmTest.ID)\n\trequire.NoError(t, err)\n\tassert.True(t, refreshed.OrganizationAccess.ManageMembership)\n\n\t// Check that other org access fields are not updated\n\toriginalTeamAccess.ManageMembership = true\n\tassert.Equal(t, originalTeamAccess, refreshed.OrganizationAccess)\n}\n\nfunc TestTeamsUpdateManageTeams(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tteamRead, err := client.Teams.Read(ctx, tmTest.ID)\n\trequire.NoError(t, err)\n\tassert.False(t, teamRead.OrganizationAccess.ManageTeams, \"manage teams is false by default\")\n\n\toriginalTeamAccess := teamRead.OrganizationAccess\n\n\toptions := TeamUpdateOptions{\n\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\t// **Note: ManageTeams requires ManageMembership.**\n\t\t\tManageMembership: Bool(true),\n\t\t\tManageTeams:      Bool(true),\n\t\t},\n\t}\n\n\ttm, err := client.Teams.Update(ctx, tmTest.ID, options)\n\trequire.NoError(t, err)\n\tassert.True(t, tm.OrganizationAccess.ManageMembership)\n\tassert.True(t, tm.OrganizationAccess.ManageTeams)\n\n\trefreshed, err := client.Teams.Read(ctx, tmTest.ID)\n\trequire.NoError(t, err)\n\tassert.True(t, refreshed.OrganizationAccess.ManageMembership)\n\tassert.True(t, refreshed.OrganizationAccess.ManageTeams)\n\n\t// Check that other org access fields are not updated\n\toriginalTeamAccess.ManageMembership = true\n\toriginalTeamAccess.ManageTeams = true\n\tassert.Equal(t, originalTeamAccess, refreshed.OrganizationAccess)\n}\n\nfunc TestTeamsUpdateManageOrganizationAccess(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tteamRead, err := client.Teams.Read(ctx, tmTest.ID)\n\trequire.NoError(t, err)\n\tassert.False(t, teamRead.OrganizationAccess.ManageOrganizationAccess, \"manage organization access is false by default\")\n\n\toriginalTeamAccess := teamRead.OrganizationAccess\n\n\toptions := TeamUpdateOptions{\n\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\t// **Note: ManageOrganizationAccess requires ManageMembership and ManageTeams.**\n\t\t\tManageMembership:         Bool(true),\n\t\t\tManageTeams:              Bool(true),\n\t\t\tManageOrganizationAccess: Bool(true),\n\t\t},\n\t}\n\n\ttm, err := client.Teams.Update(ctx, tmTest.ID, options)\n\trequire.NoError(t, err)\n\tassert.True(t, tm.OrganizationAccess.ManageMembership)\n\tassert.True(t, tm.OrganizationAccess.ManageTeams)\n\tassert.True(t, tm.OrganizationAccess.ManageOrganizationAccess)\n\n\trefreshed, err := client.Teams.Read(ctx, tmTest.ID)\n\trequire.NoError(t, err)\n\tassert.True(t, refreshed.OrganizationAccess.ManageMembership)\n\tassert.True(t, refreshed.OrganizationAccess.ManageTeams)\n\tassert.True(t, refreshed.OrganizationAccess.ManageOrganizationAccess)\n\n\t// Check that other org access fields are not updated\n\toriginalTeamAccess.ManageMembership = true\n\toriginalTeamAccess.ManageTeams = true\n\toriginalTeamAccess.ManageOrganizationAccess = true\n\tassert.Equal(t, originalTeamAccess, refreshed.OrganizationAccess)\n}\n\nfunc TestTeamsUpdateAccessSecretTeams(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tteamRead, err := client.Teams.Read(ctx, tmTest.ID)\n\trequire.NoError(t, err)\n\tassert.False(t, teamRead.OrganizationAccess.AccessSecretTeams, \"access secret teams is false by default\")\n\n\toriginalTeamAccess := teamRead.OrganizationAccess\n\n\toptions := TeamUpdateOptions{\n\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\t// **Note: AccessSecretTeams requires at least one granular permission to be set\n\t\t\t// for it to be set, and ManageTeams requires ManageMembership.**\n\t\t\tManageMembership:  Bool(true),\n\t\t\tManageTeams:       Bool(true),\n\t\t\tAccessSecretTeams: Bool(true),\n\t\t},\n\t}\n\n\ttm, err := client.Teams.Update(ctx, tmTest.ID, options)\n\trequire.NoError(t, err)\n\tassert.True(t, tm.OrganizationAccess.ManageMembership)\n\tassert.True(t, tm.OrganizationAccess.ManageTeams)\n\tassert.True(t, tm.OrganizationAccess.AccessSecretTeams)\n\n\trefreshed, err := client.Teams.Read(ctx, tmTest.ID)\n\trequire.NoError(t, err)\n\tassert.True(t, refreshed.OrganizationAccess.ManageMembership)\n\tassert.True(t, refreshed.OrganizationAccess.ManageTeams)\n\tassert.True(t, refreshed.OrganizationAccess.AccessSecretTeams)\n\n\t// Check that other org access fields are not updated\n\toriginalTeamAccess.ManageMembership = true\n\toriginalTeamAccess.ManageTeams = true\n\toriginalTeamAccess.AccessSecretTeams = true\n\tassert.Equal(t, originalTeamAccess, refreshed.OrganizationAccess)\n}\n\nfunc TestTeamsUpdateManageAgentPools(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tteamRead, err := client.Teams.Read(ctx, tmTest.ID)\n\trequire.NoError(t, err)\n\tassert.False(t, teamRead.OrganizationAccess.ManageAgentPools, \"manage agent pools is false by default\")\n\n\toriginalTeamAccess := teamRead.OrganizationAccess\n\n\toptions := TeamUpdateOptions{\n\t\tOrganizationAccess: &OrganizationAccessOptions{\n\t\t\tManageAgentPools: Bool(true),\n\t\t},\n\t}\n\n\ttm, err := client.Teams.Update(ctx, tmTest.ID, options)\n\trequire.NoError(t, err)\n\tassert.True(t, tm.OrganizationAccess.ManageAgentPools)\n\n\trefreshed, err := client.Teams.Read(ctx, tmTest.ID)\n\trequire.NoError(t, err)\n\tassert.True(t, refreshed.OrganizationAccess.ManageAgentPools)\n\n\t// Check that other org access fields are not updated\n\toriginalTeamAccess.ManageAgentPools = true\n\tassert.Equal(t, originalTeamAccess, refreshed.OrganizationAccess)\n}\n"
  },
  {
    "path": "team_member.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ TeamMembers = (*teamMembers)(nil)\n\n// TeamMembers describes all the team member related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/team-members\ntype TeamMembers interface {\n\t// List returns all Users of a team calling ListUsers\n\t// See ListOrganizationMemberships for fetching memberships\n\tList(ctx context.Context, teamID string) ([]*User, error)\n\n\t// ListUsers returns the Users of this team.\n\tListUsers(ctx context.Context, teamID string) ([]*User, error)\n\n\t// ListOrganizationMemberships returns the OrganizationMemberships of this team.\n\tListOrganizationMemberships(ctx context.Context, teamID string) ([]*OrganizationMembership, error)\n\n\t// Add multiple users to a team.\n\tAdd(ctx context.Context, teamID string, options TeamMemberAddOptions) error\n\n\t// Remove multiple users from a team.\n\tRemove(ctx context.Context, teamID string, options TeamMemberRemoveOptions) error\n}\n\n// teamMembers implements TeamMembers.\ntype teamMembers struct {\n\tclient *Client\n}\n\ntype teamMemberUser struct {\n\tUsername string `jsonapi:\"primary,users\"`\n}\n\ntype teamMemberOrgMembership struct {\n\tID string `jsonapi:\"primary,organization-memberships\"`\n}\n\n// TeamMemberAddOptions represents the options for\n// adding or removing team members.\ntype TeamMemberAddOptions struct {\n\tUsernames                 []string\n\tOrganizationMembershipIDs []string\n}\n\n// TeamMemberRemoveOptions represents the options for\n// adding or removing team members.\ntype TeamMemberRemoveOptions struct {\n\tUsernames                 []string\n\tOrganizationMembershipIDs []string\n}\n\n// List returns all Users of a team calling ListUsers\n// See ListOrganizationMemberships for fetching memberships\nfunc (s *teamMembers) List(ctx context.Context, teamID string) ([]*User, error) {\n\treturn s.ListUsers(ctx, teamID)\n}\n\n// ListUsers returns the Users of this team.\nfunc (s *teamMembers) ListUsers(ctx context.Context, teamID string) ([]*User, error) {\n\tif !validStringID(&teamID) {\n\t\treturn nil, ErrInvalidTeamID\n\t}\n\n\toptions := struct {\n\t\tInclude []TeamIncludeOpt `url:\"include,omitempty\"`\n\t}{\n\t\tInclude: []TeamIncludeOpt{TeamUsers},\n\t}\n\n\tu := fmt.Sprintf(\"teams/%s\", url.PathEscape(teamID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt := &Team{}\n\terr = req.Do(ctx, t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn t.Users, nil\n}\n\n// ListOrganizationMemberships returns the OrganizationMemberships of this team.\nfunc (s *teamMembers) ListOrganizationMemberships(ctx context.Context, teamID string) ([]*OrganizationMembership, error) {\n\tif !validStringID(&teamID) {\n\t\treturn nil, ErrInvalidTeamID\n\t}\n\n\toptions := struct {\n\t\tInclude []TeamIncludeOpt `url:\"include,omitempty\"`\n\t}{\n\t\tInclude: []TeamIncludeOpt{TeamOrganizationMemberships},\n\t}\n\n\tu := fmt.Sprintf(\"teams/%s\", url.PathEscape(teamID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt := &Team{}\n\terr = req.Do(ctx, t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn t.OrganizationMemberships, nil\n}\n\n// Add multiple users to a team.\nfunc (s *teamMembers) Add(ctx context.Context, teamID string, options TeamMemberAddOptions) error {\n\tif !validStringID(&teamID) {\n\t\treturn ErrInvalidTeamID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tusersOrMemberships := options.kind()\n\tu := fmt.Sprintf(\"teams/%s/relationships/%s\", url.PathEscape(teamID), usersOrMemberships)\n\n\tvar req *ClientRequest\n\n\tif usersOrMemberships == \"users\" {\n\t\tvar err error\n\t\tvar members []*teamMemberUser\n\t\tfor _, name := range options.Usernames {\n\t\t\tmembers = append(members, &teamMemberUser{Username: name})\n\t\t}\n\t\treq, err = s.client.NewRequest(\"POST\", u, members)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tvar err error\n\t\tvar members []*teamMemberOrgMembership\n\t\tfor _, ID := range options.OrganizationMembershipIDs {\n\t\t\tmembers = append(members, &teamMemberOrgMembership{ID: ID})\n\t\t}\n\t\treq, err = s.client.NewRequest(\"POST\", u, members)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Remove multiple users from a team.\nfunc (s *teamMembers) Remove(ctx context.Context, teamID string, options TeamMemberRemoveOptions) error {\n\tif !validStringID(&teamID) {\n\t\treturn ErrInvalidTeamID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tusersOrMemberships := options.kind()\n\tu := fmt.Sprintf(\"teams/%s/relationships/%s\", url.PathEscape(teamID), usersOrMemberships)\n\n\tvar req *ClientRequest\n\n\tif usersOrMemberships == \"users\" {\n\t\tvar err error\n\t\tvar members []*teamMemberUser\n\t\tfor _, name := range options.Usernames {\n\t\t\tmembers = append(members, &teamMemberUser{Username: name})\n\t\t}\n\t\treq, err = s.client.NewRequest(\"DELETE\", u, members)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tvar err error\n\t\tvar members []*teamMemberOrgMembership\n\t\tfor _, ID := range options.OrganizationMembershipIDs {\n\t\t\tmembers = append(members, &teamMemberOrgMembership{ID: ID})\n\t\t}\n\t\treq, err = s.client.NewRequest(\"DELETE\", u, members)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// kind returns \"users\" or \"organization-memberships\"\n// depending on which is defined\nfunc (o *TeamMemberAddOptions) kind() string {\n\tif len(o.Usernames) != 0 {\n\t\treturn \"users\"\n\t}\n\treturn \"organization-memberships\"\n}\n\n// kind returns \"users\" or \"organization-memberships\"\n// depending on which is defined\nfunc (o *TeamMemberRemoveOptions) kind() string {\n\tif len(o.Usernames) != 0 {\n\t\treturn \"users\"\n\t}\n\treturn \"organization-memberships\"\n}\n\nfunc (o *TeamMemberAddOptions) valid() error {\n\tif o.Usernames == nil && o.OrganizationMembershipIDs == nil {\n\t\treturn ErrRequiredUsernameOrMembershipIds\n\t}\n\tif o.Usernames != nil && o.OrganizationMembershipIDs != nil {\n\t\treturn ErrRequiredOnlyOneField\n\t}\n\tif o.Usernames != nil && len(o.Usernames) == 0 {\n\t\treturn ErrInvalidUsernames\n\t}\n\tif o.OrganizationMembershipIDs != nil && len(o.OrganizationMembershipIDs) == 0 {\n\t\treturn ErrInvalidMembershipIDs\n\t}\n\treturn nil\n}\n\nfunc (o *TeamMemberRemoveOptions) valid() error {\n\tif o.Usernames == nil && o.OrganizationMembershipIDs == nil {\n\t\treturn ErrRequiredUsernameOrMembershipIds\n\t}\n\tif o.Usernames != nil && o.OrganizationMembershipIDs != nil {\n\t\treturn ErrRequiredOnlyOneField\n\t}\n\tif o.Usernames != nil && len(o.Usernames) == 0 {\n\t\treturn ErrInvalidUsernames\n\t}\n\tif o.OrganizationMembershipIDs != nil && len(o.OrganizationMembershipIDs) == 0 {\n\t\treturn ErrInvalidMembershipIDs\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "team_member_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTeamMembersList(t *testing.T) {\n\tt.Parallel()\n\t// The TeamMembers.List() endpoint is available for everyone,\n\t// but this test uses extra functionality that is only available\n\t// to paid accounts. Organizations under a free account can\n\t// create team tokens, but they only have access to one team: the\n\t// owners team. This test creates new teams, and that feature is\n\t// unavaiable to paid accounts.\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tdefer tmTestCleanup()\n\n\ttestAcct := fetchTestAccountDetails(t, client)\n\n\toptions := TeamMemberAddOptions{\n\t\tUsernames: []string{testAcct.Username},\n\t}\n\terr := client.TeamMembers.Add(ctx, tmTest.ID, options)\n\trequire.NoError(t, err)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tusers, err := client.TeamMembers.List(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, len(users))\n\n\t\tfound := false\n\t\tfor _, user := range users {\n\t\t\tif user.Username == testAcct.Username {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tassert.True(t, found)\n\t})\n\n\tt.Run(\"when the team ID is invalid\", func(t *testing.T) {\n\t\tusers, err := client.TeamMembers.List(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidTeamID)\n\t\tassert.Nil(t, users)\n\t})\n}\n\nfunc TestTeamMembersAddWithInvalidOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tdefer tmTestCleanup()\n\n\tt.Run(\"when options is missing usernames and organization membership ids\", func(t *testing.T) {\n\t\terr := client.TeamMembers.Add(ctx, tmTest.ID, TeamMemberAddOptions{})\n\t\tassert.Equal(t, err, ErrRequiredUsernameOrMembershipIds)\n\t})\n\n\tt.Run(\"when options has both usernames and organization membership ids\", func(t *testing.T) {\n\t\terr := client.TeamMembers.Add(ctx, tmTest.ID, TeamMemberAddOptions{\n\t\t\tUsernames:                 []string{},\n\t\t\tOrganizationMembershipIDs: []string{},\n\t\t})\n\t\tassert.Equal(t, err, ErrRequiredOnlyOneField)\n\t})\n\n\tt.Run(\"when usernames is empty\", func(t *testing.T) {\n\t\terr := client.TeamMembers.Add(ctx, tmTest.ID, TeamMemberAddOptions{\n\t\t\tUsernames: []string{},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidUsernames)\n\t})\n\n\tt.Run(\"when organization membership ids is empty\", func(t *testing.T) {\n\t\terr := client.TeamMembers.Add(ctx, tmTest.ID, TeamMemberAddOptions{\n\t\t\tOrganizationMembershipIDs: []string{},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidMembershipIDs)\n\t})\n\n\tt.Run(\"when the team ID is invalid\", func(t *testing.T) {\n\t\terr := client.TeamMembers.Add(ctx, badIdentifier, TeamMemberAddOptions{\n\t\t\tUsernames: []string{\"user1\"},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidTeamID)\n\t})\n}\n\nfunc TestTeamMembersAddByUsername(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tdefer tmTestCleanup()\n\n\ttestAcct := fetchTestAccountDetails(t, client)\n\n\tt.Run(\"with valid username option\", func(t *testing.T) {\n\t\toptions := TeamMemberAddOptions{\n\t\t\tUsernames: []string{testAcct.Username},\n\t\t}\n\n\t\terr := client.TeamMembers.Add(ctx, tmTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tusers, err := client.TeamMembers.List(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfound := false\n\t\tfor _, user := range users {\n\t\t\tif user.Username == testAcct.Username {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tassert.True(t, found)\n\t})\n}\n\nfunc TestTeamMembersAddByOrganizationMembers(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tmemTest, memTestCleanup := createOrganizationMembership(t, client, orgTest)\n\tdefer memTestCleanup()\n\n\tt.Run(\"with valid membership IDs option\", func(t *testing.T) {\n\t\toptions := TeamMemberAddOptions{\n\t\t\tOrganizationMembershipIDs: []string{memTest.ID},\n\t\t}\n\n\t\terr := client.TeamMembers.Add(ctx, tmTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\torgMemberships, err := client.TeamMembers.ListOrganizationMemberships(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfound := false\n\t\tfor _, orgMembership := range orgMemberships {\n\t\t\tif orgMembership.ID == memTest.ID {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tassert.True(t, found)\n\t})\n}\n\nfunc TestTeamMembersRemoveWithInvalidOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tdefer tmTestCleanup()\n\n\tt.Run(\"when options is missing usernames and organization membership ids\", func(t *testing.T) {\n\t\terr := client.TeamMembers.Remove(ctx, tmTest.ID, TeamMemberRemoveOptions{})\n\t\tassert.Equal(t, err, ErrRequiredUsernameOrMembershipIds)\n\t})\n\n\tt.Run(\"when options has both usernames and organization membership ids\", func(t *testing.T) {\n\t\terr := client.TeamMembers.Remove(ctx, tmTest.ID, TeamMemberRemoveOptions{\n\t\t\tUsernames:                 []string{},\n\t\t\tOrganizationMembershipIDs: []string{},\n\t\t})\n\t\tassert.Equal(t, err, ErrRequiredOnlyOneField)\n\t})\n\n\tt.Run(\"when usernames is empty\", func(t *testing.T) {\n\t\terr := client.TeamMembers.Remove(ctx, tmTest.ID, TeamMemberRemoveOptions{\n\t\t\tUsernames: []string{},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidUsernames)\n\t})\n\n\tt.Run(\"when organization membership ids is empty\", func(t *testing.T) {\n\t\terr := client.TeamMembers.Remove(ctx, tmTest.ID, TeamMemberRemoveOptions{\n\t\t\tOrganizationMembershipIDs: []string{},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidMembershipIDs)\n\t})\n\n\tt.Run(\"when the team ID is invalid\", func(t *testing.T) {\n\t\terr := client.TeamMembers.Remove(ctx, badIdentifier, TeamMemberRemoveOptions{\n\t\t\tUsernames: []string{\"user1\"},\n\t\t})\n\t\tassert.Equal(t, err, ErrInvalidTeamID)\n\t})\n}\n\nfunc TestTeamMembersRemoveByUsernames(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tdefer tmTestCleanup()\n\n\ttestAcct := fetchTestAccountDetails(t, client)\n\n\toptions := TeamMemberAddOptions{\n\t\tUsernames: []string{testAcct.Username},\n\t}\n\terr := client.TeamMembers.Add(ctx, tmTest.ID, options)\n\trequire.NoError(t, err)\n\n\tt.Run(\"with valid usernames\", func(t *testing.T) {\n\t\toptions := TeamMemberRemoveOptions{\n\t\t\tUsernames: []string{testAcct.Username},\n\t\t}\n\n\t\terr := client.TeamMembers.Remove(ctx, tmTest.ID, options)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestTeamMembersRemoveByOrganizationMemberships(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tmemTest, memTestCleanup := createOrganizationMembership(t, client, orgTest)\n\tdefer memTestCleanup()\n\n\toptions := TeamMemberAddOptions{\n\t\tOrganizationMembershipIDs: []string{memTest.ID},\n\t}\n\terr := client.TeamMembers.Add(ctx, tmTest.ID, options)\n\trequire.NoError(t, err)\n\n\tt.Run(\"with valid org membership ids\", func(t *testing.T) {\n\t\toptions := TeamMemberRemoveOptions{\n\t\t\tOrganizationMembershipIDs: []string{memTest.ID},\n\t\t}\n\n\t\terr := client.TeamMembers.Remove(ctx, tmTest.ID, options)\n\t\trequire.NoError(t, err)\n\t})\n}\n"
  },
  {
    "path": "team_project_access.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ TeamProjectAccesses = (*teamProjectAccesses)(nil)\n\n// TeamProjectAccesses describes all the team project access related methods that the Terraform\n// Enterprise API supports\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/project-team-access\ntype TeamProjectAccesses interface {\n\t// List all project accesses for a given project.\n\tList(ctx context.Context, options TeamProjectAccessListOptions) (*TeamProjectAccessList, error)\n\n\t// Add team access for a project.\n\tAdd(ctx context.Context, options TeamProjectAccessAddOptions) (*TeamProjectAccess, error)\n\n\t// Read team access by project ID.\n\tRead(ctx context.Context, teamProjectAccessID string) (*TeamProjectAccess, error)\n\n\t// Update team access on a project.\n\tUpdate(ctx context.Context, teamProjectAccessID string, options TeamProjectAccessUpdateOptions) (*TeamProjectAccess, error)\n\n\t// Remove team access from a project.\n\tRemove(ctx context.Context, teamProjectAccessID string) error\n}\n\n// teamProjectAccesses implements TeamProjectAccesses\ntype teamProjectAccesses struct {\n\tclient *Client\n}\n\n// TeamProjectAccessType represents a team project access type.\ntype TeamProjectAccessType string\n\nconst (\n\tTeamProjectAccessAdmin    TeamProjectAccessType = \"admin\"\n\tTeamProjectAccessMaintain TeamProjectAccessType = \"maintain\"\n\tTeamProjectAccessWrite    TeamProjectAccessType = \"write\"\n\tTeamProjectAccessRead     TeamProjectAccessType = \"read\"\n\tTeamProjectAccessCustom   TeamProjectAccessType = \"custom\"\n)\n\n// TeamProjectAccessList represents a list of team project accesses\ntype TeamProjectAccessList struct {\n\t*Pagination\n\tItems []*TeamProjectAccess\n}\n\n// TeamProjectAccess represents a project access for a team\ntype TeamProjectAccess struct {\n\tID              string                                 `jsonapi:\"primary,team-projects\"`\n\tAccess          TeamProjectAccessType                  `jsonapi:\"attr,access\"`\n\tProjectAccess   *TeamProjectAccessProjectPermissions   `jsonapi:\"attr,project-access\"`\n\tWorkspaceAccess *TeamProjectAccessWorkspacePermissions `jsonapi:\"attr,workspace-access\"`\n\n\t// Relations\n\tTeam    *Team    `jsonapi:\"relation,team\"`\n\tProject *Project `jsonapi:\"relation,project\"`\n}\n\n// ProjectPermissions represents the team's permissions on its project\ntype TeamProjectAccessProjectPermissions struct {\n\tProjectSettingsPermission ProjectSettingsPermissionType `jsonapi:\"attr,settings\"`\n\tProjectTeamsPermission    ProjectTeamsPermissionType    `jsonapi:\"attr,teams\"`\n\t// ProjectVariableSetsPermission represents read, manage, and no access custom permission for project-level variable sets\n\tProjectVariableSetsPermission ProjectVariableSetsPermissionType `jsonapi:\"attr,variable-sets\"`\n}\n\n// WorkspacePermissions represents the team's permission on all workspaces in its project\ntype TeamProjectAccessWorkspacePermissions struct {\n\tWorkspaceRunsPermission          WorkspaceRunsPermissionType          `jsonapi:\"attr,runs\"`\n\tWorkspaceSentinelMocksPermission WorkspaceSentinelMocksPermissionType `jsonapi:\"attr,sentinel-mocks\"`\n\tWorkspaceStateVersionsPermission WorkspaceStateVersionsPermissionType `jsonapi:\"attr,state-versions\"`\n\tWorkspaceVariablesPermission     WorkspaceVariablesPermissionType     `jsonapi:\"attr,variables\"`\n\tWorkspaceCreatePermission        bool                                 `jsonapi:\"attr,create\"`\n\tWorkspaceLockingPermission       bool                                 `jsonapi:\"attr,locking\"`\n\tWorkspaceMovePermission          bool                                 `jsonapi:\"attr,move\"`\n\tWorkspaceDeletePermission        bool                                 `jsonapi:\"attr,delete\"`\n\tWorkspaceRunTasksPermission      bool                                 `jsonapi:\"attr,run-tasks\"`\n\t// **Note: This API is still in BETA and subject to change.**\n\tWorkspacePolicyOverridesPermission bool `jsonapi:\"attr,policy-overrides\"`\n}\n\n// ProjectSettingsPermissionType represents the permissiontype to a project's settings\ntype ProjectSettingsPermissionType string\n\nconst (\n\tProjectSettingsPermissionRead   ProjectSettingsPermissionType = \"read\"\n\tProjectSettingsPermissionUpdate ProjectSettingsPermissionType = \"update\"\n\tProjectSettingsPermissionDelete ProjectSettingsPermissionType = \"delete\"\n)\n\n// ProjectTeamsPermissionType represents the permissiontype to a project's teams\ntype ProjectTeamsPermissionType string\n\nconst (\n\tProjectTeamsPermissionNone   ProjectTeamsPermissionType = \"none\"\n\tProjectTeamsPermissionRead   ProjectTeamsPermissionType = \"read\"\n\tProjectTeamsPermissionManage ProjectTeamsPermissionType = \"manage\"\n)\n\n// ProjectVariableSetsPermissionType represents the permission type to a project's variable sets\ntype ProjectVariableSetsPermissionType string\n\nconst (\n\tProjectVariableSetsPermissionNone  ProjectVariableSetsPermissionType = \"none\"\n\tProjectVariableSetsPermissionRead  ProjectVariableSetsPermissionType = \"read\"\n\tProjectVariableSetsPermissionWrite ProjectVariableSetsPermissionType = \"write\"\n)\n\n// WorkspaceRunsPermissionType represents the permissiontype to project workspaces' runs\ntype WorkspaceRunsPermissionType string\n\nconst (\n\tWorkspaceRunsPermissionRead  WorkspaceRunsPermissionType = \"read\"\n\tWorkspaceRunsPermissionPlan  WorkspaceRunsPermissionType = \"plan\"\n\tWorkspaceRunsPermissionApply WorkspaceRunsPermissionType = \"apply\"\n)\n\n// WorkspaceSentinelMocksPermissionType represents the permissiontype to project workspaces' sentinel-mocks\ntype WorkspaceSentinelMocksPermissionType string\n\nconst (\n\tWorkspaceSentinelMocksPermissionNone WorkspaceSentinelMocksPermissionType = \"none\"\n\tWorkspaceSentinelMocksPermissionRead WorkspaceSentinelMocksPermissionType = \"read\"\n)\n\n// WorkspaceStateVersionsPermissionType represents the permissiontype to project workspaces' state-versions\ntype WorkspaceStateVersionsPermissionType string\n\nconst (\n\tWorkspaceStateVersionsPermissionNone        WorkspaceStateVersionsPermissionType = \"none\"\n\tWorkspaceStateVersionsPermissionReadOutputs WorkspaceStateVersionsPermissionType = \"read-outputs\"\n\tWorkspaceStateVersionsPermissionRead        WorkspaceStateVersionsPermissionType = \"read\"\n\tWorkspaceStateVersionsPermissionWrite       WorkspaceStateVersionsPermissionType = \"write\"\n)\n\n// WorkspaceVariablesPermissionType represents the permissiontype to project workspaces' variables\ntype WorkspaceVariablesPermissionType string\n\nconst (\n\tWorkspaceVariablesPermissionNone  WorkspaceVariablesPermissionType = \"none\"\n\tWorkspaceVariablesPermissionRead  WorkspaceVariablesPermissionType = \"read\"\n\tWorkspaceVariablesPermissionWrite WorkspaceVariablesPermissionType = \"write\"\n)\n\ntype TeamProjectAccessProjectPermissionsOptions struct {\n\tSettings     *ProjectSettingsPermissionType     `json:\"settings,omitempty\"`\n\tTeams        *ProjectTeamsPermissionType        `json:\"teams,omitempty\"`\n\tVariableSets *ProjectVariableSetsPermissionType `json:\"variable-sets,omitempty\"`\n}\n\ntype TeamProjectAccessWorkspacePermissionsOptions struct {\n\tRuns          *WorkspaceRunsPermissionType          `json:\"runs,omitempty\"`\n\tSentinelMocks *WorkspaceSentinelMocksPermissionType `json:\"sentinel-mocks,omitempty\"`\n\tStateVersions *WorkspaceStateVersionsPermissionType `json:\"state-versions,omitempty\"`\n\tVariables     *WorkspaceVariablesPermissionType     `json:\"variables,omitempty\"`\n\tCreate        *bool                                 `json:\"create,omitempty\"`\n\tLocking       *bool                                 `json:\"locking,omitempty\"`\n\tMove          *bool                                 `json:\"move,omitempty\"`\n\tDelete        *bool                                 `json:\"delete,omitempty\"`\n\tRunTasks      *bool                                 `json:\"run-tasks,omitempty\"`\n\t// **Note: This API is still in BETA and subject to change.**\n\tPolicyOverrides *bool `json:\"policy-overrides,omitempty\"`\n}\n\n// TeamProjectAccessListOptions represents the options for listing team project accesses\ntype TeamProjectAccessListOptions struct {\n\tListOptions\n\tProjectID string `url:\"filter[project][id]\"`\n}\n\n// TeamProjectAccessAddOptions represents the options for adding team access for a project\ntype TeamProjectAccessAddOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,team-projects\"`\n\t// The type of access to grant.\n\tAccess TeamProjectAccessType `jsonapi:\"attr,access\"`\n\t// The levels that project and workspace permissions grant\n\tProjectAccess   *TeamProjectAccessProjectPermissionsOptions   `jsonapi:\"attr,project-access,omitempty\"`\n\tWorkspaceAccess *TeamProjectAccessWorkspacePermissionsOptions `jsonapi:\"attr,workspace-access,omitempty\"`\n\n\t// The team to add to the project\n\tTeam *Team `jsonapi:\"relation,team\"`\n\t// The project to which the team is to be added.\n\tProject *Project `jsonapi:\"relation,project\"`\n}\n\n// TeamProjectAccessUpdateOptions represents the options for updating a team project access\ntype TeamProjectAccessUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,team-projects\"`\n\t// The type of access to grant.\n\tAccess          *TeamProjectAccessType                        `jsonapi:\"attr,access,omitempty\"`\n\tProjectAccess   *TeamProjectAccessProjectPermissionsOptions   `jsonapi:\"attr,project-access,omitempty\"`\n\tWorkspaceAccess *TeamProjectAccessWorkspacePermissionsOptions `jsonapi:\"attr,workspace-access,omitempty\"`\n}\n\n// List all team accesses for a given project.\nfunc (s *teamProjectAccesses) List(ctx context.Context, options TeamProjectAccessListOptions) (*TeamProjectAccessList, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", \"team-projects\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttpal := &TeamProjectAccessList{}\n\terr = req.Do(ctx, tpal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tpal, nil\n}\n\n// Add team access for a project.\nfunc (s *teamProjectAccesses) Add(ctx context.Context, options TeamProjectAccessAddOptions) (*TeamProjectAccess, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := validateTeamProjectAccessType(options.Access); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", \"team-projects\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttpa := &TeamProjectAccess{}\n\terr = req.Do(ctx, tpa)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tpa, nil\n}\n\n// Read a team project access by its ID.\nfunc (s *teamProjectAccesses) Read(ctx context.Context, teamProjectAccessID string) (*TeamProjectAccess, error) {\n\tif !validStringID(&teamProjectAccessID) {\n\t\treturn nil, ErrInvalidTeamProjectAccessID\n\t}\n\n\tu := fmt.Sprintf(\"team-projects/%s\", url.PathEscape(teamProjectAccessID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttpa := &TeamProjectAccess{}\n\terr = req.Do(ctx, tpa)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tpa, nil\n}\n\n// Update team access for a project.\nfunc (s *teamProjectAccesses) Update(ctx context.Context, teamProjectAccessID string, options TeamProjectAccessUpdateOptions) (*TeamProjectAccess, error) {\n\tif !validStringID(&teamProjectAccessID) {\n\t\treturn nil, ErrInvalidTeamProjectAccessID\n\t}\n\n\tif err := validateTeamProjectAccessType(*options.Access); err != nil {\n\t\treturn nil, err\n\t}\n\tu := fmt.Sprintf(\"team-projects/%s\", url.PathEscape(teamProjectAccessID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tta := &TeamProjectAccess{}\n\terr = req.Do(ctx, ta)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ta, err\n}\n\n// Remove team access from a project.\nfunc (s *teamProjectAccesses) Remove(ctx context.Context, teamProjectAccessID string) error {\n\tif !validStringID(&teamProjectAccessID) {\n\t\treturn ErrInvalidTeamProjectAccessID\n\t}\n\n\tu := fmt.Sprintf(\"team-projects/%s\", url.PathEscape(teamProjectAccessID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o TeamProjectAccessListOptions) valid() error {\n\tif !validStringID(&o.ProjectID) {\n\t\treturn ErrInvalidProjectID\n\t}\n\n\treturn nil\n}\n\nfunc (o TeamProjectAccessAddOptions) valid() error {\n\tif err := validateTeamProjectAccessType(o.Access); err != nil {\n\t\treturn err\n\t}\n\tif o.Team == nil {\n\t\treturn ErrRequiredTeam\n\t}\n\tif o.Project == nil {\n\t\treturn ErrRequiredProject\n\t}\n\n\treturn nil\n}\n\nfunc validateTeamProjectAccessType(t TeamProjectAccessType) error {\n\tswitch t {\n\tcase TeamProjectAccessAdmin,\n\t\tTeamProjectAccessMaintain,\n\t\tTeamProjectAccessWrite,\n\t\tTeamProjectAccessRead,\n\t\tTeamProjectAccessCustom:\n\t\t// do nothing\n\tdefault:\n\t\treturn ErrInvalidTeamProjectAccessType\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "team_project_access_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTeamProjectAccessesList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest, pTestCleanup := createProject(t, client, orgTest)\n\tdefer pTestCleanup()\n\n\ttmTest1, tmTest1Cleanup := createTeam(t, client, orgTest)\n\tdefer tmTest1Cleanup()\n\ttmTest2, tmTest2Cleanup := createTeam(t, client, orgTest)\n\tdefer tmTest2Cleanup()\n\n\ttpaTest1, tpaTest1Cleanup := createTeamProjectAccess(t, client, tmTest1, pTest, orgTest)\n\tdefer tpaTest1Cleanup()\n\ttpaTest2, tpaTest2Cleanup := createTeamProjectAccess(t, client, tmTest2, pTest, orgTest)\n\tdefer tpaTest2Cleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\ttpal, err := client.TeamProjectAccess.List(ctx, TeamProjectAccessListOptions{\n\t\t\tProjectID: pTest.ID,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, tpal.Items, tpaTest1)\n\t\tassert.Contains(t, tpal.Items, tpaTest2)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\ttpal, err := client.TeamProjectAccess.List(ctx, TeamProjectAccessListOptions{\n\t\t\tProjectID: pTest.ID,\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, tpal.Items)\n\t\tassert.Equal(t, 999, tpal.CurrentPage)\n\t\tassert.Equal(t, 2, tpal.TotalCount)\n\t})\n\n\tt.Run(\"without projectID options\", func(t *testing.T) {\n\t\ttpal, err := client.TeamProjectAccess.List(ctx, TeamProjectAccessListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 2,\n\t\t\t\tPageSize:   25,\n\t\t\t},\n\t\t})\n\t\tassert.Nil(t, tpal)\n\t\tassert.Equal(t, err, ErrInvalidProjectID)\n\t})\n\n\tt.Run(\"without a valid projectID\", func(t *testing.T) {\n\t\ttpal, err := client.TeamProjectAccess.List(ctx, TeamProjectAccessListOptions{\n\t\t\tProjectID: badIdentifier,\n\t\t})\n\t\tassert.Nil(t, tpal)\n\t\tassert.EqualError(t, err, ErrInvalidProjectID.Error())\n\t})\n}\n\nfunc TestTeamProjectAccessesRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest, pTestCleanup := createProject(t, client, orgTest)\n\tdefer pTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\ttpaTest, tpaTestCleanup := createTeamProjectAccess(t, client, tmTest, pTest, orgTest)\n\tdefer tpaTestCleanup()\n\n\tt.Run(\"when the team access exists\", func(t *testing.T) {\n\t\ttpa, err := client.TeamProjectAccess.Read(ctx, tpaTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, TeamProjectAccessAdmin, tpa.Access)\n\n\t\tt.Run(\"team relationship is decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, tpa.Team)\n\t\t})\n\n\t\tt.Run(\"project relationship is decoded\", func(t *testing.T) {\n\t\t\tassert.NotEmpty(t, tpa.Project)\n\t\t})\n\t})\n\n\tt.Run(\"when the team access does not exist\", func(t *testing.T) {\n\t\ttpa, err := client.TeamProjectAccess.Read(ctx, \"nonexisting\")\n\t\tassert.Nil(t, tpa)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without a valid team access ID\", func(t *testing.T) {\n\t\ttpa, err := client.TeamProjectAccess.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, tpa)\n\t\tassert.Equal(t, err, ErrInvalidTeamProjectAccessID)\n\t})\n}\n\nfunc TestTeamProjectAccessesAdd(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest, pTestCleanup := createProject(t, client, orgTest)\n\tdefer pTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := TeamProjectAccessAddOptions{\n\t\t\tAccess:  *ProjectAccess(TeamProjectAccessAdmin),\n\t\t\tTeam:    tmTest,\n\t\t\tProject: pTest,\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, options)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\terr := client.TeamProjectAccess.Remove(ctx, tpa.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error removing team access (%s): %s\", tpa.ID, err)\n\t\t\t}\n\t\t}()\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.TeamProjectAccess.Read(ctx, tpa.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*TeamProjectAccess{\n\t\t\ttpa,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, options.Access, item.Access)\n\t\t}\n\t})\n\n\tt.Run(\"with no project access options for custom TeamProject permissions\", func(t *testing.T) {\n\t\toptions := TeamProjectAccessAddOptions{\n\t\t\tAccess:        *ProjectAccess(TeamProjectAccessCustom),\n\t\t\tTeam:          tmTest,\n\t\t\tProject:       pTest,\n\t\t\tProjectAccess: &TeamProjectAccessProjectPermissionsOptions{},\n\t\t\tWorkspaceAccess: &TeamProjectAccessWorkspacePermissionsOptions{\n\t\t\t\tRuns:          WorkspaceRunsPermission(WorkspaceRunsPermissionApply),\n\t\t\t\tSentinelMocks: WorkspaceSentinelMocksPermission(WorkspaceSentinelMocksPermissionRead),\n\t\t\t\tStateVersions: WorkspaceStateVersionsPermission(WorkspaceStateVersionsPermissionWrite),\n\t\t\t\tVariables:     WorkspaceVariablesPermission(WorkspaceVariablesPermissionWrite),\n\t\t\t\tCreate:        Bool(true),\n\t\t\t\tLocking:       Bool(true),\n\t\t\t\tMove:          Bool(true),\n\t\t\t\tDelete:        Bool(false),\n\t\t\t\tRunTasks:      Bool(false),\n\t\t\t},\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, options)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\terr := client.TeamProjectAccess.Remove(ctx, tpa.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error removing team access (%s): %s\", tpa.ID, err)\n\t\t\t}\n\t\t}()\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.TeamProjectAccess.Read(ctx, tpa.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*TeamProjectAccess{\n\t\t\ttpa,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, options.Access, item.Access)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.Runs, item.WorkspaceAccess.WorkspaceRunsPermission)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.SentinelMocks, item.WorkspaceAccess.WorkspaceSentinelMocksPermission)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.StateVersions, item.WorkspaceAccess.WorkspaceStateVersionsPermission)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.Variables, item.WorkspaceAccess.WorkspaceVariablesPermission)\n\t\t\tassert.Equal(t, item.WorkspaceAccess.WorkspaceCreatePermission, true)\n\t\t\tassert.Equal(t, item.WorkspaceAccess.WorkspaceLockingPermission, true)\n\t\t\tassert.Equal(t, item.WorkspaceAccess.WorkspaceMovePermission, true)\n\t\t\tassert.Equal(t, item.WorkspaceAccess.WorkspaceDeletePermission, false)\n\t\t\tassert.Equal(t, item.WorkspaceAccess.WorkspaceRunTasksPermission, false)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options for all custom TeamProject permissions\", func(t *testing.T) {\n\t\toptions := TeamProjectAccessAddOptions{\n\t\t\tAccess:  *ProjectAccess(TeamProjectAccessCustom),\n\t\t\tTeam:    tmTest,\n\t\t\tProject: pTest,\n\t\t\tProjectAccess: &TeamProjectAccessProjectPermissionsOptions{\n\t\t\t\tSettings: ProjectSettingsPermission(ProjectSettingsPermissionUpdate),\n\t\t\t\tTeams:    ProjectTeamsPermission(ProjectTeamsPermissionManage),\n\t\t\t},\n\t\t\tWorkspaceAccess: &TeamProjectAccessWorkspacePermissionsOptions{\n\t\t\t\tRuns:          WorkspaceRunsPermission(WorkspaceRunsPermissionApply),\n\t\t\t\tSentinelMocks: WorkspaceSentinelMocksPermission(WorkspaceSentinelMocksPermissionRead),\n\t\t\t\tStateVersions: WorkspaceStateVersionsPermission(WorkspaceStateVersionsPermissionWrite),\n\t\t\t\tVariables:     WorkspaceVariablesPermission(WorkspaceVariablesPermissionWrite),\n\t\t\t\tCreate:        Bool(true),\n\t\t\t\tLocking:       Bool(true),\n\t\t\t\tMove:          Bool(true),\n\t\t\t\tDelete:        Bool(false),\n\t\t\t\tRunTasks:      Bool(false),\n\t\t\t},\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, options)\n\t\trequire.NoError(t, err)\n\n\t\tdefer func() {\n\t\t\terr := client.TeamProjectAccess.Remove(ctx, tpa.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error removing team access (%s): %s\", tpa.ID, err)\n\t\t\t}\n\t\t}()\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.TeamProjectAccess.Read(ctx, tpa.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*TeamProjectAccess{\n\t\t\ttpa,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, options.Access, item.Access)\n\t\t\tassert.Equal(t, *options.ProjectAccess.Settings, item.ProjectAccess.ProjectSettingsPermission)\n\t\t\tassert.Equal(t, *options.ProjectAccess.Teams, item.ProjectAccess.ProjectTeamsPermission)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.Runs, item.WorkspaceAccess.WorkspaceRunsPermission)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.SentinelMocks, item.WorkspaceAccess.WorkspaceSentinelMocksPermission)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.StateVersions, item.WorkspaceAccess.WorkspaceStateVersionsPermission)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.Variables, item.WorkspaceAccess.WorkspaceVariablesPermission)\n\t\t\tassert.Equal(t, item.WorkspaceAccess.WorkspaceCreatePermission, true)\n\t\t\tassert.Equal(t, item.WorkspaceAccess.WorkspaceLockingPermission, true)\n\t\t\tassert.Equal(t, item.WorkspaceAccess.WorkspaceMovePermission, true)\n\t\t\tassert.Equal(t, item.WorkspaceAccess.WorkspaceDeletePermission, false)\n\t\t\tassert.Equal(t, item.WorkspaceAccess.WorkspaceRunTasksPermission, false)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options for custom variable sets permissions\", func(t *testing.T) {\n\t\toptions := TeamProjectAccessAddOptions{\n\t\t\tAccess:  *ProjectAccess(TeamProjectAccessCustom),\n\t\t\tTeam:    tmTest,\n\t\t\tProject: pTest,\n\t\t\tProjectAccess: &TeamProjectAccessProjectPermissionsOptions{\n\t\t\t\tVariableSets: ProjectVariableSetsPermission(ProjectVariableSetsPermissionWrite),\n\t\t\t},\n\t\t\tWorkspaceAccess: &TeamProjectAccessWorkspacePermissionsOptions{\n\t\t\t\tRuns: WorkspaceRunsPermission(WorkspaceRunsPermissionApply),\n\t\t\t},\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, options)\n\t\tt.Cleanup(func() {\n\t\t\terr := client.TeamProjectAccess.Remove(ctx, tpa.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error removing team access (%s): %s\", tpa.ID, err)\n\t\t\t}\n\t\t})\n\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.TeamProjectAccess.Read(ctx, tpa.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*TeamProjectAccess{\n\t\t\ttpa,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, options.Access, item.Access)\n\t\t\tassert.Equal(t, *options.ProjectAccess.VariableSets, item.ProjectAccess.ProjectVariableSetsPermission)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.Runs, item.WorkspaceAccess.WorkspaceRunsPermission)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options for some custom TeamProject permissions\", func(t *testing.T) {\n\t\toptions := TeamProjectAccessAddOptions{\n\t\t\tAccess:  *ProjectAccess(TeamProjectAccessCustom),\n\t\t\tTeam:    tmTest,\n\t\t\tProject: pTest,\n\t\t\tProjectAccess: &TeamProjectAccessProjectPermissionsOptions{\n\t\t\t\tSettings: ProjectSettingsPermission(ProjectSettingsPermissionUpdate),\n\t\t\t},\n\t\t\tWorkspaceAccess: &TeamProjectAccessWorkspacePermissionsOptions{\n\t\t\t\tRuns: WorkspaceRunsPermission(WorkspaceRunsPermissionApply),\n\t\t\t},\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, options)\n\t\tt.Cleanup(func() {\n\t\t\terr := client.TeamProjectAccess.Remove(ctx, tpa.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error removing team access (%s): %s\", tpa.ID, err)\n\t\t\t}\n\t\t})\n\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.TeamProjectAccess.Read(ctx, tpa.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*TeamProjectAccess{\n\t\t\ttpa,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, options.Access, item.Access)\n\t\t\tassert.Equal(t, *options.ProjectAccess.Settings, item.ProjectAccess.ProjectSettingsPermission)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.Runs, item.WorkspaceAccess.WorkspaceRunsPermission)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options for custom workspace policy overrides permission\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\toptions := TeamProjectAccessAddOptions{\n\t\t\tAccess:  *ProjectAccess(TeamProjectAccessCustom),\n\t\t\tTeam:    tmTest,\n\t\t\tProject: pTest,\n\t\t\tWorkspaceAccess: &TeamProjectAccessWorkspacePermissionsOptions{\n\t\t\t\tRuns:            WorkspaceRunsPermission(WorkspaceRunsPermissionApply),\n\t\t\t\tPolicyOverrides: Bool(true),\n\t\t\t},\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, options)\n\t\tt.Cleanup(func() {\n\t\t\terr := client.TeamProjectAccess.Remove(ctx, tpa.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error removing team access (%s): %s\", tpa.ID, err)\n\t\t\t}\n\t\t})\n\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.TeamProjectAccess.Read(ctx, tpa.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*TeamProjectAccess{\n\t\t\ttpa,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, options.Access, item.Access)\n\t\t\tassert.Equal(t, *options.WorkspaceAccess.Runs, item.WorkspaceAccess.WorkspaceRunsPermission)\n\t\t\tassert.Equal(t, true, item.WorkspaceAccess.WorkspacePolicyOverridesPermission)\n\t\t}\n\t})\n\n\tt.Run(\"when the team already has access to the project\", func(t *testing.T) {\n\t\t_, tpaTestCleanup := createTeamProjectAccess(t, client, tmTest, pTest, nil)\n\t\tdefer tpaTestCleanup()\n\n\t\toptions := TeamProjectAccessAddOptions{\n\t\t\tAccess:  *ProjectAccess(TeamProjectAccessAdmin),\n\t\t\tTeam:    tmTest,\n\t\t\tProject: pTest,\n\t\t}\n\n\t\t_, err := client.TeamProjectAccess.Add(ctx, options)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"when options is missing access\", func(t *testing.T) {\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, TeamProjectAccessAddOptions{\n\t\t\tTeam:    tmTest,\n\t\t\tProject: pTest,\n\t\t})\n\t\tassert.Nil(t, tpa)\n\t\tassert.Equal(t, err, ErrInvalidTeamProjectAccessType)\n\t})\n\n\tt.Run(\"when options is missing team\", func(t *testing.T) {\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, TeamProjectAccessAddOptions{\n\t\t\tAccess:  *ProjectAccess(TeamProjectAccessAdmin),\n\t\t\tProject: pTest,\n\t\t})\n\t\tassert.Nil(t, tpa)\n\t\tassert.Equal(t, err, ErrRequiredTeam)\n\t})\n\n\tt.Run(\"when options is missing project\", func(t *testing.T) {\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, TeamProjectAccessAddOptions{\n\t\t\tAccess: *ProjectAccess(TeamProjectAccessAdmin),\n\t\t\tTeam:   tmTest,\n\t\t})\n\t\tassert.Nil(t, tpa)\n\t\tassert.Equal(t, err, ErrRequiredProject)\n\t})\n\n\tt.Run(\"when invalid custom project permission is provided in options\", func(t *testing.T) {\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, TeamProjectAccessAddOptions{\n\t\t\tAccess:  *ProjectAccess(TeamProjectAccessCustom),\n\t\t\tTeam:    tmTest,\n\t\t\tProject: pTest,\n\t\t\tProjectAccess: &TeamProjectAccessProjectPermissionsOptions{\n\t\t\t\tTeams: ProjectTeamsPermission(badIdentifier),\n\t\t\t},\n\t\t})\n\t\tassert.Nil(t, tpa)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"when invalid access is provided in options\", func(t *testing.T) {\n\t\ttpa, err := client.TeamProjectAccess.Add(ctx, TeamProjectAccessAddOptions{\n\t\t\tAccess:  badIdentifier,\n\t\t\tTeam:    tmTest,\n\t\t\tProject: pTest,\n\t\t})\n\t\tassert.Nil(t, tpa)\n\t\tassert.Equal(t, err, ErrInvalidTeamProjectAccessType)\n\t})\n}\n\nfunc TestTeamProjectAccessesUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest, pTestCleanup := createProject(t, client, orgTest)\n\tdefer pTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\ttpaTest, tpaTestCleanup := createTeamProjectAccess(t, client, tmTest, pTest, orgTest)\n\tdefer tpaTestCleanup()\n\n\tt.Run(\"with valid attributes\", func(t *testing.T) {\n\t\toptions := TeamProjectAccessUpdateOptions{\n\t\t\tAccess: ProjectAccess(TeamProjectAccessRead),\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Update(ctx, tpaTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, tpa.Access, TeamProjectAccessRead)\n\t})\n\n\tt.Run(\"with valid custom permissions attributes for all permissions\", func(t *testing.T) {\n\t\toptions := TeamProjectAccessUpdateOptions{\n\t\t\tAccess: ProjectAccess(TeamProjectAccessCustom),\n\t\t\tProjectAccess: &TeamProjectAccessProjectPermissionsOptions{\n\t\t\t\tSettings: ProjectSettingsPermission(ProjectSettingsPermissionUpdate),\n\t\t\t\tTeams:    ProjectTeamsPermission(ProjectTeamsPermissionManage),\n\t\t\t},\n\t\t\tWorkspaceAccess: &TeamProjectAccessWorkspacePermissionsOptions{\n\t\t\t\tRuns:          WorkspaceRunsPermission(WorkspaceRunsPermissionPlan),\n\t\t\t\tSentinelMocks: WorkspaceSentinelMocksPermission(WorkspaceSentinelMocksPermissionNone),\n\t\t\t\tStateVersions: WorkspaceStateVersionsPermission(WorkspaceStateVersionsPermissionReadOutputs),\n\t\t\t\tVariables:     WorkspaceVariablesPermission(WorkspaceVariablesPermissionRead),\n\t\t\t\tCreate:        Bool(false),\n\t\t\t\tLocking:       Bool(false),\n\t\t\t\tMove:          Bool(false),\n\t\t\t\tDelete:        Bool(true),\n\t\t\t\tRunTasks:      Bool(true),\n\t\t\t},\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Update(ctx, tpaTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, options.ProjectAccess)\n\t\trequire.NotNil(t, options.WorkspaceAccess)\n\t\tassert.Equal(t, tpa.Access, TeamProjectAccessCustom)\n\t\tassert.Equal(t, *options.ProjectAccess.Teams, tpa.ProjectAccess.ProjectTeamsPermission)\n\t\tassert.Equal(t, *options.ProjectAccess.Settings, tpa.ProjectAccess.ProjectSettingsPermission)\n\t\tassert.Equal(t, *options.WorkspaceAccess.Runs, tpa.WorkspaceAccess.WorkspaceRunsPermission)\n\t\tassert.Equal(t, *options.WorkspaceAccess.SentinelMocks, tpa.WorkspaceAccess.WorkspaceSentinelMocksPermission)\n\t\tassert.Equal(t, *options.WorkspaceAccess.StateVersions, tpa.WorkspaceAccess.WorkspaceStateVersionsPermission)\n\t\tassert.Equal(t, *options.WorkspaceAccess.Variables, tpa.WorkspaceAccess.WorkspaceVariablesPermission)\n\t\tassert.Equal(t, false, tpa.WorkspaceAccess.WorkspaceCreatePermission)\n\t\tassert.Equal(t, false, tpa.WorkspaceAccess.WorkspaceLockingPermission)\n\t\tassert.Equal(t, false, tpa.WorkspaceAccess.WorkspaceMovePermission)\n\t\tassert.Equal(t, true, tpa.WorkspaceAccess.WorkspaceDeletePermission)\n\t\tassert.Equal(t, true, tpa.WorkspaceAccess.WorkspaceRunTasksPermission)\n\t})\n\n\tt.Run(\"with valid custom permissions attributes for variable sets permissions\", func(t *testing.T) {\n\t\t// create tpaCustomTest to verify unupdated attributes stay the same for custom permissions\n\t\t// because going from admin to read to custom changes the values of all custom permissions\n\t\ttm2Test, tm2TestCleanup := createTeam(t, client, orgTest)\n\t\tdefer tm2TestCleanup()\n\n\t\tTpaOptions := TeamProjectAccessAddOptions{\n\t\t\tAccess:  *ProjectAccess(TeamProjectAccessCustom),\n\t\t\tTeam:    tm2Test,\n\t\t\tProject: pTest,\n\t\t}\n\n\t\ttpaCustomTest, err := client.TeamProjectAccess.Add(ctx, TpaOptions)\n\t\trequire.NoError(t, err)\n\n\t\toptions := TeamProjectAccessUpdateOptions{\n\t\t\tAccess: ProjectAccess(TeamProjectAccessCustom),\n\t\t\tProjectAccess: &TeamProjectAccessProjectPermissionsOptions{\n\t\t\t\tVariableSets: ProjectVariableSetsPermission(ProjectVariableSetsPermissionRead),\n\t\t\t},\n\t\t\tWorkspaceAccess: &TeamProjectAccessWorkspacePermissionsOptions{\n\t\t\t\tCreate: Bool(false),\n\t\t\t},\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Update(ctx, tpaCustomTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, options.ProjectAccess)\n\t\trequire.NotNil(t, options.WorkspaceAccess)\n\t\tassert.Equal(t, *options.ProjectAccess.VariableSets, tpa.ProjectAccess.ProjectVariableSetsPermission)\n\t\tassert.Equal(t, false, tpa.WorkspaceAccess.WorkspaceCreatePermission)\n\t\t// assert that other attributes remain the same\n\t\tassert.Equal(t, tpaCustomTest.ProjectAccess.ProjectSettingsPermission, tpa.ProjectAccess.ProjectSettingsPermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceLockingPermission, tpa.WorkspaceAccess.WorkspaceLockingPermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceMovePermission, tpa.WorkspaceAccess.WorkspaceMovePermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceDeletePermission, tpa.WorkspaceAccess.WorkspaceDeletePermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceRunsPermission, tpa.WorkspaceAccess.WorkspaceRunsPermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceSentinelMocksPermission, tpa.WorkspaceAccess.WorkspaceSentinelMocksPermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceStateVersionsPermission, tpa.WorkspaceAccess.WorkspaceStateVersionsPermission)\n\t})\n\n\tt.Run(\"with valid custom permissions attributes for some permissions\", func(t *testing.T) {\n\t\t// create tpaCustomTest to verify unupdated attributes stay the same for custom permissions\n\t\t// because going from admin to read to custom changes the values of all custom permissions\n\t\ttm2Test, tm2TestCleanup := createTeam(t, client, orgTest)\n\t\tdefer tm2TestCleanup()\n\n\t\tTpaOptions := TeamProjectAccessAddOptions{\n\t\t\tAccess:  *ProjectAccess(TeamProjectAccessCustom),\n\t\t\tTeam:    tm2Test,\n\t\t\tProject: pTest,\n\t\t}\n\n\t\ttpaCustomTest, err := client.TeamProjectAccess.Add(ctx, TpaOptions)\n\t\trequire.NoError(t, err)\n\n\t\toptions := TeamProjectAccessUpdateOptions{\n\t\t\tAccess: ProjectAccess(TeamProjectAccessCustom),\n\t\t\tProjectAccess: &TeamProjectAccessProjectPermissionsOptions{\n\t\t\t\tTeams: ProjectTeamsPermission(ProjectTeamsPermissionManage),\n\t\t\t},\n\t\t\tWorkspaceAccess: &TeamProjectAccessWorkspacePermissionsOptions{\n\t\t\t\tCreate: Bool(false),\n\t\t\t},\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Update(ctx, tpaCustomTest.ID, options)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, options.ProjectAccess)\n\t\trequire.NotNil(t, options.WorkspaceAccess)\n\t\tassert.Equal(t, *options.ProjectAccess.Teams, tpa.ProjectAccess.ProjectTeamsPermission)\n\t\tassert.Equal(t, false, tpa.WorkspaceAccess.WorkspaceCreatePermission)\n\t\t// assert that other attributes remain the same\n\t\tassert.Equal(t, tpaCustomTest.ProjectAccess.ProjectSettingsPermission, tpa.ProjectAccess.ProjectSettingsPermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceLockingPermission, tpa.WorkspaceAccess.WorkspaceLockingPermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceMovePermission, tpa.WorkspaceAccess.WorkspaceMovePermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceDeletePermission, tpa.WorkspaceAccess.WorkspaceDeletePermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceRunsPermission, tpa.WorkspaceAccess.WorkspaceRunsPermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceSentinelMocksPermission, tpa.WorkspaceAccess.WorkspaceSentinelMocksPermission)\n\t\tassert.Equal(t, tpaCustomTest.WorkspaceAccess.WorkspaceStateVersionsPermission, tpa.WorkspaceAccess.WorkspaceStateVersionsPermission)\n\t})\n\n\tt.Run(\"with invalid custom permissions attributes\", func(t *testing.T) {\n\t\toptions := TeamProjectAccessUpdateOptions{\n\t\t\tAccess: ProjectAccess(TeamProjectAccessCustom),\n\t\t\tProjectAccess: &TeamProjectAccessProjectPermissionsOptions{\n\t\t\t\tTeams: ProjectTeamsPermission(badIdentifier),\n\t\t\t},\n\t\t}\n\n\t\ttpa, err := client.TeamProjectAccess.Update(ctx, tpaTest.ID, options)\n\n\t\tassert.Nil(t, tpa)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestTeamProjectAccessesRemove(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tpTest, pTestCleanup := createProject(t, client, orgTest)\n\tdefer pTestCleanup()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\tdefer tmTestCleanup()\n\n\ttpaTest, _ := createTeamProjectAccess(t, client, tmTest, pTest, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.TeamProjectAccess.Remove(ctx, tpaTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the project - it should fail.\n\t\t_, err = client.TeamProjectAccess.Read(ctx, tpaTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the team access does not exist\", func(t *testing.T) {\n\t\terr := client.TeamProjectAccess.Remove(ctx, tpaTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the team access ID is invalid\", func(t *testing.T) {\n\t\terr := client.TeamProjectAccess.Remove(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidTeamProjectAccessID)\n\t})\n}\n"
  },
  {
    "path": "team_token.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ TeamTokens = (*teamTokens)(nil)\n\n// TeamTokens describes all the team token related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/team-tokens\ntype TeamTokens interface {\n\t// Create a new team token using the legacy creation behavior, which creates a token without a description\n\t// or regenerates the existing, descriptionless token.\n\tCreate(ctx context.Context, teamID string) (*TeamToken, error)\n\n\t// CreateWithOptions creates a team token, with options. If no description is provided, it uses the legacy\n\t// creation behavior, which regenerates the descriptionless token if it already exists. Otherwise, it create\n\t//  a new token with the given unique description, allowing for the creation of multiple team tokens.\n\tCreateWithOptions(ctx context.Context, teamID string, options TeamTokenCreateOptions) (*TeamToken, error)\n\n\t// Read a team token by its team ID.\n\tRead(ctx context.Context, teamID string) (*TeamToken, error)\n\n\t// Read a team token by its token ID.\n\tReadByID(ctx context.Context, teamID string) (*TeamToken, error)\n\n\t// List an organization's team tokens.\n\tList(ctx context.Context, organizationID string, options *TeamTokenListOptions) (*TeamTokenList, error)\n\n\t// Delete a team token by its team ID.\n\tDelete(ctx context.Context, teamID string) error\n\n\t// Delete a team token by its token ID.\n\tDeleteByID(ctx context.Context, tokenID string) error\n}\n\n// teamTokens implements TeamTokens.\ntype teamTokens struct {\n\tclient *Client\n}\n\n// TeamToken represents a Terraform Enterprise team token.\ntype TeamToken struct {\n\tID          string           `jsonapi:\"primary,authentication-tokens\"`\n\tCreatedAt   time.Time        `jsonapi:\"attr,created-at,iso8601\"`\n\tDescription *string          `jsonapi:\"attr,description\"`\n\tLastUsedAt  time.Time        `jsonapi:\"attr,last-used-at,iso8601\"`\n\tToken       string           `jsonapi:\"attr,token\"`\n\tExpiredAt   time.Time        `jsonapi:\"attr,expired-at,iso8601\"`\n\tCreatedBy   *CreatedByChoice `jsonapi:\"polyrelation,created-by\"`\n\tTeam        *Team            `jsonapi:\"relation,team\"`\n}\n\n// TeamTokenCreateOptions contains the options for creating a team token.\ntype TeamTokenCreateOptions struct {\n\t// Optional: The token's expiration date.\n\t// This feature is available in TFE release v202305-1 and later\n\tExpiredAt *time.Time `jsonapi:\"attr,expired-at,iso8601,omitempty\"`\n\n\t// Optional: The token's description, which must unique per team.\n\t// This feature is considered BETA, SUBJECT TO CHANGE, and likely unavailable to most users.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n}\n\n// TeamTokenListOptions contains the options for listing team tokens.\ntype TeamTokenListOptions struct {\n\tListOptions\n\n\t// Optional: A query string used to filter team tokens by\n\t// a specified team name.\n\tQuery string `url:\"q,omitempty\"`\n\n\t// Optional: Allows sorting the team tokens by \"team-name\",\n\t// \"created-by\", \"expired-at\", and \"last-used-at\"\n\tSort string `url:\"sort,omitempty\"`\n}\n\n// TeamTokenList represents a list of team tokens.\ntype TeamTokenList struct {\n\t*Pagination\n\tItems []*TeamToken\n}\n\n// Create a new team token using the legacy creation behavior, which creates a token without a description\n// or regenerates the existing, descriptionless token.\nfunc (s *teamTokens) Create(ctx context.Context, teamID string) (*TeamToken, error) {\n\treturn s.CreateWithOptions(ctx, teamID, TeamTokenCreateOptions{})\n}\n\n// CreateWithOptions creates a team token, with options. If no description is provided, it uses the legacy\n// creation behavior, which regenerates the descriptionless token if it already exists. Otherwise, it create\n// a new token with the given unique description, allowing for the creation of multiple team tokens.\nfunc (s *teamTokens) CreateWithOptions(ctx context.Context, teamID string, options TeamTokenCreateOptions) (*TeamToken, error) {\n\tif !validStringID(&teamID) {\n\t\treturn nil, ErrInvalidTeamID\n\t}\n\n\tvar u string\n\tif options.Description != nil {\n\t\tu = fmt.Sprintf(\"teams/%s/authentication-tokens\", url.PathEscape(teamID))\n\t} else {\n\t\tu = fmt.Sprintf(\"teams/%s/authentication-token\", url.PathEscape(teamID))\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttt := &TeamToken{}\n\terr = req.Do(ctx, tt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tt, err\n}\n\n// Read a team token by its team ID.\nfunc (s *teamTokens) Read(ctx context.Context, teamID string) (*TeamToken, error) {\n\tif !validStringID(&teamID) {\n\t\treturn nil, ErrInvalidTeamID\n\t}\n\n\tu := fmt.Sprintf(\"teams/%s/authentication-token\", url.PathEscape(teamID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttt := &TeamToken{}\n\terr = req.Do(ctx, tt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tt, err\n}\n\n// Read a team token by its token ID.\nfunc (s *teamTokens) ReadByID(ctx context.Context, tokenID string) (*TeamToken, error) {\n\tif !validStringID(&tokenID) {\n\t\treturn nil, ErrInvalidTokenID\n\t}\n\n\tu := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(tokenID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttt := &TeamToken{}\n\terr = req.Do(ctx, tt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tt, err\n}\n\n// List an organization's team tokens with the option to filter by team name.\nfunc (s *teamTokens) List(ctx context.Context, organizationID string, options *TeamTokenListOptions) (*TeamTokenList, error) {\n\tif !validStringID(&organizationID) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/team-tokens\", url.PathEscape(organizationID))\n\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttt := &TeamTokenList{}\n\terr = req.Do(ctx, tt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tt, err\n}\n\n// Delete a team token by its team ID.\nfunc (s *teamTokens) Delete(ctx context.Context, teamID string) error {\n\tif !validStringID(&teamID) {\n\t\treturn ErrInvalidTeamID\n\t}\n\n\tu := fmt.Sprintf(\"teams/%s/authentication-token\", url.PathEscape(teamID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Delete a team token by its token ID.\nfunc (s *teamTokens) DeleteByID(ctx context.Context, tokenID string) error {\n\tif !validStringID(&tokenID) {\n\t\treturn ErrInvalidTokenID\n\t}\n\n\tu := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(tokenID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "team_token_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTeamTokensCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tdefer tmTestCleanup()\n\n\tvar tmToken string\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\ttt, err := client.TeamTokens.Create(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\trequire.NotEmpty(t, tt.CreatedBy)\n\t\trequireExactlyOneNotEmpty(t, tt.CreatedBy.Organization, tt.CreatedBy.Team, tt.CreatedBy.User)\n\t\ttmToken = tt.Token\n\t})\n\n\tt.Run(\"when a token already exists\", func(t *testing.T) {\n\t\ttt, err := client.TeamTokens.Create(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\tassert.NotEqual(t, tmToken, tt.Token)\n\t})\n\n\tt.Run(\"without valid team ID\", func(t *testing.T) {\n\t\ttt, err := client.TeamTokens.Create(ctx, badIdentifier)\n\t\tassert.Nil(t, tt)\n\t\tassert.Equal(t, err, ErrInvalidTeamID)\n\t})\n}\n\nfunc TestTeamTokens_CreateWithOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tdefer tmTestCleanup()\n\n\tvar tmToken string\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\ttt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\ttmToken = tt.Token\n\t})\n\n\tt.Run(\"when a token already exists\", func(t *testing.T) {\n\t\ttt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\tassert.NotEqual(t, tmToken, tt.Token)\n\t})\n\n\tt.Run(\"without valid team ID\", func(t *testing.T) {\n\t\ttt, err := client.TeamTokens.CreateWithOptions(ctx, badIdentifier, TeamTokenCreateOptions{})\n\t\tassert.Nil(t, tt)\n\t\tassert.Equal(t, err, ErrInvalidTeamID)\n\t})\n\n\tt.Run(\"without an expiration date\", func(t *testing.T) {\n\t\ttt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\tassert.NotZero(t, tt.ExpiredAt)\n\t\texpectedExpiry := tt.CreatedAt.AddDate(defaultTokenExpirationYears, 0, 0)\n\t\t// Allow a small buffer (1 minute) for timestamp precision differences.\n\t\tassert.WithinDuration(t, expectedExpiry, tt.ExpiredAt, time.Minute)\n\t\ttmToken = tt.Token\n\t})\n\n\tt.Run(\"with an expiration date\", func(t *testing.T) {\n\t\tcurrentTime := time.Now().UTC().Truncate(time.Second)\n\t\toneDayLater := currentTime.Add(24 * time.Hour)\n\t\ttt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{\n\t\t\tExpiredAt: &oneDayLater,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\tassert.Equal(t, tt.ExpiredAt, oneDayLater)\n\t\ttmToken = tt.Token\n\t})\n}\n\nfunc TestTeamTokens_CreateWithOptions_MultipleTokens(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tt.Cleanup(tmTestCleanup)\n\n\tt.Run(\"with multiple tokens\", func(t *testing.T) {\n\t\tdesc1 := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\ttt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{\n\t\t\tDescription: &desc1,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\trequire.NotNil(t, tt.Description)\n\t\trequire.Equal(t, *tt.Description, desc1)\n\n\t\tdesc2 := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\ttt, err = client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{\n\t\t\tDescription: &desc2,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\trequire.NotNil(t, tt.Description)\n\t\trequire.Equal(t, *tt.Description, desc2)\n\n\t\temptyString := \"\"\n\t\ttt, err = client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{\n\t\t\tDescription: &emptyString,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\trequire.NotNil(t, tt.Description)\n\t\trequire.Equal(t, *tt.Description, emptyString)\n\n\t\ttt, err = client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\trequire.Nil(t, tt.Description)\n\t})\n\n\tt.Run(\"with an expiration date\", func(t *testing.T) {\n\t\tdesc := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\tcurrentTime := time.Now().UTC().Truncate(time.Second)\n\t\toneDayLater := currentTime.Add(24 * time.Hour)\n\t\ttt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{\n\t\t\tDescription: &desc,\n\t\t\tExpiredAt:   &oneDayLater,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\tassert.Equal(t, tt.ExpiredAt, oneDayLater)\n\t\trequire.NotNil(t, tt.Description)\n\t\trequire.Equal(t, *tt.Description, desc)\n\t})\n\n\tt.Run(\"without an expiration date\", func(t *testing.T) {\n\t\tdesc := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\ttt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{\n\t\t\tDescription: &desc,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\tassert.NotZero(t, tt.ExpiredAt)\n\t\texpectedExpiry := tt.CreatedAt.AddDate(defaultTokenExpirationYears, 0, 0)\n\t\t// Allow a small buffer (1 minute) for timestamp precision differences.\n\t\tassert.WithinDuration(t, expectedExpiry, tt.ExpiredAt, time.Minute)\n\t\trequire.NotNil(t, tt.Description)\n\t\trequire.Equal(t, *tt.Description, desc)\n\t})\n\n\tt.Run(\"when a token already exists with the same description\", func(t *testing.T) {\n\t\tdesc := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\ttt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{\n\t\t\tDescription: &desc,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt.Token)\n\t\trequire.NotNil(t, tt.Description)\n\t\trequire.Equal(t, *tt.Description, desc)\n\n\t\ttt, err = client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{\n\t\t\tDescription: &desc,\n\t\t})\n\t\tassert.Nil(t, tt)\n\t\tassert.Equal(t, err, ErrInvalidDescriptionConflict)\n\t})\n\n\tt.Run(\"without valid team ID\", func(t *testing.T) {\n\t\ttt, err := client.TeamTokens.CreateWithOptions(ctx, badIdentifier, TeamTokenCreateOptions{})\n\t\tassert.Nil(t, tt)\n\t\tassert.Equal(t, err, ErrInvalidTeamID)\n\t})\n}\n\nfunc TestTeamTokensRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tdefer tmTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\t_, ttTestCleanup := createTeamToken(t, client, tmTest)\n\n\t\ttt, err := client.TeamTokens.Read(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, tt)\n\t\trequire.NotEmpty(t, tt.Team)\n\t\tassert.Equal(t, tt.Team.ID, tmTest.ID)\n\n\t\tttTestCleanup()\n\t})\n\n\tt.Run(\"with an expiration date passed as a valid option\", func(t *testing.T) {\n\t\tcurrentTime := time.Now().UTC().Truncate(time.Second)\n\t\toneDayLater := currentTime.Add(24 * time.Hour)\n\n\t\t_, ttTestCleanup := createTeamTokenWithOptions(t, client, tmTest, TeamTokenCreateOptions{ExpiredAt: &oneDayLater})\n\n\t\ttt, err := client.TeamTokens.Read(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt)\n\t\tassert.Equal(t, tt.ExpiredAt, oneDayLater)\n\t\trequire.NotEmpty(t, tt.Team)\n\t\tassert.Equal(t, tt.Team.ID, tmTest.ID)\n\n\t\tttTestCleanup()\n\t})\n\n\tt.Run(\"when a token doesn't exists\", func(t *testing.T) {\n\t\ttt, err := client.TeamTokens.Read(ctx, tmTest.ID)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t\tassert.Nil(t, tt)\n\t})\n\n\tt.Run(\"without valid organization\", func(t *testing.T) {\n\t\ttt, err := client.OrganizationTokens.Read(ctx, badIdentifier)\n\t\tassert.Nil(t, tt)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestTeamTokensReadByID(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tt.Cleanup(tmTestCleanup)\n\n\tcurrentTime := time.Now().UTC().Truncate(time.Second)\n\toneDayLater := currentTime.Add(24 * time.Hour)\n\tt.Run(\"with legacy, descriptionless tokens\", func(t *testing.T) {\n\t\ttoken, ttTestCleanup := createTeamTokenWithOptions(t, client, tmTest, TeamTokenCreateOptions{\n\t\t\tExpiredAt: &oneDayLater,\n\t\t})\n\t\tt.Cleanup(ttTestCleanup)\n\n\t\ttt, err := client.TeamTokens.ReadByID(ctx, token.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt)\n\t\tassert.Nil(t, tt.Description)\n\t\tassert.Equal(t, tt.ExpiredAt, oneDayLater)\n\t\trequire.NotEmpty(t, tt.Team)\n\t\tassert.Equal(t, tt.Team.ID, tmTest.ID)\n\t})\n\n\tt.Run(\"with multiple team tokens\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\t\tdesc1 := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\ttoken, ttTestCleanup := createTeamTokenWithOptions(t, client, tmTest, TeamTokenCreateOptions{\n\t\t\tDescription: &desc1,\n\t\t})\n\t\tt.Cleanup(ttTestCleanup)\n\n\t\ttt, err := client.TeamTokens.ReadByID(ctx, token.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt)\n\t\trequire.NotNil(t, tt.Description)\n\t\tassert.Equal(t, *tt.Description, desc1)\n\t\tassert.NotZero(t, tt.ExpiredAt)\n\t\texpectedExpiry := tt.CreatedAt.AddDate(defaultTokenExpirationYears, 0, 0)\n\t\t// Allow a small buffer (1 minute) for timestamp precision differences.\n\t\tassert.WithinDuration(t, expectedExpiry, tt.ExpiredAt, time.Minute)\n\t\trequire.NotEmpty(t, tt.Team)\n\t\tassert.Equal(t, tt.Team.ID, tmTest.ID)\n\n\t\tdesc2 := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\ttokenWithExpiration, ttTestCleanup2 := createTeamTokenWithOptions(t, client, tmTest, TeamTokenCreateOptions{\n\t\t\tExpiredAt:   &oneDayLater,\n\t\t\tDescription: &desc2,\n\t\t})\n\t\tt.Cleanup(ttTestCleanup2)\n\n\t\ttt2, err := client.TeamTokens.ReadByID(ctx, tokenWithExpiration.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, tt2)\n\t\trequire.NotNil(t, tt2.Description)\n\t\tassert.Equal(t, *tt2.Description, desc2)\n\t\tassert.Equal(t, tt2.ExpiredAt, oneDayLater)\n\t\trequire.NotEmpty(t, tt.Team)\n\t\tassert.Equal(t, tt.Team.ID, tmTest.ID)\n\t})\n\n\tt.Run(\"when a token doesn't exists\", func(t *testing.T) {\n\t\ttt, err := client.TeamTokens.ReadByID(ctx, \"nonexistent-token-id\")\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t\tassert.Nil(t, tt)\n\t})\n}\n\nfunc TestTeamTokensList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torg, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\t// Create a team with a token\n\tteam1, tmTestCleanup1 := createTeam(t, client, org)\n\tt.Cleanup(tmTestCleanup1)\n\n\tcurrentTime := time.Now().UTC().Truncate(time.Second)\n\toneDayLater := currentTime.Add(24 * time.Hour)\n\ttoken1, ttTestCleanup := createTeamTokenWithOptions(t, client, team1, TeamTokenCreateOptions{\n\t\tExpiredAt: &oneDayLater,\n\t})\n\tt.Cleanup(ttTestCleanup)\n\n\t// Create a second team with a token that has a later expiration date\n\tteam2, tmTestCleanup2 := createTeam(t, client, org)\n\tt.Cleanup(tmTestCleanup2)\n\n\ttwoDaysLater := currentTime.Add(48 * time.Hour)\n\ttoken2, ttTestCleanup := createTeamTokenWithOptions(t, client, team2, TeamTokenCreateOptions{\n\t\tExpiredAt: &twoDaysLater,\n\t})\n\tt.Cleanup(ttTestCleanup)\n\n\tt.Run(\"with team tokens across multiple teams\", func(t *testing.T) {\n\t\ttokens, err := client.TeamTokens.List(ctx, org.Name, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tokens)\n\t\trequire.Len(t, tokens.Items, 2)\n\t\trequire.ElementsMatch(t, []string{token1.ID, token2.ID}, []string{tokens.Items[0].ID, tokens.Items[1].ID})\n\t})\n\n\tt.Run(\"with filtering by team name\", func(t *testing.T) {\n\t\ttokens, err := client.TeamTokens.List(ctx, org.Name, &TeamTokenListOptions{\n\t\t\tQuery: team1.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tokens)\n\t\trequire.Len(t, tokens.Items, 1)\n\t\trequire.Equal(t, token1.ID, tokens.Items[0].ID)\n\t})\n\n\tt.Run(\"with sorting\", func(t *testing.T) {\n\t\ttokens, err := client.TeamTokens.List(ctx, org.Name, &TeamTokenListOptions{\n\t\t\tSort: \"expired-at\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tokens)\n\t\trequire.Len(t, tokens.Items, 2)\n\t\trequire.Equal(t, []string{token1.ID, token2.ID}, []string{tokens.Items[0].ID, tokens.Items[1].ID})\n\n\t\ttokens, err = client.TeamTokens.List(ctx, org.Name, &TeamTokenListOptions{\n\t\t\tSort: \"-expired-at\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tokens)\n\t\trequire.Len(t, tokens.Items, 2)\n\t\trequire.Equal(t, []string{token2.ID, token1.ID}, []string{tokens.Items[0].ID, tokens.Items[1].ID})\n\t})\n\n\tt.Run(\"with multiple team tokens in a single team\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\t\tdesc1 := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\tmultiToken1, ttTestCleanup := createTeamTokenWithOptions(t, client, team1, TeamTokenCreateOptions{\n\t\t\tDescription: &desc1,\n\t\t})\n\t\tt.Cleanup(ttTestCleanup)\n\n\t\tdesc2 := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\tmultiToken2, ttTestCleanup := createTeamTokenWithOptions(t, client, team1, TeamTokenCreateOptions{\n\t\t\tDescription: &desc2,\n\t\t})\n\t\tt.Cleanup(ttTestCleanup)\n\n\t\ttokens, err := client.TeamTokens.List(ctx, org.Name, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tokens)\n\t\trequire.Len(t, tokens.Items, 4)\n\t\tactualIDs := []string{}\n\t\tfor _, token := range tokens.Items {\n\t\t\tactualIDs = append(actualIDs, token.ID)\n\t\t}\n\t\trequire.ElementsMatch(t, []string{token1.ID, token2.ID, multiToken1.ID, multiToken2.ID},\n\t\t\tactualIDs)\n\t})\n}\n\nfunc TestTeamTokensDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tdefer tmTestCleanup()\n\n\tcreateTeamToken(t, client, tmTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.TeamTokens.Delete(ctx, tmTest.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when a token does not exist\", func(t *testing.T) {\n\t\terr := client.TeamTokens.Delete(ctx, tmTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"without valid team ID\", func(t *testing.T) {\n\t\terr := client.TeamTokens.Delete(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidTeamID)\n\t})\n}\n\nfunc TestTeamTokensDeleteByID(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\ttmTest, tmTestCleanup := createTeam(t, client, nil)\n\tt.Cleanup(tmTestCleanup)\n\n\tt.Run(\"with legacy, descriptionless tokens\", func(t *testing.T) {\n\t\ttoken, _ := createTeamToken(t, client, tmTest)\n\t\terr := client.TeamTokens.DeleteByID(ctx, token.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with multiple team tokens\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\t\tdesc1 := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\ttoken1, _ := createTeamTokenWithOptions(t, client, tmTest, TeamTokenCreateOptions{\n\t\t\tDescription: &desc1,\n\t\t})\n\n\t\tdesc2 := fmt.Sprintf(\"go-tfe-team-token-test-%s\", randomString(t))\n\t\ttoken2, _ := createTeamTokenWithOptions(t, client, tmTest, TeamTokenCreateOptions{\n\t\t\tDescription: &desc2,\n\t\t})\n\n\t\terr := client.TeamTokens.DeleteByID(ctx, token1.ID)\n\t\trequire.NoError(t, err)\n\n\t\terr = client.TeamTokens.DeleteByID(ctx, token2.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"when a token does not exist\", func(t *testing.T) {\n\t\terr := client.TeamTokens.DeleteByID(ctx, \"nonexistent-token-id\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid token ID\", func(t *testing.T) {\n\t\terr := client.TeamTokens.DeleteByID(ctx, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidTokenID)\n\t})\n}\n"
  },
  {
    "path": "test-fixtures/archive-dir/bar.txt",
    "content": "bar\n"
  },
  {
    "path": "test-fixtures/archive-dir/exe",
    "content": ""
  },
  {
    "path": "test-fixtures/archive-dir/foo.txt",
    "content": "foo\n"
  },
  {
    "path": "test-fixtures/archive-dir/sub/zip.txt",
    "content": "zip\n"
  },
  {
    "path": "test-fixtures/config-version/main.tf",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\nresource \"null_resource\" \"foo\" {}\n"
  },
  {
    "path": "test-fixtures/config-version-with-test/main.tf",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\nvariable \"wait_time\" {\n  type = string\n  default = \"0s\"\n}\n\nresource \"null_resource\" \"foo\" {}\n\nresource \"time_sleep\" \"wait_5_seconds\" {\n  depends_on = [null_resource.foo]\n\n  create_duration = var.wait_time\n}\n\nresource \"null_resource\" \"bar\" {\n  depends_on = [time_sleep.wait_5_seconds]\n}\n"
  },
  {
    "path": "test-fixtures/config-version-with-test/main.tftest.hcl",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\n\nrun \"test\" {}\n"
  },
  {
    "path": "test-fixtures/json-state/state.json",
    "content": "{\n  \"format_version\": \"1.0\",\n  \"terraform_version\": \"1.2.0\",\n  \"values\": {\n    \"outputs\": {\n      \"a-decimal\": {\n        \"sensitive\": false,\n        \"value\": 1000.1,\n        \"type\": \"number\"\n      },\n      \"a-false-bool\": {\n        \"sensitive\": false,\n        \"value\": false,\n        \"type\": \"bool\"\n      },\n      \"a-list\": {\n        \"sensitive\": false,\n        \"value\": [\n          \"example\",\n          \"1001\",\n          \"1000.1\"\n        ],\n        \"type\": [\n          \"list\",\n          \"string\"\n        ]\n      },\n      \"a-long-string\": {\n        \"sensitive\": false,\n        \"value\": \"The private integer of the main server instance is where you want to go when you have the most fun in every Terraform instance you can see in the world that you live in except for dogs because they don't run servers in the same place that humans do.\",\n        \"type\": \"string\"\n      },\n      \"a-object\": {\n        \"sensitive\": false,\n        \"value\": {\n          \"bar\": 1000.1,\n          \"example\": 1001\n        },\n        \"type\": [\n          \"object\",\n          {\n            \"bar\": \"number\",\n            \"example\": \"number\"\n          }\n        ]\n      },\n      \"a-sensitive-value\": {\n        \"sensitive\": true,\n        \"value\": \"hopefully you cannot see me\",\n        \"type\": \"string\"\n      },\n      \"a-string\": {\n        \"sensitive\": false,\n        \"value\": \"example string\",\n        \"type\": \"string\"\n      },\n      \"a-true-bool\": {\n        \"sensitive\": false,\n        \"value\": true,\n        \"type\": \"bool\"\n      },\n      \"a-tuple\": {\n        \"sensitive\": false,\n        \"value\": [\n          1,\n          \"example\"\n        ],\n        \"type\": [\n          \"tuple\",\n          [\n            \"number\",\n            \"string\"\n          ]\n        ]\n      },\n      \"an-int\": {\n        \"sensitive\": false,\n        \"value\": 1001,\n        \"type\": \"number\"\n      },\n      \"escapes\": {\n        \"sensitive\": false,\n        \"value\": \"line 1\\nline 2\\n\\\\\\\\\\\\\\\\\\n\",\n        \"type\": \"string\"\n      },\n      \"myoutput\": {\n        \"sensitive\": false,\n        \"value\": {\n          \"nesting1\": {\n            \"nesting2\": {\n              \"nesting3\": \"4263891374290101092\"\n            }\n          }\n        },\n        \"type\": [\n          \"object\",\n          {\n            \"nesting1\": [\n              \"object\",\n              {\n                \"nesting2\": [\n                  \"object\",\n                  {\n                    \"nesting3\": \"string\"\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      \"random\": {\n        \"sensitive\": false,\n        \"value\": \"8b3086889a9ef7a5\",\n        \"type\": \"string\"\n      }\n    },\n    \"root_module\": {\n      \"resources\": [\n        {\n          \"address\": \"null_resource.test\",\n          \"mode\": \"managed\",\n          \"type\": \"null_resource\",\n          \"name\": \"test\",\n          \"provider_name\": \"registry.terraform.io/hashicorp/null\",\n          \"schema_version\": 0,\n          \"values\": {\n            \"id\": \"4263891374290101092\",\n            \"triggers\": {\n              \"hello\": \"wat3\"\n            }\n          },\n          \"sensitive_values\": {\n            \"triggers\": {}\n          }\n        },\n        {\n          \"address\": \"random_id.random\",\n          \"mode\": \"managed\",\n          \"type\": \"random_id\",\n          \"name\": \"random\",\n          \"provider_name\": \"registry.terraform.io/hashicorp/random\",\n          \"schema_version\": 0,\n          \"values\": {\n            \"b64_std\": \"izCGiJqe96U=\",\n            \"b64_url\": \"izCGiJqe96U\",\n            \"byte_length\": 8,\n            \"dec\": \"10029664291421878181\",\n            \"hex\": \"8b3086889a9ef7a5\",\n            \"id\": \"izCGiJqe96U\",\n            \"keepers\": {\n              \"uuid\": \"437a1415-932b-9f74-c214-184d88215353\"\n            },\n            \"prefix\": null\n          },\n          \"sensitive_values\": {\n            \"keepers\": {}\n          }\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "test-fixtures/json-state-outputs/everything.json",
    "content": "{\n  \"a-decimal\": {\n    \"sensitive\": false,\n    \"value\": 1000.1,\n    \"type\": \"number\"\n  },\n  \"a-false-bool\": {\n    \"sensitive\": false,\n    \"value\": false,\n    \"type\": \"bool\"\n  },\n  \"a-list\": {\n    \"sensitive\": false,\n    \"value\": [\n      \"example\",\n      \"1001\",\n      \"1000.1\"\n    ],\n    \"type\": [\n      \"list\",\n      \"string\"\n    ]\n  },\n  \"a-long-string\": {\n    \"sensitive\": false,\n    \"value\": \"The private integer of the main server instance is where you want to go when you have the most fun in every Terraform instance you can see in the world that you live in except for dogs because they don't run servers in the same place that humans do.\",\n    \"type\": \"string\"\n  },\n  \"a-object\": {\n    \"sensitive\": false,\n    \"value\": {\n      \"bar\": 1000.1,\n      \"example\": 1001\n    },\n    \"type\": [\n      \"object\",\n      {\n        \"bar\": \"number\",\n        \"example\": \"number\"\n      }\n    ]\n  },\n  \"a-sensitive-value\": {\n    \"sensitive\": true,\n    \"value\": \"hopefully you cannot see me\",\n    \"type\": \"string\"\n  },\n  \"a-string\": {\n    \"sensitive\": false,\n    \"value\": \"example string\",\n    \"type\": \"string\"\n  },\n  \"a-true-bool\": {\n    \"sensitive\": false,\n    \"value\": true,\n    \"type\": \"bool\"\n  },\n  \"a-tuple\": {\n    \"sensitive\": false,\n    \"value\": [\n      1,\n      \"example\"\n    ],\n    \"type\": [\n      \"tuple\",\n      [\n        \"number\",\n        \"string\"\n      ]\n    ]\n  },\n  \"an-int\": {\n    \"sensitive\": false,\n    \"value\": 1001,\n    \"type\": \"number\"\n  },\n  \"escapes\": {\n    \"sensitive\": false,\n    \"value\": \"line 1\\nline 2\\n\\\\\\\\\\\\\\\\\\n\",\n    \"type\": \"string\"\n  },\n  \"myoutput\": {\n    \"sensitive\": false,\n    \"value\": {\n      \"nesting1\": {\n        \"nesting2\": {\n          \"nesting3\": \"4263891374290101092\"\n        }\n      }\n    },\n    \"type\": [\n      \"object\",\n      {\n        \"nesting1\": [\n          \"object\",\n          {\n            \"nesting2\": [\n              \"object\",\n              {\n                \"nesting3\": \"string\"\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  },\n  \"random\": {\n    \"sensitive\": false,\n    \"value\": \"8b3086889a9ef7a5\",\n    \"type\": \"string\"\n  }\n}\n"
  },
  {
    "path": "test-fixtures/policy-set-version/enforce-mandatory-tags.sentinel",
    "content": "# This policy is a sample policy that has a list of tags and \n# has a rule to confirm the length of the tags.\n\n# # Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\n# List of environment tags\ntags = [\n  \"Production\",\n  \"Staging\",\n]\n\n# Main rule\nmain = rule {\n  length(tags) is 2\n}\n"
  },
  {
    "path": "test-fixtures/policy-set-version/sentinel.hcl",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\npolicy \"enforce-mandatory-tags\" {\n  source = \"./enforce-mandatory-tags.sentinel\"\n  enforcement_level = \"hard-mandatory\"\n}\n"
  },
  {
    "path": "test-fixtures/stack-source/.terraform-version",
    "content": "1.10.0-alpha20240807\n"
  },
  {
    "path": "test-fixtures/stack-source/components.tfstack.hcl",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\nvariable \"prefix\" {\n  type = string\n}\n\nvariable \"instances\" {\n  type = number\n}\n\nrequired_providers {\n  random = {\n    source  = \"hashicorp/random\"\n    version = \"~> 3.5.1\"\n  }\n\n  null = {\n    source  = \"hashicorp/null\"\n    version = \"~> 3.2.2\"\n  }\n}\n\nprovider \"random\" \"this\" {}\nprovider \"null\" \"this\" {}\n\ncomponent \"pet\" {\n  source = \"./pet\"\n\n  inputs = {\n    prefix = var.prefix\n  }\n\n  providers = {\n    random = provider.random.this\n  }\n}\n\ncomponent \"nulls\" {\n  source = \"./nulls\"\n\n  inputs = {\n    pet       = component.pet.name\n    instances = var.instances\n  }\n\n  providers = {\n    null = provider.null.this\n  }\n}\n"
  },
  {
    "path": "test-fixtures/stack-source/deployments.tfdeploy.hcl",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\ndeployment \"simple\" {\n  inputs = {\n    prefix           = \"simple\"\n    instances        = 1\n  }\n}\n\ndeployment \"complex\" {\n  inputs = {\n    prefix           = \"complex\"\n    instances        = 3\n  }\n}\n"
  },
  {
    "path": "test-fixtures/stack-source/nulls/main.tf",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\nterraform {\n  required_providers {\n    null = {\n      source = \"hashicorp/null\"\n      version = \"3.1.1\"\n    }\n  }\n}\n\nvariable \"pet\" {\n  type = string\n}\n\nvariable \"instances\" {\n  type = number\n}\n\nresource \"null_resource\" \"this\" {\n  count = var.instances\n\n  triggers = {\n    pet = var.pet\n  }\n}\n\noutput \"ids\" {\n  value = [for n in null_resource.this: n.id]\n}\n"
  },
  {
    "path": "test-fixtures/stack-source/pet/main.tf",
    "content": "# Copyright IBM Corp. 2018, 2025\n# SPDX-License-Identifier: MPL-2.0\n\nterraform {\n  required_providers {\n    random = {\n      source = \"hashicorp/random\"\n      version = \"3.3.2\"\n    }\n  }\n}\n\nvariable \"prefix\" {\n  type = string\n}\n\nresource \"random_pet\" \"this\" {\n  prefix = var.prefix\n  length = 3\n}\n\noutput \"name\" {\n  value = random_pet.this.id\n}\n"
  },
  {
    "path": "test-fixtures/state-version/terraform.tfstate",
    "content": "{\n  \"version\": 4,\n  \"terraform_version\": \"1.3.6\",\n  \"serial\": 5,\n  \"lineage\": \"8094ef40-1dbd-95cd-1f60-bb25d84d883b\",\n  \"outputs\": {\n    \"test_output_list_string\": {\n      \"value\": [\n        \"us-west-1a\"\n      ],\n      \"type\": [\n        \"list\",\n        \"string\"\n      ]\n    },\n    \"test_output_string\": {\n      \"value\": \"9023256633839603543\",\n      \"type\": \"string\",\n      \"sensitive\": true\n    },\n    \"test_output_tuple_number\": {\n      \"value\": [\n        1,\n        2\n      ],\n      \"type\": [\n        \"tuple\",\n        [\n          \"number\",\n          \"number\"\n        ]\n      ]\n    },\n    \"test_output_tuple_string\": {\n      \"value\": [\n        \"one\",\n        \"two\"\n      ],\n      \"type\": [\n        \"tuple\",\n        [\n          \"string\",\n          \"string\"\n        ]\n      ]\n    },\n    \"test_output_object\": {\n      \"value\": {\n        \"foo\": \"bar\"\n      },\n      \"type\": [\n        \"object\",\n        {\n          \"foo\": \"string\"\n        }\n      ]\n    },\n    \"test_output_number\": {\n      \"value\": 5,\n      \"type\": \"number\"\n    },\n    \"test_output_bool\": {\n      \"value\": true,\n      \"type\": \"bool\"\n    }\n  },\n  \"resources\": [\n    {\n      \"module\": \"module.media_bucket\",\n      \"mode\": \"managed\",\n      \"type\": \"aws_s3_bucket_public_access_block\",\n      \"name\": \"this\",\n      \"provider\": \"provider[\\\"registry.terraform.io/hashicorp/aws\\\"]\",\n      \"instances\": [\n        {\n          \"index_key\": 0,\n          \"schema_version\": 0,\n          \"attributes\": {\n            \"block_public_acls\": true,\n            \"block_public_policy\": true,\n            \"bucket\": \"1234-edited-videos\",\n            \"id\": \"1234-edited-videos\",\n            \"ignore_public_acls\": true,\n            \"restrict_public_buckets\": true\n          },\n          \"sensitive_attributes\": [],\n          \"private\": \"XXXX==\"\n        }\n      ]\n    }\n  ],\n  \"check_results\": null\n}\n"
  },
  {
    "path": "test_config.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\ntype TestConfig struct {\n\tTestsEnabled       bool    `jsonapi:\"attr,tests-enabled\"`\n\tAgentExecutionMode *string `jsonapi:\"attr,agent-execution-mode,omitempty\"`\n\tAgentPoolID        *string `jsonapi:\"attr,agent-pool-id,omitempty\"`\n}\n"
  },
  {
    "path": "test_run.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ TestRuns = (*testRuns)(nil)\n\n// TestRuns describes all the test run related methods that the Terraform\n// Enterprise API supports.\n//\n// **Note: These methods are still in BETA and subject to change.**\ntype TestRuns interface {\n\t// List all the test runs for a given private registry module.\n\tList(ctx context.Context, moduleID RegistryModuleID, options *TestRunListOptions) (*TestRunList, error)\n\n\t// Read a test run by its ID.\n\tRead(ctx context.Context, moduleID RegistryModuleID, testRunID string) (*TestRun, error)\n\n\t// Create a new test run with the given options.\n\tCreate(ctx context.Context, options TestRunCreateOptions) (*TestRun, error)\n\n\t// Logs retrieves the logs for a test run by its ID.\n\tLogs(ctx context.Context, moduleID RegistryModuleID, testRunID string) (io.Reader, error)\n\n\t// Cancel a test run by its ID.\n\tCancel(ctx context.Context, moduleID RegistryModuleID, testRunID string) error\n\n\t// ForceCancel a test run by its ID.\n\tForceCancel(ctx context.Context, moduleID RegistryModuleID, testRunID string) error\n}\n\n// testRuns implements TestRuns.\ntype testRuns struct {\n\tclient *Client\n}\n\n// TestRunStatus represents the status of a test run.\ntype TestRunStatus string\n\n// List all available test run statuses.\nconst (\n\tTestRunPending  TestRunStatus = \"pending\"\n\tTestRunQueued   TestRunStatus = \"queued\"\n\tTestRunRunning  TestRunStatus = \"running\"\n\tTestRunErrored  TestRunStatus = \"errored\"\n\tTestRunCanceled TestRunStatus = \"canceled\"\n\tTestRunFinished TestRunStatus = \"finished\"\n)\n\n// TestStatus represents the status of an individual test within an overall test\n// run.\ntype TestStatus string\n\n// List all available test statuses.\nconst (\n\tTestPending TestStatus = \"pending\"\n\tTestSkip    TestStatus = \"skip\"\n\tTestPass    TestStatus = \"pass\"\n\tTestFail    TestStatus = \"fail\"\n\tTestError   TestStatus = \"error\"\n)\n\n// TestRun represents a Terraform Enterprise test run.\ntype TestRun struct {\n\tID               string                  `jsonapi:\"primary,test-runs\"`\n\tStatus           TestRunStatus           `jsonapi:\"attr,status\"`\n\tStatusTimestamps TestRunStatusTimestamps `jsonapi:\"attr,status-timestamps\"`\n\tTestStatus       TestStatus              `jsonapi:\"attr,test-status\"`\n\tTestsPassed      int                     `jsonapi:\"attr,tests-passed\"`\n\tTestsFailed      int                     `jsonapi:\"attr,tests-failed\"`\n\tTestsErrored     int                     `jsonapi:\"attr,tests-errored\"`\n\tTestsSkipped     int                     `jsonapi:\"attr,tests-skipped\"`\n\tLogReadURL       string                  `jsonapi:\"attr,log-read-url\"`\n\n\t// Relations\n\tConfigurationVersion *ConfigurationVersion `jsonapi:\"relation,configuration-version\"`\n\tRegistryModule       *RegistryModule       `jsonapi:\"relation,registry-module\"`\n}\n\n// TestRunStatusTimestamps holds the timestamps for individual test run\n// statuses.\ntype TestRunStatusTimestamps struct {\n\tCanceledAt      time.Time `jsonapi:\"attr,canceled-at,rfc3339\"`\n\tErroredAt       time.Time `jsonapi:\"attr,errored-at,rfc3339\"`\n\tFinishedAt      time.Time `jsonapi:\"attr,finished-at,rfc3339\"`\n\tForceCanceledAt time.Time `jsonapi:\"attr,force-canceled-at,rfc3339\"`\n\tQueuedAt        time.Time `jsonapi:\"attr,queued-at,rfc3339\"`\n\tStartedAt       time.Time `jsonapi:\"attr,started-at,rfc3339\"`\n}\n\n// TestRunCreateOptions represents the options for creating a run.\ntype TestRunCreateOptions struct {\n\t// Type is a public field utitilized by JSON:API to set the resource type\n\t// via the field tag. It is not a user-defined value and does not need to\n\t// be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,test-runs\"`\n\n\t// If non-empty, requests that only a subset of testing files within the\n\t// ConfigurationVersion should be executed.\n\tFilters []string `jsonapi:\"attr,filters,omitempty\"`\n\n\t// Specifies the directory within the ConfigurationVersion that test files\n\t// should be loaded from. Defaults to \"tests\" if empty.\n\tTestDirectory *string `jsonapi:\"attr,test-directory,omitempty\"`\n\n\t// Verbose prints out the plan and state files for each run block that is\n\t// executed by this TestRun.\n\tVerbose *bool `jsonapi:\"attr,verbose,omitempty\"`\n\n\t// Parallelism controls the number of parallel operations to execute within a single test run.\n\tParallelism *int `jsonapi:\"attr,parallelism,omitempty\"`\n\n\t// Variables allows you to specify terraform input variables for\n\t// a particular run, prioritized over variables defined on the workspace.\n\tVariables []*RunVariable `jsonapi:\"attr,variables,omitempty\"`\n\n\t// ConfigurationVersion specifies the configuration version to use for this\n\t// test run.\n\tConfigurationVersion *ConfigurationVersion `jsonapi:\"relation,configuration-version\"`\n\n\t// RegistryModule specifies the registry module this test run should be\n\t// assigned to.\n\tRegistryModule *RegistryModule `jsonapi:\"relation,registry-module\"`\n}\n\n// TestRunList represents a list of test runs.\ntype TestRunList struct {\n\t*Pagination\n\tItems []*TestRun\n}\n\n// TestRunListOptions represents the options for listing runs.\ntype TestRunListOptions struct {\n\tListOptions\n}\n\n// List all the test runs for a given private registry module.\nfunc (s *testRuns) List(ctx context.Context, moduleID RegistryModuleID, options *TestRunListOptions) (*TestRunList, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", testRunsPath(moduleID), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttrl := &TestRunList{}\n\terr = req.Do(ctx, trl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn trl, nil\n}\n\n// Read a test run by its ID.\nfunc (s *testRuns) Read(ctx context.Context, moduleID RegistryModuleID, testRunID string) (*TestRun, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !validStringID(&testRunID) {\n\t\treturn nil, ErrInvalidTestRunID\n\t}\n\n\tu := fmt.Sprintf(\"%s/%s\", testRunsPath(moduleID), url.PathEscape(testRunID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttr := &TestRun{}\n\terr = req.Do(ctx, tr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tr, nil\n}\n\n// Create a new test run with the given options.\nfunc (s *testRuns) Create(ctx context.Context, options TestRunCreateOptions) (*TestRun, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tmoduleID := RegistryModuleID{\n\t\tOrganization: options.RegistryModule.Organization.Name,\n\t\tName:         options.RegistryModule.Name,\n\t\tProvider:     options.RegistryModule.Provider,\n\t\tNamespace:    options.RegistryModule.Namespace,\n\t\tRegistryName: options.RegistryModule.RegistryName,\n\t}\n\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", testRunsPath(moduleID), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttr := &TestRun{}\n\terr = req.Do(ctx, tr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tr, nil\n}\n\n// Logs retrieves the logs for a test run by its ID.\nfunc (s *testRuns) Logs(ctx context.Context, moduleID RegistryModuleID, testRunID string) (io.Reader, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !validStringID(&testRunID) {\n\t\treturn nil, ErrInvalidTestRunID\n\t}\n\n\ttr, err := s.Read(ctx, moduleID, testRunID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif tr.LogReadURL == \"\" {\n\t\treturn nil, fmt.Errorf(\"test run %s does not have a log URL\", testRunID)\n\t}\n\n\tu, err := url.Parse(tr.LogReadURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid log URL: %w\", err)\n\t}\n\n\tdone := func() (bool, error) {\n\t\ttr, err := s.Read(ctx, moduleID, testRunID)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tswitch tr.Status {\n\t\tcase TestRunErrored, TestRunCanceled, TestRunFinished:\n\t\t\treturn true, nil\n\t\tdefault:\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\treturn &LogReader{\n\t\tclient: s.client,\n\t\tctx:    ctx,\n\t\tdone:   done,\n\t\tlogURL: u,\n\t}, nil\n}\n\n// Cancel a test run by its ID.\nfunc (s *testRuns) Cancel(ctx context.Context, moduleID RegistryModuleID, testRunID string) error {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tif !validStringID(&testRunID) {\n\t\treturn ErrInvalidTestRunID\n\t}\n\n\tu := fmt.Sprintf(\"%s/%s/cancel\", testRunsPath(moduleID), url.PathEscape(testRunID))\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// ForceCancel a test run by its ID.\nfunc (s *testRuns) ForceCancel(ctx context.Context, moduleID RegistryModuleID, testRunID string) error {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tif !validStringID(&testRunID) {\n\t\treturn ErrInvalidTestRunID\n\t}\n\n\tu := fmt.Sprintf(\"%s/%s/force-cancel\", testRunsPath(moduleID), url.PathEscape(testRunID))\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o TestRunCreateOptions) valid() error {\n\tif o.ConfigurationVersion == nil {\n\t\treturn ErrInvalidConfigVersionID\n\t}\n\n\tif o.RegistryModule == nil {\n\t\treturn ErrRequiredRegistryModule\n\t}\n\n\tif o.RegistryModule.Organization == nil {\n\t\treturn ErrRequiredOrg\n\t}\n\n\treturn nil\n}\n\nfunc testRunsPath(moduleID RegistryModuleID) string {\n\treturn fmt.Sprintf(\"organizations/%s/tests/registry-modules/%s/%s/%s/%s/test-runs\",\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(string(moduleID.RegistryName)),\n\t\turl.PathEscape(moduleID.Namespace),\n\t\turl.PathEscape(moduleID.Name),\n\t\turl.PathEscape(moduleID.Provider))\n}\n"
  },
  {
    "path": "test_run_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTestRunsList_RunDependent(t *testing.T) {\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, registryModuleTestCleanup := createBranchBasedRegistryModuleWithTests(t, client, orgTest)\n\tdefer registryModuleTestCleanup()\n\n\tid := RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rmTest.Name,\n\t\tProvider:     rmTest.Provider,\n\t\tNamespace:    rmTest.Namespace,\n\t\tRegistryName: rmTest.RegistryName,\n\t}\n\n\ttrTest1, trTestCleanup1 := createTestRun(t, client, rmTest)\n\ttrTest2, trTestCleanup2 := createTestRun(t, client, rmTest)\n\n\tdefer trTestCleanup1()\n\tdefer trTestCleanup2()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\ttrl, err := client.TestRuns.List(ctx, id, nil)\n\t\tvar found []string\n\t\tfor _, r := range trl.Items {\n\t\t\tfound = append(found, r.ID)\n\t\t}\n\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, found, trTest1.ID)\n\t\tassert.Contains(t, found, trTest2.ID)\n\t\tassert.Equal(t, 1, trl.CurrentPage)\n\t\tassert.Equal(t, 2, trl.TotalCount)\n\t})\n\n\tt.Run(\"empty list options\", func(t *testing.T) {\n\t\ttrl, err := client.TestRuns.List(ctx, id, &TestRunListOptions{})\n\t\tvar found []string\n\t\tfor _, r := range trl.Items {\n\t\t\tfound = append(found, r.ID)\n\t\t}\n\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, found, trTest1.ID)\n\t\tassert.Contains(t, found, trTest2.ID)\n\t\tassert.Equal(t, 1, trl.CurrentPage)\n\t\tassert.Equal(t, 2, trl.TotalCount)\n\t})\n\n\tt.Run(\"with page size\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\ttrl, err := client.TestRuns.List(ctx, id, &TestRunListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, trl.Items)\n\t\tassert.Equal(t, 999, trl.CurrentPage)\n\t\tassert.Equal(t, 2, trl.TotalCount)\n\t})\n}\n\nfunc TestTestRunsRead_RunDependent(t *testing.T) {\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, registryModuleTestCleanup := createBranchBasedRegistryModuleWithTests(t, client, orgTest)\n\tdefer registryModuleTestCleanup()\n\n\tid := RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rmTest.Name,\n\t\tProvider:     rmTest.Provider,\n\t\tNamespace:    rmTest.Namespace,\n\t\tRegistryName: rmTest.RegistryName,\n\t}\n\n\ttrTest, trTestCleanup := createTestRun(t, client, rmTest)\n\tdefer trTestCleanup()\n\n\tt.Run(\"when the test run exists\", func(t *testing.T) {\n\t\ttr, err := client.TestRuns.Read(ctx, id, trTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, trTest.ID, tr.ID)\n\t})\n\tt.Run(\"when the test run does not exist\", func(t *testing.T) {\n\t\t_, err := client.TestRuns.Read(ctx, id, \"trun-NoTaReAlId\")\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, ErrResourceNotFound, err)\n\t})\n}\n\nfunc TestTestRunsCreate(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, rmTestCleanup := createBranchBasedRegistryModuleWithTests(t, client, orgTest)\n\tdefer rmTestCleanup()\n\n\tcvTest, cvTestCleanup := createUploadedTestRunConfigurationVersion(t, client, rmTest)\n\tdefer cvTestCleanup()\n\n\tt.Run(\"with a configuration version\", func(t *testing.T) {\n\t\toptions := TestRunCreateOptions{\n\t\t\tConfigurationVersion: cvTest,\n\t\t\tRegistryModule:       rmTest,\n\t\t}\n\n\t\t_, err := client.TestRuns.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t})\n\n\tp := 10\n\tt.Run(\"with a custom parallelism version\", func(t *testing.T) {\n\t\toptions := TestRunCreateOptions{\n\t\t\tConfigurationVersion: cvTest,\n\t\t\tRegistryModule:       rmTest,\n\t\t\tParallelism:          &p,\n\t\t}\n\t\t_, err := client.TestRuns.Create(ctx, options)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"without a configuration version\", func(t *testing.T) {\n\t\toptions := TestRunCreateOptions{\n\t\t\tRegistryModule: rmTest,\n\t\t}\n\n\t\t_, err := client.TestRuns.Create(ctx, options)\n\t\trequire.Equal(t, ErrInvalidConfigVersionID, err)\n\t})\n\tt.Run(\"without a module\", func(t *testing.T) {\n\t\toptions := TestRunCreateOptions{\n\t\t\tConfigurationVersion: cvTest,\n\t\t}\n\n\t\t_, err := client.TestRuns.Create(ctx, options)\n\t\trequire.Equal(t, ErrRequiredRegistryModule, err)\n\t})\n\tt.Run(\"without an organization\", func(t *testing.T) {\n\t\trm := &RegistryModule{\n\t\t\tID:              rmTest.ID,\n\t\t\tName:            rmTest.Name,\n\t\t\tProvider:        rmTest.Provider,\n\t\t\tRegistryName:    rmTest.RegistryName,\n\t\t\tNamespace:       rmTest.Namespace,\n\t\t\tNoCode:          rmTest.NoCode,\n\t\t\tPermissions:     rmTest.Permissions,\n\t\t\tStatus:          rmTest.Status,\n\t\t\tVCSRepo:         rmTest.VCSRepo,\n\t\t\tVersionStatuses: rmTest.VersionStatuses,\n\t\t\tCreatedAt:       rmTest.CreatedAt,\n\t\t\tUpdatedAt:       rmTest.UpdatedAt,\n\t\t\tOrganization:    nil, // Leave this as nil.\n\t\t}\n\n\t\toptions := TestRunCreateOptions{\n\t\t\tConfigurationVersion: cvTest,\n\t\t\tRegistryModule:       rm,\n\t\t}\n\n\t\t_, err := client.TestRuns.Create(ctx, options)\n\t\trequire.Equal(t, ErrRequiredOrg, err)\n\t})\n}\n\nfunc TestTestRunsLogs_RunDependent(t *testing.T) {\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, rmTestCleanup := createBranchBasedRegistryModuleWithTests(t, client, orgTest)\n\tdefer rmTestCleanup()\n\n\tid := RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rmTest.Name,\n\t\tProvider:     rmTest.Provider,\n\t\tNamespace:    rmTest.Namespace,\n\t\tRegistryName: rmTest.RegistryName,\n\t}\n\n\ttr, trCleanup := createTestRun(t, client, rmTest)\n\tdefer trCleanup()\n\n\tt.Run(\"when the log exists\", func(t *testing.T) {\n\t\twaitUntilTestRunStatus(t, client, id, tr, TestRunFinished, 15)\n\n\t\tlogReader, err := client.TestRuns.Logs(ctx, id, tr.ID)\n\t\trequire.NoError(t, err)\n\n\t\tlogs, err := io.ReadAll(logReader)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Contains(t, string(logs), \"Success!\")\n\t})\n\n\tt.Run(\"when the log does not exist\", func(t *testing.T) {\n\t\tlogs, err := client.TestRuns.Logs(ctx, id, \"notreal\")\n\t\tassert.Nil(t, logs)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestTestRunsCancel_RunDependent(t *testing.T) {\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, rmTestCleanup := createBranchBasedRegistryModuleWithTests(t, client, orgTest)\n\tdefer rmTestCleanup()\n\n\tid := RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rmTest.Name,\n\t\tProvider:     rmTest.Provider,\n\t\tNamespace:    rmTest.Namespace,\n\t\tRegistryName: rmTest.RegistryName,\n\t}\n\n\ttr, trCleanup := createTestRun(t, client, rmTest, &RunVariable{\n\t\tKey:   \"wait_time\",\n\t\tValue: \"5s\", // Create a long-running test run that we'll have time to cancel.\n\t})\n\tdefer trCleanup()\n\n\tt.Run(\"when the run exists\", func(t *testing.T) {\n\t\terr := client.TestRuns.Cancel(ctx, id, tr.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\t/* TODO: Enable force cancel test when supported.\n\tt.Run(\"can force cancel\", func(t *testing.T) {\n\t\t  var err error\n\n\t\tfor i := 1; ; i++ {\n\t\t  \ttr, err = client.TestRuns.Read(ctx, id, tr.ID)\n\t\t  \trequire.NoError(t, err)\n\n\t\t\t// TODO: Check if we can force cancel yet, not available in the\n\t        //       API yet.\n\n\t\t\tif i > 30 {\n\t\t\t\tt.Fatal(\"Timeout waiting for run to be canceled\")\n\t\t\t}\n\n\t\t\ttime.Sleep(time.Second)\n\t\t}\n\t})\n\t*/\n\n\tt.Run(\"when the run does not exist\", func(t *testing.T) {\n\t\terr := client.TestRuns.Cancel(ctx, id, \"notreal\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n}\n"
  },
  {
    "path": "test_variables.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ TestVariables = (*testVariables)(nil)\n\n// Variables describes all the variable related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/tests\ntype TestVariables interface {\n\t// List all the test variables associated with the given module.\n\tList(ctx context.Context, moduleID RegistryModuleID, options *VariableListOptions) (*VariableList, error)\n\n\t// Read a test variable by its ID.\n\tRead(ctx context.Context, moduleID RegistryModuleID, variableID string) (*Variable, error)\n\n\t// Create is used to create a new variable.\n\tCreate(ctx context.Context, moduleID RegistryModuleID, options VariableCreateOptions) (*Variable, error)\n\n\t// Update values of an existing variable.\n\tUpdate(ctx context.Context, moduleID RegistryModuleID, variableID string, options VariableUpdateOptions) (*Variable, error)\n\n\t// Delete a variable by its ID.\n\tDelete(ctx context.Context, moduleID RegistryModuleID, variableID string) error\n}\n\n// variables implements Variables.\ntype testVariables struct {\n\tclient *Client\n}\n\n// List all the variables associated with the given module.\nfunc (s *testVariables) List(ctx context.Context, moduleID RegistryModuleID, options *VariableListOptions) (*VariableList, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"GET\", testVarsPath(moduleID), options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvl := &VariableList{}\n\terr = req.Do(ctx, vl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vl, nil\n}\n\n// Read a variable by its ID.\nfunc (s *testVariables) Read(ctx context.Context, moduleID RegistryModuleID, variableID string) (*Variable, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !validStringID(&variableID) {\n\t\treturn nil, ErrInvalidVariableID\n\t}\n\n\tu := fmt.Sprintf(\"%s/%s\", testVarsPath(moduleID), url.PathEscape(variableID))\n\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &Variable{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, err\n}\n\n// Create is used to create a new variable.\nfunc (s *testVariables) Create(ctx context.Context, moduleID RegistryModuleID, options VariableCreateOptions) (*Variable, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := s.client.NewRequest(\"POST\", testVarsPath(moduleID), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &Variable{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, nil\n}\n\n// Update values of an existing variable.\nfunc (s *testVariables) Update(ctx context.Context, moduleID RegistryModuleID, variableID string, options VariableUpdateOptions) (*Variable, error) {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\tif !validStringID(&variableID) {\n\t\treturn nil, ErrInvalidVariableID\n\t}\n\n\tu := fmt.Sprintf(\"%s/%s\", testVarsPath(moduleID), url.PathEscape(variableID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &Variable{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, nil\n}\n\n// Delete a variable by its ID.\nfunc (s *testVariables) Delete(ctx context.Context, moduleID RegistryModuleID, variableID string) error {\n\tif err := moduleID.valid(); err != nil {\n\t\treturn err\n\t}\n\tif !validStringID(&variableID) {\n\t\treturn ErrInvalidVariableID\n\t}\n\n\tu := fmt.Sprintf(\"%s/%s\", testVarsPath(moduleID), url.PathEscape(variableID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc testVarsPath(moduleID RegistryModuleID) string {\n\treturn fmt.Sprintf(\"organizations/%s/tests/registry-modules/%s/%s/%s/%s/vars\",\n\t\turl.PathEscape(moduleID.Organization),\n\t\turl.PathEscape(string(moduleID.RegistryName)),\n\t\turl.PathEscape(moduleID.Namespace),\n\t\turl.PathEscape(moduleID.Name),\n\t\turl.PathEscape(moduleID.Provider))\n}\n"
  },
  {
    "path": "test_variables_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTestVariablesList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, registryModuleTestCleanup := createBranchBasedRegistryModule(t, client, orgTest)\n\tdefer registryModuleTestCleanup()\n\n\tid := RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rmTest.Name,\n\t\tProvider:     rmTest.Provider,\n\t\tNamespace:    rmTest.Namespace,\n\t\tRegistryName: rmTest.RegistryName,\n\t}\n\n\ttv1, tvCleanup1 := createTestVariable(t, client, rmTest)\n\ttv2, tvCleanup2 := createTestVariable(t, client, rmTest)\n\n\tdefer tvCleanup1()\n\tdefer tvCleanup2()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\ttrl, err := client.TestVariables.List(ctx, id, nil)\n\t\tvar found []string\n\t\tfor _, r := range trl.Items {\n\t\t\tfound = append(found, r.ID)\n\t\t}\n\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, found, tv1.ID)\n\t\tassert.Contains(t, found, tv2.ID)\n\t\tassert.Equal(t, 1, trl.CurrentPage)\n\t\tassert.Equal(t, 2, trl.TotalCount)\n\t})\n\n\tt.Run(\"empty list options\", func(t *testing.T) {\n\t\ttrl, err := client.TestVariables.List(ctx, id, &VariableListOptions{})\n\t\tvar found []string\n\t\tfor _, r := range trl.Items {\n\t\t\tfound = append(found, r.ID)\n\t\t}\n\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, found, tv1.ID)\n\t\tassert.Contains(t, found, tv2.ID)\n\t\tassert.Equal(t, 1, trl.CurrentPage)\n\t\tassert.Equal(t, 2, trl.TotalCount)\n\t})\n\n\tt.Run(\"with page size\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\ttvl, err := client.TestVariables.List(ctx, id, &VariableListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, tvl.Items)\n\t\tassert.Equal(t, 999, tvl.CurrentPage)\n\t\tassert.Equal(t, 2, tvl.TotalCount)\n\t})\n}\n\nfunc TestTestVariablesRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, registryModuleTestCleanup := createBranchBasedRegistryModule(t, client, orgTest)\n\tdefer registryModuleTestCleanup()\n\n\tid := RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rmTest.Name,\n\t\tProvider:     rmTest.Provider,\n\t\tNamespace:    rmTest.Namespace,\n\t\tRegistryName: rmTest.RegistryName,\n\t}\n\n\ttv, tvCleanup := createTestVariable(t, client, rmTest)\n\n\tdefer tvCleanup()\n\n\tt.Run(\"when the variable exists\", func(t *testing.T) {\n\t\tv, err := client.TestVariables.Read(ctx, id, tv.ID)\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, tv.ID, v.ID)\n\t\tassert.Equal(t, tv.Category, v.Category)\n\t\tassert.Equal(t, tv.HCL, v.HCL)\n\t\tassert.Equal(t, tv.Key, v.Key)\n\t\tassert.Equal(t, tv.Sensitive, v.Sensitive)\n\t\tassert.Equal(t, tv.Value, v.Value)\n\t\tassert.Equal(t, tv.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"when the variable does not exist\", func(t *testing.T) {\n\t\tv, err := client.TestVariables.Read(ctx, id, \"nonexisting\")\n\t\tassert.Nil(t, v)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"without a valid module ID\", func(t *testing.T) {\n\t\tv, err := client.TestVariables.Read(ctx, RegistryModuleID{}, tv.ID)\n\t\tassert.Nil(t, v)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n}\n\nfunc TestTestVariablesCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, registryModuleTestCleanup := createBranchBasedRegistryModule(t, client, orgTest)\n\tdefer registryModuleTestCleanup()\n\n\tid := RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rmTest.Name,\n\t\tProvider:     rmTest.Provider,\n\t\tNamespace:    rmTest.Namespace,\n\t\tRegistryName: rmTest.RegistryName,\n\t}\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:         String(randomKeyValue(t)),\n\t\t\tValue:       String(randomStringWithoutSpecialChar(t)),\n\t\t\tCategory:    Category(CategoryEnv),\n\t\t\tDescription: String(\"testing\"),\n\t\t}\n\n\t\tv, err := client.TestVariables.Create(ctx, id, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.Equal(t, *options.Description, v.Description)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options has an empty string value\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:         String(randomKeyValue(t)),\n\t\t\tValue:       String(\"\"),\n\t\t\tDescription: String(\"testing\"),\n\t\t\tCategory:    Category(CategoryEnv),\n\t\t}\n\n\t\tv, err := client.TestVariables.Create(ctx, id, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.Equal(t, *options.Description, v.Description)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options has an empty string description\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:         String(randomKeyValue(t)),\n\t\t\tValue:       String(randomStringWithoutSpecialChar(t)),\n\t\t\tDescription: String(\"\"),\n\t\t\tCategory:    Category(CategoryEnv),\n\t\t}\n\n\t\tv, err := client.TestVariables.Create(ctx, id, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.Equal(t, *options.Description, v.Description)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options has a too-long description\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:         String(randomKeyValue(t)),\n\t\t\tValue:       String(randomStringWithoutSpecialChar(t)),\n\t\t\tDescription: String(\"tortor aliquam nulla go lint is fussy about spelling cras fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel pretium lectus quam id leo in vitae turpis massa sed elementum tempus egestas sed sed risus pretium quam vulputate dignissim suspendisse in est ante in nibh mauris cursus mattis molestie a iaculis at erat pellentesque adipiscing commodo elit at imperdiet dui accumsan sit amet nulla redacted morbi tempus iaculis urna id volutpat lacus laoreet non curabitur gravida arcu ac tortor dignissim convallis aenean et tortor\"),\n\t\t\tCategory:    Category(CategoryEnv),\n\t\t}\n\n\t\t_, err := client.TestVariables.Create(ctx, id, options)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"when options is missing value\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:      String(randomKeyValue(t)),\n\t\t\tCategory: Category(CategoryEnv),\n\t\t}\n\n\t\tv, err := client.TestVariables.Create(ctx, id, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, \"\", v.Value)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options is missing key\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tValue:    String(randomStringWithoutSpecialChar(t)),\n\t\t\tCategory: Category(CategoryEnv),\n\t\t}\n\n\t\t_, err := client.TestVariables.Create(ctx, id, options)\n\t\tassert.Equal(t, err, ErrRequiredKey)\n\t})\n\n\tt.Run(\"when options has an empty key\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:      String(\"\"),\n\t\t\tValue:    String(randomStringWithoutSpecialChar(t)),\n\t\t\tCategory: Category(CategoryEnv),\n\t\t}\n\n\t\t_, err := client.TestVariables.Create(ctx, id, options)\n\t\tassert.Equal(t, err, ErrRequiredKey)\n\t})\n\n\tt.Run(\"when options is missing category\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:   String(randomKeyValue(t)),\n\t\t\tValue: String(randomStringWithoutSpecialChar(t)),\n\t\t}\n\n\t\t_, err := client.TestVariables.Create(ctx, id, options)\n\t\tassert.Equal(t, err, ErrRequiredCategory)\n\t})\n}\n\nfunc TestTestVariablesUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, registryModuleTestCleanup := createBranchBasedRegistryModule(t, client, orgTest)\n\tdefer registryModuleTestCleanup()\n\n\tid := RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rmTest.Name,\n\t\tProvider:     rmTest.Provider,\n\t\tNamespace:    rmTest.Namespace,\n\t\tRegistryName: rmTest.RegistryName,\n\t}\n\n\tvTest, tvCleanup1 := createTestVariable(t, client, rmTest)\n\n\tdefer tvCleanup1()\n\n\tt.Run(\"without any changes\", func(t *testing.T) {\n\t\tv, err := client.TestVariables.Update(ctx, id, vTest.ID, VariableUpdateOptions{})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, vTest.ID, v.ID)\n\t\tassert.Equal(t, vTest.Key, v.Key)\n\t\tassert.Equal(t, vTest.Value, v.Value)\n\t\tassert.Equal(t, vTest.Description, v.Description)\n\t\tassert.Equal(t, vTest.Category, v.Category)\n\t\tassert.Equal(t, vTest.HCL, v.HCL)\n\t\tassert.Equal(t, vTest.Sensitive, v.Sensitive)\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := VariableUpdateOptions{\n\t\t\tKey:   String(\"newname\"),\n\t\t\tValue: String(\"newvalue\"),\n\t\t\tHCL:   Bool(true),\n\t\t}\n\n\t\tv, err := client.TestVariables.Update(ctx, id, vTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.HCL, v.HCL)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"when updating a subset of values\", func(t *testing.T) {\n\t\toptions := VariableUpdateOptions{\n\t\t\tKey: String(\"someothername\"),\n\t\t\tHCL: Bool(false),\n\t\t}\n\n\t\tv, err := client.TestVariables.Update(ctx, id, vTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.HCL, v.HCL)\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"with sensitive set\", func(t *testing.T) {\n\t\toptions := VariableUpdateOptions{\n\t\t\tSensitive: Bool(true),\n\t\t}\n\n\t\tv, err := client.TestVariables.Update(ctx, id, vTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Sensitive, v.Sensitive)\n\t\tassert.Empty(t, v.Value) // Because its now sensitive\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"with invalid variable ID\", func(t *testing.T) {\n\t\t_, err := client.TestVariables.Update(ctx, id, badIdentifier, VariableUpdateOptions{})\n\t\tassert.Equal(t, err, ErrInvalidVariableID)\n\t})\n}\n\nfunc TestTestVariablesDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\trmTest, registryModuleTestCleanup := createBranchBasedRegistryModule(t, client, orgTest)\n\tdefer registryModuleTestCleanup()\n\n\tid := RegistryModuleID{\n\t\tOrganization: orgTest.Name,\n\t\tName:         rmTest.Name,\n\t\tProvider:     rmTest.Provider,\n\t\tNamespace:    rmTest.Namespace,\n\t\tRegistryName: rmTest.RegistryName,\n\t}\n\n\tvTest, _ := createTestVariable(t, client, rmTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.TestVariables.Delete(ctx, id, vTest.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with non existing variable ID\", func(t *testing.T) {\n\t\terr := client.TestVariables.Delete(ctx, id, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid variable ID\", func(t *testing.T) {\n\t\terr := client.TestVariables.Delete(ctx, id, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidVariableID)\n\t})\n}\n"
  },
  {
    "path": "tfe.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"log\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/go-querystring/query\"\n\tcleanhttp \"github.com/hashicorp/go-cleanhttp\"\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n\t\"github.com/hashicorp/jsonapi\"\n\t\"golang.org/x/time/rate\"\n\n\tslug \"github.com/hashicorp/go-slug\"\n)\n\nconst (\n\t_userAgent               = \"go-tfe\"\n\t_headerRateLimit         = \"X-RateLimit-Limit\"\n\t_headerRateReset         = \"X-RateLimit-Reset\"\n\t_headerAppName           = \"TFP-AppName\"\n\t_headerAPIVersion        = \"TFP-API-Version\"\n\t_headerTFEVersion        = \"X-TFE-Version\"\n\t_headerTFENumericVersion = \"X-TFE-Current-Version\"\n\t_includeQueryParam       = \"include\"\n\n\tDefaultAddress      = \"https://app.terraform.io\"\n\tDefaultBasePath     = \"/api/v2/\"\n\tDefaultRegistryPath = \"/api/registry/\"\n\t// PingEndpoint is a no-op API endpoint used to configure the rate limiter\n\tPingEndpoint       = \"ping\"\n\tContentTypeJSONAPI = \"application/vnd.api+json\"\n)\n\n// RetryLogHook allows a function to run before each retry.\n\ntype RetryLogHook func(attemptNum int, resp *http.Response)\n\n// Config provides configuration details to the API client.\n\ntype Config struct {\n\t// The address of the Terraform Enterprise API.\n\tAddress string\n\n\t// The base path on which the API is served.\n\tBasePath string\n\n\t// The base path for the Registry API\n\tRegistryBasePath string\n\n\t// API token used to access the Terraform Enterprise API.\n\tToken string\n\n\t// Headers that will be added to every request.\n\tHeaders http.Header\n\n\t// A custom HTTP client to use.\n\tHTTPClient *http.Client\n\n\t// RetryLogHook is invoked each time a request is retried.\n\tRetryLogHook RetryLogHook\n\n\t// RetryServerErrors enables the retry logic in the client.\n\tRetryServerErrors bool\n}\n\n// DefaultConfig returns a default config structure.\n\nfunc DefaultConfig() *Config {\n\tconfig := &Config{\n\t\tAddress:           os.Getenv(\"TFE_ADDRESS\"),\n\t\tBasePath:          DefaultBasePath,\n\t\tRegistryBasePath:  DefaultRegistryPath,\n\t\tToken:             os.Getenv(\"TFE_TOKEN\"),\n\t\tHeaders:           make(http.Header),\n\t\tHTTPClient:        cleanhttp.DefaultPooledClient(),\n\t\tRetryServerErrors: false,\n\t}\n\n\t// Set the default address if none is given.\n\tif config.Address == \"\" {\n\t\tif host := os.Getenv(\"TFE_HOSTNAME\"); host != \"\" {\n\t\t\tconfig.Address = fmt.Sprintf(\"https://%s\", host)\n\t\t} else {\n\t\t\tconfig.Address = DefaultAddress\n\t\t}\n\t}\n\n\t// Set the default user agent.\n\tconfig.Headers.Set(\"User-Agent\", _userAgent)\n\n\treturn config\n}\n\n// Client is the Terraform Enterprise API client. It provides the basic\n// connectivity and configuration for accessing the TFE API\ntype Client struct {\n\tbaseURL                 *url.URL\n\tregistryBaseURL         *url.URL\n\ttoken                   string\n\theaders                 http.Header\n\thttp                    *retryablehttp.Client\n\tlimiter                 *rate.Limiter\n\tretryLogHook            RetryLogHook\n\tretryServerErrors       bool\n\tremoteAPIVersion        string\n\tremoteTFEVersion        string\n\tremoteTFENumericVersion string\n\tappName                 string\n\n\tAdmin                           Admin\n\tAgents                          Agents\n\tAgentPools                      AgentPools\n\tAgentTokens                     AgentTokens\n\tApplies                         Applies\n\tAuditTrails                     AuditTrails\n\tAWSOIDCConfigurations           AWSOIDCConfigurations\n\tGCPOIDCConfigurations           GCPOIDCConfigurations\n\tAzureOIDCConfigurations         AzureOIDCConfigurations\n\tVaultOIDCConfigurations         VaultOIDCConfigurations\n\tComments                        Comments\n\tConfigurationVersions           ConfigurationVersions\n\tCostEstimates                   CostEstimates\n\tGHAInstallations                GHAInstallations\n\tGPGKeys                         GPGKeys\n\tNotificationConfigurations      NotificationConfigurations\n\tOAuthClients                    OAuthClients\n\tOAuthTokens                     OAuthTokens\n\tOrganizationAuditConfigurations OrganizationAuditConfigurations\n\tOrganizationMemberships         OrganizationMemberships\n\tOrganizations                   Organizations\n\tOrganizationTags                OrganizationTags\n\tOrganizationTokens              OrganizationTokens\n\tPlans                           Plans\n\tPlanExports                     PlanExports\n\tPolicies                        Policies\n\tPolicyChecks                    PolicyChecks\n\tPolicyEvaluations               PolicyEvaluations\n\tPolicySetOutcomes               PolicySetOutcomes\n\tPolicySetParameters             PolicySetParameters\n\tPolicySetVersions               PolicySetVersions\n\tPolicySets                      PolicySets\n\tQueryRuns                       QueryRuns\n\tRegistryModules                 RegistryModules\n\tRegistryNoCodeModules           RegistryNoCodeModules\n\tRegistryProviders               RegistryProviders\n\tRegistryProviderPlatforms       RegistryProviderPlatforms\n\tRegistryProviderVersions        RegistryProviderVersions\n\tReservedTagKeys                 ReservedTagKeys\n\tRuns                            Runs\n\tRunEvents                       RunEvents\n\tRunTasks                        RunTasks\n\tRunTasksIntegration             RunTasksIntegration\n\tRunTriggers                     RunTriggers\n\tSSHKeys                         SSHKeys\n\tStacks                          Stacks\n\tHYOKConfigurations              HYOKConfigurations\n\tHYOKCustomerKeyVersions         HYOKCustomerKeyVersions\n\tHYOKEncryptedDataKeys           HYOKEncryptedDataKeys\n\tStackConfigurations             StackConfigurations\n\tStackConfigurationSummaries     StackConfigurationSummaries\n\tStackDeployments                StackDeployments\n\tStackDeploymentGroups           StackDeploymentGroups\n\tStackDeploymentGroupSummaries   StackDeploymentGroupSummaries\n\tStackDeploymentRuns             StackDeploymentRuns\n\tStackDeploymentSteps            StackDeploymentSteps\n\tStackDiagnostics                StackDiagnostics\n\tStackStates                     StackStates\n\tStateVersionOutputs             StateVersionOutputs\n\tStateVersions                   StateVersions\n\tTaskResults                     TaskResults\n\tTaskStages                      TaskStages\n\tTeams                           Teams\n\tTeamAccess                      TeamAccesses\n\tTeamMembers                     TeamMembers\n\tTeamProjectAccess               TeamProjectAccesses\n\tTeamTokens                      TeamTokens\n\tTestRuns                        TestRuns\n\tTestVariables                   TestVariables\n\tUsers                           Users\n\tUserTokens                      UserTokens\n\tVariables                       Variables\n\tVariableSets                    VariableSets\n\tVariableSetVariables            VariableSetVariables\n\tWorkspaces                      Workspaces\n\tWorkspaceResources              WorkspaceResources\n\tWorkspaceRunTasks               WorkspaceRunTasks\n\tProjects                        Projects\n\n\tMeta Meta\n}\n\n// Admin is the the Terraform Enterprise Admin API. It provides access to site\n// wide admin settings. These are only available for Terraform Enterprise and\n// do not function against HCP Terraform\ntype Admin struct {\n\tOrganizations     AdminOrganizations\n\tWorkspaces        AdminWorkspaces\n\tRuns              AdminRuns\n\tTerraformVersions AdminTerraformVersions\n\tOPAVersions       AdminOPAVersions\n\tSentinelVersions  AdminSentinelVersions\n\tUsers             AdminUsers\n\tSettings          *AdminSettings\n}\n\n// Meta contains any HCP Terraform APIs which provide data about the API itself.\ntype Meta struct {\n\tIPRanges IPRanges\n}\n\n// doForeignPUTRequest performs a PUT request using the specific data body. The Content-Type\n// header is set to application/octet-stream but no Authentication header is sent. No response\n// body is decoded.\nfunc (c *Client) doForeignPUTRequest(ctx context.Context, foreignURL string, data io.Reader) error {\n\tu, err := url.Parse(foreignURL)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"specified URL was not valid: %w\", err)\n\t}\n\n\treqHeaders := make(http.Header)\n\treqHeaders.Set(\"Accept\", \"application/json, */*\")\n\treqHeaders.Set(\"Content-Type\", \"application/octet-stream\")\n\n\treq, err := retryablehttp.NewRequest(\"PUT\", u.String(), data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Set the default headers.\n\tfor k, v := range c.headers {\n\t\treq.Header[k] = v\n\t}\n\n\t// Set the request specific headers.\n\tfor k, v := range reqHeaders {\n\t\treq.Header[k] = v\n\t}\n\n\trequest := &ClientRequest{\n\t\tretryableRequest: req,\n\t\thttp:             c.http,\n\t\tHeader:           req.Header,\n\t}\n\n\treturn request.DoJSON(ctx, nil)\n}\n\n// NewRequest performs some basic API request preparation based on the method\n// specified. For GET requests, the reqBody is encoded as query parameters.\n// For DELETE, PATCH, and POST requests, the request body is serialized as JSONAPI.\n// For PUT requests, the request body is sent as a stream of bytes.\nfunc (c *Client) NewRequest(method, path string, reqBody any) (*ClientRequest, error) {\n\treturn c.NewRequestWithAdditionalQueryParams(method, path, reqBody, nil)\n}\n\n// NewRequestWithAdditionalQueryParams performs some basic API request\n// preparation based on the method specified. For GET requests, the reqBody is\n// encoded as query parameters. For DELETE, PATCH, and POST requests, the\n// request body is serialized as JSONAPI. For PUT requests, the request body is\n// sent as a stream of bytes. Additional query parameters can be added to the\n// request as a string map. Note that if a key exists in both the reqBody and\n// additionalQueryParams, the value in additionalQueryParams will be used.\nfunc (c *Client) NewRequestWithAdditionalQueryParams(method, path string, reqBody any, additionalQueryParams map[string][]string) (*ClientRequest, error) {\n\tvar u *url.URL\n\tvar err error\n\tif strings.Contains(path, \"/api/registry/\") {\n\t\tu, err = c.registryBaseURL.Parse(path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tu, err = c.baseURL.Parse(path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// Will contain combined query values from path parsing and\n\t// additionalQueryParams parameter\n\tq := make(url.Values)\n\n\t// Create a request specific headers map.\n\treqHeaders := make(http.Header)\n\treqHeaders.Set(\"Authorization\", \"Bearer \"+c.token)\n\n\tvar body any\n\tswitch method {\n\tcase \"GET\":\n\t\treqHeaders.Set(\"Accept\", ContentTypeJSONAPI)\n\n\t\t// Encode the reqBody as query parameters\n\t\tif reqBody != nil {\n\t\t\tq, err = query.Values(reqBody)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\tcase \"DELETE\", \"PATCH\", \"POST\":\n\t\treqHeaders.Set(\"Accept\", ContentTypeJSONAPI)\n\t\treqHeaders.Set(\"Content-Type\", ContentTypeJSONAPI)\n\n\t\tif reqBody != nil {\n\t\t\tif body, err = serializeRequestBody(reqBody); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\tcase \"PUT\":\n\t\treqHeaders.Set(\"Accept\", \"application/json\")\n\t\treqHeaders.Set(\"Content-Type\", \"application/octet-stream\")\n\t\tbody = reqBody\n\t}\n\n\tfor k, v := range u.Query() {\n\t\tq[k] = v\n\t}\n\tfor k, v := range additionalQueryParams {\n\t\tq[k] = v\n\t}\n\n\tu.RawQuery = encodeQueryParams(q)\n\n\treq, err := retryablehttp.NewRequest(method, u.String(), body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Set the default headers.\n\tfor k, v := range c.headers {\n\t\treq.Header[k] = v\n\t}\n\n\t// Set the request specific headers.\n\tfor k, v := range reqHeaders {\n\t\treq.Header[k] = v\n\t}\n\n\treturn &ClientRequest{\n\t\tretryableRequest: req,\n\t\thttp:             c.http,\n\t\tlimiter:          c.limiter,\n\t\tHeader:           req.Header,\n\t}, nil\n}\n\n// NewClient creates a new Terraform Enterprise API client.\nfunc NewClient(cfg *Config) (*Client, error) {\n\tconfig := DefaultConfig()\n\n\t// Layer in the provided config for any non-blank values.\n\tif cfg != nil { // nolint\n\t\tif cfg.Address != \"\" {\n\t\t\tconfig.Address = cfg.Address\n\t\t}\n\t\tif cfg.BasePath != \"\" {\n\t\t\tconfig.BasePath = cfg.BasePath\n\t\t}\n\t\tif cfg.RegistryBasePath != \"\" {\n\t\t\tconfig.RegistryBasePath = cfg.RegistryBasePath\n\t\t}\n\t\tif cfg.Token != \"\" {\n\t\t\tconfig.Token = cfg.Token\n\t\t}\n\t\tfor k, v := range cfg.Headers {\n\t\t\tconfig.Headers[k] = v\n\t\t}\n\t\tif cfg.HTTPClient != nil {\n\t\t\tconfig.HTTPClient = cfg.HTTPClient\n\t\t}\n\t\tif cfg.RetryLogHook != nil {\n\t\t\tconfig.RetryLogHook = cfg.RetryLogHook\n\t\t}\n\t\tconfig.RetryServerErrors = cfg.RetryServerErrors\n\t}\n\n\t// Parse the address to make sure its a valid URL.\n\tbaseURL, err := url.Parse(config.Address)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid address: %w\", err)\n\t}\n\n\tbaseURL.Path = config.BasePath\n\tif !strings.HasSuffix(baseURL.Path, \"/\") {\n\t\tbaseURL.Path += \"/\"\n\t}\n\n\tregistryURL, err := url.Parse(config.Address)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid address: %w\", err)\n\t}\n\n\tregistryURL.Path = config.RegistryBasePath\n\tif !strings.HasSuffix(registryURL.Path, \"/\") {\n\t\tregistryURL.Path += \"/\"\n\t}\n\n\t// This value must be provided by the user.\n\tif config.Token == \"\" {\n\t\treturn nil, fmt.Errorf(\"missing API token\")\n\t}\n\n\t// Create the client.\n\tclient := &Client{\n\t\tbaseURL:           baseURL,\n\t\tregistryBaseURL:   registryURL,\n\t\ttoken:             config.Token,\n\t\theaders:           config.Headers,\n\t\tretryLogHook:      config.RetryLogHook,\n\t\tretryServerErrors: config.RetryServerErrors,\n\t}\n\n\tclient.http = &retryablehttp.Client{\n\t\tBackoff:      client.retryHTTPBackoff,\n\t\tCheckRetry:   client.retryHTTPCheck,\n\t\tErrorHandler: retryablehttp.PassthroughErrorHandler,\n\t\tHTTPClient:   config.HTTPClient,\n\t\tRetryWaitMin: 100 * time.Millisecond,\n\t\tRetryWaitMax: 400 * time.Millisecond,\n\t\tRetryMax:     30,\n\t}\n\n\tmeta, err := client.getRawAPIMetadata()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Configure the rate limiter.\n\tclient.configureLimiter(meta.RateLimit)\n\n\t// Save the API version so we can return it from the RemoteAPIVersion\n\t// method later.\n\tclient.remoteAPIVersion = meta.APIVersion\n\n\t// Save the TFE version\n\tclient.remoteTFEVersion = meta.TFEVersion\n\n\t// Save the TFE Numeric version\n\tclient.remoteTFENumericVersion = meta.TFENumericVersion\n\n\t// Save the app name\n\tclient.appName = meta.AppName\n\n\t// Create Admin\n\tclient.Admin = Admin{\n\t\tOrganizations:     &adminOrganizations{client: client},\n\t\tWorkspaces:        &adminWorkspaces{client: client},\n\t\tRuns:              &adminRuns{client: client},\n\t\tSettings:          newAdminSettings(client),\n\t\tTerraformVersions: &adminTerraformVersions{client: client},\n\t\tOPAVersions:       &adminOPAVersions{client: client},\n\t\tSentinelVersions:  &adminSentinelVersions{client: client},\n\t\tUsers:             &adminUsers{client: client},\n\t}\n\n\t// Create the services.\n\tclient.AgentPools = &agentPools{client: client}\n\tclient.Agents = &agents{client: client}\n\tclient.AgentTokens = &agentTokens{client: client}\n\tclient.Applies = &applies{client: client}\n\tclient.AuditTrails = &auditTrails{client: client}\n\tclient.AWSOIDCConfigurations = &awsOIDCConfigurations{client: client}\n\tclient.GCPOIDCConfigurations = &gcpOIDCConfigurations{client: client}\n\tclient.AzureOIDCConfigurations = &azureOIDCConfigurations{client: client}\n\tclient.VaultOIDCConfigurations = &vaultOIDCConfigurations{client: client}\n\tclient.Comments = &comments{client: client}\n\tclient.ConfigurationVersions = &configurationVersions{client: client}\n\tclient.CostEstimates = &costEstimates{client: client}\n\tclient.GHAInstallations = &gHAInstallations{client: client}\n\tclient.GPGKeys = &gpgKeys{client: client}\n\tclient.RegistryNoCodeModules = &registryNoCodeModules{client: client}\n\tclient.NotificationConfigurations = &notificationConfigurations{client: client}\n\tclient.OAuthClients = &oAuthClients{client: client}\n\tclient.OAuthTokens = &oAuthTokens{client: client}\n\tclient.OrganizationMemberships = &organizationMemberships{client: client}\n\tclient.Organizations = &organizations{client: client}\n\tclient.OrganizationTags = &organizationTags{client: client}\n\tclient.OrganizationTokens = &organizationTokens{client: client}\n\tclient.OrganizationAuditConfigurations = &organizationAuditConfigurations{client: client}\n\tclient.PlanExports = &planExports{client: client}\n\tclient.Plans = &plans{client: client}\n\tclient.Policies = &policies{client: client}\n\tclient.PolicyChecks = &policyChecks{client: client}\n\tclient.PolicyEvaluations = &policyEvaluation{client: client}\n\tclient.PolicySetOutcomes = &policySetOutcome{client: client}\n\tclient.PolicySetParameters = &policySetParameters{client: client}\n\tclient.PolicySets = &policySets{client: client}\n\tclient.PolicySetVersions = &policySetVersions{client: client}\n\tclient.Projects = &projects{client: client}\n\tclient.QueryRuns = &queryRuns{client: client}\n\tclient.RegistryModules = &registryModules{client: client}\n\tclient.RegistryProviderPlatforms = &registryProviderPlatforms{client: client}\n\tclient.RegistryProviders = &registryProviders{client: client}\n\tclient.RegistryProviderVersions = &registryProviderVersions{client: client}\n\tclient.ReservedTagKeys = &reservedTagKeys{client: client}\n\tclient.Runs = &runs{client: client}\n\tclient.RunEvents = &runEvents{client: client}\n\tclient.RunTasks = &runTasks{client: client}\n\tclient.RunTasksIntegration = &runTaskIntegration{client: client}\n\tclient.RunTriggers = &runTriggers{client: client}\n\tclient.SSHKeys = &sshKeys{client: client}\n\tclient.Stacks = &stacks{client: client}\n\tclient.HYOKConfigurations = &hyokConfigurations{client: client}\n\tclient.HYOKCustomerKeyVersions = &hyokCustomerKeyVersions{client: client}\n\tclient.HYOKEncryptedDataKeys = &hyokEncryptedDataKeys{client: client}\n\tclient.StackConfigurations = &stackConfigurations{client: client}\n\tclient.StackConfigurationSummaries = &stackConfigurationSummaries{client: client}\n\tclient.StackDeployments = &stackDeployments{client: client}\n\tclient.StackDeploymentGroups = &stackDeploymentGroups{client: client}\n\tclient.StackDeploymentGroupSummaries = &stackDeploymentGroupSummaries{client: client}\n\tclient.StackDeploymentRuns = &stackDeploymentRuns{client: client}\n\tclient.StackDeploymentSteps = &stackDeploymentSteps{client: client}\n\tclient.StackDiagnostics = &stackDiagnostics{client: client}\n\tclient.StackStates = &stackStates{client: client}\n\tclient.StateVersionOutputs = &stateVersionOutputs{client: client}\n\tclient.StateVersions = &stateVersions{client: client}\n\tclient.TaskResults = &taskResults{client: client}\n\tclient.TaskStages = &taskStages{client: client}\n\tclient.TeamAccess = &teamAccesses{client: client}\n\tclient.TeamMembers = &teamMembers{client: client}\n\tclient.TeamProjectAccess = &teamProjectAccesses{client: client}\n\tclient.Teams = &teams{client: client}\n\tclient.TeamTokens = &teamTokens{client: client}\n\tclient.TestRuns = &testRuns{client: client}\n\tclient.TestVariables = &testVariables{client: client}\n\tclient.Users = &users{client: client}\n\tclient.UserTokens = &userTokens{client: client}\n\tclient.Variables = &variables{client: client}\n\tclient.VariableSets = &variableSets{client: client}\n\tclient.VariableSetVariables = &variableSetVariables{client: client}\n\tclient.WorkspaceRunTasks = &workspaceRunTasks{client: client}\n\tclient.Workspaces = &workspaces{client: client}\n\tclient.WorkspaceResources = &workspaceResources{client: client}\n\n\tclient.Meta = Meta{\n\t\tIPRanges: &ipRanges{client: client},\n\t}\n\n\tclient.StackDeploymentRuns = &stackDeploymentRuns{client: client}\n\n\treturn client, nil\n}\n\n// AppName returns the name of the instance.\nfunc (c Client) AppName() string {\n\treturn c.appName\n}\n\n// IsCloud returns true if the client is configured against a HCP Terraform\n// instance.\n//\n// Whether an instance is HCP Terraform or Terraform Enterprise is derived from the TFP-AppName header.\nfunc (c Client) IsCloud() bool {\n\treturn c.appName == \"HCP Terraform\"\n}\n\n// IsEnterprise returns true if the client is configured against a Terraform\n// Enterprise instance.\n//\n// Whether an instance is HCP Terraform or TFE is derived from the TFP-AppName header. Note:\n// not all TFE releases include this header in API responses.\nfunc (c Client) IsEnterprise() bool {\n\treturn !c.IsCloud()\n}\n\n// RemoteAPIVersion returns the server's declared API version string.\n//\n// A HCP Terraform or Enterprise API server returns its API version in an\n// HTTP header field in all responses. The NewClient function saves the\n// version number returned in its initial setup request and RemoteAPIVersion\n// returns that cached value.\n//\n// The API protocol calls for this string to be a dotted-decimal version number\n// like 2.3.0, where the first number indicates the API major version while the\n// second indicates a minor version which may have introduced some\n// backward-compatible additional features compared to its predecessor.\n//\n// Explicit API versioning was added to the HCP Terraform and Enterprise\n// APIs as a later addition, so older servers will not return version\n// information. In that case, this function returns an empty string as the\n// version.\nfunc (c Client) RemoteAPIVersion() string {\n\treturn c.remoteAPIVersion\n}\n\n// BaseURL returns the base URL as configured in the client\nfunc (c Client) BaseURL() url.URL {\n\treturn *c.baseURL\n}\n\n// BaseRegistryURL returns the registry base URL as configured in the client\nfunc (c Client) BaseRegistryURL() url.URL {\n\treturn *c.registryBaseURL\n}\n\n// SetFakeRemoteAPIVersion allows setting a given string as the client's remoteAPIVersion,\n// overriding the value pulled from the API header during client initialization.\n//\n// This is intended for use in tests, when you may want to configure your TFE client to\n// return something different than the actual API version in order to test error handling.\nfunc (c *Client) SetFakeRemoteAPIVersion(fakeAPIVersion string) {\n\tc.remoteAPIVersion = fakeAPIVersion\n}\n\n// RemoteTFEVersion returns the server's declared TFE monthly version string.\n//\n// A Terraform Enterprise API server includes its current version in an\n// HTTP header field in all responses. This value is saved by the client\n// during the initial setup request and RemoteTFEVersion returns that cached\n// value. This function returns an empty string for any Terraform Enterprise version\n// earlier than v202208-3 and for HCP Terraform.\nfunc (c Client) RemoteTFEVersion() string {\n\treturn c.remoteTFEVersion\n}\n\n// RemoteTFENumericVersion returns the server's declared TFE version string.\n//\n// A Terraform Enterprise API server includes its current numeric version in an\n// HTTP header field in all responses. This value is saved by the client\n// during the initial setup request and RemoteTFENumericVersion returns that cached\n// value. This function returns an empty string for any Terraform Enterprise version\n// earlier than 1.0.3 and for HCP Terraform.\nfunc (c Client) RemoteTFENumericVersion() string {\n\treturn c.remoteTFENumericVersion\n}\n\n// RetryServerErrors configures the retry HTTP check to also retry\n// unexpected errors or requests that failed with a server error.\nfunc (c *Client) RetryServerErrors(retry bool) {\n\tc.retryServerErrors = retry\n}\n\n// retryHTTPCheck provides a callback for Client.CheckRetry which\n// will retry both rate limit (429) and server (>= 500) errors.\nfunc (c *Client) retryHTTPCheck(ctx context.Context, resp *http.Response, err error) (bool, error) {\n\tif ctx.Err() != nil {\n\t\treturn false, ctx.Err()\n\t}\n\tif err != nil {\n\t\treturn c.retryServerErrors, err\n\t}\n\tif resp.StatusCode == 429 || (c.retryServerErrors && resp.StatusCode >= 500) {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\n// retryHTTPBackoff provides a generic callback for Client.Backoff which\n// will pass through all calls based on the status code of the response.\nfunc (c *Client) retryHTTPBackoff(minimum, maximum time.Duration, attemptNum int, resp *http.Response) time.Duration {\n\tif c.retryLogHook != nil {\n\t\tc.retryLogHook(attemptNum, resp)\n\t}\n\n\t// Use the rate limit backoff function when we are rate limited.\n\tif resp != nil && resp.StatusCode == 429 {\n\t\treturn rateLimitBackoff(minimum, maximum, resp)\n\t}\n\n\t// Set custom duration's when we experience a service interruption.\n\tminimum = 700 * time.Millisecond\n\tmaximum = 900 * time.Millisecond\n\n\treturn retryablehttp.LinearJitterBackoff(minimum, maximum, attemptNum, resp)\n}\n\n// rateLimitBackoff provides a callback for Client.Backoff which will use the\n// X-RateLimit_Reset header to determine the time to wait. We add some jitter\n// to prevent a thundering herd.\n//\n// minimum and maximum are mainly used for bounding the jitter that will be added to\n// the reset time retrieved from the headers. But if the final wait time is\n// less than minimum, minimum will be used instead.\nfunc rateLimitBackoff(minimum, maximum time.Duration, resp *http.Response) time.Duration {\n\t// rnd is used to generate pseudo-random numbers.\n\trnd := rand.New(rand.NewSource(time.Now().UnixNano()))\n\n\t// First create some jitter bounded by the min and max durations.\n\tjitter := time.Duration(rnd.Float64() * float64(maximum-minimum))\n\n\tif resp != nil && resp.Header.Get(_headerRateReset) != \"\" {\n\t\tv := resp.Header.Get(_headerRateReset)\n\t\treset, err := strconv.ParseFloat(v, 64)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\t// Only update min if the given time to wait is longer\n\t\tif reset > 0 && time.Duration(reset*1e9) > minimum {\n\t\t\tminimum = time.Duration(reset * 1e9)\n\t\t}\n\t}\n\n\treturn minimum + jitter\n}\n\ntype rawAPIMetadata struct {\n\t// APIVersion is the raw API version string reported by the server in the\n\t// TFP-API-Version response header, or an empty string if that header\n\t// field was not included in the response.\n\tAPIVersion string\n\n\t// TFEVersion is the raw TFE monthly version string reported by the server in the\n\t// X-TFE-Version response header, or an empty string if that header\n\t// field was not included in the response.\n\tTFEVersion string\n\n\t// TFENumericVersion is the raw TFE Numeric version string reported by the server in the\n\t// X-TFE-Current-Version response header, or an empty string if that header\n\t// field was not included in the response.\n\tTFENumericVersion string\n\n\t// RateLimit is the raw API version string reported by the server in the\n\t// X-RateLimit-Limit response header, or an empty string if that header\n\t// field was not included in the response.\n\tRateLimit string\n\n\t// AppName is either 'HCP Terraform' or 'Terraform Enterprise'\n\tAppName string\n}\n\nfunc (c *Client) getRawAPIMetadata() (rawAPIMetadata, error) {\n\tvar meta rawAPIMetadata\n\n\t// Create a new request.\n\tu, err := c.baseURL.Parse(PingEndpoint)\n\tif err != nil {\n\t\treturn meta, err\n\t}\n\treq, err := http.NewRequest(\"GET\", u.String(), nil)\n\tif err != nil {\n\t\treturn meta, err\n\t}\n\n\t// Attach the default headers.\n\tfor k, v := range c.headers {\n\t\treq.Header[k] = v\n\t}\n\treq.Header.Set(\"Accept\", ContentTypeJSONAPI)\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.token)\n\n\t// Make a single request to retrieve the rate limit headers.\n\tresp, err := c.http.HTTPClient.Do(req)\n\tif err != nil {\n\t\treturn meta, err\n\t}\n\tresp.Body.Close() //nolint:errcheck\n\n\tmeta.APIVersion = resp.Header.Get(_headerAPIVersion)\n\tmeta.RateLimit = resp.Header.Get(_headerRateLimit)\n\tmeta.TFEVersion = resp.Header.Get(_headerTFEVersion)\n\tmeta.TFENumericVersion = resp.Header.Get(_headerTFENumericVersion)\n\tmeta.AppName = resp.Header.Get(_headerAppName)\n\n\treturn meta, nil\n}\n\n// configureLimiter configures the rate limiter.\nfunc (c *Client) configureLimiter(rawLimit string) {\n\t// Set default values for when rate limiting is disabled.\n\tlimit := rate.Inf\n\tburst := 0\n\n\tif v := rawLimit; v != \"\" {\n\t\tif rateLimit, err := strconv.ParseFloat(v, 64); rateLimit > 0 {\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t\t// Configure the limit and burst using a split of 2/3 for the limit and\n\t\t\t// 1/3 for the burst. This enables clients to burst 1/3 of the allowed\n\t\t\t// calls before the limiter kicks in. The remaining calls will then be\n\t\t\t// spread out evenly using intervals of time.Second / limit which should\n\t\t\t// prevent hitting the rate limit.\n\t\t\tlimit = rate.Limit(rateLimit * 0.66)\n\t\t\tburst = int(rateLimit * 0.33)\n\t\t}\n\t}\n\n\t// Create a new limiter using the calculated values.\n\tc.limiter = rate.NewLimiter(limit, burst)\n}\n\n// encodeQueryParams encodes the values into \"URL encoded\" form\n// (\"bar=baz&foo=quux\") sorted by key. This version behaves as url.Values\n// Encode, except that it encodes certain keys as comma-separated values instead\n// of using multiple keys.\nfunc encodeQueryParams(v url.Values) string {\n\tif v == nil {\n\t\treturn \"\"\n\t}\n\tvar buf strings.Builder\n\tkeys := make([]string, 0, len(v))\n\tfor k := range v {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tfor _, k := range keys {\n\t\tvs := v[k]\n\t\tif len(vs) > 1 && validSliceKey(k) {\n\t\t\tval := strings.Join(vs, \",\")\n\t\t\tvs = vs[:0]\n\t\t\tvs = append(vs, val)\n\t\t}\n\t\tkeyEscaped := url.QueryEscape(k)\n\n\t\tfor _, v := range vs {\n\t\t\tif buf.Len() > 0 {\n\t\t\t\tbuf.WriteByte('&')\n\t\t\t}\n\t\t\tbuf.WriteString(keyEscaped)\n\t\t\tbuf.WriteByte('=')\n\t\t\tbuf.WriteString(url.QueryEscape(v))\n\t\t}\n\t}\n\treturn buf.String()\n}\n\n// decodeQueryParams types an object and converts the struct fields into\n// Query Parameters, which can be used with NewRequestWithAdditionalQueryParams\n// Note that a field without a `url` annotation will be converted into a query\n// parameter. Use url:\"-\" to ignore struct fields.\nfunc decodeQueryParams(v any) (url.Values, error) {\n\tif v == nil {\n\t\treturn make(url.Values, 0), nil\n\t}\n\treturn query.Values(v)\n}\n\n// serializeRequestBody serializes the given ptr or ptr slice into a JSON\n// request. It automatically uses jsonapi or json serialization, depending\n// on the body type's tags.\nfunc serializeRequestBody(v interface{}) (interface{}, error) {\n\t// The body can be a slice of pointers or a pointer. In either\n\t// case we want to choose the serialization type based on the\n\t// individual record type. To determine that type, we need\n\t// to either follow the pointer or examine the slice element type.\n\t// There are other theoretical possibilities (e. g. maps,\n\t// non-pointers) but they wouldn't work anyway because the\n\t// json-api library doesn't support serializing other things.\n\tvar modelType reflect.Type\n\tbodyType := reflect.TypeOf(v)\n\tswitch bodyType.Kind() {\n\tcase reflect.Slice:\n\t\tsliceElem := bodyType.Elem()\n\t\tif sliceElem.Kind() != reflect.Ptr {\n\t\t\treturn nil, ErrInvalidRequestBody\n\t\t}\n\t\tmodelType = sliceElem.Elem()\n\tcase reflect.Ptr:\n\t\tmodelType = reflect.ValueOf(v).Elem().Type()\n\tdefault:\n\t\treturn nil, ErrInvalidRequestBody\n\t}\n\n\t// Infer whether the request uses jsonapi or regular json\n\t// serialization based on how the fields are tagged.\n\tjsonAPIFields := 0\n\tjsonFields := 0\n\tfor i := 0; i < modelType.NumField(); i++ {\n\t\tstructField := modelType.Field(i)\n\t\tif structField.Tag.Get(\"jsonapi\") != \"\" {\n\t\t\tjsonAPIFields++\n\t\t}\n\t\tif structField.Tag.Get(\"json\") != \"\" {\n\t\t\tjsonFields++\n\t\t}\n\t}\n\tif jsonAPIFields > 0 && jsonFields > 0 {\n\t\t// Defining a struct with both json and jsonapi tags doesn't\n\t\t// make sense, because a struct can only be serialized\n\t\t// as one or another. If this does happen, it's a bug\n\t\t// in the library that should be fixed at development time\n\t\treturn nil, ErrInvalidStructFormat\n\t}\n\n\tif jsonFields > 0 {\n\t\treturn json.Marshal(v)\n\t}\n\tbuf := bytes.NewBuffer(nil)\n\tif err := jsonapi.MarshalPayloadWithoutIncluded(buf, v); err != nil {\n\t\treturn nil, err\n\t}\n\treturn buf, nil\n}\n\nfunc unmarshalResponse(responseBody io.Reader, model interface{}) error {\n\t// Get the value of model so we can test if it's a struct.\n\tdst := reflect.Indirect(reflect.ValueOf(model))\n\n\t// Return an error if model is not a struct or an io.Writer.\n\tif dst.Kind() != reflect.Struct {\n\t\treturn fmt.Errorf(\"%v must be a struct or an io.Writer\", dst)\n\t}\n\n\t// Try to get the Items and Pagination struct fields.\n\titems := dst.FieldByName(\"Items\")\n\n\t// Unmarshal a single value if model does not contain the\n\t// Items and Pagination struct fields.\n\tif !items.IsValid() {\n\t\treturn jsonapi.UnmarshalPayload(responseBody, model)\n\t}\n\n\t// Return an error if model.Items is not a slice.\n\tif items.Type().Kind() != reflect.Slice {\n\t\treturn ErrItemsMustBeSlice\n\t}\n\n\t// Create a temporary buffer and copy all the read data into it.\n\tbody := bytes.NewBuffer(nil)\n\treader := io.TeeReader(responseBody, body)\n\n\t// Unmarshal as a list of values as model.Items is a slice.\n\traw, err := jsonapi.UnmarshalManyPayload(reader, items.Type().Elem())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Make a new slice to hold the results.\n\tsliceType := reflect.SliceOf(items.Type().Elem())\n\tresult := reflect.MakeSlice(sliceType, 0, len(raw))\n\n\t// Add all of the results to the new slice.\n\tfor _, v := range raw {\n\t\tresult = reflect.Append(result, reflect.ValueOf(v))\n\t}\n\n\t// Pointer-swap the result.\n\titems.Set(result)\n\n\tpagination := dst.FieldByName(\"Pagination\")\n\tpaginationWithoutTotals := dst.FieldByName(\"PaginationNextPrev\")\n\n\t// As we are getting a list of values, we need to decode\n\t// the pagination details out of the response body.\n\t// Pointer-swap the decoded pagination details.\n\tif paginationWithoutTotals.IsValid() {\n\t\tp, err := parsePaginationWithoutTotal(body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpaginationWithoutTotals.Set(reflect.ValueOf(p))\n\t} else if pagination.IsValid() {\n\t\tp, err := parsePagination(body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpagination.Set(reflect.ValueOf(p))\n\t}\n\n\treturn nil\n}\n\n// ListOptions is used to specify pagination options when making API requests.\n// Pagination allows breaking up large result sets into chunks, or \"pages\".\ntype ListOptions struct {\n\t// The page number to request. The results vary based on the PageSize.\n\tPageNumber int `url:\"page[number],omitempty\"`\n\n\t// The number of elements returned in a single page.\n\tPageSize int `url:\"page[size],omitempty\"`\n}\n\n// PaginationNextPrev is used to return the pagination details of an API request.\ntype PaginationNextPrev struct {\n\tCurrentPage  int `json:\"current-page\"`\n\tPreviousPage int `json:\"prev-page\"`\n\tNextPage     int `json:\"next-page\"`\n}\n\n// Pagination is used to return the pagination details of an API request including TotalCount.\ntype Pagination struct {\n\tCurrentPage  int `json:\"current-page\"`\n\tPreviousPage int `json:\"prev-page\"`\n\tNextPage     int `json:\"next-page\"`\n\tTotalCount   int `json:\"total-count\"`\n\tTotalPages   int `json:\"total-pages\"`\n}\n\nfunc parsePaginationWithoutTotal(body io.Reader) (*PaginationNextPrev, error) {\n\tvar raw struct {\n\t\tMeta struct {\n\t\t\tPagination PaginationNextPrev `jsonapi:\"pagination\"`\n\t\t} `jsonapi:\"meta\"`\n\t}\n\n\t// JSON decode the raw response.\n\tif err := json.NewDecoder(body).Decode(&raw); err != nil {\n\t\treturn &PaginationNextPrev{}, err\n\t}\n\n\treturn &raw.Meta.Pagination, nil\n}\n\nfunc parsePagination(body io.Reader) (*Pagination, error) {\n\tvar raw struct {\n\t\tMeta struct {\n\t\t\tPagination Pagination `jsonapi:\"pagination\"`\n\t\t} `jsonapi:\"meta\"`\n\t}\n\n\t// JSON decode the raw response.\n\tif err := json.NewDecoder(body).Decode(&raw); err != nil {\n\t\treturn &Pagination{}, err\n\t}\n\n\treturn &raw.Meta.Pagination, nil\n}\n\n// checkResponseCode refines typical API errors into more specific errors\n// if possible. It returns nil if the response code < 400\nfunc checkResponseCode(r *http.Response) error {\n\tif r.StatusCode >= 200 && r.StatusCode <= 399 {\n\t\treturn nil\n\t}\n\n\tvar errs []string\n\tvar err error\n\n\tswitch r.StatusCode {\n\tcase 400:\n\t\terrs, err = decodeErrorPayload(r)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif errorPayloadContains(errs, \"include parameter\") {\n\t\t\treturn ErrInvalidIncludeValue\n\t\t}\n\t\treturn errors.New(strings.Join(errs, \"\\n\"))\n\tcase 401:\n\t\treturn ErrUnauthorized\n\tcase 404:\n\t\treturn ErrResourceNotFound\n\tcase 409:\n\t\tswitch {\n\t\tcase strings.HasSuffix(r.Request.URL.Path, \"actions/lock\"):\n\t\t\treturn ErrWorkspaceLocked\n\t\tcase strings.HasSuffix(r.Request.URL.Path, \"actions/unlock\"):\n\t\t\terrs, err = decodeErrorPayload(r)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif errorPayloadContains(errs, \"is locked by Run\") {\n\t\t\t\treturn ErrWorkspaceLockedByRun\n\t\t\t}\n\n\t\t\tif errorPayloadContains(errs, \"is locked by Team\") {\n\t\t\t\treturn ErrWorkspaceLockedByTeam\n\t\t\t}\n\n\t\t\tif errorPayloadContains(errs, \"is locked by User\") {\n\t\t\t\treturn ErrWorkspaceLockedByUser\n\t\t\t}\n\n\t\t\treturn ErrWorkspaceNotLocked\n\t\tcase strings.HasSuffix(r.Request.URL.Path, \"actions/force-unlock\"):\n\t\t\treturn ErrWorkspaceNotLocked\n\t\tcase strings.HasSuffix(r.Request.URL.Path, \"actions/safe-delete\"):\n\t\t\terrs, err = decodeErrorPayload(r)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif errorPayloadContains(errs, \"locked\") {\n\t\t\t\treturn ErrWorkspaceLockedCannotDelete\n\t\t\t}\n\t\t\tif errorPayloadContains(errs, \"being processed\") {\n\t\t\t\treturn ErrWorkspaceStillProcessing\n\t\t\t}\n\n\t\t\treturn ErrWorkspaceNotSafeToDelete\n\t\t}\n\t}\n\n\terrs, err = decodeErrorPayload(r)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn errors.New(strings.Join(errs, \"\\n\"))\n}\n\nfunc decodeErrorPayload(r *http.Response) ([]string, error) {\n\t// Decode the error payload.\n\tvar errs []string\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\treturn errs, errors.New(r.Status)\n\t}\n\n\t// attempt JSON:API error payloads unwrapping\n\terrPayload := &jsonapi.ErrorsPayload{}\n\tif err := json.Unmarshal(body, errPayload); err == nil && len(errPayload.Errors) > 0 {\n\t\tfor _, e := range errPayload.Errors {\n\t\t\tif e.Detail == \"\" {\n\t\t\t\terrs = append(errs, e.Title)\n\t\t\t} else {\n\t\t\t\terrs = append(errs, fmt.Sprintf(\"%s\\n\\n%s\", e.Title, e.Detail))\n\t\t\t}\n\t\t}\n\t\treturn errs, nil\n\t}\n\n\t// attempt JSON error payloads unwrapping: like {\"errors\":[\"...\"]}.\n\tvar rawErrs struct {\n\t\tErrors []string `json:\"errors\"`\n\t}\n\tif err := json.Unmarshal(body, &rawErrs); err == nil && len(rawErrs.Errors) > 0 {\n\t\treturn rawErrs.Errors, nil\n\t}\n\n\treturn errs, errors.New(r.Status)\n}\n\nfunc errorPayloadContains(payloadErrors []string, match string) bool {\n\tfor _, e := range payloadErrors {\n\t\tif strings.Contains(e, match) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc packContents(path string) (*bytes.Buffer, error) {\n\tbody := bytes.NewBuffer(nil)\n\n\tfile, err := os.Stat(path)\n\tif err != nil {\n\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn body, fmt.Errorf(`failed to find files under the path \"%v\": %w`, path, err)\n\t\t}\n\t\treturn body, fmt.Errorf(`unable to upload files from the path \"%v\": %w`, path, err)\n\t}\n\n\tif !file.Mode().IsDir() {\n\t\treturn body, ErrMissingDirectory\n\t}\n\n\t_, errSlug := slug.Pack(path, body, true)\n\tif errSlug != nil {\n\t\treturn body, errSlug\n\t}\n\n\treturn body, nil\n}\n\nfunc validSliceKey(key string) bool {\n\treturn key == _includeQueryParam || strings.Contains(key, \"filter[\")\n}\n"
  },
  {
    "path": "tfe_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/hashicorp/jsonapi\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"golang.org/x/time/rate\"\n)\n\nfunc TestClient_newClient(t *testing.T) {\n\tt.Parallel()\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", ContentTypeJSONAPI)\n\t\tw.Header().Set(\"X-RateLimit-Limit\", \"30\")\n\t\tw.Header().Set(\"TFP-API-Version\", \"34.21.9\")\n\t\tw.Header().Set(\"X-TFE-Version\", \"202205-1\")\n\t\tw.Header().Set(\"X-TFE-Current-Version\", \"1.1.0\")\n\t\tif enterpriseEnabled() {\n\t\t\tw.Header().Set(\"TFP-AppName\", \"Terraform Enterprise\")\n\t\t} else {\n\t\t\tw.Header().Set(\"TFP-AppName\", \"HCP Terraform\")\n\t\t}\n\t\tw.WriteHeader(204) // We query the configured ping URL which should return a 204.\n\t}))\n\tdefer ts.Close()\n\n\tcfg := &Config{\n\t\tHTTPClient: ts.Client(),\n\t}\n\n\tt.Run(\"uses env vars if values are missing\", func(t *testing.T) {\n\t\tdefer setupEnvVars(\"abcd1234\", ts.URL)()\n\n\t\tclient, err := NewClient(cfg)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif client.token != \"abcd1234\" {\n\t\t\tt.Fatalf(\"unexpected token: %q\", client.token)\n\t\t}\n\t\tif client.baseURL.String() != ts.URL+DefaultBasePath {\n\t\t\tt.Fatalf(\"unexpected address: %q\", client.baseURL.String())\n\t\t}\n\t})\n\n\tt.Run(\"fails if token is empty\", func(t *testing.T) {\n\t\tdefer setupEnvVars(\"\", \"\")()\n\n\t\t_, err := NewClient(cfg)\n\t\tif err == nil || err.Error() != \"missing API token\" {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"makes a new client with good settings\", func(t *testing.T) {\n\t\tconfig := &Config{\n\t\t\tAddress:    ts.URL,\n\t\t\tToken:      \"abcd1234\",\n\t\t\tHTTPClient: ts.Client(),\n\t\t}\n\n\t\tclient, err := NewClient(config)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif config.Address+DefaultBasePath != client.baseURL.String() {\n\t\t\tt.Fatalf(\"unexpected client address %q\", client.baseURL.String())\n\t\t}\n\t\tif config.Token != client.token {\n\t\t\tt.Fatalf(\"unexpected client token %q\", client.token)\n\t\t}\n\t\tif ts.Client() != client.http.HTTPClient {\n\t\t\tt.Fatal(\"unexpected HTTP client value\")\n\t\t}\n\t\tif want := \"34.21.9\"; client.RemoteAPIVersion() != want {\n\t\t\tt.Errorf(\"unexpected remote API version %q; want %q\", client.RemoteAPIVersion(), want)\n\t\t}\n\t\tif want := \"202205-1\"; client.RemoteTFEVersion() != want {\n\t\t\tt.Errorf(\"unexpected remote TFE monthly version %q; want %q\", client.RemoteTFEVersion(), want)\n\t\t}\n\t\tif want := \"1.1.0\"; client.RemoteTFENumericVersion() != want {\n\t\t\tt.Errorf(\"unexpected remote TFE numeric version %q; want %q\", client.RemoteTFENumericVersion(), want)\n\t\t}\n\n\t\tif enterpriseEnabled() {\n\t\t\tassert.True(t, client.IsEnterprise())\n\t\t} else {\n\t\t\tassert.True(t, client.IsCloud())\n\t\t}\n\n\t\tclient.SetFakeRemoteAPIVersion(\"1.0\")\n\n\t\tif want := \"1.0\"; client.RemoteAPIVersion() != want {\n\t\t\tt.Errorf(\"unexpected remote API version %q; want %q\", client.RemoteAPIVersion(), want)\n\t\t}\n\t})\n}\n\nfunc TestClient_defaultConfig(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"with no environment variables\", func(t *testing.T) {\n\t\tdefer setupEnvVars(\"\", \"\")()\n\t\tos.Unsetenv(\"TFE_HOSTNAME\")\n\n\t\tconfig := DefaultConfig()\n\n\t\tassert.Equal(t, config.Address, DefaultAddress)\n\t\tassert.Equal(t, config.Token, \"\")\n\t\tassert.NotNil(t, config.HTTPClient)\n\t})\n\n\tt.Run(\"with environment variables\", func(t *testing.T) {\n\t\tt.Run(\"with TFE_ADDRESS set\", func(t *testing.T) {\n\t\t\tdefer setupEnvVars(\"abcd1234\", \"https://mytfe.local\")()\n\n\t\t\tclient := DefaultConfig()\n\t\t\tassert.Equal(t, client.Address, \"https://mytfe.local\")\n\t\t})\n\n\t\tt.Run(\"with TFE_HOSTNAME set\", func(t *testing.T) {\n\t\t\tdefer setupEnvVars(\"abcd1234\", \"\")()\n\t\t\tos.Setenv(\"TFE_HOSTNAME\", \"iloveterraform.io\")\n\n\t\t\tclient := DefaultConfig()\n\t\t\tassert.Equal(t, client.Address, \"https://iloveterraform.io\")\n\n\t\t\tos.Unsetenv(\"TFE_HOSTNAME\")\n\t\t})\n\t})\n}\n\nfunc TestClient_headers(t *testing.T) {\n\tt.Parallel()\n\ttestedCalls := 0\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\ttestedCalls++\n\n\t\tif testedCalls == 1 {\n\t\t\tw.Header().Set(\"Content-Type\", ContentTypeJSONAPI)\n\t\t\tw.Header().Set(\"X-RateLimit-Limit\", \"30\")\n\t\t\tw.WriteHeader(204) // We query the configured ping URL which should return a 204.\n\t\t\treturn\n\t\t}\n\n\t\tif r.Header.Get(\"Accept\") != ContentTypeJSONAPI {\n\t\t\tt.Fatalf(\"unexpected accept header: %q\", r.Header.Get(\"Accept\"))\n\t\t}\n\t\tif r.Header.Get(\"Authorization\") != \"Bearer dummy-token\" {\n\t\t\tt.Fatalf(\"unexpected authorization header: %q\", r.Header.Get(\"Authorization\"))\n\t\t}\n\t\tif r.Header.Get(\"My-Custom-Header\") != \"foobar\" {\n\t\t\tt.Fatalf(\"unexpected custom header: %q\", r.Header.Get(\"My-Custom-Header\"))\n\t\t}\n\t\tif r.Header.Get(\"Terraform-Version\") != \"0.11.9\" {\n\t\t\tt.Fatalf(\"unexpected Terraform version header: %q\", r.Header.Get(\"Terraform-Version\"))\n\t\t}\n\t\tif r.Header.Get(\"User-Agent\") != \"go-tfe\" {\n\t\t\tt.Fatalf(\"unexpected user agent header: %q\", r.Header.Get(\"User-Agent\"))\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tcfg := &Config{\n\t\tAddress:    ts.URL,\n\t\tToken:      \"dummy-token\",\n\t\tHeaders:    make(http.Header),\n\t\tHTTPClient: ts.Client(),\n\t}\n\n\t// Set some custom header.\n\tcfg.Headers.Set(\"My-Custom-Header\", \"foobar\")\n\tcfg.Headers.Set(\"Terraform-Version\", \"0.11.9\")\n\n\t// This one should be overridden!\n\tcfg.Headers.Set(\"Authorization\", \"bad-token\")\n\n\tclient, err := NewClient(cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tctx := context.Background()\n\n\t// Make a few calls so we can check they all send the expected headers.\n\tclient.Organizations.List(ctx, nil)\n\tclient.Plans.Logs(ctx, \"plan-123456789\")\n\tclient.Runs.Apply(ctx, \"run-123456789\", RunApplyOptions{})\n\tclient.Workspaces.Lock(ctx, \"ws-123456789\", WorkspaceLockOptions{})\n\tclient.Workspaces.Read(ctx, \"organization\", \"workspace\")\n\n\tif testedCalls != 6 {\n\t\tt.Fatalf(\"expected 6 tested calls, got: %d\", testedCalls)\n\t}\n}\n\nfunc TestClient_userAgent(t *testing.T) {\n\tt.Parallel()\n\ttestedCalls := 0\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\ttestedCalls++\n\n\t\tif testedCalls == 1 {\n\t\t\tw.Header().Set(\"Content-Type\", ContentTypeJSONAPI)\n\t\t\tw.Header().Set(\"X-RateLimit-Limit\", \"30\")\n\t\t\tw.WriteHeader(204) // We query the configured ping URL which should return a 204.\n\t\t\treturn\n\t\t}\n\n\t\tif r.Header.Get(\"User-Agent\") != \"hashicorp\" {\n\t\t\tt.Fatalf(\"unexpected user agent header: %q\", r.Header.Get(\"User-Agent\"))\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tcfg := &Config{\n\t\tAddress:    ts.URL,\n\t\tToken:      \"dummy-token\",\n\t\tHeaders:    make(http.Header),\n\t\tHTTPClient: ts.Client(),\n\t}\n\n\t// Set a custom user agent.\n\tcfg.Headers.Set(\"User-Agent\", \"hashicorp\")\n\n\tclient, err := NewClient(cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tctx := context.Background()\n\n\t// Make a few calls so we can check they all send the expected headers.\n\tclient.Organizations.List(ctx, nil)\n\tclient.Plans.Logs(ctx, \"plan-123456789\")\n\tclient.Runs.Apply(ctx, \"run-123456789\", RunApplyOptions{})\n\tclient.Workspaces.Lock(ctx, \"ws-123456789\", WorkspaceLockOptions{})\n\tclient.Workspaces.Read(ctx, \"organization\", \"workspace\")\n\n\tif testedCalls != 6 {\n\t\tt.Fatalf(\"expected 6 tested calls, got: %d\", testedCalls)\n\t}\n}\n\ntype JSONAPIBody struct {\n\tStrAttr string `jsonapi:\"attr,str_attr\"`\n}\n\ntype JSONPlainBody struct {\n\tStrAttr string `json:\"str_attr\"`\n}\n\ntype InvalidBody struct {\n\tAttr1 string `json:\"attr1\"`\n\tAttr2 string `jsonapi:\"attr,attr2\"`\n}\n\nfunc TestClient_requestBodySerialization(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"jsonapi request\", func(t *testing.T) {\n\t\tbody := JSONAPIBody{StrAttr: \"foo\"}\n\t\trequestBody, err := createRequest(&body)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tunmarshalledRequestBody := JSONAPIBody{}\n\t\terr = jsonapi.UnmarshalPayload(bytes.NewReader(requestBody), &unmarshalledRequestBody)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif unmarshalledRequestBody.StrAttr != body.StrAttr {\n\t\t\tt.Fatal(\"Request serialized incorrectly\")\n\t\t}\n\t})\n\n\tt.Run(\"jsonapi slice of pointers request\", func(t *testing.T) {\n\t\tvar body []*JSONAPIBody\n\t\tbody = append(body, &JSONAPIBody{StrAttr: \"foo\"})\n\t\trequestBody, err := createRequest(body)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// The jsonapi library doesn't support unmarshalling bulk objects,\n\t\t// so for this test we deserialize to the jsonapi intermediate\n\t\t// format and validate it manually\n\t\tparsedResponse := new(jsonapi.ManyPayload)\n\t\terr = json.Unmarshal(requestBody, &parsedResponse)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif len(parsedResponse.Data) != 1 || parsedResponse.Data[0].Attributes[\"str_attr\"] != \"foo\" {\n\t\t\tt.Fatal(\"Request serialized incorrectly\")\n\t\t}\n\t})\n\n\tt.Run(\"plain json request\", func(t *testing.T) {\n\t\tbody := JSONPlainBody{StrAttr: \"foo\"}\n\t\trequestBody, err := createRequest(&body)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tunmarshalledRequestBody := JSONPlainBody{}\n\t\terr = json.Unmarshal(requestBody, &unmarshalledRequestBody)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif unmarshalledRequestBody.StrAttr != body.StrAttr {\n\t\t\tt.Fatal(\"Request serialized incorrectly\")\n\t\t}\n\t})\n\n\tt.Run(\"plain json slice of pointers request\", func(t *testing.T) {\n\t\tvar body []*JSONPlainBody\n\t\tbody = append(body, &JSONPlainBody{StrAttr: \"foo\"})\n\t\trequestBody, err := createRequest(body)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tvar unmarshalledRequestBody []*JSONPlainBody\n\t\terr = json.Unmarshal(requestBody, &unmarshalledRequestBody)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif len(unmarshalledRequestBody) != 1 || unmarshalledRequestBody[0].StrAttr != body[0].StrAttr {\n\t\t\tt.Fatal(\"Request serialized incorrectly\")\n\t\t}\n\t})\n\n\tt.Run(\"nil request\", func(t *testing.T) {\n\t\trequestBody, err := createRequest(nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif len(requestBody) != 0 {\n\t\t\tt.Fatal(\"nil request serialized incorrectly\")\n\t\t}\n\t})\n\n\tt.Run(\"invalid struct request\", func(t *testing.T) {\n\t\tbody := InvalidBody{}\n\t\t_, err := createRequest(&body)\n\t\tif err == nil || err != ErrInvalidStructFormat {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\texpectedErr := \"go-tfe bug: DELETE/PATCH/POST body must be nil, ptr, or ptr slice\"\n\n\tt.Run(\"non-pointer request\", func(t *testing.T) {\n\t\tbody := InvalidBody{}\n\t\t_, err := createRequest(body)\n\t\tif err == nil || err.Error() != expectedErr {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"slice of non-pointer request\", func(t *testing.T) {\n\t\tbody := []InvalidBody{{}}\n\t\t_, err := createRequest(body)\n\t\tif err == nil || err.Error() != expectedErr {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"map request\", func(t *testing.T) {\n\t\tbody := make(map[string]string)\n\t\t_, err := createRequest(body)\n\t\tif err == nil || err.Error() != expectedErr {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"string request\", func(t *testing.T) {\n\t\tbody := \"foo\"\n\t\t_, err := createRequest(body)\n\t\tif err == nil || err.Error() != expectedErr {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n}\n\nfunc createRequest(v interface{}) ([]byte, error) {\n\tconfig := DefaultConfig()\n\tconfig.Token = \"dummy\"\n\tclient, err := NewClient(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trequest, err := client.NewRequest(\"POST\", \"/bar\", v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbody, err := request.retryableRequest.BodyBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn body, nil\n}\n\nfunc TestClient_configureLimiter(t *testing.T) {\n\tt.Parallel()\n\trateLimit := \"\"\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", ContentTypeJSONAPI)\n\t\tw.Header().Set(\"X-RateLimit-Limit\", rateLimit)\n\t\tw.WriteHeader(204) // We query the configured ping URL which should return a 204.\n\t}))\n\tdefer ts.Close()\n\n\tcfg := &Config{\n\t\tAddress:    ts.URL,\n\t\tToken:      \"dummy-token\",\n\t\tHTTPClient: ts.Client(),\n\t}\n\n\tcases := map[string]struct {\n\t\trate  string\n\t\tlimit rate.Limit\n\t\tburst int\n\t}{\n\t\t\"no-value\": {\n\t\t\trate:  \"\",\n\t\t\tlimit: rate.Inf,\n\t\t\tburst: 0,\n\t\t},\n\t\t\"limit-0\": {\n\t\t\trate:  \"0\",\n\t\t\tlimit: rate.Inf,\n\t\t\tburst: 0,\n\t\t},\n\t\t\"limit-30\": {\n\t\t\trate:  \"30\",\n\t\t\tlimit: rate.Limit(19.8),\n\t\t\tburst: 9,\n\t\t},\n\t\t\"limit-100\": {\n\t\t\trate:  \"100\",\n\t\t\tlimit: rate.Limit(66),\n\t\t\tburst: 33,\n\t\t},\n\t}\n\n\tfor name, tc := range cases {\n\t\t// First set the test rate limit.\n\t\trateLimit = tc.rate\n\n\t\tclient, err := NewClient(cfg)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif client.limiter.Limit() != tc.limit {\n\t\t\tt.Fatalf(\"test %s expected limit %f, got: %f\", name, tc.limit, client.limiter.Limit())\n\t\t}\n\n\t\tif client.limiter.Burst() != tc.burst {\n\t\t\tt.Fatalf(\"test %s expected burst %d, got: %d\", name, tc.burst, client.limiter.Burst())\n\t\t}\n\t}\n}\n\nfunc TestClient_retryHTTPCheck(t *testing.T) {\n\tt.Parallel()\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", ContentTypeJSONAPI)\n\t\tw.Header().Set(\"X-RateLimit-Limit\", \"30\")\n\t\tw.WriteHeader(204) // We query the configured ping URL which should return a 204.\n\t}))\n\tdefer ts.Close()\n\n\tcfg := &Config{\n\t\tAddress:    ts.URL,\n\t\tToken:      \"dummy-token\",\n\t\tHTTPClient: ts.Client(),\n\t}\n\n\tconnErr := errors.New(\"connection error\")\n\n\tcases := map[string]struct {\n\t\tresp              *http.Response\n\t\terr               error\n\t\tretryServerErrors bool\n\t\tcheckOK           bool\n\t\tcheckErr          error\n\t}{\n\t\t\"429-no-server-errors\": {\n\t\t\tresp:     &http.Response{StatusCode: 429},\n\t\t\terr:      nil,\n\t\t\tcheckOK:  true,\n\t\t\tcheckErr: nil,\n\t\t},\n\t\t\"429-with-server-errors\": {\n\t\t\tresp:              &http.Response{StatusCode: 429},\n\t\t\terr:               nil,\n\t\t\tretryServerErrors: true,\n\t\t\tcheckOK:           true,\n\t\t\tcheckErr:          nil,\n\t\t},\n\t\t\"500-no-server-errors\": {\n\t\t\tresp:     &http.Response{StatusCode: 500},\n\t\t\terr:      nil,\n\t\t\tcheckOK:  false,\n\t\t\tcheckErr: nil,\n\t\t},\n\t\t\"500-with-server-errors\": {\n\t\t\tresp:              &http.Response{StatusCode: 500},\n\t\t\terr:               nil,\n\t\t\tretryServerErrors: true,\n\t\t\tcheckOK:           true,\n\t\t\tcheckErr:          nil,\n\t\t},\n\t\t\"err-no-server-errors\": {\n\t\t\terr:      connErr,\n\t\t\tcheckOK:  false,\n\t\t\tcheckErr: connErr,\n\t\t},\n\t\t\"err-with-server-errors\": {\n\t\t\terr:               connErr,\n\t\t\tretryServerErrors: true,\n\t\t\tcheckOK:           true,\n\t\t\tcheckErr:          connErr,\n\t\t},\n\t}\n\n\tctx := context.Background()\n\n\tfor name, tc := range cases {\n\t\tclient, err := NewClient(cfg)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tclient.RetryServerErrors(tc.retryServerErrors)\n\n\t\tcheckOK, checkErr := client.retryHTTPCheck(ctx, tc.resp, tc.err)\n\t\tif checkOK != tc.checkOK {\n\t\t\tt.Fatalf(\"test %s expected checkOK %t, got: %t\", name, tc.checkOK, checkOK)\n\t\t}\n\t\tif checkErr != tc.checkErr {\n\t\t\tt.Fatalf(\"test %s expected checkErr %v, got: %v\", name, tc.checkErr, checkErr)\n\t\t}\n\t}\n}\n\nfunc TestClient_retryHTTPBackoff(t *testing.T) {\n\tt.Parallel()\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", ContentTypeJSONAPI)\n\t\tw.Header().Set(\"X-RateLimit-Limit\", \"30\")\n\t\tw.WriteHeader(204) // We query the configured ping URL which should return a 204.\n\t}))\n\tdefer ts.Close()\n\n\tvar attempts int\n\tretryLogHook := func(attemptNum int, resp *http.Response) {\n\t\tattempts++\n\t}\n\n\tcfg := &Config{\n\t\tAddress:      ts.URL,\n\t\tToken:        \"dummy-token\",\n\t\tHTTPClient:   ts.Client(),\n\t\tRetryLogHook: retryLogHook,\n\t}\n\n\tclient, err := NewClient(cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tretries := 3\n\tresp := &http.Response{StatusCode: 500}\n\n\tfor i := 0; i < retries; i++ {\n\t\tclient.retryHTTPBackoff(time.Second, time.Second, i, resp)\n\t}\n\n\tif attempts != retries {\n\t\tt.Fatalf(\"expected %d log hook callbacks, got: %d callbacks\", retries, attempts)\n\t}\n}\n\nfunc setupEnvVars(token, address string) func() {\n\torigToken := os.Getenv(\"TFE_TOKEN\")\n\torigAddress := os.Getenv(\"TFE_ADDRESS\")\n\n\tos.Setenv(\"TFE_TOKEN\", token)\n\tos.Setenv(\"TFE_ADDRESS\", address)\n\n\treturn func() {\n\t\tos.Setenv(\"TFE_TOKEN\", origToken)\n\t\tos.Setenv(\"TFE_ADDRESS\", origAddress)\n\t}\n}\n"
  },
  {
    "path": "tfe_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype tfeAPI struct {\n\tID                string                   `jsonapi:\"primary,tfe\"`\n\tName              string                   `jsonapi:\"attr,name\"`\n\tCreatedAt         time.Time                `jsonapi:\"attr,created-at,iso8601\"`\n\tEnabled           bool                     `jsonapi:\"attr,enabled\"`\n\tEmails            []string                 `jsonapi:\"attr,emails\"`\n\tStatus            tfeAPIStatus             `jsonapi:\"attr,status\"`\n\tStatusTimestamps  tfeAPITimestamps         `jsonapi:\"attr,status-timestamps\"`\n\tDeliveryResponses []tfeAPIDeliveryResponse `jsonapi:\"attr,delivery-responses\"`\n}\n\ntype tfeAPIDeliveryResponse struct {\n\tBody string `jsonapi:\"attr,body\"`\n\tCode int    `jsonapi:\"attr,code\"`\n}\n\ntype tfeAPIStatus string\n\ntype tfeAPITimestamps struct {\n\tQueuedAt time.Time `jsonapi:\"attr,queued-at,rfc3339\"`\n}\n\nconst (\n\ttfeAPIStatusNormal tfeAPIStatus = \"normal\"\n)\n\nfunc Test_unmarshalResponse(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"unmarshal properly formatted json\", func(t *testing.T) {\n\t\t// This structure is intended to include multiple possible fields and\n\t\t// formats that are valid for JSON:API\n\t\tdata := map[string]interface{}{\n\t\t\t\"data\": map[string]interface{}{\n\t\t\t\t\"type\": \"tfe\",\n\t\t\t\t\"id\":   \"1\",\n\t\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\t\"name\":       \"terraform\",\n\t\t\t\t\t\"created-at\": \"2016-08-17T08:27:12Z\",\n\t\t\t\t\t\"enabled\":    true,\n\t\t\t\t\t\"status\":     tfeAPIStatusNormal,\n\t\t\t\t\t\"emails\":     []string{\"test@hashicorp.com\"},\n\t\t\t\t\t\"delivery-responses\": []interface{}{\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"body\": \"<html>\",\n\t\t\t\t\t\t\t\"code\": 200,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"body\": \"<body>\",\n\t\t\t\t\t\t\t\"code\": 300,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"status-timestamps\": map[string]string{\n\t\t\t\t\t\t\"queued-at\": \"2020-03-16T23:15:59+00:00\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tbyteData, errMarshal := json.Marshal(data)\n\t\trequire.NoError(t, errMarshal)\n\t\tresponseBody := bytes.NewReader(byteData)\n\n\t\tunmarshalledRequestBody := tfeAPI{}\n\t\terr := unmarshalResponse(responseBody, &unmarshalledRequestBody)\n\t\trequire.NoError(t, err)\n\t\tqueuedParsedTime, err := time.Parse(time.RFC3339, \"2020-03-16T23:15:59+00:00\")\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, unmarshalledRequestBody.ID, \"1\")\n\t\tassert.Equal(t, unmarshalledRequestBody.Name, \"terraform\")\n\t\tassert.Equal(t, unmarshalledRequestBody.Status, tfeAPIStatusNormal)\n\t\tassert.Equal(t, len(unmarshalledRequestBody.Emails), 1)\n\t\tassert.Equal(t, unmarshalledRequestBody.Emails[0], \"test@hashicorp.com\")\n\t\tassert.Equal(t, unmarshalledRequestBody.StatusTimestamps.QueuedAt, queuedParsedTime)\n\t\tassert.NotEmpty(t, unmarshalledRequestBody.DeliveryResponses)\n\t\tassert.Equal(t, len(unmarshalledRequestBody.DeliveryResponses), 2)\n\t\tassert.Equal(t, unmarshalledRequestBody.DeliveryResponses[0].Body, \"<html>\")\n\t\tassert.Equal(t, unmarshalledRequestBody.DeliveryResponses[0].Code, 200)\n\t\tassert.Equal(t, unmarshalledRequestBody.DeliveryResponses[1].Body, \"<body>\")\n\t\tassert.Equal(t, unmarshalledRequestBody.DeliveryResponses[1].Code, 300)\n\t\tassert.Equal(t, unmarshalledRequestBody.Enabled, true)\n\t})\n\n\tt.Run(\"can only unmarshal Items that are slices\", func(t *testing.T) {\n\t\tresponseBody := bytes.NewReader([]byte(\"\"))\n\t\tmalformattedItemStruct := struct {\n\t\t\t*Pagination\n\t\t\tItems int\n\t\t}{\n\t\t\tItems: 1,\n\t\t}\n\t\terr := unmarshalResponse(responseBody, &malformattedItemStruct)\n\t\trequire.Error(t, err)\n\t\tassert.Equal(t, err, ErrItemsMustBeSlice)\n\t})\n\n\tt.Run(\"can only unmarshal a struct\", func(t *testing.T) {\n\t\tpayload := \"random\"\n\t\tresponseBody := bytes.NewReader([]byte(payload))\n\n\t\tnotStruct := \"not a struct\"\n\t\terr := unmarshalResponse(responseBody, notStruct)\n\t\tassert.Error(t, err)\n\t\tassert.EqualError(t, err, fmt.Sprintf(\"%v must be a struct or an io.Writer\", notStruct))\n\t})\n}\n\nfunc Test_decodeErrorPayload(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"with jsonapi errors payload\", func(t *testing.T) {\n\t\tresp := &http.Response{\n\t\t\tStatus:     \"400 Bad Request\",\n\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\tBody: io.NopCloser(bytes.NewBufferString(\n\t\t\t\t`{\"errors\":[{\"title\":\"org name invalid\",\"detail\":\"Org name is invalid\"}]}`,\n\t\t\t)),\n\t\t}\n\n\t\terrs, err := decodeErrorPayload(resp)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, errs, 1)\n\t\tassert.Equal(t, \"org name invalid\\n\\nOrg name is invalid\", errs[0])\n\t})\n\n\tt.Run(\"with regular json errors payload\", func(t *testing.T) {\n\t\tresp := &http.Response{\n\t\t\tStatus:     \"400 Bad Request\",\n\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\tBody: io.NopCloser(bytes.NewBufferString(\n\t\t\t\t`{\"errors\":[\"Unsupported GPG Key algorithm. Supported key algorithms are [RSA, DSA]\"]}`,\n\t\t\t)),\n\t\t}\n\n\t\terrs, err := decodeErrorPayload(resp)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, errs, 1)\n\t\tassert.Equal(t, \"Unsupported GPG Key algorithm. Supported key algorithms are [RSA, DSA]\", errs[0])\n\t})\n\n\tt.Run(\"with non-json error body\", func(t *testing.T) {\n\t\tresp := &http.Response{\n\t\t\tStatus:     \"400 Bad Request\",\n\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\tBody:       io.NopCloser(bytes.NewBufferString(\"this is not json\")),\n\t\t}\n\n\t\terrs, err := decodeErrorPayload(resp)\n\t\trequire.EqualError(t, err, \"400 Bad Request\")\n\t\tassert.Empty(t, errs)\n\t})\n}\n\nfunc Test_checkResponseCode(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"returns regular json error message\", func(t *testing.T) {\n\t\tresp := &http.Response{\n\t\t\tStatus:     \"400 Bad Request\",\n\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\tBody: io.NopCloser(bytes.NewBufferString(\n\t\t\t\t`{\"errors\":[\"Unsupported GPG Key algorithm. Supported key algorithms are [RSA, DSA]\"]}`,\n\t\t\t)),\n\t\t}\n\n\t\terr := checkResponseCode(resp)\n\t\trequire.EqualError(t, err, \"Unsupported GPG Key algorithm. Supported key algorithms are [RSA, DSA]\")\n\t})\n\n\tt.Run(\"still maps invalid include message\", func(t *testing.T) {\n\t\tresp := &http.Response{\n\t\t\tStatus:     \"400 Bad Request\",\n\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\tBody: io.NopCloser(bytes.NewBufferString(\n\t\t\t\t`{\"errors\":[\"include parameter is invalid\"]}`,\n\t\t\t)),\n\t\t}\n\n\t\terr := checkResponseCode(resp)\n\t\tassert.ErrorIs(t, err, ErrInvalidIncludeValue)\n\t})\n}\n\nfunc Test_BaseURL(t *testing.T) {\n\tt.Parallel()\n\tclient, err := NewClient(&Config{\n\t\tAddress:  \"https://example.com\",\n\t\tBasePath: \"api/v99\",\n\t})\n\n\trequire.NoError(t, err)\n\n\turl := client.BaseURL()\n\tassert.Equal(t, \"https://example.com/api/v99/\", url.String())\n}\n\nfunc Test_DefaultBaseURL(t *testing.T) {\n\tt.Parallel()\n\tclient, err := NewClient(&Config{\n\t\tAddress: \"https://example.com\",\n\t})\n\n\trequire.NoError(t, err)\n\n\turl := client.BaseURL()\n\tassert.Equal(t, \"https://example.com/api/v2/\", url.String())\n}\n\nfunc Test_DefaultRegistryBaseURL(t *testing.T) {\n\tt.Parallel()\n\tclient, err := NewClient(&Config{\n\t\tAddress: \"https://example.com\",\n\t})\n\n\trequire.NoError(t, err)\n\n\turl := client.BaseRegistryURL()\n\tassert.Equal(t, \"https://example.com/api/registry/\", url.String())\n}\n\nfunc Test_RegistryBaseURL(t *testing.T) {\n\tt.Parallel()\n\tclient, err := NewClient(&Config{\n\t\tAddress:          \"https://example.com\",\n\t\tRegistryBasePath: \"/api/registry99\",\n\t})\n\n\trequire.NoError(t, err)\n\n\turl := client.BaseRegistryURL()\n\tassert.Equal(t, \"https://example.com/api/registry99/\", url.String())\n}\n\nfunc Test_EncodeQueryParams(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"with no listOptions and therefore no include field defined\", func(t *testing.T) {\n\t\turlVals := map[string][]string{\n\t\t\t\"include\": {},\n\t\t}\n\t\trequestURLquery := encodeQueryParams(urlVals)\n\t\tassert.Equal(t, requestURLquery, \"\")\n\t})\n\tt.Run(\"with listOptions setting multiple include options\", func(t *testing.T) {\n\t\turlVals := map[string][]string{\n\t\t\t\"include\": {\"workspace\", \"cost_estimate\"},\n\t\t}\n\t\trequestURLquery := encodeQueryParams(urlVals)\n\t\tassert.Equal(t, requestURLquery, \"include=workspace%2Ccost_estimate\")\n\t})\n}\n\nfunc Test_RegistryBasePath(t *testing.T) {\n\tt.Parallel()\n\tclient, err := NewClient(&Config{\n\t\tToken: \"foo\",\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"ensures client creates a request with registry base path\", func(t *testing.T) {\n\t\tpath := \"/api/registry/some/path/to/resource\"\n\t\treq, err := client.NewRequest(\"GET\", path, nil)\n\t\trequire.NoError(t, err)\n\n\t\texpected := os.Getenv(\"TFE_ADDRESS\") + path\n\t\tassert.Equal(t, req.retryableRequest.URL.String(), expected)\n\t})\n}\n\nfunc Test_NewRequest(t *testing.T) {\n\tt.Parallel()\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/get_request_with_query_param\":\n\t\t\tval := r.URL.Query().Get(\"include\")\n\t\t\tif val != \"workspace,cost_estimate\" {\n\t\t\t\tt.Fatalf(\"unexpected include value: %q\", val)\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\tcase \"/api/v2/ping\":\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected request: %s\", r.URL.String())\n\t\t}\n\t}))\n\n\tt.Cleanup(func() {\n\t\ttestServer.Close()\n\t})\n\n\tclient, err := NewClient(&Config{\n\t\tAddress: testServer.URL,\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"allows path to include query params\", func(t *testing.T) {\n\t\trequest, err := client.NewRequest(\"GET\", \"/get_request_with_query_param?include=workspace,cost_estimate\", nil)\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\t\terr = request.DoJSON(ctx, nil)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc Test_NewRequestWithAdditionalQueryParams(t *testing.T) {\n\tt.Parallel()\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/get_request_include\":\n\t\t\tval := r.URL.Query().Get(\"include\")\n\t\t\tif val != \"workspace,cost_estimate\" {\n\t\t\t\tt.Fatalf(\"unexpected include value: %q\", val)\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\tcase \"/get_request_include_extra\":\n\t\t\tval := r.URL.Query().Get(\"include\")\n\t\t\tif val != \"workspace,cost_estimate\" {\n\t\t\t\tt.Fatalf(\"unexpected include value: expected %q, got %q\", \"extra,workspace,cost_estimate\", val)\n\t\t\t}\n\t\t\textra := r.URL.Query().Get(\"extra\")\n\t\t\tif extra != \"value\" {\n\t\t\t\tt.Fatalf(\"unexpected extra value: expected %q, got %q\", \"value\", extra)\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\tcase \"/get_request_include_raw\":\n\t\t\textra := r.URL.Query().Get(\"Name\")\n\t\t\tif extra != \"yes\" {\n\t\t\t\tt.Fatalf(\"unexpected query: %s\", r.URL.RawQuery)\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\tcase \"/delete_with_query\":\n\t\t\textra := r.URL.Query().Get(\"extra\")\n\t\t\tif extra != \"value\" {\n\t\t\t\tt.Fatalf(\"unexpected query: expected %q, got %q\", \"value\", extra)\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\tcase \"/api/v2/ping\":\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected request: %s\", r.URL.String())\n\t\t}\n\t}))\n\tt.Cleanup(func() {\n\t\ttestServer.Close()\n\t})\n\n\tclient, err := NewClient(&Config{\n\t\tAddress: testServer.URL,\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"with additional query parameters\", func(t *testing.T) {\n\t\trequest, err := client.NewRequestWithAdditionalQueryParams(\"GET\", \"/get_request_include\", nil, map[string][]string{\n\t\t\t\"include\": {\"workspace\", \"cost_estimate\"},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\t\terr = request.DoJSON(ctx, nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\ttype extra struct {\n\t\tExtra string `url:\"extra\"`\n\t}\n\n\t// json-encoded structs use the field name as the query parameter name\n\ttype raw struct {\n\t\tName string `json:\"extra\"`\n\t}\n\n\tt.Run(\"GET request with req attr and additional request attributes\", func(t *testing.T) {\n\t\trequest, err := client.NewRequestWithAdditionalQueryParams(\"GET\", \"/get_request_include_extra\", &extra{Extra: \"value\"}, map[string][]string{\n\t\t\t\"include\": {\"workspace\", \"cost_estimate\"},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\t\terr = request.DoJSON(ctx, nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"DELETE request with additional request attributes\", func(t *testing.T) {\n\t\trequest, err := client.NewRequestWithAdditionalQueryParams(\"DELETE\", \"/delete_with_query\", nil, map[string][]string{\n\t\t\t\"extra\": {\"value\"},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\t\terr = request.DoJSON(ctx, nil)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"GET request with other kinds of annotations\", func(t *testing.T) {\n\t\trequest, err := client.NewRequestWithAdditionalQueryParams(\"GET\", \"/get_request_include_raw\", &raw{Name: \"yes\"}, nil)\n\t\trequire.NoError(t, err)\n\n\t\tctx := context.Background()\n\t\terr = request.DoJSON(ctx, nil)\n\t\trequire.NoError(t, err)\n\t})\n}\n"
  },
  {
    "path": "type_helpers.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"time\"\n\n\t\"github.com/hashicorp/jsonapi\"\n)\n\n// Access returns a pointer to the given team access type.\nfunc Access(v AccessType) *AccessType {\n\treturn &v\n}\n\nfunc AgentExecutionModePtr(v AgentExecutionMode) *AgentExecutionMode {\n\treturn &v\n}\n\n// ProjectAccess returns a pointer to the given team access project type.\nfunc ProjectAccess(v TeamProjectAccessType) *TeamProjectAccessType {\n\treturn &v\n}\n\n// ProjectSettingsPermission returns a pointer to the given team access project type.\nfunc ProjectSettingsPermission(v ProjectSettingsPermissionType) *ProjectSettingsPermissionType {\n\treturn &v\n}\n\n// ProjectTeamsPermission returns a pointer to the given team access project type.\nfunc ProjectTeamsPermission(v ProjectTeamsPermissionType) *ProjectTeamsPermissionType {\n\treturn &v\n}\n\n// ProjectVariableSetsPermission returns a pointer to the given team access project type.\nfunc ProjectVariableSetsPermission(v ProjectVariableSetsPermissionType) *ProjectVariableSetsPermissionType {\n\treturn &v\n}\n\n// WorkspaceRunsPermission returns a pointer to the given team access project type.\nfunc WorkspaceRunsPermission(v WorkspaceRunsPermissionType) *WorkspaceRunsPermissionType {\n\treturn &v\n}\n\n// WorkspaceSentinelMocksPermission returns a pointer to the given team access project type.\nfunc WorkspaceSentinelMocksPermission(v WorkspaceSentinelMocksPermissionType) *WorkspaceSentinelMocksPermissionType {\n\treturn &v\n}\n\n// WorkspaceStateVersionsPermission returns a pointer to the given team access project type.\nfunc WorkspaceStateVersionsPermission(v WorkspaceStateVersionsPermissionType) *WorkspaceStateVersionsPermissionType {\n\treturn &v\n}\n\n// WorkspaceStateVersionsPermission returns a pointer to the given team access project type.\nfunc WorkspaceVariablesPermission(v WorkspaceVariablesPermissionType) *WorkspaceVariablesPermissionType {\n\treturn &v\n}\n\n// RunsPermission returns a pointer to the given team runs permission type.\nfunc RunsPermission(v RunsPermissionType) *RunsPermissionType {\n\treturn &v\n}\n\n// VariablesPermission returns a pointer to the given team variables permission type.\nfunc VariablesPermission(v VariablesPermissionType) *VariablesPermissionType {\n\treturn &v\n}\n\n// StateVersionsPermission returns a pointer to the given team state versions permission type.\nfunc StateVersionsPermission(v StateVersionsPermissionType) *StateVersionsPermissionType {\n\treturn &v\n}\n\n// SentinelMocksPermission returns a pointer to the given team Sentinel mocks permission type.\nfunc SentinelMocksPermission(v SentinelMocksPermissionType) *SentinelMocksPermissionType {\n\treturn &v\n}\n\n// AuthPolicy returns a pointer to the given authentication poliy.\nfunc AuthPolicy(v AuthPolicyType) *AuthPolicyType {\n\treturn &v\n}\n\n// Bool returns a pointer to the given bool\nfunc Bool(v bool) *bool {\n\treturn &v\n}\n\n// Category returns a pointer to the given category type.\nfunc Category(v CategoryType) *CategoryType {\n\treturn &v\n}\n\n// EnforcementMode returns a pointer to the given enforcement level.\nfunc EnforcementMode(v EnforcementLevel) *EnforcementLevel {\n\treturn &v\n}\n\n// Int returns a pointer to the given int.\nfunc Int(v int) *int {\n\treturn &v\n}\n\n// Int64 returns a pointer to the given int64.\nfunc Int64(v int64) *int64 {\n\treturn &v\n}\n\n// NotificationDestination returns a pointer to the given notification configuration destination type\nfunc NotificationDestination(v NotificationDestinationType) *NotificationDestinationType {\n\treturn &v\n}\n\n// PlanExportType returns a pointer to the given plan export data type.\nfunc PlanExportType(v PlanExportDataType) *PlanExportDataType {\n\treturn &v\n}\n\n// ServiceProvider returns a pointer to the given service provider type.\nfunc ServiceProvider(v ServiceProviderType) *ServiceProviderType {\n\treturn &v\n}\n\n// SMTPAuthValue returns a pointer to a given smtp auth type.\nfunc SMTPAuthValue(v SMTPAuthType) *SMTPAuthType {\n\treturn &v\n}\n\n// String returns a pointer to the given string.\nfunc String(v string) *string {\n\treturn &v\n}\n\n// SAMLProvider returns a pointer to the given SAML provider type.\nfunc SAMLProvider(v SAMLProviderType) *SAMLProviderType {\n\treturn &v\n}\n\nfunc NullableBool(v bool) jsonapi.NullableAttr[bool] {\n\treturn jsonapi.NewNullableAttrWithValue[bool](v)\n}\n\nfunc NullBool() jsonapi.NullableAttr[bool] {\n\treturn jsonapi.NewNullNullableAttr[bool]()\n}\n\nfunc NullableTime(v time.Time) jsonapi.NullableAttr[time.Time] {\n\treturn jsonapi.NewNullableAttrWithValue[time.Time](v)\n}\n\nfunc NullTime() jsonapi.NullableAttr[time.Time] {\n\treturn jsonapi.NewNullNullableAttr[time.Time]()\n}\n\n// Ptr returns a pointer to the given value of any type.\nfunc Ptr[T any](v T) *T {\n\treturn &v\n}\n"
  },
  {
    "path": "user.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Users = (*users)(nil)\n\n// Users describes all the user related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/account\ntype Users interface {\n\t// ReadCurrent reads the details of the currently authenticated user.\n\tReadCurrent(ctx context.Context) (*User, error)\n\n\t// UpdateCurrent updates attributes of the currently authenticated user.\n\tUpdateCurrent(ctx context.Context, options UserUpdateOptions) (*User, error)\n}\n\n// users implements Users.\ntype users struct {\n\tclient *Client\n}\n\n// User represents a Terraform Enterprise user.\ntype User struct {\n\tID               string     `jsonapi:\"primary,users\"`\n\tAvatarURL        string     `jsonapi:\"attr,avatar-url\"`\n\tEmail            string     `jsonapi:\"attr,email\"`\n\tIsServiceAccount bool       `jsonapi:\"attr,is-service-account\"`\n\tTwoFactor        *TwoFactor `jsonapi:\"attr,two-factor\"`\n\tUnconfirmedEmail string     `jsonapi:\"attr,unconfirmed-email\"`\n\tUsername         string     `jsonapi:\"attr,username\"`\n\tV2Only           bool       `jsonapi:\"attr,v2-only\"`\n\t// Deprecated: IsSiteAdmin was deprecated in v202406 and will be removed in a future version of Terraform Enterprise\n\tIsSiteAdmin *bool            `jsonapi:\"attr,is-site-admin\"`\n\tIsAdmin     *bool            `jsonapi:\"attr,is-admin\"`\n\tIsSsoLogin  *bool            `jsonapi:\"attr,is-sso-login\"`\n\tPermissions *UserPermissions `jsonapi:\"attr,permissions\"`\n\n\t// Relations\n\t// AuthenticationTokens *AuthenticationTokens `jsonapi:\"relation,authentication-tokens\"`\n}\n\n// UserPermissions represents the user permissions.\ntype UserPermissions struct {\n\tCanCreateOrganizations bool `jsonapi:\"attr,can-create-organizations\"`\n\tCanChangeEmail         bool `jsonapi:\"attr,can-change-email\"`\n\tCanChangeUsername      bool `jsonapi:\"attr,can-change-username\"`\n\tCanManageUserTokens    bool `jsonapi:\"attr,can-manage-user-tokens\"`\n\tCanView2FaSettings     bool `jsonapi:\"attr,can-view2fa-settings\"`\n\tCanManageHcpAccount    bool `jsonapi:\"attr,can-manage-hcp-account\"`\n}\n\n// TwoFactor represents the organization permissions.\ntype TwoFactor struct {\n\tEnabled  bool `jsonapi:\"attr,enabled\"`\n\tVerified bool `jsonapi:\"attr,verified\"`\n}\n\n// UserUpdateOptions represents the options for updating a user.\ntype UserUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,users\"`\n\n\t// Optional: New username.\n\tUsername *string `jsonapi:\"attr,username,omitempty\"`\n\n\t// Optional: New email address (must be consumed afterwards to take effect).\n\tEmail *string `jsonapi:\"attr,email,omitempty\"`\n}\n\n// ReadCurrent reads the details of the currently authenticated user.\nfunc (s *users) ReadCurrent(ctx context.Context) (*User, error) {\n\treq, err := s.client.NewRequest(\"GET\", \"account/details\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := &User{}\n\terr = req.Do(ctx, u)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn u, nil\n}\n\n// UpdateCurrent updates attributes of the currently authenticated user.\nfunc (s *users) UpdateCurrent(ctx context.Context, options UserUpdateOptions) (*User, error) {\n\treq, err := s.client.NewRequest(\"PATCH\", \"account/update\", &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := &User{}\n\terr = req.Do(ctx, u)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn u, nil\n}\n"
  },
  {
    "path": "user_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestUsersReadCurrent(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tu, err := client.Users.ReadCurrent(ctx)\n\trequire.NoError(t, err)\n\n\tassert.NotEmpty(t, u.ID)\n\tassert.NotEmpty(t, u.AvatarURL)\n\tassert.NotEmpty(t, u.Username)\n\n\tt.Run(\"two factor options are decoded\", func(t *testing.T) {\n\t\tassert.NotNil(t, u.TwoFactor)\n\t})\n\n\tt.Run(\"permissions are decoded\", func(t *testing.T) {\n\t\tassert.NotNil(t, u.Permissions)\n\t})\n}\n\nfunc TestUsersUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tuTest, err := client.Users.ReadCurrent(ctx)\n\trequire.NoError(t, err)\n\n\t// Make sure we reset the current user when we're done.\n\tdefer func() {\n\t\t_, err := client.Users.UpdateCurrent(ctx, UserUpdateOptions{\n\t\t\tEmail:    String(uTest.Email),\n\t\t\tUsername: String(uTest.Username),\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Logf(\"Error updating user: %s\", err)\n\t\t}\n\t}()\n\n\tt.Run(\"without any options\", func(t *testing.T) {\n\t\t_, err := client.Users.UpdateCurrent(ctx, UserUpdateOptions{})\n\t\trequire.NoError(t, err)\n\n\t\tu, err := client.Users.ReadCurrent(ctx)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, u, uTest)\n\t})\n\n\tt.Run(\"with a new username\", func(t *testing.T) {\n\t\t_, err := client.Users.UpdateCurrent(ctx, UserUpdateOptions{\n\t\t\tUsername: String(\"NewTestUsername\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tu, err := client.Users.ReadCurrent(ctx)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"NewTestUsername\", u.Username)\n\t})\n\n\tt.Run(\"with a new email address\", func(t *testing.T) {\n\t\t_, err := client.Users.UpdateCurrent(ctx, UserUpdateOptions{\n\t\t\tEmail: String(\"newtestemail@hashicorp.com\"),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tu, err := client.Users.ReadCurrent(ctx)\n\n\t\temail := \"\"\n\t\tif u.UnconfirmedEmail != \"\" {\n\t\t\temail = u.UnconfirmedEmail\n\t\t} else if u.Email != \"\" {\n\t\t\temail = u.Email\n\t\t} else {\n\t\t\tt.Fatalf(\"cannot test with user %q because both email and unconfirmed email are empty\", u.ID)\n\t\t}\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"newtestemail@hashicorp.com\", email)\n\t})\n\n\tt.Run(\"with invalid email address\", func(t *testing.T) {\n\t\tu, err := client.Users.UpdateCurrent(ctx, UserUpdateOptions{\n\t\t\tEmail: String(\"notamailaddress\"),\n\t\t})\n\t\tassert.Nil(t, u)\n\t\tassert.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "user_token.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ UserTokens = (*userTokens)(nil)\n\n// UserTokens describes all the user token related methods that the\n// HCP Terraform and Terraform Enterprise API supports.\n//\n// TFE API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/user-tokens\ntype UserTokens interface {\n\t// List all the tokens of the given user ID.\n\tList(ctx context.Context, userID string) (*UserTokenList, error)\n\n\t// Create a new user token\n\tCreate(ctx context.Context, userID string, options UserTokenCreateOptions) (*UserToken, error)\n\n\t// Read a user token by its ID.\n\tRead(ctx context.Context, tokenID string) (*UserToken, error)\n\n\t// Delete a user token by its ID.\n\tDelete(ctx context.Context, tokenID string) error\n}\n\n// userTokens implements UserTokens.\ntype userTokens struct {\n\tclient *Client\n}\n\n// UserTokenList is a list of tokens for the given user ID.\ntype UserTokenList struct {\n\t*Pagination\n\tItems []*UserToken\n}\n\n// CreatedByChoice is a choice type struct that represents the possible values\n// within a polymorphic relation. If a value is available, exactly one field\n// will be non-nil.\ntype CreatedByChoice struct {\n\tOrganization *Organization\n\tTeam         *Team\n\tUser         *User\n}\n\n// UserToken represents a Terraform Enterprise user token.\ntype UserToken struct {\n\tID          string           `jsonapi:\"primary,authentication-tokens\"`\n\tCreatedAt   time.Time        `jsonapi:\"attr,created-at,iso8601\"`\n\tDescription string           `jsonapi:\"attr,description\"`\n\tLastUsedAt  time.Time        `jsonapi:\"attr,last-used-at,iso8601\"`\n\tToken       string           `jsonapi:\"attr,token\"`\n\tExpiredAt   time.Time        `jsonapi:\"attr,expired-at,iso8601\"`\n\tCreatedBy   *CreatedByChoice `jsonapi:\"polyrelation,created-by\"`\n}\n\n// UserTokenCreateOptions contains the options for creating a user token.\ntype UserTokenCreateOptions struct {\n\tDescription string `jsonapi:\"attr,description,omitempty\"`\n\t// Optional: The token's expiration date.\n\t// This feature is available in TFE release v202305-1 and later\n\tExpiredAt *time.Time `jsonapi:\"attr,expired-at,iso8601,omitempty\"`\n}\n\n// Create a new user token\nfunc (s *userTokens) Create(ctx context.Context, userID string, options UserTokenCreateOptions) (*UserToken, error) {\n\tif !validStringID(&userID) {\n\t\treturn nil, ErrInvalidUserID\n\t}\n\n\tu := fmt.Sprintf(\"users/%s/authentication-tokens\", url.PathEscape(userID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tut := &UserToken{}\n\terr = req.Do(ctx, ut)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ut, err\n}\n\n// List shows existing user tokens\nfunc (s *userTokens) List(ctx context.Context, userID string) (*UserTokenList, error) {\n\tif !validStringID(&userID) {\n\t\treturn nil, ErrInvalidUserID\n\t}\n\n\tu := fmt.Sprintf(\"users/%s/authentication-tokens\", url.PathEscape(userID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttl := &UserTokenList{}\n\terr = req.Do(ctx, tl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tl, err\n}\n\n// Read a user token by its ID.\nfunc (s *userTokens) Read(ctx context.Context, tokenID string) (*UserToken, error) {\n\tif !validStringID(&tokenID) {\n\t\treturn nil, ErrInvalidTokenID\n\t}\n\n\tu := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(tokenID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttt := &UserToken{}\n\terr = req.Do(ctx, tt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tt, err\n}\n\n// Delete a user token by its ID.\nfunc (s *userTokens) Delete(ctx context.Context, tokenID string) error {\n\tif !validStringID(&tokenID) {\n\t\treturn ErrInvalidTokenID\n\t}\n\n\tu := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(tokenID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "user_token_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// TestUserTokens_List tests listing user tokens\nfunc TestUserTokens_List(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\tuser, err := client.Users.ReadCurrent(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttoken, cleanupFunc := createToken(t, client, user)\n\tdefer cleanupFunc()\n\n\tt.Run(\"listing existing tokens\", func(t *testing.T) {\n\t\tctx := context.Background()\n\t\ttl, err := client.UserTokens.List(ctx, user.ID)\n\t\trequire.NoError(t, err)\n\t\tvar found bool\n\t\tfor _, j := range tl.Items {\n\t\t\tif j.ID == token.ID {\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.Fatalf(\"token (%s) not found in token list\", token.ID)\n\t\t}\n\t})\n}\n\n// TestUserTokens_Create tests basic creation of user tokens\nfunc TestUserTokens_Create(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\tuser, err := client.Users.ReadCurrent(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// collect the created tokens for revoking after the test\n\tvar tokens []string\n\tdefer func(t *testing.T) {\n\t\tfor _, token := range tokens {\n\t\t\terr := client.UserTokens.Delete(ctx, token)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Error deleting token in cleanup:%s\", err)\n\t\t\t}\n\t\t}\n\t}(t)\n\n\tt.Run(\"create token with no description\", func(t *testing.T) {\n\t\ttoken, err := client.UserTokens.Create(ctx, user.ID, UserTokenCreateOptions{})\n\t\ttokens = append(tokens, token.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\n\tt.Run(\"create token with description\", func(t *testing.T) {\n\t\ttoken, err := client.UserTokens.Create(ctx, user.ID, UserTokenCreateOptions{\n\t\t\tDescription: fmt.Sprintf(\"go-tfe-user-token-test-%s\", randomString(t)),\n\t\t})\n\t\ttokens = append(tokens, token.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n\n\tt.Run(\"create token without an expiration date\", func(t *testing.T) {\n\t\ttoken, err := client.UserTokens.Create(ctx, user.ID, UserTokenCreateOptions{})\n\t\ttokens = append(tokens, token.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tassert.NotZero(t, token.ExpiredAt)\n\t\texpectedExpiry := token.CreatedAt.AddDate(defaultTokenExpirationYears, 0, 0)\n\t\t// Allow a small buffer (1 minute) for timestamp precision differences.\n\t\tassert.WithinDuration(t, expectedExpiry, token.ExpiredAt, time.Minute)\n\t})\n\n\tt.Run(\"create token with an expiration date\", func(t *testing.T) {\n\t\tcurrentTime := time.Now().UTC().Truncate(time.Second)\n\t\toneDayLater := currentTime.Add(24 * time.Hour)\n\t\ttoken, err := client.UserTokens.Create(ctx, user.ID, UserTokenCreateOptions{\n\t\t\tExpiredAt: &oneDayLater,\n\t\t})\n\t\ttokens = append(tokens, token.ID)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tassert.Equal(t, token.ExpiredAt, oneDayLater)\n\t})\n}\n\n// TestUserTokens_Read tests basic creation of user tokens\nfunc TestUserTokens_Read(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\tuser, err := client.Users.ReadCurrent(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttoken, tokenCleanupFunc := createToken(t, client, user)\n\tdefer tokenCleanupFunc()\n\n\tt.Run(\"read token\", func(t *testing.T) {\n\t\tto, err := client.UserTokens.Read(ctx, token.ID)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"expected to read token (%s), got error: %s\", token.ID, err)\n\t\t}\n\t\t// The initial API call to create a token will return a value in the token\n\t\t// object. Empty that out for comparison\n\t\ttoken.Token = \"\"\n\t\tassert.Equal(t, token, to)\n\n\t\trequireExactlyOneNotEmpty(t, token.CreatedBy.Organization, token.CreatedBy.Team, token.CreatedBy.User)\n\t})\n}\n\n// createToken is a helper method to create a valid token for a given user,\n// which returns both the token and a function to revoke it\nfunc createToken(t *testing.T, client *Client, user *User) (*UserToken, func()) {\n\tt.Helper()\n\tctx := context.Background()\n\tif user == nil {\n\t\tt.Fatal(\"Nil user in createToken\")\n\t}\n\ttoken, err := client.UserTokens.Create(ctx, user.ID, UserTokenCreateOptions{\n\t\tDescription: fmt.Sprintf(\"go-tfe-user-token-test-%s\", randomString(t)),\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn token, func() {\n\t\tif err := client.UserTokens.Delete(ctx, token.ID); err != nil {\n\t\t\tt.Errorf(\"Error destroying token! WARNING: Dangling resources\\n\"+\n\t\t\t\t\"may exist! The full error is shown below.\\n\\n\"+\n\t\t\t\t\"Token: %s\\nError: %s\", token.ID, err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "validations.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"net/mail\"\n\t\"regexp\"\n\n\tversion \"github.com/hashicorp/go-version\"\n)\n\n// A regular expression used to validate common string ID patterns.\nvar reStringID = regexp.MustCompile(`^[^/\\s]+$`)\n\n// validEmail checks if the given input is a correct email\nfunc validEmail(v string) bool {\n\t_, err := mail.ParseAddress(v)\n\treturn err == nil\n}\n\n// validString checks if the given input is present and non-empty.\nfunc validString(v *string) bool {\n\treturn v != nil && *v != \"\"\n}\n\n// validStringID checks if the given string pointer is non-nil and\n// contains a typical string identifier.\nfunc validStringID(v *string) bool {\n\treturn v != nil && reStringID.MatchString(*v)\n}\n\n// validVersion checks if the given input is a valid version.\nfunc validVersion(v string) bool {\n\t_, err := version.NewVersion(v)\n\treturn err == nil\n}\n"
  },
  {
    "path": "validations_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestValidStringID(t *testing.T) {\n\tt.Parallel()\n\ttype testCase struct {\n\t\texternalID    *string\n\t\texpectedValue bool\n\t}\n\n\tunifiedTeamID := \"iam.group:kmpwhkwf6tkgWzgJKPcP\"\n\tunifiedProjectID := \"616f63c1-3ef5-46a5-b5e8-6d1d86c3f93f\"\n\tnonUnifiedID := \"prj-AywVvpbtLQTcwf8K\"\n\tinvalidID := \"test/with-a-slash\"\n\tinvalidIDWithSpace := \"test with-space\"\n\n\tcases := map[string]testCase{\n\t\t\"external-id-is-nil\":                {externalID: nil, expectedValue: false},\n\t\t\"external-id-is-empty-string\":       {externalID: new(string), expectedValue: false},\n\t\t\"external-id-is-invalid-with-slash\": {externalID: &invalidID, expectedValue: false},\n\t\t\"external-id-is-invalid-with-space\": {externalID: &invalidIDWithSpace, expectedValue: false},\n\t\t\"external-id-is-unified-team-id\":    {externalID: &unifiedTeamID, expectedValue: true},\n\t\t\"external-id-is-unified-project-id\": {externalID: &unifiedProjectID, expectedValue: true},\n\t\t\"external-id-is-non-unified\":        {externalID: &nonUnifiedID, expectedValue: true},\n\t}\n\n\tfor name, tcase := range cases {\n\t\tt.Run(name, func(tt *testing.T) {\n\t\t\tactual := validStringID(tcase.externalID)\n\t\t\tassert.Equal(tt, tcase.expectedValue, actual)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "variable.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Variables = (*variables)(nil)\n\n// Variables describes all the variable related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspace-variables\ntype Variables interface {\n\t// List all the variables associated with the given workspace (doesn't include variables inherited from varsets).\n\tList(ctx context.Context, workspaceID string, options *VariableListOptions) (*VariableList, error)\n\n\t// ListAll all the variables associated with the given workspace including variables inherited from varsets.\n\tListAll(ctx context.Context, workspaceID string, options *VariableListOptions) (*VariableList, error)\n\n\t// Create is used to create a new variable.\n\tCreate(ctx context.Context, workspaceID string, options VariableCreateOptions) (*Variable, error)\n\n\t// Read a variable by its ID.\n\tRead(ctx context.Context, workspaceID string, variableID string) (*Variable, error)\n\n\t// Update values of an existing variable.\n\tUpdate(ctx context.Context, workspaceID string, variableID string, options VariableUpdateOptions) (*Variable, error)\n\n\t// Delete a variable by its ID.\n\tDelete(ctx context.Context, workspaceID string, variableID string) error\n}\n\n// variables implements Variables.\ntype variables struct {\n\tclient *Client\n}\n\n// CategoryType represents a category type.\ntype CategoryType string\n\n// List all available categories.\nconst (\n\tCategoryEnv       CategoryType = \"env\"\n\tCategoryPolicySet CategoryType = \"policy-set\"\n\tCategoryTerraform CategoryType = \"terraform\"\n)\n\n// VariableList represents a list of variables.\ntype VariableList struct {\n\t*Pagination\n\tItems []*Variable\n}\n\n// Variable represents a Terraform Enterprise variable.\ntype Variable struct {\n\tID          string       `jsonapi:\"primary,vars\"`\n\tKey         string       `jsonapi:\"attr,key\"`\n\tValue       string       `jsonapi:\"attr,value\"`\n\tDescription string       `jsonapi:\"attr,description\"`\n\tCategory    CategoryType `jsonapi:\"attr,category\"`\n\tHCL         bool         `jsonapi:\"attr,hcl\"`\n\tSensitive   bool         `jsonapi:\"attr,sensitive\"`\n\tVersionID   string       `jsonapi:\"attr,version-id\"`\n\n\t// Relations\n\tWorkspace *Workspace `jsonapi:\"relation,configurable\"`\n}\n\n// VariableListOptions represents the options for listing variables.\ntype VariableListOptions struct {\n\tListOptions\n}\n\n// VariableCreateOptions represents the options for creating a new variable.\ntype VariableCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,vars\"`\n\n\t// Required: The name of the variable.\n\tKey *string `jsonapi:\"attr,key\"`\n\n\t// Optional: The value of the variable.\n\tValue *string `jsonapi:\"attr,value,omitempty\"`\n\n\t// Optional: The description of the variable.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Required: Whether this is a Terraform or environment variable.\n\tCategory *CategoryType `jsonapi:\"attr,category\"`\n\n\t// Optional: Whether to evaluate the value of the variable as a string of HCL code.\n\tHCL *bool `jsonapi:\"attr,hcl,omitempty\"`\n\n\t// Optional: Whether the value is sensitive.\n\tSensitive *bool `jsonapi:\"attr,sensitive,omitempty\"`\n}\n\n// VariableUpdateOptions represents the options for updating a variable.\ntype VariableUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,vars\"`\n\n\t// The name of the variable.\n\tKey *string `jsonapi:\"attr,key,omitempty\"`\n\n\t// The value of the variable.\n\tValue *string `jsonapi:\"attr,value,omitempty\"`\n\n\t// The description of the variable.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Whether this is a Terraform or environment variable.\n\tCategory *CategoryType `jsonapi:\"attr,category,omitempty\"`\n\n\t// Whether to evaluate the value of the variable as a string of HCL code.\n\tHCL *bool `jsonapi:\"attr,hcl,omitempty\"`\n\n\t// Whether the value is sensitive.\n\tSensitive *bool `jsonapi:\"attr,sensitive,omitempty\"`\n}\n\n// List all the variables associated with the given workspace (doesn't include variables inherited from varsets).\nfunc (s *variables) List(ctx context.Context, workspaceID string, options *VariableListOptions) (*VariableList, error) {\n\treturn s.getList(ctx, workspaceID, options, \"workspaces/%s/vars\")\n}\n\n// ListAll the variables associated with the given workspace including variables inherited from varsets.\nfunc (s *variables) ListAll(ctx context.Context, workspaceID string, options *VariableListOptions) (*VariableList, error) {\n\treturn s.getList(ctx, workspaceID, options, \"workspaces/%s/all-vars\")\n}\n\nfunc (s *variables) getList(ctx context.Context, workspaceID string, options *VariableListOptions, path string) (*VariableList, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(path, url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvl := &VariableList{}\n\terr = req.Do(ctx, vl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vl, nil\n}\n\n// Create is used to create a new variable.\nfunc (s *variables) Create(ctx context.Context, workspaceID string, options VariableCreateOptions) (*Variable, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/vars\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &Variable{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, nil\n}\n\n// Read a variable by its ID.\nfunc (s *variables) Read(ctx context.Context, workspaceID, variableID string) (*Variable, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif !validStringID(&variableID) {\n\t\treturn nil, ErrInvalidVariableID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/vars/%s\", url.PathEscape(workspaceID), url.PathEscape(variableID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &Variable{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, err\n}\n\n// Update values of an existing variable.\nfunc (s *variables) Update(ctx context.Context, workspaceID, variableID string, options VariableUpdateOptions) (*Variable, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif !validStringID(&variableID) {\n\t\treturn nil, ErrInvalidVariableID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/vars/%s\", url.PathEscape(workspaceID), url.PathEscape(variableID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &Variable{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, nil\n}\n\n// Delete a variable by its ID.\nfunc (s *variables) Delete(ctx context.Context, workspaceID, variableID string) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\tif !validStringID(&variableID) {\n\t\treturn ErrInvalidVariableID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/vars/%s\", url.PathEscape(workspaceID), url.PathEscape(variableID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o VariableCreateOptions) valid() error {\n\tif !validString(o.Key) {\n\t\treturn ErrRequiredKey\n\t}\n\tif o.Category == nil {\n\t\treturn ErrRequiredCategory\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "variable_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestVariablesList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\tvTest1, vTestCleanup1 := createVariable(t, client, wTest)\n\tdefer vTestCleanup1()\n\tvTest2, vTestCleanup2 := createVariable(t, client, wTest)\n\tdefer vTestCleanup2()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tvl, err := client.Variables.List(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, vl.Items, vTest1)\n\t\tassert.Contains(t, vl.Items, vTest2)\n\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\tassert.Equal(t, 1, vl.CurrentPage)\n\t\tassert.Equal(t, 2, vl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tvl, err := client.Variables.List(ctx, wTest.ID, &VariableListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, vl.Items)\n\t\tassert.Equal(t, 999, vl.CurrentPage)\n\t\tassert.Equal(t, 2, vl.TotalCount)\n\t})\n\n\tt.Run(\"when workspace ID is invalid ID\", func(t *testing.T) {\n\t\tvl, err := client.Variables.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, vl)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestVariablesListAll(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tprjTest, prjTestCleanup := createProject(t, client, orgTest)\n\tt.Cleanup(prjTestCleanup)\n\n\twTest, wTestCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\tName:    String(randomString(t)),\n\t\tProject: prjTest,\n\t})\n\tt.Cleanup(wTestCleanup)\n\n\torgVarset, orgVarsetCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(orgVarsetCleanup)\n\n\tprjVarset, prjVarsetCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{\n\t\tParent: &Parent{\n\t\t\tOrganization: orgTest,\n\t\t\tProject:      prjTest,\n\t\t},\n\t})\n\tt.Cleanup(prjVarsetCleanup)\n\n\tglVar, glVarCleanup := createVariableSetVariable(t, client, orgVarset, VariableSetVariableCreateOptions{\n\t\tKey:         String(\"key1\"),\n\t\tValue:       String(\"gl_value1\"),\n\t\tCategory:    Category(CategoryTerraform),\n\t\tDescription: String(randomString(t)),\n\t})\n\tt.Cleanup(glVarCleanup)\n\n\tglVarOverwrite, glVarOverwriteCleanup := createVariableSetVariable(t, client, orgVarset, VariableSetVariableCreateOptions{\n\t\tKey:         String(\"key2\"),\n\t\tValue:       String(\"gl_value2\"),\n\t\tCategory:    Category(CategoryTerraform),\n\t\tDescription: String(randomString(t)),\n\t})\n\tt.Cleanup(glVarOverwriteCleanup)\n\n\tprjVar, prjVarCleanup := createVariableSetVariable(t, client, prjVarset, VariableSetVariableCreateOptions{\n\t\tKey:         String(\"key3\"),\n\t\tValue:       String(\"prj_value3\"),\n\t\tCategory:    Category(CategoryTerraform),\n\t\tDescription: String(randomString(t)),\n\t})\n\tt.Cleanup(prjVarCleanup)\n\n\tprjVarOverwrite, prjVarOverwriteCleanup := createVariableSetVariable(t, client, prjVarset, VariableSetVariableCreateOptions{\n\t\tKey:         String(\"key4\"),\n\t\tValue:       String(\"prj_value4\"),\n\t\tCategory:    Category(CategoryTerraform),\n\t\tDescription: String(randomString(t)),\n\t})\n\tt.Cleanup(prjVarOverwriteCleanup)\n\n\twsVar1, wsVar1Cleanup := createVariableWithOptions(t, client, wTest, VariableCreateOptions{\n\t\tKey:         String(\"key2\"),\n\t\tValue:       String(\"ws_value2\"),\n\t\tCategory:    Category(CategoryTerraform),\n\t\tDescription: String(randomString(t)),\n\t})\n\tt.Cleanup(wsVar1Cleanup)\n\n\twsVar2, wsVar2Cleanup := createVariableWithOptions(t, client, wTest, VariableCreateOptions{\n\t\tKey:         String(\"key4\"),\n\t\tValue:       String(\"ws_value4\"),\n\t\tCategory:    Category(CategoryTerraform),\n\t\tDescription: String(randomString(t)),\n\t})\n\tt.Cleanup(wsVar2Cleanup)\n\n\twsVar3, wsVar3Cleanup := createVariableWithOptions(t, client, wTest, VariableCreateOptions{\n\t\tKey:         String(\"key5\"),\n\t\tValue:       String(\"ws_value5\"),\n\t\tCategory:    Category(CategoryTerraform),\n\t\tDescription: String(randomString(t)),\n\t})\n\tt.Cleanup(wsVar3Cleanup)\n\n\tapplyVariableSetToWorkspace(t, client, orgVarset.ID, wTest.ID)\n\tapplyVariableSetToWorkspace(t, client, prjVarset.ID, wTest.ID)\n\n\tt.Run(\"when /workspaces/{external_id}/all-vars API is called\", func(t *testing.T) {\n\t\tvl, err := client.Variables.ListAll(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNilf(t, vl, \"expected to get a non-empty variables list\")\n\n\t\tvariableIDToValueMap := make(map[string]string)\n\t\tfor _, variable := range vl.Items {\n\t\t\tvariableIDToValueMap[variable.ID] = variable.Value\n\t\t}\n\t\tassert.Equal(t, len(vl.Items), 5)\n\t\tassert.NotContains(t, variableIDToValueMap, glVarOverwrite.ID)\n\t\tassert.NotContains(t, variableIDToValueMap, prjVarOverwrite.ID)\n\t\tassert.Contains(t, variableIDToValueMap, glVar.ID)\n\t\tassert.Contains(t, variableIDToValueMap, prjVar.ID)\n\t\tassert.Contains(t, variableIDToValueMap, wsVar1.ID)\n\t\tassert.Contains(t, variableIDToValueMap, wsVar2.ID)\n\t\tassert.Contains(t, variableIDToValueMap, wsVar3.ID)\n\t\tassert.Equal(t, glVar.Value, variableIDToValueMap[glVar.ID])\n\t\tassert.Equal(t, prjVar.Value, variableIDToValueMap[prjVar.ID])\n\t\tassert.Equal(t, wsVar1.Value, variableIDToValueMap[wsVar1.ID])\n\t\tassert.Equal(t, wsVar2.Value, variableIDToValueMap[wsVar2.ID])\n\t\tassert.Equal(t, wsVar3.Value, variableIDToValueMap[wsVar3.ID])\n\t})\n\n\tt.Run(\"when workspace ID is invalid ID\", func(t *testing.T) {\n\t\tvl, err := client.Variables.ListAll(ctx, badIdentifier, nil)\n\t\tassert.Nilf(t, vl, \"expected variables list to be nil\")\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestVariablesCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:         String(randomString(t)),\n\t\t\tValue:       String(randomString(t)),\n\t\t\tCategory:    Category(CategoryTerraform),\n\t\t\tDescription: String(randomString(t)),\n\t\t}\n\n\t\tv, err := client.Variables.Create(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Refresh workspace once the variable is created.\n\t\treWorkspace, err := client.Workspaces.ReadByID(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.Equal(t, *options.Description, v.Description)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\t// The workspace isn't returned correcly by the API.\n\t\t// assert.Equal(t, *options.Workspace, v.Workspace)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t\t// Validate that the same Variable is now listed in Workspace relations.\n\t\tassert.NotEmpty(t, reWorkspace.Variables)\n\t\tassert.Equal(t, reWorkspace.Variables[0].ID, v.ID)\n\t})\n\n\tt.Run(\"when options has an empty string value\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:         String(randomString(t)),\n\t\t\tValue:       String(\"\"),\n\t\t\tDescription: String(randomString(t)),\n\t\t\tCategory:    Category(CategoryTerraform),\n\t\t}\n\n\t\tv, err := client.Variables.Create(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.Equal(t, *options.Description, v.Description)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options has an empty string description\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:         String(randomString(t)),\n\t\t\tValue:       String(randomString(t)),\n\t\t\tDescription: String(\"\"),\n\t\t\tCategory:    Category(CategoryTerraform),\n\t\t}\n\n\t\tv, err := client.Variables.Create(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.Equal(t, *options.Description, v.Description)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options has a too-long description\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:         String(randomString(t)),\n\t\t\tValue:       String(randomString(t)),\n\t\t\tDescription: String(\"tortor aliquam nulla go lint is fussy about spelling cras fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel pretium lectus quam id leo in vitae turpis massa sed elementum tempus egestas sed sed risus pretium quam vulputate dignissim suspendisse in est ante in nibh mauris cursus mattis molestie a iaculis at erat pellentesque adipiscing commodo elit at imperdiet dui accumsan sit amet nulla redacted morbi tempus iaculis urna id volutpat lacus laoreet non curabitur gravida arcu ac tortor dignissim convallis aenean et tortor\"),\n\t\t\tCategory:    Category(CategoryTerraform),\n\t\t}\n\n\t\t_, err := client.Variables.Create(ctx, wTest.ID, options)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"when options is missing value\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:      String(randomString(t)),\n\t\t\tCategory: Category(CategoryTerraform),\n\t\t}\n\n\t\tv, err := client.Variables.Create(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, \"\", v.Value)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options is missing key\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tValue:    String(randomString(t)),\n\t\t\tCategory: Category(CategoryTerraform),\n\t\t}\n\n\t\t_, err := client.Variables.Create(ctx, wTest.ID, options)\n\t\tassert.Equal(t, err, ErrRequiredKey)\n\t})\n\n\tt.Run(\"when options has an empty key\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:      String(\"\"),\n\t\t\tValue:    String(randomString(t)),\n\t\t\tCategory: Category(CategoryTerraform),\n\t\t}\n\n\t\t_, err := client.Variables.Create(ctx, wTest.ID, options)\n\t\tassert.Equal(t, err, ErrRequiredKey)\n\t})\n\n\tt.Run(\"when options is missing category\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:   String(randomString(t)),\n\t\t\tValue: String(randomString(t)),\n\t\t}\n\n\t\t_, err := client.Variables.Create(ctx, wTest.ID, options)\n\t\tassert.Equal(t, err, ErrRequiredCategory)\n\t})\n\n\tt.Run(\"when workspace ID is invalid\", func(t *testing.T) {\n\t\toptions := VariableCreateOptions{\n\t\t\tKey:      String(randomString(t)),\n\t\t\tValue:    String(randomString(t)),\n\t\t\tCategory: Category(CategoryTerraform),\n\t\t}\n\n\t\t_, err := client.Variables.Create(ctx, badIdentifier, options)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestVariablesRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tvTest, vTestCleanup := createVariable(t, client, nil)\n\tdefer vTestCleanup()\n\n\tt.Run(\"when the variable exists\", func(t *testing.T) {\n\t\tv, err := client.Variables.Read(ctx, vTest.Workspace.ID, vTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, vTest.ID, v.ID)\n\t\tassert.Equal(t, vTest.Category, v.Category)\n\t\tassert.Equal(t, vTest.HCL, v.HCL)\n\t\tassert.Equal(t, vTest.Key, v.Key)\n\t\tassert.Equal(t, vTest.Sensitive, v.Sensitive)\n\t\tassert.Equal(t, vTest.Value, v.Value)\n\t\tassert.Equal(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"when the variable does not exist\", func(t *testing.T) {\n\t\tv, err := client.Variables.Read(ctx, vTest.Workspace.ID, \"nonexisting\")\n\t\tassert.Nil(t, v)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\tv, err := client.Variables.Read(ctx, badIdentifier, vTest.ID)\n\t\tassert.Nil(t, v)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n\n\tt.Run(\"without a valid variable ID\", func(t *testing.T) {\n\t\tv, err := client.Variables.Read(ctx, vTest.Workspace.ID, badIdentifier)\n\t\tassert.Nil(t, v)\n\t\tassert.Equal(t, err, ErrInvalidVariableID)\n\t})\n}\n\nfunc TestVariablesUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tvTest, vTestCleanup := createVariable(t, client, nil)\n\tdefer vTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := VariableUpdateOptions{\n\t\t\tKey:   String(\"newname\"),\n\t\t\tValue: String(\"newvalue\"),\n\t\t\tHCL:   Bool(true),\n\t\t}\n\n\t\tv, err := client.Variables.Update(ctx, vTest.Workspace.ID, vTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.HCL, v.HCL)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"when updating a subset of values\", func(t *testing.T) {\n\t\toptions := VariableUpdateOptions{\n\t\t\tKey: String(\"someothername\"),\n\t\t\tHCL: Bool(false),\n\t\t}\n\n\t\tv, err := client.Variables.Update(ctx, vTest.Workspace.ID, vTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.HCL, v.HCL)\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"with sensitive set\", func(t *testing.T) {\n\t\toptions := VariableUpdateOptions{\n\t\t\tSensitive: Bool(true),\n\t\t}\n\n\t\tv, err := client.Variables.Update(ctx, vTest.Workspace.ID, vTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Sensitive, v.Sensitive)\n\t\tassert.Empty(t, v.Value) // Because its now sensitive\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"with category set\", func(t *testing.T) {\n\t\tcategory := CategoryEnv\n\t\toptions := VariableUpdateOptions{\n\t\t\tCategory: &category,\n\t\t}\n\n\t\tv, err := client.Variables.Update(ctx, vTest.Workspace.ID, vTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"without any changes\", func(t *testing.T) {\n\t\tvTest, vTestCleanup := createVariable(t, client, nil)\n\t\tdefer vTestCleanup()\n\n\t\tv, err := client.Variables.Update(ctx, vTest.Workspace.ID, vTest.ID, VariableUpdateOptions{})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, vTest.ID, v.ID)\n\t\tassert.Equal(t, vTest.Key, v.Key)\n\t\tassert.Equal(t, vTest.Value, v.Value)\n\t\tassert.Equal(t, vTest.Description, v.Description)\n\t\tassert.Equal(t, vTest.Category, v.Category)\n\t\tassert.Equal(t, vTest.HCL, v.HCL)\n\t\tassert.Equal(t, vTest.Sensitive, v.Sensitive)\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"with invalid variable ID\", func(t *testing.T) {\n\t\t_, err := client.Variables.Update(ctx, badIdentifier, vTest.ID, VariableUpdateOptions{})\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n\n\tt.Run(\"with invalid variable ID\", func(t *testing.T) {\n\t\t_, err := client.Variables.Update(ctx, vTest.Workspace.ID, badIdentifier, VariableUpdateOptions{})\n\t\tassert.Equal(t, err, ErrInvalidVariableID)\n\t})\n}\n\nfunc TestVariablesDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\tvTest, _ := createVariable(t, client, wTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.Variables.Delete(ctx, wTest.ID, vTest.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with non existing variable ID\", func(t *testing.T) {\n\t\terr := client.Variables.Delete(ctx, wTest.ID, \"nonexisting\")\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"with invalid workspace ID\", func(t *testing.T) {\n\t\terr := client.Variables.Delete(ctx, badIdentifier, vTest.ID)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n\n\tt.Run(\"with invalid variable ID\", func(t *testing.T) {\n\t\terr := client.Variables.Delete(ctx, wTest.ID, badIdentifier)\n\t\tassert.Equal(t, err, ErrInvalidVariableID)\n\t})\n}\n"
  },
  {
    "path": "variable_set.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ VariableSets = (*variableSets)(nil)\n\n// VariableSets describes all the Variable Set related methods that the\n// Terraform Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets\ntype VariableSets interface {\n\t// List all the variable sets within an organization.\n\tList(ctx context.Context, organization string, options *VariableSetListOptions) (*VariableSetList, error)\n\n\t// ListForWorkspace gets the associated variable sets for a workspace.\n\tListForWorkspace(ctx context.Context, workspaceID string, options *VariableSetListOptions) (*VariableSetList, error)\n\n\t// ListForProject gets the associated variable sets for a project.\n\tListForProject(ctx context.Context, projectID string, options *VariableSetListOptions) (*VariableSetList, error)\n\n\t// Create is used to create a new variable set.\n\tCreate(ctx context.Context, organization string, options *VariableSetCreateOptions) (*VariableSet, error)\n\n\t// Read a variable set by its ID.\n\tRead(ctx context.Context, variableSetID string, options *VariableSetReadOptions) (*VariableSet, error)\n\n\t// Update an existing variable set.\n\tUpdate(ctx context.Context, variableSetID string, options *VariableSetUpdateOptions) (*VariableSet, error)\n\n\t// Delete a variable set by ID.\n\tDelete(ctx context.Context, variableSetID string) error\n\n\t// Apply variable set to workspaces in the supplied list.\n\tApplyToWorkspaces(ctx context.Context, variableSetID string, options *VariableSetApplyToWorkspacesOptions) error\n\n\t// Remove variable set from workspaces in the supplied list.\n\tRemoveFromWorkspaces(ctx context.Context, variableSetID string, options *VariableSetRemoveFromWorkspacesOptions) error\n\n\t// Apply variable set to projects in the supplied list.\n\tApplyToProjects(ctx context.Context, variableSetID string, options VariableSetApplyToProjectsOptions) error\n\n\t// Remove variable set from projects in the supplied list.\n\tRemoveFromProjects(ctx context.Context, variableSetID string, options VariableSetRemoveFromProjectsOptions) error\n\n\t// Apply variable set to stacks in the supplied list.\n\tApplyToStacks(ctx context.Context, variableSetID string, options *VariableSetApplyToStacksOptions) error\n\n\t// Remove variable set from stacks in the supplied list.\n\tRemoveFromStacks(ctx context.Context, variableSetID string, options *VariableSetRemoveFromStacksOptions) error\n\n\t// Update list of workspaces to which the variable set is applied to match the supplied list.\n\tUpdateWorkspaces(ctx context.Context, variableSetID string, options *VariableSetUpdateWorkspacesOptions) (*VariableSet, error)\n\n\t// Update list of stacks to which the variable set is applied to match the supplied list.\n\tUpdateStacks(ctx context.Context, variableSetID string, options *VariableSetUpdateStacksOptions) (*VariableSet, error)\n}\n\n// variableSets implements VariableSets.\ntype variableSets struct {\n\tclient *Client\n}\n\n// VariableSetList represents a list of variable sets.\ntype VariableSetList struct {\n\t*Pagination\n\tItems []*VariableSet\n}\n\n// Parent represents the variable set's parent (currently only organizations and projects are supported).\n// This relation is considered BETA, SUBJECT TO CHANGE, and likely unavailable to most users.\ntype Parent struct {\n\tOrganization *Organization\n\tProject      *Project\n}\n\n// VariableSet represents a Terraform Enterprise variable set.\ntype VariableSet struct {\n\tID          string `jsonapi:\"primary,varsets\"`\n\tName        string `jsonapi:\"attr,name\"`\n\tDescription string `jsonapi:\"attr,description\"`\n\tGlobal      bool   `jsonapi:\"attr,global\"`\n\tPriority    bool   `jsonapi:\"attr,priority\"`\n\n\t// Relations\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n\t// Optional: Parent represents the variable set's parent (currently only organizations and projects are supported).\n\t// This relation is considered BETA, SUBJECT TO CHANGE, and likely unavailable to most users.\n\tParent     *Parent                `jsonapi:\"polyrelation,parent\"`\n\tWorkspaces []*Workspace           `jsonapi:\"relation,workspaces,omitempty\"`\n\tProjects   []*Project             `jsonapi:\"relation,projects,omitempty\"`\n\tStacks     []*Stack               `jsonapi:\"relation,stacks,omitempty\"`\n\tVariables  []*VariableSetVariable `jsonapi:\"relation,vars,omitempty\"`\n}\n\n// A list of relations to include. See available resources\n// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/organizations#available-related-resources\ntype VariableSetIncludeOpt string\n\nconst (\n\tVariableSetWorkspaces VariableSetIncludeOpt = \"workspaces\"\n\tVariableSetProjects   VariableSetIncludeOpt = \"projects\"\n\tVariableSetStacks     VariableSetIncludeOpt = \"stacks\"\n\tVariableSetVars       VariableSetIncludeOpt = \"vars\"\n)\n\n// VariableSetListOptions represents the options for listing variable sets.\ntype VariableSetListOptions struct {\n\tListOptions\n\tInclude string `url:\"include\"`\n\n\t// Optional: A query string used to filter variable sets.\n\t// Any variable sets with a name partially matching this value will be returned.\n\tQuery string `url:\"q,omitempty\"`\n}\n\n// VariableSetCreateOptions represents the options for creating a new variable set within in a organization.\ntype VariableSetCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,varsets\"`\n\n\t// The name of the variable set.\n\t// Affects variable precedence when there are conflicts between Variable Sets\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets#apply-variable-set-to-workspaces\n\tName *string `jsonapi:\"attr,name\"`\n\n\t// A description to provide context for the variable set.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// If true the variable set is considered in all runs in the organization.\n\tGlobal *bool `jsonapi:\"attr,global,omitempty\"`\n\n\t// If true the variables in the set override any other variable values set\n\t// in a more specific scope including values set on the command line.\n\tPriority *bool `jsonapi:\"attr,priority,omitempty\"`\n\n\t// Optional: Parent represents the variable set's parent (currently only organizations and projects are supported).\n\t// This relation is considered BETA, SUBJECT TO CHANGE, and likely unavailable to most users.\n\tParent *Parent `jsonapi:\"polyrelation,parent\"`\n}\n\n// VariableSetReadOptions represents the options for reading variable sets.\ntype VariableSetReadOptions struct {\n\tInclude *[]VariableSetIncludeOpt `url:\"include,omitempty\"`\n}\n\n// VariableSetUpdateOptions represents the options for updating a variable set.\ntype VariableSetUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,varsets\"`\n\n\t// The name of the variable set.\n\t// Affects variable precedence when there are conflicts between Variable Sets\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets#apply-variable-set-to-workspaces\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// A description to provide context for the variable set.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// If true the variable set is considered in all runs in the organization.\n\tGlobal *bool `jsonapi:\"attr,global,omitempty\"`\n\n\t// If true the variables in the set override any other variable values set\n\t// in a more specific scope including values set on the command line.\n\tPriority *bool `jsonapi:\"attr,priority,omitempty\"`\n}\n\n// VariableSetApplyToWorkspacesOptions represents the options for applying variable sets to workspaces.\ntype VariableSetApplyToWorkspacesOptions struct {\n\t// The workspaces to apply the variable set to (additive).\n\tWorkspaces []*Workspace\n}\n\n// VariableSetRemoveFromWorkspacesOptions represents the options for removing variable sets from workspaces.\ntype VariableSetRemoveFromWorkspacesOptions struct {\n\t// The workspaces to remove the variable set from.\n\tWorkspaces []*Workspace\n}\n\n// VariableSetApplyToProjectsOptions represents the options for applying variable sets to projects.\ntype VariableSetApplyToProjectsOptions struct {\n\t// The projects to apply the variable set to (additive).\n\tProjects []*Project\n}\n\n// VariableSetApplyToStacksOptions represents the options for applying variable sets to stacks.\ntype VariableSetApplyToStacksOptions struct {\n\t// The stacks to apply the variable set to (additive).\n\tStacks []*Stack\n}\n\n// VariableSetRemoveFromProjectsOptions represents the options for removing variable sets from projects.\ntype VariableSetRemoveFromProjectsOptions struct {\n\t// The projects to remove the variable set from.\n\tProjects []*Project\n}\n\n// VariableSetRemoveFromStacksOptions represents the options for removing variable sets from stacks.\ntype VariableSetRemoveFromStacksOptions struct {\n\t// The stacks to remove the variable set from.\n\tStacks []*Stack\n}\n\n// VariableSetUpdateWorkspacesOptions represents a subset of update options specifically for applying variable sets to workspaces\ntype VariableSetUpdateWorkspacesOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,varsets\"`\n\n\t// The workspaces to be applied to. An empty set means remove all applied\n\tWorkspaces []*Workspace `jsonapi:\"relation,workspaces\"`\n}\n\n// VariableSetUpdateStacksOptions represents a subset of update options specifically for applying variable sets to stacks\ntype VariableSetUpdateStacksOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,varsets\"`\n\n\t// The stacks to be applied to. An empty set means remove all applied\n\tStacks []*Stack `jsonapi:\"relation,stacks\"`\n}\n\ntype privateVariableSetUpdateWorkspacesOptions struct {\n\tType       string       `jsonapi:\"primary,varsets\"`\n\tGlobal     bool         `jsonapi:\"attr,global\"`\n\tWorkspaces []*Workspace `jsonapi:\"relation,workspaces\"`\n}\n\ntype privateVariableSetUpdateStacksOptions struct {\n\tType   string   `jsonapi:\"primary,varsets\"`\n\tGlobal bool     `jsonapi:\"attr,global\"`\n\tStacks []*Stack `jsonapi:\"relation,stacks\"`\n}\n\n// List all Variable Sets in the organization\nfunc (s *variableSets) List(ctx context.Context, organization string, options *VariableSetListOptions) (*VariableSetList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif options != nil {\n\t\tif err := options.valid(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/varsets\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvl := &VariableSetList{}\n\terr = req.Do(ctx, vl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vl, nil\n}\n\n// ListForWorkspace gets the associated variable sets for a workspace.\nfunc (s *variableSets) ListForWorkspace(ctx context.Context, workspaceID string, options *VariableSetListOptions) (*VariableSetList, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif options != nil {\n\t\tif err := options.valid(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/varsets\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvl := &VariableSetList{}\n\terr = req.Do(ctx, vl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vl, nil\n}\n\n// ListForProject gets the associated variable sets for a project.\nfunc (s *variableSets) ListForProject(ctx context.Context, projectID string, options *VariableSetListOptions) (*VariableSetList, error) {\n\tif !validStringID(&projectID) {\n\t\treturn nil, ErrInvalidProjectID\n\t}\n\tif options != nil {\n\t\tif err := options.valid(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tu := fmt.Sprintf(\"projects/%s/varsets\", url.PathEscape(projectID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvl := &VariableSetList{}\n\terr = req.Do(ctx, vl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vl, nil\n}\n\n// Create is used to create a new variable set.\nfunc (s *variableSets) Create(ctx context.Context, organization string, options *VariableSetCreateOptions) (*VariableSet, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/varsets\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvl := &VariableSet{}\n\terr = req.Do(ctx, vl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vl, nil\n}\n\n// Read is used to inspect a given variable set based on ID\nfunc (s *variableSets) Read(ctx context.Context, variableSetID string, options *VariableSetReadOptions) (*VariableSet, error) {\n\tif !validStringID(&variableSetID) {\n\t\treturn nil, ErrInvalidVariableSetID\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvs := &VariableSet{}\n\terr = req.Do(ctx, vs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vs, err\n}\n\n// Update an existing variable set.\nfunc (s *variableSets) Update(ctx context.Context, variableSetID string, options *VariableSetUpdateOptions) (*VariableSet, error) {\n\tif !validStringID(&variableSetID) {\n\t\treturn nil, ErrInvalidVariableSetID\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &VariableSet{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, nil\n}\n\n// Delete a variable set by its ID.\nfunc (s *variableSets) Delete(ctx context.Context, variableSetID string) error {\n\tif !validStringID(&variableSetID) {\n\t\treturn ErrInvalidVariableSetID\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Apply variable set to workspaces in the supplied list.\n// Note: this method will return an error if the variable set has global = true.\nfunc (s *variableSets) ApplyToWorkspaces(ctx context.Context, variableSetID string, options *VariableSetApplyToWorkspacesOptions) error {\n\tif !validStringID(&variableSetID) {\n\t\treturn ErrInvalidVariableSetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/workspaces\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.Workspaces)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Remove variable set from workspaces in the supplied list.\n// Note: this method will return an error if the variable set has global = true.\nfunc (s *variableSets) RemoveFromWorkspaces(ctx context.Context, variableSetID string, options *VariableSetRemoveFromWorkspacesOptions) error {\n\tif !validStringID(&variableSetID) {\n\t\treturn ErrInvalidVariableSetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/workspaces\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.Workspaces)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// ApplyToProjects applies the variable set to projects in the supplied list.\n// This method will return an error if the variable set has global = true.\nfunc (s variableSets) ApplyToProjects(ctx context.Context, variableSetID string, options VariableSetApplyToProjectsOptions) error {\n\tif !validStringID(&variableSetID) {\n\t\treturn ErrInvalidVariableSetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/projects\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.Projects)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// RemoveFromProjects removes the variable set from projects in the supplied list.\n// This method will return an error if the variable set has global = true.\nfunc (s variableSets) RemoveFromProjects(ctx context.Context, variableSetID string, options VariableSetRemoveFromProjectsOptions) error {\n\tif !validStringID(&variableSetID) {\n\t\treturn ErrInvalidVariableSetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/projects\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.Projects)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// ApplyToStacks applies the variable set to stacks in the supplied list.\n// This method will return an error if the variable set has global = true.\nfunc (s *variableSets) ApplyToStacks(ctx context.Context, variableSetID string, options *VariableSetApplyToStacksOptions) error {\n\tif !validStringID(&variableSetID) {\n\t\treturn ErrInvalidVariableSetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/stacks\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.Stacks)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (s *variableSets) RemoveFromStacks(ctx context.Context, variableSetID string, options *VariableSetRemoveFromStacksOptions) error {\n\tif !validStringID(&variableSetID) {\n\t\treturn ErrInvalidVariableSetID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/stacks\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.Stacks)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Update variable set to be applied to only the workspaces in the supplied list.\nfunc (s *variableSets) UpdateWorkspaces(ctx context.Context, variableSetID string, options *VariableSetUpdateWorkspacesOptions) (*VariableSet, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Use private struct to ensure global is set to false when applying to workspaces\n\to := privateVariableSetUpdateWorkspacesOptions{\n\t\tGlobal:     bool(false),\n\t\tWorkspaces: options.Workspaces,\n\t}\n\n\t// We force inclusion of workspaces as that is the primary data for which we are concerned with confirming changes.\n\tu := fmt.Sprintf(\"varsets/%s?include=%s\", url.PathEscape(variableSetID), VariableSetWorkspaces)\n\treq, err := s.client.NewRequest(\"PATCH\", u, &o)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &VariableSet{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, nil\n}\n\n// Update variable set to be applied to only the stacks in the supplied list.\nfunc (s *variableSets) UpdateStacks(ctx context.Context, variableSetID string, options *VariableSetUpdateStacksOptions) (*VariableSet, error) {\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Use private struct to ensure global is set to false when applying to stacks\n\to := privateVariableSetUpdateStacksOptions{\n\t\tGlobal: bool(false),\n\t\tStacks: options.Stacks,\n\t}\n\n\t// We force inclusion of stacks as that is the primary data for which we are concerned with confirming changes.\n\tu := fmt.Sprintf(\"varsets/%s?include=%s\", url.PathEscape(variableSetID), VariableSetStacks)\n\treq, err := s.client.NewRequest(\"PATCH\", u, &o)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &VariableSet{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, nil\n}\n\nfunc (o *VariableSetListOptions) valid() error {\n\treturn nil\n}\n\nfunc (o *VariableSetCreateOptions) valid() error {\n\tif o == nil {\n\t\treturn nil\n\t}\n\tif !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\tif o.Global == nil {\n\t\treturn ErrRequiredGlobalFlag\n\t}\n\treturn nil\n}\n\nfunc (o *VariableSetApplyToWorkspacesOptions) valid() error {\n\tfor _, s := range o.Workspaces {\n\t\tif !validStringID(&s.ID) {\n\t\t\treturn ErrRequiredWorkspaceID\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o *VariableSetRemoveFromWorkspacesOptions) valid() error {\n\tfor _, s := range o.Workspaces {\n\t\tif !validStringID(&s.ID) {\n\t\t\treturn ErrRequiredWorkspaceID\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o *VariableSetApplyToProjectsOptions) valid() error {\n\tfor _, s := range o.Projects {\n\t\tif !validStringID(&s.ID) {\n\t\t\treturn ErrRequiredProjectID\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o VariableSetRemoveFromProjectsOptions) valid() error {\n\tfor _, s := range o.Projects {\n\t\tif !validStringID(&s.ID) {\n\t\t\treturn ErrRequiredProjectID\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o VariableSetApplyToStacksOptions) valid() error {\n\tfor _, s := range o.Stacks {\n\t\tif !validStringID(&s.ID) {\n\t\t\treturn ErrRequiredStackID\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o VariableSetRemoveFromStacksOptions) valid() error {\n\tfor _, s := range o.Stacks {\n\t\tif !validStringID(&s.ID) {\n\t\t\treturn ErrRequiredStackID\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o *VariableSetUpdateWorkspacesOptions) valid() error {\n\tif o == nil || o.Workspaces == nil {\n\t\treturn ErrRequiredWorkspacesList\n\t}\n\treturn nil\n}\n\nfunc (o *VariableSetUpdateStacksOptions) valid() error {\n\tif o == nil || o.Stacks == nil {\n\t\treturn ErrRequiredStacksList\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "variable_set_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestVariableSetsList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tvsTest1, vsTestCleanup1 := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup1)\n\tvsTest2, vsTestCleanup2 := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup2)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tvsl, err := client.VariableSets.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, vsl.Items)\n\t\tassert.Contains(t, vsl.Items, vsTest1)\n\t\tassert.Contains(t, vsl.Items, vsTest2)\n\n\t\tassert.Equal(t, 1, vsl.CurrentPage)\n\t\tassert.Equal(t, 2, vsl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tvsl, err := client.VariableSets.List(ctx, orgTest.Name, &VariableSetListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, vsl.Items)\n\t\tassert.Equal(t, 999, vsl.CurrentPage)\n\t\tassert.Equal(t, 2, vsl.TotalCount)\n\t})\n\n\tt.Run(\"when Organization name is an invalid ID\", func(t *testing.T) {\n\t\tvsl, err := client.VariableSets.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, vsl)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"with query parameter\", func(t *testing.T) {\n\t\tvsl, err := client.VariableSets.List(ctx, orgTest.Name, &VariableSetListOptions{\n\t\t\tQuery: vsTest2.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, vsl.Items, 1)\n\t\tassert.Equal(t, vsTest2.ID, vsl.Items[0].ID)\n\t})\n}\n\nfunc TestVariableSetsListForWorkspace(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\tworkspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(workspaceTestCleanup)\n\n\tvsTest1, vsTestCleanup1 := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup1)\n\tvsTest2, vsTestCleanup2 := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup2)\n\n\tapplyVariableSetToWorkspace(t, client, vsTest1.ID, workspaceTest.ID)\n\tapplyVariableSetToWorkspace(t, client, vsTest2.ID, workspaceTest.ID)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tvsl, err := client.VariableSets.ListForWorkspace(ctx, workspaceTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, vsl.Items, 2)\n\n\t\tids := []string{vsTest1.ID, vsTest2.ID}\n\t\tfor _, varset := range vsl.Items {\n\t\t\tassert.Contains(t, ids, varset.ID)\n\t\t}\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tvsl, err := client.VariableSets.ListForWorkspace(ctx, workspaceTest.ID, &VariableSetListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, vsl.Items)\n\t\tassert.Equal(t, 999, vsl.CurrentPage)\n\t\tassert.Equal(t, 2, vsl.TotalCount)\n\t})\n\n\tt.Run(\"when Workspace ID is an invalid ID\", func(t *testing.T) {\n\t\tvsl, err := client.VariableSets.ListForWorkspace(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, vsl)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n\n\tt.Run(\"with query parameter\", func(t *testing.T) {\n\t\tvsl, err := client.VariableSets.List(ctx, orgTest.Name, &VariableSetListOptions{\n\t\t\tQuery: vsTest2.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, vsl.Items, 1)\n\t\tassert.Equal(t, vsTest2.ID, vsl.Items[0].ID)\n\t})\n}\n\nfunc TestVariableSetsListForProject(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\tprojectTest, projectTestCleanup := createProject(t, client, orgTest)\n\tt.Cleanup(projectTestCleanup)\n\n\tvsTest1, vsTestCleanup1 := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup1)\n\tvsTest2, vsTestCleanup2 := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup2)\n\n\tapplyVariableSetToProject(t, client, vsTest1.ID, projectTest.ID)\n\tapplyVariableSetToProject(t, client, vsTest2.ID, projectTest.ID)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tvsl, err := client.VariableSets.ListForProject(ctx, projectTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, vsl.Items, 2)\n\n\t\tids := []string{vsTest1.ID, vsTest2.ID}\n\t\tfor _, varset := range vsl.Items {\n\t\t\tassert.Contains(t, ids, varset.ID)\n\t\t}\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tvsl, err := client.VariableSets.ListForProject(ctx, projectTest.ID, &VariableSetListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, vsl.Items)\n\t\tassert.Equal(t, 999, vsl.CurrentPage)\n\t\tassert.Equal(t, 2, vsl.TotalCount)\n\t})\n\n\tt.Run(\"when Project ID is an invalid ID\", func(t *testing.T) {\n\t\tvsl, err := client.VariableSets.ListForProject(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, vsl)\n\t\tassert.EqualError(t, err, ErrInvalidProjectID.Error())\n\t})\n\n\tt.Run(\"with query parameter\", func(t *testing.T) {\n\t\tvsl, err := client.VariableSets.List(ctx, orgTest.Name, &VariableSetListOptions{\n\t\t\tQuery: vsTest2.Name,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, vsl.Items, 1)\n\t\tassert.Equal(t, vsTest2.ID, vsl.Items[0].ID)\n\t})\n}\n\nfunc TestVariableSetsCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := VariableSetCreateOptions{\n\t\t\tName:        String(\"varset\"),\n\t\t\tDescription: String(\"a variable set\"),\n\t\t\tGlobal:      Bool(false),\n\t\t\tPriority:    Bool(false),\n\t\t}\n\n\t\tvs, err := client.VariableSets.Create(ctx, orgTest.Name, &options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get refreshed view from the API\n\t\trefreshed, err := client.VariableSets.Read(ctx, vs.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*VariableSet{\n\t\t\tvs,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, *options.Description, item.Description)\n\t\t\tassert.Equal(t, *options.Global, item.Global)\n\t\t\tassert.Equal(t, *options.Priority, item.Priority)\n\t\t}\n\t})\n\n\tt.Run(\"when options is missing name\", func(t *testing.T) {\n\t\tvs, err := client.VariableSets.Create(ctx, \"foo\", &VariableSetCreateOptions{\n\t\t\tGlobal: Bool(true),\n\t\t})\n\t\tassert.Nil(t, vs)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"when options is missing global flag\", func(t *testing.T) {\n\t\tvs, err := client.VariableSets.Create(ctx, \"foo\", &VariableSetCreateOptions{\n\t\t\tName: String(\"foo\"),\n\t\t})\n\t\tassert.Nil(t, vs)\n\t\tassert.EqualError(t, err, ErrRequiredGlobalFlag.Error())\n\t})\n\n\tt.Run(\"when creating project-owned variable set\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\tprjTest, prjTestCleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(prjTestCleanup)\n\n\t\toptions := VariableSetCreateOptions{\n\t\t\tName:        String(\"project-varset\"),\n\t\t\tDescription: String(\"a project variable set\"),\n\t\t\tGlobal:      Bool(false),\n\t\t\tParent: &Parent{\n\t\t\t\tProject: prjTest,\n\t\t\t},\n\t\t}\n\n\t\tvs, err := client.VariableSets.Create(ctx, orgTest.Name, &options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get refreshed view from the API\n\t\trefreshed, err := client.VariableSets.Read(ctx, vs.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*VariableSet{\n\t\t\tvs,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, *options.Description, item.Description)\n\t\t\tassert.Equal(t, *options.Global, item.Global)\n\t\t\tassert.Equal(t, options.Parent.Project.ID, item.Parent.Project.ID)\n\t\t}\n\t})\n}\n\nfunc TestVariableSetsRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup)\n\n\tt.Run(\"when the variable set exists\", func(t *testing.T) {\n\t\tvs, err := client.VariableSets.Read(ctx, vsTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, vsTest, vs)\n\t})\n\n\tt.Run(\"when variable set does not exist\", func(t *testing.T) {\n\t\tvs, err := client.VariableSets.Read(ctx, \"nonexisting\", nil)\n\t\tassert.Nil(t, vs)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"with parent relationship\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\tvs, err := client.VariableSets.Read(ctx, vsTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, vsTest, vs)\n\t\tassert.Equal(t, orgTest.Name, vs.Parent.Organization.Name)\n\t})\n}\n\nfunc TestVariableSetsUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\tvsTest, _ := createVariableSet(t, client, orgTest, VariableSetCreateOptions{\n\t\tName:        String(\"OriginalName\"),\n\t\tDescription: String(\"Original Description\"),\n\t\tGlobal:      Bool(false),\n\t\tPriority:    Bool(false),\n\t})\n\tt.Run(\"when updating a subset of values\", func(t *testing.T) {\n\t\toptions := VariableSetUpdateOptions{\n\t\t\tName:        String(\"UpdatedName\"),\n\t\t\tDescription: String(\"Updated Description\"),\n\t\t\tGlobal:      Bool(true),\n\t\t\tPriority:    Bool(true),\n\t\t}\n\t\tvsAfter, err := client.VariableSets.Update(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, *options.Name, vsAfter.Name)\n\t\tassert.Equal(t, *options.Description, vsAfter.Description)\n\t\tassert.Equal(t, *options.Global, vsAfter.Global)\n\t\tassert.Equal(t, *options.Priority, vsAfter.Priority)\n\t})\n\tt.Run(\"when options has an invalid variable set ID\", func(t *testing.T) {\n\t\tvsAfter, err := client.VariableSets.Update(ctx, badIdentifier, &VariableSetUpdateOptions{\n\t\t\tName:        String(\"UpdatedName\"),\n\t\t\tDescription: String(\"Updated Description\"),\n\t\t\tGlobal:      Bool(true),\n\t\t})\n\t\tassert.Nil(t, vsAfter)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\t})\n}\n\nfunc TestVariableSetsDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\t// Do not defer cleanup since the next step in this test is to delete it\n\tvsTest, _ := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\n\tt.Run(\"with valid ID\", func(t *testing.T) {\n\t\terr := client.VariableSets.Delete(ctx, vsTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the variable set - it should fail.\n\t\t_, err = client.VariableSets.Read(ctx, vsTest.ID, nil)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"when ID is invalid\", func(t *testing.T) {\n\t\terr := client.VariableSets.Delete(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\t})\n}\n\nfunc TestVariableSetsApplyToAndRemoveFromWorkspaces(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup)\n\n\twTest1, wTest1Cleanup := createWorkspace(t, client, orgTest)\n\tdefer wTest1Cleanup()\n\twTest2, wTest2Cleanup := createWorkspace(t, client, orgTest)\n\tdefer wTest2Cleanup()\n\n\tt.Run(\"with first workspace added\", func(t *testing.T) {\n\t\toptions := VariableSetApplyToWorkspacesOptions{\n\t\t\tWorkspaces: []*Workspace{wTest1},\n\t\t}\n\n\t\terr := client.VariableSets.ApplyToWorkspaces(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tvsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// Variable set should be applied to [wTest1]\n\t\tassert.Equal(t, 1, len(vsAfter.Workspaces))\n\t\tassert.Equal(t, wTest1.ID, vsAfter.Workspaces[0].ID)\n\t})\n\n\tt.Run(\"with second workspace added\", func(t *testing.T) {\n\t\toptions := VariableSetApplyToWorkspacesOptions{\n\t\t\tWorkspaces: []*Workspace{wTest2},\n\t\t}\n\n\t\terr := client.VariableSets.ApplyToWorkspaces(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tvsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// Variable set should be applied to [wTest1, wTest2]\n\t\tassert.Equal(t, 2, len(vsAfter.Workspaces))\n\t\twsIDs := []string{vsAfter.Workspaces[0].ID, vsAfter.Workspaces[1].ID}\n\n\t\tassert.Contains(t, wsIDs, wTest1.ID)\n\t\tassert.Contains(t, wsIDs, wTest2.ID)\n\t})\n\n\tt.Run(\"with first workspace removed\", func(t *testing.T) {\n\t\toptions := VariableSetRemoveFromWorkspacesOptions{\n\t\t\tWorkspaces: []*Workspace{wTest1},\n\t\t}\n\n\t\terr := client.VariableSets.RemoveFromWorkspaces(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tvsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// Variable set should be applied to [wTest2]\n\t\tassert.Equal(t, 1, len(vsAfter.Workspaces))\n\t\tassert.Equal(t, wTest2.ID, vsAfter.Workspaces[0].ID)\n\t})\n\n\tt.Run(\"when variable set ID is invalid\", func(t *testing.T) {\n\t\tapplyOptions := VariableSetApplyToWorkspacesOptions{\n\t\t\tWorkspaces: []*Workspace{wTest1},\n\t\t}\n\n\t\terr := client.VariableSets.ApplyToWorkspaces(ctx, badIdentifier, &applyOptions)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\n\t\tremoveOptions := VariableSetRemoveFromWorkspacesOptions{\n\t\t\tWorkspaces: []*Workspace{wTest1},\n\t\t}\n\t\terr = client.VariableSets.RemoveFromWorkspaces(ctx, badIdentifier, &removeOptions)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\t})\n\n\tt.Run(\"when workspace ID is invalid\", func(t *testing.T) {\n\t\tbadWorkspace := &Workspace{\n\t\t\tID: badIdentifier,\n\t\t}\n\n\t\tapplyOptions := VariableSetApplyToWorkspacesOptions{\n\t\t\tWorkspaces: []*Workspace{badWorkspace},\n\t\t}\n\n\t\terr := client.VariableSets.ApplyToWorkspaces(ctx, vsTest.ID, &applyOptions)\n\t\tassert.EqualError(t, err, ErrRequiredWorkspaceID.Error())\n\n\t\tremoveOptions := VariableSetRemoveFromWorkspacesOptions{\n\t\t\tWorkspaces: []*Workspace{badWorkspace},\n\t\t}\n\n\t\terr = client.VariableSets.RemoveFromWorkspaces(ctx, vsTest.ID, &removeOptions)\n\t\tassert.EqualError(t, err, ErrRequiredWorkspaceID.Error())\n\t})\n}\n\nfunc TestVariableSetsApplyToAndRemoveFromProjects(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup)\n\n\tprjTest1, prjTest1Cleanup := createProject(t, client, orgTest)\n\tdefer prjTest1Cleanup()\n\tprjTest2, prjTest2Cleanup := createProject(t, client, orgTest)\n\tdefer prjTest2Cleanup()\n\tt.Run(\"with first project added\", func(t *testing.T) {\n\t\toptions := VariableSetApplyToProjectsOptions{\n\t\t\tProjects: []*Project{prjTest1},\n\t\t}\n\n\t\terr := client.VariableSets.ApplyToProjects(ctx, vsTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tvsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// Variable set should be applied to [prjTest1]\n\t\tassert.Equal(t, 1, len(vsAfter.Projects))\n\t\tassert.Equal(t, prjTest1.ID, vsAfter.Projects[0].ID)\n\t})\n\n\tt.Run(\"with second project added\", func(t *testing.T) {\n\t\toptions := VariableSetApplyToProjectsOptions{\n\t\t\tProjects: []*Project{prjTest2},\n\t\t}\n\n\t\terr := client.VariableSets.ApplyToProjects(ctx, vsTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tvsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// Variable set should be applied to [prjTest1, prjTest2]\n\t\tassert.Equal(t, 2, len(vsAfter.Projects))\n\t\tprjIDs := []string{vsAfter.Projects[0].ID, vsAfter.Projects[1].ID}\n\n\t\tassert.Contains(t, prjIDs, prjTest1.ID)\n\t\tassert.Contains(t, prjIDs, prjTest2.ID)\n\t})\n\n\tt.Run(\"with first project removed\", func(t *testing.T) {\n\t\toptions := VariableSetRemoveFromProjectsOptions{\n\t\t\tProjects: []*Project{prjTest1},\n\t\t}\n\n\t\terr := client.VariableSets.RemoveFromProjects(ctx, vsTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tvsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// Variable set should be applied to [wTest2]\n\t\tassert.Equal(t, 1, len(vsAfter.Projects))\n\t\tassert.Equal(t, prjTest2.ID, vsAfter.Projects[0].ID)\n\t})\n\n\tt.Run(\"when variable set ID is invalid\", func(t *testing.T) {\n\t\tapplyOptions := VariableSetApplyToProjectsOptions{\n\t\t\tProjects: []*Project{prjTest1},\n\t\t}\n\n\t\terr := client.VariableSets.ApplyToProjects(ctx, badIdentifier, applyOptions)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\n\t\tremoveOptions := VariableSetRemoveFromProjectsOptions{\n\t\t\tProjects: []*Project{prjTest1},\n\t\t}\n\t\terr = client.VariableSets.RemoveFromProjects(ctx, badIdentifier, removeOptions)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\t})\n\n\tt.Run(\"when project ID is invalid\", func(t *testing.T) {\n\t\tbadProject := &Project{\n\t\t\tID: badIdentifier,\n\t\t}\n\n\t\tapplyOptions := VariableSetApplyToProjectsOptions{\n\t\t\tProjects: []*Project{badProject},\n\t\t}\n\n\t\terr := client.VariableSets.ApplyToProjects(ctx, vsTest.ID, applyOptions)\n\t\tassert.EqualError(t, err, ErrRequiredProjectID.Error())\n\n\t\tremoveOptions := VariableSetRemoveFromProjectsOptions{\n\t\t\tProjects: []*Project{badProject},\n\t\t}\n\n\t\terr = client.VariableSets.RemoveFromProjects(ctx, vsTest.ID, removeOptions)\n\t\tassert.EqualError(t, err, ErrRequiredProjectID.Error())\n\t})\n}\n\nfunc TestVariableSetsApplyToAndRemoveFromStacks(t *testing.T) {\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstackTest1, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack-1\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\tif err := client.Stacks.Delete(ctx, stackTest1.ID); err != nil {\n\t\t\tt.Logf(\"Failed to cleanup stack %s: %v\", stackTest1.ID, err)\n\t\t}\n\t})\n\n\t// Wait for stack to be ready by triggering configuration update\n\t_, err = client.Stacks.FetchLatestFromVcs(ctx, stackTest1.ID)\n\trequire.NoError(t, err)\n\n\tstackTest2, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack-2\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\tif err := client.Stacks.Delete(ctx, stackTest2.ID); err != nil {\n\t\t\tt.Logf(\"Failed to cleanup stack %s: %v\", stackTest2.ID, err)\n\t\t}\n\t})\n\n\t// Wait for stack to be ready by triggering configuration update\n\t_, err = client.Stacks.FetchLatestFromVcs(ctx, stackTest2.ID)\n\t// Don't require this to succeed as it might not be needed\n\n\tt.Run(\"with first stack added\", func(t *testing.T) {\n\t\toptions := VariableSetApplyToStacksOptions{\n\t\t\tStacks: []*Stack{{ID: stackTest1.ID}},\n\t\t}\n\t\terr = client.VariableSets.ApplyToStacks(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\treadOpts := &VariableSetReadOptions{Include: &[]VariableSetIncludeOpt{VariableSetStacks}}\n\t\tvsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, readOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 1, len(vsAfter.Stacks))\n\t\tassert.Equal(t, stackTest1.ID, vsAfter.Stacks[0].ID)\n\t})\n\n\tt.Run(\"with second stack added\", func(t *testing.T) {\n\t\toptions := VariableSetApplyToStacksOptions{\n\t\t\tStacks: []*Stack{stackTest2},\n\t\t}\n\n\t\terr := client.VariableSets.ApplyToStacks(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\treadOpts := &VariableSetReadOptions{Include: &[]VariableSetIncludeOpt{VariableSetStacks}}\n\t\tvsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, readOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 2, len(vsAfter.Stacks))\n\t\tstackIDs := []string{vsAfter.Stacks[0].ID, vsAfter.Stacks[1].ID}\n\n\t\tassert.Contains(t, stackIDs, stackTest1.ID)\n\t\tassert.Contains(t, stackIDs, stackTest2.ID)\n\t})\n\n\tt.Run(\"with first stack removed\", func(t *testing.T) {\n\t\toptions := VariableSetRemoveFromStacksOptions{\n\t\t\tStacks: []*Stack{stackTest1},\n\t\t}\n\n\t\terr := client.VariableSets.RemoveFromStacks(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\treadOpts := &VariableSetReadOptions{Include: &[]VariableSetIncludeOpt{VariableSetStacks}}\n\t\tvsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, readOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 1, len(vsAfter.Stacks))\n\t\tassert.Equal(t, stackTest2.ID, vsAfter.Stacks[0].ID)\n\t})\n\n\tt.Run(\"when variable set ID is invalid\", func(t *testing.T) {\n\t\tapplyOptions := VariableSetApplyToStacksOptions{\n\t\t\tStacks: []*Stack{stackTest1},\n\t\t}\n\t\terr := client.VariableSets.ApplyToStacks(ctx, badIdentifier, &applyOptions)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\n\t\tremoveOptions := VariableSetRemoveFromStacksOptions{\n\t\t\tStacks: []*Stack{stackTest1},\n\t\t}\n\t\terr = client.VariableSets.RemoveFromStacks(ctx, badIdentifier, &removeOptions)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\t})\n\n\tt.Run(\"when stack ID is invalid\", func(t *testing.T) {\n\t\tbadStack := &Stack{\n\t\t\tID: badIdentifier,\n\t\t}\n\n\t\tapplyOptions := VariableSetApplyToStacksOptions{\n\t\t\tStacks: []*Stack{badStack},\n\t\t}\n\t\terr := client.VariableSets.ApplyToStacks(ctx, vsTest.ID, &applyOptions)\n\t\tassert.EqualError(t, err, ErrRequiredStackID.Error())\n\n\t\tremoveOptions := VariableSetRemoveFromStacksOptions{\n\t\t\tStacks: []*Stack{badStack},\n\t\t}\n\t\terr = client.VariableSets.RemoveFromStacks(ctx, vsTest.ID, &removeOptions)\n\t\tassert.EqualError(t, err, ErrRequiredStackID.Error())\n\t})\n}\n\nfunc TestVariableSetsUpdateWorkspaces(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tt.Run(\"with valid workspaces\", func(t *testing.T) {\n\t\toptions := VariableSetUpdateWorkspacesOptions{\n\t\t\tWorkspaces: []*Workspace{wTest},\n\t\t}\n\n\t\tvsAfter, err := client.VariableSets.UpdateWorkspaces(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, len(options.Workspaces), len(vsAfter.Workspaces))\n\t\tassert.Equal(t, options.Workspaces[0].ID, vsAfter.Workspaces[0].ID)\n\n\t\toptions = VariableSetUpdateWorkspacesOptions{\n\t\t\tWorkspaces: []*Workspace{},\n\t\t}\n\n\t\tvsAfter, err = client.VariableSets.UpdateWorkspaces(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, len(options.Workspaces), len(vsAfter.Workspaces))\n\t})\n}\n\nfunc TestVariableSetsUpdateStacks(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tt.Cleanup(vsTestCleanup)\n\n\toauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)\n\tt.Cleanup(cleanup)\n\n\tstackTest, err := client.Stacks.Create(ctx, StackCreateOptions{\n\t\tName: \"test-stack\",\n\t\tVCSRepo: &StackVCSRepoOptions{\n\t\t\tIdentifier:   \"hashicorp-guides/pet-nulls-stack\",\n\t\t\tOAuthTokenID: oauthClient.OAuthTokens[0].ID,\n\t\t},\n\t\tProject: &Project{\n\t\t\tID: orgTest.DefaultProject.ID,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\tif err := client.Stacks.Delete(ctx, stackTest.ID); err != nil {\n\t\t\tt.Logf(\"Failed to cleanup stack %s: %v\", stackTest.ID, err)\n\t\t}\n\t})\n\n\t// Wait for stack to be ready by triggering configuration update\n\t_, err = client.Stacks.FetchLatestFromVcs(ctx, stackTest.ID)\n\trequire.NoError(t, err)\n\n\tt.Run(\"with valid stacks\", func(t *testing.T) {\n\t\toptions := VariableSetUpdateStacksOptions{\n\t\t\tStacks: []*Stack{stackTest},\n\t\t}\n\n\t\t_, err := client.VariableSets.UpdateStacks(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\treadOpts := &VariableSetReadOptions{Include: &[]VariableSetIncludeOpt{VariableSetStacks}}\n\t\tvsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, readOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, len(options.Stacks), len(vsAfter.Stacks))\n\t\tassert.Equal(t, options.Stacks[0].ID, vsAfter.Stacks[0].ID)\n\n\t\toptions = VariableSetUpdateStacksOptions{\n\t\t\tStacks: []*Stack{},\n\t\t}\n\n\t\t_, err = client.VariableSets.UpdateStacks(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\treadOpts = &VariableSetReadOptions{Include: &[]VariableSetIncludeOpt{VariableSetStacks}}\n\t\tvsAfter, err = client.VariableSets.Read(ctx, vsTest.ID, readOpts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, len(options.Stacks), len(vsAfter.Stacks))\n\t})\n}\n"
  },
  {
    "path": "variable_set_variable.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ VariableSetVariables = (*variableSetVariables)(nil)\n\n// VariableSetVariables describes all variable variable related methods within the scope of\n// Variable Sets that the Terraform Enterprise API supports\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets#variable-relationships\ntype VariableSetVariables interface {\n\t// List all variables in the variable set.\n\tList(ctx context.Context, variableSetID string, options *VariableSetVariableListOptions) (*VariableSetVariableList, error)\n\n\t// Create is used to create a new variable within a given variable set\n\tCreate(ctx context.Context, variableSetID string, options *VariableSetVariableCreateOptions) (*VariableSetVariable, error)\n\n\t// Read a variable by its ID\n\tRead(ctx context.Context, variableSetID string, variableID string) (*VariableSetVariable, error)\n\n\t// Update valuse of an existing variable\n\tUpdate(ctx context.Context, variableSetID string, variableID string, options *VariableSetVariableUpdateOptions) (*VariableSetVariable, error)\n\n\t// Delete a variable by its ID\n\tDelete(ctx context.Context, variableSetID string, variableID string) error\n}\n\ntype variableSetVariables struct {\n\tclient *Client\n}\n\ntype VariableSetVariableList struct {\n\t*Pagination\n\tItems []*VariableSetVariable\n}\n\ntype VariableSetVariable struct {\n\tID          string       `jsonapi:\"primary,vars\"`\n\tKey         string       `jsonapi:\"attr,key\"`\n\tValue       string       `jsonapi:\"attr,value\"`\n\tDescription string       `jsonapi:\"attr,description\"`\n\tCategory    CategoryType `jsonapi:\"attr,category\"`\n\tHCL         bool         `jsonapi:\"attr,hcl\"`\n\tSensitive   bool         `jsonapi:\"attr,sensitive\"`\n\tVersionID   string       `jsonapi:\"attr,version-id\"`\n\n\t// Relations\n\tVariableSet *VariableSet `jsonapi:\"relation,varset\"`\n}\n\ntype VariableSetVariableListOptions struct {\n\tListOptions\n}\n\nfunc (o VariableSetVariableListOptions) valid() error {\n\treturn nil\n}\n\n// List all variables associated with the given variable set.\nfunc (s *variableSetVariables) List(ctx context.Context, variableSetID string, options *VariableSetVariableListOptions) (*VariableSetVariableList, error) {\n\tif !validStringID(&variableSetID) {\n\t\treturn nil, ErrInvalidVariableSetID\n\t}\n\tif options != nil {\n\t\tif err := options.valid(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/vars\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvl := &VariableSetVariableList{}\n\terr = req.Do(ctx, vl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vl, nil\n}\n\n// VariableSetVariableCreatOptions represents the options for creating a new variable within a variable set\ntype VariableSetVariableCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,vars\"`\n\n\t// The name of the variable.\n\tKey *string `jsonapi:\"attr,key\"`\n\n\t// The value of the variable.\n\tValue *string `jsonapi:\"attr,value,omitempty\"`\n\n\t// The description of the variable.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Whether this is a Terraform or environment variable.\n\tCategory *CategoryType `jsonapi:\"attr,category\"`\n\n\t// Whether to evaluate the value of the variable as a string of HCL code.\n\tHCL *bool `jsonapi:\"attr,hcl,omitempty\"`\n\n\t// Whether the value is sensitive.\n\tSensitive *bool `jsonapi:\"attr,sensitive,omitempty\"`\n}\n\nfunc (o VariableSetVariableCreateOptions) valid() error {\n\tif !validString(o.Key) {\n\t\treturn ErrRequiredKey\n\t}\n\tif o.Category == nil {\n\t\treturn ErrRequiredCategory\n\t}\n\treturn nil\n}\n\n// Create is used to create a new variable.\nfunc (s *variableSetVariables) Create(ctx context.Context, variableSetID string, options *VariableSetVariableCreateOptions) (*VariableSetVariable, error) {\n\tif !validStringID(&variableSetID) {\n\t\treturn nil, ErrInvalidVariableSetID\n\t}\n\tif options != nil {\n\t\tif err := options.valid(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/vars\", url.PathEscape(variableSetID))\n\treq, err := s.client.NewRequest(\"POST\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &VariableSetVariable{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, nil\n}\n\n// Read a variable by its ID.\nfunc (s *variableSetVariables) Read(ctx context.Context, variableSetID, variableID string) (*VariableSetVariable, error) {\n\tif !validStringID(&variableSetID) {\n\t\treturn nil, ErrInvalidVariableSetID\n\t}\n\tif !validStringID(&variableID) {\n\t\treturn nil, ErrInvalidVariableID\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/vars/%s\", url.PathEscape(variableSetID), url.PathEscape(variableID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &VariableSetVariable{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, err\n}\n\n// VariableSetVariableUpdateOptions represents the options for updating a variable.\ntype VariableSetVariableUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,vars\"`\n\n\t// The name of the variable.\n\tKey *string `jsonapi:\"attr,key,omitempty\"`\n\n\t// The value of the variable.\n\tValue *string `jsonapi:\"attr,value,omitempty\"`\n\n\t// The description of the variable.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Whether to evaluate the value of the variable as a string of HCL code.\n\tHCL *bool `jsonapi:\"attr,hcl,omitempty\"`\n\n\t// Whether the value is sensitive.\n\tSensitive *bool `jsonapi:\"attr,sensitive,omitempty\"`\n}\n\n// Update values of an existing variable.\nfunc (s *variableSetVariables) Update(ctx context.Context, variableSetID, variableID string, options *VariableSetVariableUpdateOptions) (*VariableSetVariable, error) {\n\tif !validStringID(&variableSetID) {\n\t\treturn nil, ErrInvalidVariableSetID\n\t}\n\tif !validStringID(&variableID) {\n\t\treturn nil, ErrInvalidVariableID\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/vars/%s\", url.PathEscape(variableSetID), url.PathEscape(variableID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &VariableSetVariable{}\n\terr = req.Do(ctx, v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn v, nil\n}\n\n// Delete a variable by its ID.\nfunc (s *variableSetVariables) Delete(ctx context.Context, variableSetID, variableID string) error {\n\tif !validStringID(&variableSetID) {\n\t\treturn ErrInvalidVariableSetID\n\t}\n\tif !validStringID(&variableID) {\n\t\treturn ErrInvalidVariableID\n\t}\n\n\tu := fmt.Sprintf(\"varsets/%s/relationships/vars/%s\", url.PathEscape(variableSetID), url.PathEscape(variableID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "variable_set_variable_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestVariableSetVariablesList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tdefer vsTestCleanup()\n\n\tvTest1, vTestCleanup1 := createVariableSetVariable(t, client, vsTest, VariableSetVariableCreateOptions{\n\t\tKey:      String(\"vTest1\"),\n\t\tValue:    String(\"vTest1\"),\n\t\tCategory: Category(CategoryTerraform),\n\t})\n\tdefer vTestCleanup1()\n\tvTest2, vTestCleanup2 := createVariableSetVariable(t, client, vsTest, VariableSetVariableCreateOptions{\n\t\tKey:      String(\"vTest2\"),\n\t\tValue:    String(\"vTest2\"),\n\t\tCategory: Category(CategoryTerraform),\n\t})\n\tdefer vTestCleanup2()\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\tvl, err := client.VariableSetVariables.List(ctx, vsTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, vl.Items)\n\t\tassert.Contains(t, vl.Items, vTest1)\n\t\tassert.Contains(t, vl.Items, vTest2)\n\n\t\tt.Run(\"variable set relationship is deserialized\", func(t *testing.T) {\n\t\t\trequire.NotNil(t, vl.Items[0].VariableSet)\n\t\t\tassert.Equal(t, vsTest.ID, vl.Items[0].VariableSet.ID)\n\t\t})\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\tt.Skip(\"paging not supported yet in API\")\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\tvl, err := client.VariableSetVariables.List(ctx, vsTest.ID, &VariableSetVariableListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, vl.Items)\n\t\tassert.Equal(t, 999, vl.CurrentPage)\n\t\tassert.Equal(t, 2, vl.TotalCount)\n\t})\n\n\tt.Run(\"when variable set ID is invalid ID\", func(t *testing.T) {\n\t\tvl, err := client.VariableSetVariables.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, vl)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\t})\n}\n\nfunc TestVariableSetVariablesCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tdefer vsTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := VariableSetVariableCreateOptions{\n\t\t\tKey:         String(randomString(t)),\n\t\t\tValue:       String(randomString(t)),\n\t\t\tCategory:    Category(CategoryTerraform),\n\t\t\tDescription: String(randomString(t)),\n\t\t\tHCL:         Bool(false),\n\t\t\tSensitive:   Bool(false),\n\t\t}\n\n\t\tv, err := client.VariableSetVariables.Create(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.Equal(t, *options.Description, v.Description)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options has an empty string value\", func(t *testing.T) {\n\t\toptions := VariableSetVariableCreateOptions{\n\t\t\tKey:         String(randomString(t)),\n\t\t\tValue:       String(\"\"),\n\t\t\tDescription: String(randomString(t)),\n\t\t\tCategory:    Category(CategoryTerraform),\n\t\t}\n\n\t\tv, err := client.VariableSetVariables.Create(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.Equal(t, *options.Description, v.Description)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options has an empty string description\", func(t *testing.T) {\n\t\toptions := VariableSetVariableCreateOptions{\n\t\t\tKey:         String(randomString(t)),\n\t\t\tValue:       String(randomString(t)),\n\t\t\tDescription: String(\"\"),\n\t\t\tCategory:    Category(CategoryTerraform),\n\t\t}\n\n\t\tv, err := client.VariableSetVariables.Create(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.Equal(t, *options.Description, v.Description)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options has a too-long description\", func(t *testing.T) {\n\t\toptions := VariableSetVariableCreateOptions{\n\t\t\tKey:         String(randomString(t)),\n\t\t\tValue:       String(randomString(t)),\n\t\t\tDescription: String(\"tortor aliquam nulla redacted cras fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel pretium lectus quam id leo in vitae turpis massa sed elementum tempus egestas sed sed risus pretium quam vulputate dignissim suspendisse in est ante in nibh mauris cursus mattis molestie a iaculis at erat pellentesque adipiscing commodo elit at imperdiet dui accumsan sit amet nulla redacted morbi tempus iaculis urna id volutpat lacus laoreet non curabitur gravida arcu ac tortor dignissim convallis aenean et tortor\"),\n\t\t\tCategory:    Category(CategoryTerraform),\n\t\t}\n\n\t\t_, err := client.VariableSetVariables.Create(ctx, vsTest.ID, &options)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"when options is missing value\", func(t *testing.T) {\n\t\toptions := VariableSetVariableCreateOptions{\n\t\t\tKey:      String(randomString(t)),\n\t\t\tCategory: Category(CategoryTerraform),\n\t\t}\n\n\t\tv, err := client.VariableSetVariables.Create(ctx, vsTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, v.ID)\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, \"\", v.Value)\n\t\tassert.Equal(t, *options.Category, v.Category)\n\t\tassert.NotEmpty(t, v.VersionID)\n\t})\n\n\tt.Run(\"when options is missing key\", func(t *testing.T) {\n\t\toptions := VariableSetVariableCreateOptions{\n\t\t\tValue:    String(randomString(t)),\n\t\t\tCategory: Category(CategoryTerraform),\n\t\t}\n\n\t\t_, err := client.VariableSetVariables.Create(ctx, vsTest.ID, &options)\n\t\tassert.EqualError(t, err, ErrRequiredKey.Error())\n\t})\n\n\tt.Run(\"when options has an empty key\", func(t *testing.T) {\n\t\toptions := VariableSetVariableCreateOptions{\n\t\t\tKey:      String(\"\"),\n\t\t\tValue:    String(randomString(t)),\n\t\t\tCategory: Category(CategoryTerraform),\n\t\t}\n\n\t\t_, err := client.VariableSetVariables.Create(ctx, vsTest.ID, &options)\n\t\tassert.EqualError(t, err, ErrRequiredKey.Error())\n\t})\n\n\tt.Run(\"when options is missing category\", func(t *testing.T) {\n\t\toptions := VariableSetVariableCreateOptions{\n\t\t\tKey:   String(randomString(t)),\n\t\t\tValue: String(randomString(t)),\n\t\t}\n\n\t\t_, err := client.VariableSetVariables.Create(ctx, vsTest.ID, &options)\n\t\tassert.EqualError(t, err, ErrRequiredCategory.Error())\n\t})\n\n\tt.Run(\"when workspace ID is invalid\", func(t *testing.T) {\n\t\toptions := VariableSetVariableCreateOptions{\n\t\t\tKey:      String(randomString(t)),\n\t\t\tValue:    String(randomString(t)),\n\t\t\tCategory: Category(CategoryTerraform),\n\t\t}\n\n\t\t_, err := client.VariableSetVariables.Create(ctx, badIdentifier, &options)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\t})\n}\n\nfunc TestVariableSetVariablesRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})\n\tdefer vsTestCleanup()\n\n\tvTest, vTestCleanup := createVariableSetVariable(t, client, vsTest, VariableSetVariableCreateOptions{})\n\tdefer vTestCleanup()\n\n\tt.Run(\"when the variable exists\", func(t *testing.T) {\n\t\tv, err := client.VariableSetVariables.Read(ctx, vsTest.ID, vTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, vTest.ID, v.ID)\n\t\tassert.Equal(t, vTest.Category, v.Category)\n\t\tassert.Equal(t, vTest.HCL, v.HCL)\n\t\tassert.Equal(t, vTest.Key, v.Key)\n\t\tassert.Equal(t, vTest.Sensitive, v.Sensitive)\n\t\tassert.Equal(t, vTest.Value, v.Value)\n\t\tassert.Equal(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"when the variable does not exist\", func(t *testing.T) {\n\t\tv, err := client.VariableSetVariables.Read(ctx, vsTest.ID, \"nonexisting\")\n\t\tassert.Nil(t, v)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"without a valid variable set ID\", func(t *testing.T) {\n\t\tv, err := client.VariableSetVariables.Read(ctx, badIdentifier, vTest.ID)\n\t\tassert.Nil(t, v)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\t})\n\n\tt.Run(\"without a valid variable ID\", func(t *testing.T) {\n\t\tv, err := client.VariableSetVariables.Read(ctx, vsTest.ID, badIdentifier)\n\t\tassert.Nil(t, v)\n\t\tassert.EqualError(t, err, ErrInvalidVariableID.Error())\n\t})\n}\n\nfunc TestVariableSetVariablesUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, nil, VariableSetCreateOptions{})\n\tdefer vsTestCleanup()\n\n\tvTest, vTestCleanup := createVariableSetVariable(t, client, vsTest, VariableSetVariableCreateOptions{})\n\tdefer vTestCleanup()\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := VariableSetVariableUpdateOptions{\n\t\t\tKey:   String(\"newname\"),\n\t\t\tValue: String(\"newvalue\"),\n\t\t\tHCL:   Bool(true),\n\t\t}\n\n\t\tv, err := client.VariableSetVariables.Update(ctx, vsTest.ID, vTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.HCL, v.HCL)\n\t\tassert.Equal(t, *options.Value, v.Value)\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"when updating a subset of values\", func(t *testing.T) {\n\t\toptions := VariableSetVariableUpdateOptions{\n\t\t\tKey: String(\"someothername\"),\n\t\t\tHCL: Bool(false),\n\t\t}\n\n\t\tv, err := client.VariableSetVariables.Update(ctx, vsTest.ID, vTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Key, v.Key)\n\t\tassert.Equal(t, *options.HCL, v.HCL)\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"with sensitive set\", func(t *testing.T) {\n\t\toptions := VariableSetVariableUpdateOptions{\n\t\t\tSensitive: Bool(true),\n\t\t}\n\n\t\tv, err := client.VariableSetVariables.Update(ctx, vsTest.ID, vTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, *options.Sensitive, v.Sensitive)\n\t\tassert.Empty(t, v.Value) // Because its now sensitive\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"without any changes\", func(t *testing.T) {\n\t\tvTest, vTestCleanup := createVariableSetVariable(t, client, vsTest, VariableSetVariableCreateOptions{})\n\t\tdefer vTestCleanup()\n\n\t\toptions := VariableSetVariableUpdateOptions{\n\t\t\tKey:         String(vTest.Key),\n\t\t\tValue:       String(vTest.Value),\n\t\t\tDescription: String(vTest.Description),\n\t\t\tSensitive:   Bool(vTest.Sensitive),\n\t\t\tHCL:         Bool(vTest.HCL),\n\t\t}\n\n\t\tv, err := client.VariableSetVariables.Update(ctx, vsTest.ID, vTest.ID, &options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, vTest.ID, v.ID)\n\t\tassert.Equal(t, vTest.Key, v.Key)\n\t\tassert.Equal(t, vTest.Value, v.Value)\n\t\tassert.Equal(t, vTest.Description, v.Description)\n\t\tassert.Equal(t, vTest.Category, v.Category)\n\t\tassert.Equal(t, vTest.HCL, v.HCL)\n\t\tassert.Equal(t, vTest.Sensitive, v.Sensitive)\n\t\tassert.NotEqual(t, vTest.VersionID, v.VersionID)\n\t})\n\n\tt.Run(\"with invalid variable ID\", func(t *testing.T) {\n\t\t_, err := client.VariableSetVariables.Update(ctx, badIdentifier, vTest.ID, nil)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\t})\n\n\tt.Run(\"with invalid variable ID\", func(t *testing.T) {\n\t\t_, err := client.VariableSetVariables.Update(ctx, vsTest.ID, badIdentifier, nil)\n\t\tassert.EqualError(t, err, ErrInvalidVariableID.Error())\n\t})\n}\n\nfunc TestVariableSetVariablesDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tvsTest, vsTestCleanup := createVariableSet(t, client, nil, VariableSetCreateOptions{})\n\tdefer vsTestCleanup()\n\n\tvTest, _ := createVariableSetVariable(t, client, vsTest, VariableSetVariableCreateOptions{})\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.VariableSetVariables.Delete(ctx, vsTest.ID, vTest.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with non existing variable ID\", func(t *testing.T) {\n\t\terr := client.VariableSetVariables.Delete(ctx, vsTest.ID, \"nonexisting\")\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n\n\tt.Run(\"with invalid workspace ID\", func(t *testing.T) {\n\t\terr := client.VariableSetVariables.Delete(ctx, badIdentifier, vTest.ID)\n\t\tassert.EqualError(t, err, ErrInvalidVariableSetID.Error())\n\t})\n\n\tt.Run(\"with invalid variable ID\", func(t *testing.T) {\n\t\terr := client.VariableSetVariables.Delete(ctx, vsTest.ID, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidVariableID.Error())\n\t})\n}\n"
  },
  {
    "path": "vault_oidc_configuration.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// VaultOIDCConfigurations describes all the Vault OIDC configuration related methods that the HCP Terraform API supports.\n// HCP Terraform API docs:\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/vault\ntype VaultOIDCConfigurations interface {\n\tCreate(ctx context.Context, organization string, options VaultOIDCConfigurationCreateOptions) (*VaultOIDCConfiguration, error)\n\n\tRead(ctx context.Context, oidcID string) (*VaultOIDCConfiguration, error)\n\n\tUpdate(ctx context.Context, oidcID string, options VaultOIDCConfigurationUpdateOptions) (*VaultOIDCConfiguration, error)\n\n\tDelete(ctx context.Context, oidcID string) error\n}\n\ntype vaultOIDCConfigurations struct {\n\tclient *Client\n}\n\nvar _ VaultOIDCConfigurations = &vaultOIDCConfigurations{}\n\ntype VaultOIDCConfiguration struct {\n\tID               string `jsonapi:\"primary,vault-oidc-configurations\"`\n\tAddress          string `jsonapi:\"attr,address\"`\n\tRoleName         string `jsonapi:\"attr,role\"`\n\tNamespace        string `jsonapi:\"attr,namespace\"`\n\tJWTAuthPath      string `jsonapi:\"attr,auth-path\"`\n\tTLSCACertificate string `jsonapi:\"attr,encoded-cacert\"`\n\n\tOrganization *Organization `jsonapi:\"relation,organization\"`\n}\n\ntype VaultOIDCConfigurationCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,vault-oidc-configurations\"`\n\n\t// Attributes\n\tAddress          string `jsonapi:\"attr,address\"`\n\tRoleName         string `jsonapi:\"attr,role\"`\n\tNamespace        string `jsonapi:\"attr,namespace\"`\n\tJWTAuthPath      string `jsonapi:\"attr,auth-path\"`\n\tTLSCACertificate string `jsonapi:\"attr,encoded-cacert\"`\n}\n\ntype VaultOIDCConfigurationUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,vault-oidc-configurations\"`\n\n\t// Attributes\n\tAddress          *string `jsonapi:\"attr,address,omitempty\"`\n\tRoleName         *string `jsonapi:\"attr,role,omitempty\"`\n\tNamespace        *string `jsonapi:\"attr,namespace,omitempty\"`\n\tJWTAuthPath      *string `jsonapi:\"attr,auth-path,omitempty\"`\n\tTLSCACertificate *string `jsonapi:\"attr,encoded-cacert,omitempty\"`\n}\n\nfunc (o *VaultOIDCConfigurationCreateOptions) valid() error {\n\tif o.Address == \"\" {\n\t\treturn ErrRequiredVaultAddress\n\t}\n\n\tif o.RoleName == \"\" {\n\t\treturn ErrRequiredRoleName\n\t}\n\n\treturn nil\n}\n\nfunc (voc *vaultOIDCConfigurations) Create(ctx context.Context, organization string, options VaultOIDCConfigurationCreateOptions) (*VaultOIDCConfiguration, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := voc.client.NewRequest(\"POST\", fmt.Sprintf(\"organizations/%s/oidc-configurations\", url.PathEscape(organization)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvaultOIDCConfiguration := &VaultOIDCConfiguration{}\n\terr = req.Do(ctx, vaultOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vaultOIDCConfiguration, nil\n}\n\nfunc (voc *vaultOIDCConfigurations) Read(ctx context.Context, oidcID string) (*VaultOIDCConfiguration, error) {\n\treq, err := voc.client.NewRequest(\"GET\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvaultOIDCConfiguration := &VaultOIDCConfiguration{}\n\terr = req.Do(ctx, vaultOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vaultOIDCConfiguration, nil\n}\n\nfunc (voc *vaultOIDCConfigurations) Update(ctx context.Context, oidcID string, options VaultOIDCConfigurationUpdateOptions) (*VaultOIDCConfiguration, error) {\n\tif !validStringID(&oidcID) {\n\t\treturn nil, ErrInvalidOIDC\n\t}\n\n\treq, err := voc.client.NewRequest(\"PATCH\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvaultOIDCConfiguration := &VaultOIDCConfiguration{}\n\terr = req.Do(ctx, vaultOIDCConfiguration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vaultOIDCConfiguration, nil\n}\n\nfunc (voc *vaultOIDCConfigurations) Delete(ctx context.Context, oidcID string) error {\n\tif !validStringID(&oidcID) {\n\t\treturn ErrInvalidOIDC\n\t}\n\n\treq, err := voc.client.NewRequest(\"DELETE\", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n"
  },
  {
    "path": "vault_oidc_configuration_integration_test.go",
    "content": "package tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// These tests are intended for local execution only, as OIDC configurations for HYOK requires specific conditions.\n// To run them locally, follow the instructions outlined in hyok_configuration_integration_test.go\n\nfunc TestVaultOIDCConfigurationCreateDelete(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\topts := VaultOIDCConfigurationCreateOptions{\n\t\t\tAddress:          \"https://vault.example.com\",\n\t\t\tRoleName:         \"vault-role-name\",\n\t\t\tNamespace:        \"admin\",\n\t\t\tJWTAuthPath:      \"jwt\",\n\t\t\tTLSCACertificate: randomString(t),\n\t\t}\n\n\t\toidcConfig, err := client.VaultOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, oidcConfig)\n\t\tassert.Equal(t, opts.Address, oidcConfig.Address)\n\t\tassert.Equal(t, opts.RoleName, oidcConfig.RoleName)\n\t\tassert.Equal(t, opts.Namespace, oidcConfig.Namespace)\n\t\tassert.Equal(t, opts.JWTAuthPath, oidcConfig.JWTAuthPath)\n\n\t\t// delete the created configuration\n\t\terr = client.VaultOIDCConfigurations.Delete(ctx, oidcConfig.ID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"missing address\", func(t *testing.T) {\n\t\topts := VaultOIDCConfigurationCreateOptions{\n\t\t\tRoleName:         \"vault-role-name\",\n\t\t\tNamespace:        \"admin\",\n\t\t\tJWTAuthPath:      \"jwt\",\n\t\t\tTLSCACertificate: randomString(t),\n\t\t}\n\n\t\t_, err := client.VaultOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\tassert.ErrorIs(t, err, ErrRequiredVaultAddress)\n\t})\n\n\tt.Run(\"missing role name\", func(t *testing.T) {\n\t\topts := VaultOIDCConfigurationCreateOptions{\n\t\t\tAddress:          \"https://vault.example.com\",\n\t\t\tNamespace:        \"admin\",\n\t\t\tJWTAuthPath:      \"jwt\",\n\t\t\tTLSCACertificate: randomString(t),\n\t\t}\n\n\t\t_, err := client.VaultOIDCConfigurations.Create(ctx, orgTest.Name, opts)\n\t\tassert.ErrorIs(t, err, ErrRequiredRoleName)\n\t})\n}\n\nfunc TestVaultOIDCConfigurationRead(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\toidcConfig, oidcConfigCleanup := createVaultOIDCConfiguration(t, client, orgTest)\n\tt.Cleanup(oidcConfigCleanup)\n\n\tt.Run(\"fetch existing configuration\", func(t *testing.T) {\n\t\tfetched, err := client.VaultOIDCConfigurations.Read(ctx, oidcConfig.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, fetched)\n\t})\n\n\tt.Run(\"fetching non-existing configuration\", func(t *testing.T) {\n\t\t_, err := client.VaultOIDCConfigurations.Read(ctx, \"voidc-notreal\")\n\t\tassert.ErrorIs(t, err, ErrResourceNotFound)\n\t})\n}\n\nfunc TestVaultOIDCConfigurationUpdate(t *testing.T) {\n\tt.Parallel()\n\tskipHYOKIntegrationTests(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest := testHyokOrganization(t, client)\n\n\tt.Run(\"update all fields\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createVaultOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\taddress := randomString(t)\n\t\troleName := randomString(t)\n\t\tnamespace := randomString(t)\n\t\tjwtAuthPath := randomString(t)\n\t\ttlscaCertificate := randomString(t)\n\n\t\topts := VaultOIDCConfigurationUpdateOptions{\n\t\t\tAddress:          &address,\n\t\t\tRoleName:         &roleName,\n\t\t\tNamespace:        &namespace,\n\t\t\tJWTAuthPath:      &jwtAuthPath,\n\t\t\tTLSCACertificate: &tlscaCertificate,\n\t\t}\n\t\tupdated, err := client.VaultOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, *opts.Address, updated.Address)\n\t\tassert.Equal(t, *opts.RoleName, updated.RoleName)\n\t\tassert.Equal(t, *opts.Namespace, updated.Namespace)\n\t\tassert.Equal(t, *opts.JWTAuthPath, updated.JWTAuthPath)\n\t\tassert.Equal(t, *opts.TLSCACertificate, updated.TLSCACertificate)\n\t})\n\n\tt.Run(\"address not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createVaultOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\troleName := randomString(t)\n\t\tnamespace := randomString(t)\n\t\tjwtAuthPath := randomString(t)\n\t\ttlscaCertificate := randomString(t)\n\n\t\topts := VaultOIDCConfigurationUpdateOptions{\n\t\t\tRoleName:         &roleName,\n\t\t\tNamespace:        &namespace,\n\t\t\tJWTAuthPath:      &jwtAuthPath,\n\t\t\tTLSCACertificate: &tlscaCertificate,\n\t\t}\n\t\tupdated, err := client.VaultOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, oidcConfig.Address, updated.Address) // not updated\n\t\tassert.Equal(t, *opts.RoleName, updated.RoleName)\n\t\tassert.Equal(t, *opts.Namespace, updated.Namespace)\n\t\tassert.Equal(t, *opts.JWTAuthPath, updated.JWTAuthPath)\n\t\tassert.Equal(t, *opts.TLSCACertificate, updated.TLSCACertificate)\n\t})\n\n\tt.Run(\"role name not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createVaultOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\taddress := randomString(t)\n\t\tnamespace := randomString(t)\n\t\tjwtAuthPath := randomString(t)\n\t\ttlscaCertificate := randomString(t)\n\n\t\topts := VaultOIDCConfigurationUpdateOptions{\n\t\t\tAddress:          &address,\n\t\t\tNamespace:        &namespace,\n\t\t\tJWTAuthPath:      &jwtAuthPath,\n\t\t\tTLSCACertificate: &tlscaCertificate,\n\t\t}\n\t\tupdated, err := client.VaultOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, *opts.Address, updated.Address)\n\t\tassert.Equal(t, oidcConfig.RoleName, updated.RoleName) // not updated\n\t\tassert.Equal(t, *opts.Namespace, updated.Namespace)\n\t\tassert.Equal(t, *opts.JWTAuthPath, updated.JWTAuthPath)\n\t\tassert.Equal(t, *opts.TLSCACertificate, updated.TLSCACertificate)\n\t})\n\n\tt.Run(\"namespace not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createVaultOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\taddress := randomString(t)\n\t\troleName := randomString(t)\n\t\tjwtAuthPath := randomString(t)\n\t\ttlscaCertificate := randomString(t)\n\n\t\topts := VaultOIDCConfigurationUpdateOptions{\n\t\t\tAddress:          &address,\n\t\t\tRoleName:         &roleName,\n\t\t\tJWTAuthPath:      &jwtAuthPath,\n\t\t\tTLSCACertificate: &tlscaCertificate,\n\t\t}\n\t\tupdated, err := client.VaultOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, *opts.Address, updated.Address)\n\t\tassert.Equal(t, *opts.RoleName, updated.RoleName)\n\t\tassert.Equal(t, oidcConfig.Namespace, updated.Namespace) // not updated\n\t\tassert.Equal(t, *opts.JWTAuthPath, updated.JWTAuthPath)\n\t\tassert.Equal(t, *opts.TLSCACertificate, updated.TLSCACertificate)\n\t})\n\n\tt.Run(\"JWTAuthPath not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createVaultOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\taddress := randomString(t)\n\t\troleName := randomString(t)\n\t\tnamespace := randomString(t)\n\t\ttlscaCertificate := randomString(t)\n\n\t\topts := VaultOIDCConfigurationUpdateOptions{\n\t\t\tAddress:          &address,\n\t\t\tRoleName:         &roleName,\n\t\t\tNamespace:        &namespace,\n\t\t\tTLSCACertificate: &tlscaCertificate,\n\t\t}\n\t\tupdated, err := client.VaultOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, *opts.Address, updated.Address)\n\t\tassert.Equal(t, *opts.RoleName, updated.RoleName)\n\t\tassert.Equal(t, *opts.Namespace, updated.Namespace)\n\t\tassert.Equal(t, oidcConfig.JWTAuthPath, updated.JWTAuthPath) // not updated\n\t\tassert.Equal(t, *opts.TLSCACertificate, updated.TLSCACertificate)\n\t})\n\n\tt.Run(\"TLSCACertificate not provided\", func(t *testing.T) {\n\t\toidcConfig, oidcConfigCleanup := createVaultOIDCConfiguration(t, client, orgTest)\n\t\tt.Cleanup(oidcConfigCleanup)\n\n\t\taddress := randomString(t)\n\t\troleName := randomString(t)\n\t\tnamespace := randomString(t)\n\t\tjwtAuthPath := randomString(t)\n\n\t\topts := VaultOIDCConfigurationUpdateOptions{\n\t\t\tAddress:     &address,\n\t\t\tRoleName:    &roleName,\n\t\t\tNamespace:   &namespace,\n\t\t\tJWTAuthPath: &jwtAuthPath,\n\t\t}\n\t\tupdated, err := client.VaultOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, updated)\n\t\tassert.Equal(t, *opts.Address, updated.Address)\n\t\tassert.Equal(t, *opts.RoleName, updated.RoleName)\n\t\tassert.Equal(t, *opts.Namespace, updated.Namespace)\n\t\tassert.Equal(t, *opts.JWTAuthPath, updated.JWTAuthPath)\n\t\tassert.Equal(t, oidcConfig.TLSCACertificate, updated.TLSCACertificate) // not updated\n\t})\n}\n"
  },
  {
    "path": "workspace.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/hashicorp/jsonapi\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ Workspaces = (*workspaces)(nil)\n\n// Workspaces describes all the workspace related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspaces\ntype Workspaces interface {\n\t// List all the workspaces within an organization.\n\tList(ctx context.Context, organization string, options *WorkspaceListOptions) (*WorkspaceList, error)\n\n\t// Create is used to create a new workspace.\n\tCreate(ctx context.Context, organization string, options WorkspaceCreateOptions) (*Workspace, error)\n\n\t// Read a workspace by its name and organization name.\n\tRead(ctx context.Context, organization string, workspace string) (*Workspace, error)\n\n\t// ReadWithOptions reads a workspace by name and organization name with given options.\n\tReadWithOptions(ctx context.Context, organization string, workspace string, options *WorkspaceReadOptions) (*Workspace, error)\n\n\t// Readme gets the readme of a workspace by its ID.\n\tReadme(ctx context.Context, workspaceID string) (io.Reader, error)\n\n\t// ReadByID reads a workspace by its ID.\n\tReadByID(ctx context.Context, workspaceID string) (*Workspace, error)\n\n\t// ReadByIDWithOptions reads a workspace by its ID with the given options.\n\tReadByIDWithOptions(ctx context.Context, workspaceID string, options *WorkspaceReadOptions) (*Workspace, error)\n\n\t// Update settings of an existing workspace.\n\tUpdate(ctx context.Context, organization string, workspace string, options WorkspaceUpdateOptions) (*Workspace, error)\n\n\t// UpdateByID updates the settings of an existing workspace.\n\tUpdateByID(ctx context.Context, workspaceID string, options WorkspaceUpdateOptions) (*Workspace, error)\n\n\t// Delete a workspace by its name.\n\tDelete(ctx context.Context, organization string, workspace string) error\n\n\t// DeleteByID deletes a workspace by its ID.\n\tDeleteByID(ctx context.Context, workspaceID string) error\n\n\t// SafeDelete a workspace by its name.\n\tSafeDelete(ctx context.Context, organization string, workspace string) error\n\n\t// SafeDeleteByID deletes a workspace by its ID.\n\tSafeDeleteByID(ctx context.Context, workspaceID string) error\n\n\t// RemoveVCSConnection from a workspace.\n\tRemoveVCSConnection(ctx context.Context, organization, workspace string) (*Workspace, error)\n\n\t// RemoveVCSConnectionByID removes a VCS connection from a workspace.\n\tRemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*Workspace, error)\n\n\t// Lock a workspace by its ID.\n\tLock(ctx context.Context, workspaceID string, options WorkspaceLockOptions) (*Workspace, error)\n\n\t// Unlock a workspace by its ID.\n\tUnlock(ctx context.Context, workspaceID string) (*Workspace, error)\n\n\t// ForceUnlock a workspace by its ID.\n\tForceUnlock(ctx context.Context, workspaceID string) (*Workspace, error)\n\n\t// AssignSSHKey to a workspace.\n\tAssignSSHKey(ctx context.Context, workspaceID string, options WorkspaceAssignSSHKeyOptions) (*Workspace, error)\n\n\t// UnassignSSHKey from a workspace.\n\tUnassignSSHKey(ctx context.Context, workspaceID string) (*Workspace, error)\n\n\t// ListRemoteStateConsumers reads the remote state consumers for a workspace.\n\tListRemoteStateConsumers(ctx context.Context, workspaceID string, options *RemoteStateConsumersListOptions) (*WorkspaceList, error)\n\n\t// AddRemoteStateConsumers adds remote state consumers to a workspace.\n\tAddRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceAddRemoteStateConsumersOptions) error\n\n\t// RemoveRemoteStateConsumers removes remote state consumers from a workspace.\n\tRemoveRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceRemoveRemoteStateConsumersOptions) error\n\n\t// UpdateRemoteStateConsumers updates all the remote state consumers for a workspace\n\t// to match the workspaces in the update options.\n\tUpdateRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceUpdateRemoteStateConsumersOptions) error\n\n\t// ListTags reads the tags for a workspace.\n\tListTags(ctx context.Context, workspaceID string, options *WorkspaceTagListOptions) (*TagList, error)\n\n\t// AddTags appends tags to a workspace\n\tAddTags(ctx context.Context, workspaceID string, options WorkspaceAddTagsOptions) error\n\n\t// RemoveTags removes tags from a workspace\n\tRemoveTags(ctx context.Context, workspaceID string, options WorkspaceRemoveTagsOptions) error\n\n\t// ReadDataRetentionPolicy reads a workspace's data retention policy\n\t//\n\t// Deprecated: Use ReadDataRetentionPolicyChoice instead.\n\t// **Note: This functionality is only available in Terraform Enterprise versions v202311-1 and v202312-1.**\n\tReadDataRetentionPolicy(ctx context.Context, workspaceID string) (*DataRetentionPolicy, error)\n\n\t// ReadDataRetentionPolicyChoice reads a workspace's data retention policy\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tReadDataRetentionPolicyChoice(ctx context.Context, workspaceID string) (*DataRetentionPolicyChoice, error)\n\n\t// SetDataRetentionPolicy sets a workspace's data retention policy to delete data older than a certain number of days\n\t//\n\t// Deprecated: Use SetDataRetentionPolicyDeleteOlder instead\n\t// **Note: This functionality is only available in Terraform Enterprise versions v202311-1 and v202312-1.**\n\tSetDataRetentionPolicy(ctx context.Context, workspaceID string, options DataRetentionPolicySetOptions) (*DataRetentionPolicy, error)\n\n\t// SetDataRetentionPolicyDeleteOlder sets a workspace's data retention policy to delete data older than a certain number of days\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tSetDataRetentionPolicyDeleteOlder(ctx context.Context, workspaceID string, options DataRetentionPolicyDeleteOlderSetOptions) (*DataRetentionPolicyDeleteOlder, error)\n\n\t// SetDataRetentionPolicyDontDelete sets a workspace's data retention policy to explicitly not delete data\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tSetDataRetentionPolicyDontDelete(ctx context.Context, workspaceID string, options DataRetentionPolicyDontDeleteSetOptions) (*DataRetentionPolicyDontDelete, error)\n\n\t// DeleteDataRetentionPolicy deletes a workspace's data retention policy\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tDeleteDataRetentionPolicy(ctx context.Context, workspaceID string) error\n\n\t// ListTagBindings lists all tag bindings associated with the workspace.\n\tListTagBindings(ctx context.Context, workspaceID string) ([]*TagBinding, error)\n\n\t// ListEffectiveTagBindings lists all tag bindings associated with the workspace which may be\n\t// either inherited from a project or binded to the workspace itself.\n\tListEffectiveTagBindings(ctx context.Context, workspaceID string) ([]*EffectiveTagBinding, error)\n\n\t// AddTagBindings adds or modifies the value of existing tag binding keys for a workspace.\n\tAddTagBindings(ctx context.Context, workspaceID string, options WorkspaceAddTagBindingsOptions) ([]*TagBinding, error)\n\n\t// DeleteAllTagBindings removes all tag bindings for a workspace.\n\tDeleteAllTagBindings(ctx context.Context, workspaceID string) error\n}\n\n// workspaces implements Workspaces.\ntype workspaces struct {\n\tclient *Client\n}\n\n// WorkspaceSource represents a source type of a workspace.\ntype WorkspaceSource string\n\nconst (\n\tWorkspaceSourceAPI       WorkspaceSource = \"tfe-api\"\n\tWorkspaceSourceModule    WorkspaceSource = \"tfe-module\"\n\tWorkspaceSourceUI        WorkspaceSource = \"tfe-ui\"\n\tWorkspaceSourceTerraform WorkspaceSource = \"terraform\"\n)\n\n// WorkspaceList represents a list of workspaces.\ntype WorkspaceList struct {\n\t*Pagination\n\tItems []*Workspace\n}\n\n// WorkspaceAddTagBindingsOptions represents the options for adding tag bindings\n// to a workspace.\ntype WorkspaceAddTagBindingsOptions struct {\n\tTagBindings []*TagBinding\n}\n\n// LockedByChoice is a choice type struct that represents the possible values\n// within a polymorphic relation. If a value is available, exactly one field\n// will be non-nil.\ntype LockedByChoice struct {\n\tRun  *Run\n\tUser *User\n\tTeam *Team\n}\n\n// Workspace represents a Terraform Enterprise workspace.\ntype Workspace struct {\n\tID                          string                          `jsonapi:\"primary,workspaces\"`\n\tActions                     *WorkspaceActions               `jsonapi:\"attr,actions\"`\n\tAllowDestroyPlan            bool                            `jsonapi:\"attr,allow-destroy-plan\"`\n\tAssessmentsEnabled          bool                            `jsonapi:\"attr,assessments-enabled\"`\n\tAutoApply                   bool                            `jsonapi:\"attr,auto-apply\"`\n\tAutoApplyRunTrigger         bool                            `jsonapi:\"attr,auto-apply-run-trigger\"`\n\tAutoDestroyAt               jsonapi.NullableAttr[time.Time] `jsonapi:\"attr,auto-destroy-at,iso8601,omitempty\"`\n\tAutoDestroyActivityDuration jsonapi.NullableAttr[string]    `jsonapi:\"attr,auto-destroy-activity-duration,omitempty\"`\n\tCanQueueDestroyPlan         bool                            `jsonapi:\"attr,can-queue-destroy-plan\"`\n\tCreatedAt                   time.Time                       `jsonapi:\"attr,created-at,iso8601\"`\n\tDescription                 string                          `jsonapi:\"attr,description\"`\n\tEnvironment                 string                          `jsonapi:\"attr,environment\"`\n\tExecutionMode               string                          `jsonapi:\"attr,execution-mode\"`\n\tFileTriggersEnabled         bool                            `jsonapi:\"attr,file-triggers-enabled\"`\n\tGlobalRemoteState           bool                            `jsonapi:\"attr,global-remote-state\"`\n\tProjectRemoteState          bool                            `jsonapi:\"attr,project-remote-state\"`\n\tInheritsProjectAutoDestroy  bool                            `jsonapi:\"attr,inherits-project-auto-destroy\"`\n\tLocked                      bool                            `jsonapi:\"attr,locked\"`\n\tMigrationEnvironment        string                          `jsonapi:\"attr,migration-environment\"`\n\tName                        string                          `jsonapi:\"attr,name\"`\n\tNoCodeUpgradeAvailable      bool                            `jsonapi:\"attr,no-code-upgrade-available\"`\n\tOperations                  bool                            `jsonapi:\"attr,operations\"`\n\tPermissions                 *WorkspacePermissions           `jsonapi:\"attr,permissions\"`\n\tQueueAllRuns                bool                            `jsonapi:\"attr,queue-all-runs\"`\n\tSpeculativeEnabled          bool                            `jsonapi:\"attr,speculative-enabled\"`\n\tSource                      WorkspaceSource                 `jsonapi:\"attr,source\"`\n\tSourceName                  string                          `jsonapi:\"attr,source-name\"`\n\tSourceURL                   string                          `jsonapi:\"attr,source-url\"`\n\tStructuredRunOutputEnabled  bool                            `jsonapi:\"attr,structured-run-output-enabled\"`\n\tTerraformVersion            string                          `jsonapi:\"attr,terraform-version\"`\n\tTriggerPrefixes             []string                        `jsonapi:\"attr,trigger-prefixes\"`\n\tTriggerPatterns             []string                        `jsonapi:\"attr,trigger-patterns\"`\n\tVCSRepo                     *VCSRepo                        `jsonapi:\"attr,vcs-repo\"`\n\tWorkingDirectory            string                          `jsonapi:\"attr,working-directory\"`\n\tUpdatedAt                   time.Time                       `jsonapi:\"attr,updated-at,iso8601\"`\n\tResourceCount               int                             `jsonapi:\"attr,resource-count\"`\n\tApplyDurationAverage        time.Duration                   `jsonapi:\"attr,apply-duration-average\"`\n\tPlanDurationAverage         time.Duration                   `jsonapi:\"attr,plan-duration-average\"`\n\tPolicyCheckFailures         int                             `jsonapi:\"attr,policy-check-failures\"`\n\tRunFailures                 int                             `jsonapi:\"attr,run-failures\"`\n\tRunsCount                   int                             `jsonapi:\"attr,workspace-kpis-runs-count\"`\n\tTagNames                    []string                        `jsonapi:\"attr,tag-names\"`\n\tSettingOverwrites           *WorkspaceSettingOverwrites     `jsonapi:\"attr,setting-overwrites\"`\n\tHYOKEnabled                 *bool                           `jsonapi:\"attr,hyok-enabled\"`\n\n\t// Relations\n\tAgentPool                   *AgentPool             `jsonapi:\"relation,agent-pool\"`\n\tCurrentRun                  *Run                   `jsonapi:\"relation,current-run\"`\n\tCurrentStateVersion         *StateVersion          `jsonapi:\"relation,current-state-version\"`\n\tOrganization                *Organization          `jsonapi:\"relation,organization\"`\n\tSSHKey                      *SSHKey                `jsonapi:\"relation,ssh-key\"`\n\tOutputs                     []*WorkspaceOutputs    `jsonapi:\"relation,outputs\"`\n\tProject                     *Project               `jsonapi:\"relation,project\"`\n\tTags                        []*Tag                 `jsonapi:\"relation,tags\"`\n\tCurrentConfigurationVersion *ConfigurationVersion  `jsonapi:\"relation,current-configuration-version,omitempty\"`\n\tLockedBy                    *LockedByChoice        `jsonapi:\"polyrelation,locked-by\"`\n\tVariables                   []*Variable            `jsonapi:\"relation,vars\"`\n\tTagBindings                 []*TagBinding          `jsonapi:\"relation,tag-bindings\"`\n\tEffectiveTagBindings        []*EffectiveTagBinding `jsonapi:\"relation,effective-tag-bindings\"`\n\tHYOKEncryptedDataKey        *HYOKEncryptedDataKey  `jsonapi:\"relation,hyok-data-key-for-encryption\"`\n\n\t// Deprecated: Use DataRetentionPolicyChoice instead.\n\tDataRetentionPolicy *DataRetentionPolicy\n\t// **Note: This functionality is only available in Terraform Enterprise.**\n\tDataRetentionPolicyChoice *DataRetentionPolicyChoice `jsonapi:\"polyrelation,data-retention-policy\"`\n\n\t// Links\n\tLinks map[string]interface{} `jsonapi:\"links,omitempty\"`\n}\n\ntype WorkspaceOutputs struct {\n\tID        string      `jsonapi:\"primary,workspace-outputs\"`\n\tName      string      `jsonapi:\"attr,name\"`\n\tSensitive bool        `jsonapi:\"attr,sensitive\"`\n\tType      string      `jsonapi:\"attr,output-type\"`\n\tValue     interface{} `jsonapi:\"attr,value\"`\n}\n\n// workspaceWithReadme is the same as a workspace but it has a readme.\ntype workspaceWithReadme struct {\n\tID     string           `jsonapi:\"primary,workspaces\"`\n\tReadme *workspaceReadme `jsonapi:\"relation,readme\"`\n}\n\n// workspaceReadme contains the readme of the workspace.\ntype workspaceReadme struct {\n\tID          string `jsonapi:\"primary,workspace-readme\"`\n\tRawMarkdown string `jsonapi:\"attr,raw-markdown\"`\n}\n\n// VCSRepo contains the configuration of a VCS integration.\ntype VCSRepo struct {\n\tBranch            string `jsonapi:\"attr,branch\"`\n\tDisplayIdentifier string `jsonapi:\"attr,display-identifier\"`\n\tIdentifier        string `jsonapi:\"attr,identifier\"`\n\tIngressSubmodules bool   `jsonapi:\"attr,ingress-submodules\"`\n\tOAuthTokenID      string `jsonapi:\"attr,oauth-token-id\"`\n\tGHAInstallationID string `jsonapi:\"attr,github-app-installation-id\"`\n\tRepositoryHTTPURL string `jsonapi:\"attr,repository-http-url\"`\n\tServiceProvider   string `jsonapi:\"attr,service-provider\"`\n\tTags              bool   `jsonapi:\"attr,tags\"`\n\tTagsRegex         string `jsonapi:\"attr,tags-regex\"`\n\tWebhookURL        string `jsonapi:\"attr,webhook-url\"`\n\tSourceDirectory   string `jsonapi:\"attr,source-directory\"`\n\tTagPrefix         string `jsonapi:\"attr,tag-prefix\"`\n}\n\n// Note: the fields of this struct are bool pointers instead of bool values, in order to simplify support for\n// future TFE versions that support *some but not all* of the inherited defaults that go-tfe knows about.\ntype WorkspaceSettingOverwrites struct {\n\tExecutionMode *bool `jsonapi:\"attr,execution-mode\"`\n\tAgentPool     *bool `jsonapi:\"attr,agent-pool\"`\n}\n\n// WorkspaceActions represents the workspace actions.\ntype WorkspaceActions struct {\n\tIsDestroyable bool `jsonapi:\"attr,is-destroyable\"`\n}\n\n// WorkspacePermissions represents the workspace permissions.\ntype WorkspacePermissions struct {\n\tCanDestroy        bool  `jsonapi:\"attr,can-destroy\"`\n\tCanForceUnlock    bool  `jsonapi:\"attr,can-force-unlock\"`\n\tCanLock           bool  `jsonapi:\"attr,can-lock\"`\n\tCanManageRunTasks bool  `jsonapi:\"attr,can-manage-run-tasks\"`\n\tCanManageHYOK     bool  `jsonapi:\"attr,can-manage-hyok\"`\n\tCanQueueApply     bool  `jsonapi:\"attr,can-queue-apply\"`\n\tCanQueueDestroy   bool  `jsonapi:\"attr,can-queue-destroy\"`\n\tCanQueueRun       bool  `jsonapi:\"attr,can-queue-run\"`\n\tCanReadSettings   bool  `jsonapi:\"attr,can-read-settings\"`\n\tCanUnlock         bool  `jsonapi:\"attr,can-unlock\"`\n\tCanUpdate         bool  `jsonapi:\"attr,can-update\"`\n\tCanUpdateVariable bool  `jsonapi:\"attr,can-update-variable\"`\n\tCanForceDelete    *bool `jsonapi:\"attr,can-force-delete\"` // pointer b/c it will be useful to check if this property exists, as opposed to having it default to false\n}\n\n// WSIncludeOpt represents the available options for include query params.\n// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspaces#available-related-resources\ntype WSIncludeOpt string\n\nconst (\n\tWSOrganization               WSIncludeOpt = \"organization\"\n\tWSCurrentConfigVer           WSIncludeOpt = \"current_configuration_version\"\n\tWSCurrentConfigVerIngress    WSIncludeOpt = \"current_configuration_version.ingress_attributes\"\n\tWSCurrentRun                 WSIncludeOpt = \"current_run\"\n\tWSCurrentRunPlan             WSIncludeOpt = \"current_run.plan\"\n\tWSCurrentRunConfigVer        WSIncludeOpt = \"current_run.configuration_version\"\n\tWSCurrentrunConfigVerIngress WSIncludeOpt = \"current_run.configuration_version.ingress_attributes\"\n\tWSEffectiveTagBindings       WSIncludeOpt = \"effective_tag_bindings\"\n\tWSLockedBy                   WSIncludeOpt = \"locked_by\"\n\tWSReadme                     WSIncludeOpt = \"readme\"\n\tWSOutputs                    WSIncludeOpt = \"outputs\"\n\tWSCurrentStateVer            WSIncludeOpt = \"current-state-version\"\n\tWSProject                    WSIncludeOpt = \"project\"\n)\n\n// WorkspaceReadOptions represents the options for reading a workspace.\ntype WorkspaceReadOptions struct {\n\t// Optional: A list of relations to include.\n\t// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspaces#available-related-resources\n\tInclude []WSIncludeOpt `url:\"include,omitempty\"`\n}\n\n// WorkspaceListOptions represents the options for listing workspaces.\ntype WorkspaceListOptions struct {\n\tListOptions\n\n\t// Optional: A search string (partial workspace name) used to filter the results.\n\tSearch string `url:\"search[name],omitempty\"`\n\n\t// Optional: A search string (comma-separated tag names) used to filter the results.\n\tTags string `url:\"search[tags],omitempty\"`\n\n\t// Optional: A search string (comma-separated tag names to exclude) used to filter the results.\n\tExcludeTags string `url:\"search[exclude-tags],omitempty\"`\n\n\t// Optional: A search on substring matching to filter the results.\n\tWildcardName string `url:\"search[wildcard-name],omitempty\"`\n\n\t// Optional: A filter string to list all the workspaces linked to a given project id in the organization.\n\tProjectID string `url:\"filter[project][id],omitempty\"`\n\n\t// Optional: A filter string to list all the workspaces filtered by current run status.\n\tCurrentRunStatus string `url:\"filter[current-run][status],omitempty\"`\n\n\t// Optional: A filter string to list workspaces filtered by key/value tags.\n\t// These are not annotated and therefore not encoded by go-querystring\n\tTagBindings []*TagBinding\n\n\t// Optional: A list of relations to include. See available resources https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspaces#available-related-resources\n\tInclude []WSIncludeOpt `url:\"include,omitempty\"`\n\n\t// Optional: May sort on \"name\" (the default) and \"current-run.created-at\" (which sorts by the time of the current run)\n\t// Prepending a hyphen to the sort parameter will reverse the order (e.g. \"-name\" to reverse the default order)\n\tSort string `url:\"sort,omitempty\"`\n}\n\n// WorkspaceCreateOptions represents the options for creating a new workspace.\ntype WorkspaceCreateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,workspaces\"`\n\n\t// Required when: execution-mode is set to agent. The ID of the agent pool\n\t// belonging to the workspace's organization. This value must not be specified\n\t// if execution-mode is set to remote or local or if operations is set to true.\n\tAgentPoolID *string `jsonapi:\"attr,agent-pool-id,omitempty\"`\n\n\t// Optional: Whether destroy plans can be queued on the workspace.\n\tAllowDestroyPlan *bool `jsonapi:\"attr,allow-destroy-plan,omitempty\"`\n\n\t// Optional: Whether to enable health assessments (drift detection etc.) for the workspace.\n\t// Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspaces#create-a-workspace\n\t// Requires remote execution mode, HCP Terraform Business entitlement, and a valid agent pool to work\n\tAssessmentsEnabled *bool `jsonapi:\"attr,assessments-enabled,omitempty\"`\n\n\t// Optional: Whether to automatically apply changes when a Terraform plan is successful.\n\tAutoApply *bool `jsonapi:\"attr,auto-apply,omitempty\"`\n\n\t// Optional: Whether to automatically apply changes for runs that are created by run triggers\n\t// from another workspace.\n\tAutoApplyRunTrigger *bool `jsonapi:\"attr,auto-apply-run-trigger,omitempty\"`\n\n\t// Optional: The time after which an automatic destroy run will be queued\n\tAutoDestroyAt jsonapi.NullableAttr[time.Time] `jsonapi:\"attr,auto-destroy-at,iso8601,omitempty\"`\n\n\t// Optional: The period of time to wait after workspace activity to trigger a destroy run. The format\n\t// should roughly match a Go duration string limited to days and hours, e.g. \"24h\" or \"1d\".\n\tAutoDestroyActivityDuration jsonapi.NullableAttr[string] `jsonapi:\"attr,auto-destroy-activity-duration,omitempty\"`\n\n\t// Optional: Whether the workspace inherits auto destroy settings from the project\n\tInheritsProjectAutoDestroy *bool `jsonapi:\"attr,inherits-project-auto-destroy,omitempty\"`\n\n\t// Optional: A description for the workspace.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Optional: Which execution mode to use. Valid values are remote, local, and agent.\n\t// When set to local, the workspace will be used for state storage only.\n\t// This value must not be specified if operations is specified.\n\t// 'agent' execution mode is not available in Terraform Enterprise.\n\tExecutionMode *string `jsonapi:\"attr,execution-mode,omitempty\"`\n\n\t// Optional: Whether to filter runs based on the changed files in a VCS push. If\n\t// enabled, the working directory and trigger prefixes describe a set of\n\t// paths which must contain changes for a VCS push to trigger a run. If\n\t// disabled, any push will trigger a run.\n\tFileTriggersEnabled *bool `jsonapi:\"attr,file-triggers-enabled,omitempty\"`\n\n\tGlobalRemoteState *bool `jsonapi:\"attr,global-remote-state,omitempty\"`\n\n\t// Optional: Allows the workspace to share remote state at the project level.\n\t// Default is false.\n\tProjectRemoteState *bool `jsonapi:\"attr,project-remote-state,omitempty\"`\n\n\t// Optional: The legacy TFE environment to use as the source of the migration, in the\n\t// form organization/environment. Omit this unless you are migrating a legacy\n\t// environment.\n\tMigrationEnvironment *string `jsonapi:\"attr,migration-environment,omitempty\"`\n\n\t// The name of the workspace, which can only include letters, numbers, -,\n\t// and _. This will be used as an identifier and must be unique in the\n\t// organization.\n\tName *string `jsonapi:\"attr,name\"`\n\n\t// DEPRECATED. Whether the workspace will use remote or local execution mode.\n\t// Use ExecutionMode instead.\n\tOperations *bool `jsonapi:\"attr,operations,omitempty\"`\n\n\t// Whether to queue all runs. Unless this is set to true, runs triggered by\n\t// a webhook will not be queued until at least one run is manually queued.\n\tQueueAllRuns *bool `jsonapi:\"attr,queue-all-runs,omitempty\"`\n\n\t// Whether this workspace allows speculative plans. Setting this to false\n\t// prevents HCP Terraform or the Terraform Enterprise instance from\n\t// running plans on pull requests, which can improve security if the VCS\n\t// repository is public or includes untrusted contributors.\n\tSpeculativeEnabled *bool `jsonapi:\"attr,speculative-enabled,omitempty\"`\n\n\t// BETA. A friendly name for the application or client creating this\n\t// workspace. If set, this will be displayed on the workspace as\n\t// \"Created via <SOURCE NAME>\".\n\tSourceName *string `jsonapi:\"attr,source-name,omitempty\"`\n\n\t// BETA. A URL for the application or client creating this workspace. This\n\t// can be the URL of a related resource in another app, or a link to\n\t// documentation or other info about the client.\n\tSourceURL *string `jsonapi:\"attr,source-url,omitempty\"`\n\n\t// BETA. Enable the experimental advanced run user interface.\n\t// This only applies to runs using Terraform version 0.15.2 or newer,\n\t// and runs executed using older versions will see the classic experience\n\t// regardless of this setting.\n\tStructuredRunOutputEnabled *bool `jsonapi:\"attr,structured-run-output-enabled,omitempty\"`\n\n\t// The version of Terraform to use for this workspace. Upon creating a\n\t// workspace, the latest version is selected unless otherwise specified.\n\tTerraformVersion *string `jsonapi:\"attr,terraform-version,omitempty\"`\n\n\t// List of repository-root-relative paths which list all locations to be\n\t// tracked for changes. See FileTriggersEnabled above for more details.\n\tTriggerPrefixes []string `jsonapi:\"attr,trigger-prefixes,omitempty\"`\n\n\t// Optional: List of patterns used to match against changed files in order\n\t// to decide whether to trigger a run or not.\n\tTriggerPatterns []string `jsonapi:\"attr,trigger-patterns,omitempty\"`\n\n\t// Settings for the workspace's VCS repository. If omitted, the workspace is\n\t// created without a VCS repo. If included, you must specify at least the\n\t// oauth-token-id and identifier keys below.\n\tVCSRepo *VCSRepoOptions `jsonapi:\"attr,vcs-repo,omitempty\"`\n\n\t// A relative path that Terraform will execute within. This defaults to the\n\t// root of your repository and is typically set to a subdirectory matching the\n\t// environment when multiple environments exist within the same repository.\n\tWorkingDirectory *string `jsonapi:\"attr,working-directory,omitempty\"`\n\n\t// Optional: Enables HYOK in the workspace.\n\t// If set to true, the workspace will be created with HYOK enabled.\n\t// If set to false, the workspace will be created with HYOK disabled.\n\t// If not specified, the workspace will be created with HYOK disabled.\n\t// Note: HYOK is only available in HCP Terraform.\n\tHYOKEnabled *bool `jsonapi:\"attr,hyok-enabled,omitempty\"`\n\n\t// A list of tags to attach to the workspace. If the tag does not already\n\t// exist, it is created and added to the workspace.\n\tTags []*Tag `jsonapi:\"relation,tags,omitempty\"`\n\n\t// Optional: Struct of booleans, which indicate whether the workspace\n\t// specifies its own values for various settings. If you mark a setting as\n\t// `false` in this struct, it will clear the workspace's existing value for\n\t// that setting and defer to the default value that its project or\n\t// organization provides.\n\t//\n\t// In general, it's not necessary to mark a setting as `true` in this\n\t// struct; if you provide a literal value for a setting, HCP Terraform will\n\t// automatically update its overwrites field to `true`. If you do choose to\n\t// manually mark a setting as overwritten, you must provide a value for that\n\t// setting at the same time.\n\tSettingOverwrites *WorkspaceSettingOverwritesOptions `jsonapi:\"attr,setting-overwrites,omitempty\"`\n\n\t// Associated Project with the workspace. If not provided, default project\n\t// of the organization will be assigned to the workspace.\n\tProject *Project `jsonapi:\"relation,project,omitempty\"`\n\n\t// Associated TagBindings of the workspace.\n\tTagBindings []*TagBinding `jsonapi:\"relation,tag-bindings,omitempty\"`\n}\n\n// TODO: move this struct out. VCSRepoOptions is used by workspaces, policy sets, and registry modules\n// VCSRepoOptions represents the configuration options of a VCS integration.\ntype VCSRepoOptions struct {\n\tBranch            *string `json:\"branch,omitempty\"`\n\tIdentifier        *string `json:\"identifier,omitempty\"`\n\tIngressSubmodules *bool   `json:\"ingress-submodules,omitempty\"`\n\tOAuthTokenID      *string `json:\"oauth-token-id,omitempty\"`\n\tTagsRegex         *string `json:\"tags-regex,omitempty\"`\n\tGHAInstallationID *string `json:\"github-app-installation-id,omitempty\"`\n}\n\ntype WorkspaceSettingOverwritesOptions struct {\n\t// If false, the workspace will defer to its organization or project's DefaultExecutionMode value.\n\tExecutionMode *bool `json:\"execution-mode,omitempty\"`\n\t// If false, the workspace will defer to its organization or project's DefaultAgentPool value.\n\tAgentPool *bool `json:\"agent-pool,omitempty\"`\n}\n\n// WorkspaceUpdateOptions represents the options for updating a workspace.\ntype WorkspaceUpdateOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,workspaces\"`\n\n\t// Required when: execution-mode is set to agent. The ID of the agent pool\n\t// belonging to the workspace's organization. This value must not be specified\n\t// if execution-mode is set to remote or local or if operations is set to true.\n\tAgentPoolID *string `jsonapi:\"attr,agent-pool-id,omitempty\"`\n\n\t// Optional: Whether destroy plans can be queued on the workspace.\n\tAllowDestroyPlan *bool `jsonapi:\"attr,allow-destroy-plan,omitempty\"`\n\n\t// Optional: Whether to enable health assessments (drift detection etc.) for the workspace.\n\t// Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspaces#update-a-workspace\n\t// Requires remote execution mode, HCP Terraform Business entitlement, and a valid agent pool to work\n\tAssessmentsEnabled *bool `jsonapi:\"attr,assessments-enabled,omitempty\"`\n\n\t// Optional: Whether to automatically apply changes when a Terraform plan is successful.\n\tAutoApply *bool `jsonapi:\"attr,auto-apply,omitempty\"`\n\n\t// Optional: Whether to automatically apply changes for runs that are created by run triggers\n\t// from another workspace.\n\tAutoApplyRunTrigger *bool `jsonapi:\"attr,auto-apply-run-trigger,omitempty\"`\n\n\t// Optional: The time after which an automatic destroy run will be queued\n\tAutoDestroyAt jsonapi.NullableAttr[time.Time] `jsonapi:\"attr,auto-destroy-at,iso8601,omitempty\"`\n\n\t// Optional: The period of time to wait after workspace activity to trigger a destroy run. The format\n\t// should roughly match a Go duration string limited to days and hours, e.g. \"24h\" or \"1d\".\n\tAutoDestroyActivityDuration jsonapi.NullableAttr[string] `jsonapi:\"attr,auto-destroy-activity-duration,omitempty\"`\n\n\t// Optional: Whether the workspace inherits auto destroy settings from the project\n\tInheritsProjectAutoDestroy *bool `jsonapi:\"attr,inherits-project-auto-destroy,omitempty\"`\n\n\t// Optional: A new name for the workspace, which can only include letters, numbers, -,\n\t// and _. This will be used as an identifier and must be unique in the\n\t// organization. Warning: Changing a workspace's name changes its URL in the\n\t// API and UI.\n\tName *string `jsonapi:\"attr,name,omitempty\"`\n\n\t// Optional: A description for the workspace.\n\tDescription *string `jsonapi:\"attr,description,omitempty\"`\n\n\t// Optional: Which execution mode to use. Valid values are remote, local, and agent.\n\t// When set to local, the workspace will be used for state storage only.\n\t// This value must not be specified if operations is specified.\n\t// 'agent' execution mode is not available in Terraform Enterprise.\n\tExecutionMode *string `jsonapi:\"attr,execution-mode,omitempty\"`\n\n\t// Optional: Whether to filter runs based on the changed files in a VCS push. If\n\t// enabled, the working directory and trigger prefixes describe a set of\n\t// paths which must contain changes for a VCS push to trigger a run. If\n\t// disabled, any push will trigger a run.\n\tFileTriggersEnabled *bool `jsonapi:\"attr,file-triggers-enabled,omitempty\"`\n\n\t// Optional:\n\tGlobalRemoteState *bool `jsonapi:\"attr,global-remote-state,omitempty\"`\n\n\t// Optional: Allows the workspace to share remote state at the project level.\n\t// Default is false.\n\tProjectRemoteState *bool `jsonapi:\"attr,project-remote-state,omitempty\"`\n\n\t// DEPRECATED. Whether the workspace will use remote or local execution mode.\n\t// Use ExecutionMode instead.\n\tOperations *bool `jsonapi:\"attr,operations,omitempty\"`\n\n\t// Optional: Whether to queue all runs. Unless this is set to true, runs triggered by\n\t// a webhook will not be queued until at least one run is manually queued.\n\tQueueAllRuns *bool `jsonapi:\"attr,queue-all-runs,omitempty\"`\n\n\t// Optional: Whether this workspace allows speculative plans. Setting this to false\n\t// prevents HCP Terraform or the Terraform Enterprise instance from\n\t// running plans on pull requests, which can improve security if the VCS\n\t// repository is public or includes untrusted contributors.\n\tSpeculativeEnabled *bool `jsonapi:\"attr,speculative-enabled,omitempty\"`\n\n\t// BETA. Enable the experimental advanced run user interface.\n\t// This only applies to runs using Terraform version 0.15.2 or newer,\n\t// and runs executed using older versions will see the classic experience\n\t// regardless of this setting.\n\tStructuredRunOutputEnabled *bool `jsonapi:\"attr,structured-run-output-enabled,omitempty\"`\n\n\t// Optional: The version of Terraform to use for this workspace.\n\tTerraformVersion *string `jsonapi:\"attr,terraform-version,omitempty\"`\n\n\t// Optional: List of repository-root-relative paths which list all locations to be\n\t// tracked for changes. See FileTriggersEnabled above for more details.\n\tTriggerPrefixes []string `jsonapi:\"attr,trigger-prefixes,omitempty\"`\n\n\t// Optional: List of patterns used to match against changed files in order\n\t// to decide whether to trigger a run or not.\n\tTriggerPatterns []string `jsonapi:\"attr,trigger-patterns,omitempty\"`\n\n\t// Optional: To delete a workspace's existing VCS repo, specify null instead of an\n\t// object. To modify a workspace's existing VCS repo, include whichever of\n\t// the keys below you wish to modify. To add a new VCS repo to a workspace\n\t// that didn't previously have one, include at least the oauth-token-id and\n\t// identifier keys.\n\tVCSRepo *VCSRepoOptions `jsonapi:\"attr,vcs-repo,omitempty\"`\n\n\t// Optional: A relative path that Terraform will execute within. This defaults to the\n\t// root of your repository and is typically set to a subdirectory matching\n\t// the environment when multiple environments exist within the same\n\t// repository.\n\tWorkingDirectory *string `jsonapi:\"attr,working-directory,omitempty\"`\n\n\t// Optional: Struct of booleans, which indicate whether the workspace\n\t// specifies its own values for various settings. If you mark a setting as\n\t// `false` in this struct, it will clear the workspace's existing value for\n\t// that setting and defer to the default value that its project or\n\t// organization provides.\n\t//\n\t// In general, it's not necessary to mark a setting as `true` in this\n\t// struct; if you provide a literal value for a setting, HCP Terraform will\n\t// automatically update its overwrites field to `true`. If you do choose to\n\t// manually mark a setting as overwritten, you must provide a value for that\n\t// setting at the same time.\n\tSettingOverwrites *WorkspaceSettingOverwritesOptions `jsonapi:\"attr,setting-overwrites,omitempty\"`\n\n\t// Optional: Enables HYOK in the workspace.\n\t// If set to true, the workspace will be updated with HYOK enabled.\n\t// This can't be set to false, as HYOK is a one-way operation.\n\tHYOKEnabled *bool `jsonapi:\"attr,hyok-enabled,omitempty\"`\n\n\t// Associated Project with the workspace. If not provided, default project\n\t// of the organization will be assigned to the workspace\n\tProject *Project `jsonapi:\"relation,project,omitempty\"`\n\n\t// Associated TagBindings of the project. Note that this will replace\n\t// all existing tag bindings.\n\tTagBindings []*TagBinding `jsonapi:\"relation,tag-bindings,omitempty\"`\n}\n\n// WorkspaceLockOptions represents the options for locking a workspace.\ntype WorkspaceLockOptions struct {\n\t// Specifies the reason for locking the workspace.\n\tReason *string `jsonapi:\"attr,reason,omitempty\"`\n}\n\n// workspaceRemoveVCSConnectionOptions\ntype workspaceRemoveVCSConnectionOptions struct {\n\tID      string          `jsonapi:\"primary,workspaces\"`\n\tVCSRepo *VCSRepoOptions `jsonapi:\"attr,vcs-repo\"`\n}\n\n// WorkspaceAssignSSHKeyOptions represents the options to assign an SSH key to\n// a workspace.\ntype WorkspaceAssignSSHKeyOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,workspaces\"`\n\n\t// The SSH key ID to assign.\n\tSSHKeyID *string `jsonapi:\"attr,id\"`\n}\n\n// workspaceUnassignSSHKeyOptions represents the options to unassign an SSH key\n// to a workspace.\ntype workspaceUnassignSSHKeyOptions struct {\n\t// Type is a public field utilized by JSON:API to\n\t// set the resource type via the field tag.\n\t// It is not a user-defined value and does not need to be set.\n\t// https://jsonapi.org/format/#crud-creating\n\tType string `jsonapi:\"primary,workspaces\"`\n\n\t// Must be nil to unset the currently assigned SSH key.\n\tSSHKeyID *string `jsonapi:\"attr,id\"`\n}\n\ntype RemoteStateConsumersListOptions struct {\n\tListOptions\n}\n\n// WorkspaceAddRemoteStateConsumersOptions represents the options for adding remote state consumers\n// to a workspace.\ntype WorkspaceAddRemoteStateConsumersOptions struct {\n\t// The workspaces to add as remote state consumers to the workspace.\n\tWorkspaces []*Workspace\n}\n\n// WorkspaceRemoveRemoteStateConsumersOptions represents the options for removing remote state\n// consumers from a workspace.\ntype WorkspaceRemoveRemoteStateConsumersOptions struct {\n\t// The workspaces to remove as remote state consumers from the workspace.\n\tWorkspaces []*Workspace\n}\n\n// WorkspaceUpdateRemoteStateConsumersOptions represents the options for\n// updatintg remote state consumers from a workspace.\ntype WorkspaceUpdateRemoteStateConsumersOptions struct {\n\t// The workspaces to update remote state consumers for the workspace.\n\tWorkspaces []*Workspace\n}\n\ntype WorkspaceTagListOptions struct {\n\tListOptions\n\n\t// A query string used to filter workspace tags.\n\t// Any workspace tag with a name partially matching this value will be returned.\n\tQuery *string `url:\"name,omitempty\"`\n}\n\ntype WorkspaceAddTagsOptions struct {\n\tTags []*Tag\n}\n\ntype WorkspaceRemoveTagsOptions struct {\n\tTags []*Tag\n}\n\n// List all the workspaces within an organization.\nfunc (s *workspaces) List(ctx context.Context, organization string, options *WorkspaceListOptions) (*WorkspaceList, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar tagFilters map[string][]string\n\tif options != nil {\n\t\ttagFilters = encodeTagFiltersAsParams(options.TagBindings)\n\t}\n\n\t// Encode parameters that cannot be encoded by go-querystring\n\tu := fmt.Sprintf(\"organizations/%s/workspaces\", url.PathEscape(organization))\n\treq, err := s.client.NewRequestWithAdditionalQueryParams(\"GET\", u, options, tagFilters)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twl := &WorkspaceList{}\n\terr = req.Do(ctx, wl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn wl, nil\n}\n\nfunc (s *workspaces) ListTagBindings(ctx context.Context, workspaceID string) ([]*TagBinding, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/tag-bindings\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar list struct {\n\t\t*Pagination\n\t\tItems []*TagBinding\n\t}\n\n\terr = req.Do(ctx, &list)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn list.Items, nil\n}\n\nfunc (s *workspaces) ListEffectiveTagBindings(ctx context.Context, workspaceID string) ([]*EffectiveTagBinding, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/effective-tag-bindings\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar list struct {\n\t\t*Pagination\n\t\tItems []*EffectiveTagBinding\n\t}\n\n\terr = req.Do(ctx, &list)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn list.Items, nil\n}\n\n// AddTagBindings adds or modifies the value of existing tag binding keys for a workspace.\nfunc (s *workspaces) AddTagBindings(ctx context.Context, workspaceID string, options WorkspaceAddTagBindingsOptions) ([]*TagBinding, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/tag-bindings\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, options.TagBindings)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar response = struct {\n\t\t*Pagination\n\t\tItems []*TagBinding\n\t}{}\n\terr = req.Do(ctx, &response)\n\n\treturn response.Items, err\n}\n\n// DeleteAllTagBindings removes all tag bindings associated with a workspace.\n// This method will not remove any inherited tag bindings, which must be\n// explicitly removed from the parent project.\nfunc (s *workspaces) DeleteAllTagBindings(ctx context.Context, workspaceID string) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\n\ttype aliasOpts struct {\n\t\tType        string        `jsonapi:\"primary,workspaces\"`\n\t\tTagBindings []*TagBinding `jsonapi:\"relation,tag-bindings\"`\n\t}\n\n\topts := &aliasOpts{\n\t\tTagBindings: []*TagBinding{},\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// Create is used to create a new workspace.\nfunc (s *workspaces) Create(ctx context.Context, organization string, options WorkspaceCreateOptions) (*Workspace, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"organizations/%s/workspaces\", url.PathEscape(organization))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// Read a workspace by its name and organization name.\nfunc (s *workspaces) Read(ctx context.Context, organization, workspace string) (*Workspace, error) {\n\treturn s.ReadWithOptions(ctx, organization, workspace, nil)\n}\n\n// ReadWithOptions reads a workspace by name and organization name with given options.\nfunc (s *workspaces) ReadWithOptions(ctx context.Context, organization, workspace string, options *WorkspaceReadOptions) (*Workspace, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif !validStringID(&workspace) {\n\t\treturn nil, ErrInvalidWorkspaceValue\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/workspaces/%s\",\n\t\turl.PathEscape(organization),\n\t\turl.PathEscape(workspace),\n\t)\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Manually populate the deprecated DataRetentionPolicy field\n\tw.DataRetentionPolicy = w.DataRetentionPolicyChoice.ConvertToLegacyStruct()\n\n\t// durations come over in ms\n\tw.ApplyDurationAverage *= time.Millisecond\n\tw.PlanDurationAverage *= time.Millisecond\n\n\treturn w, nil\n}\n\n// ReadByID reads a workspace by its ID.\nfunc (s *workspaces) ReadByID(ctx context.Context, workspaceID string) (*Workspace, error) {\n\treturn s.ReadByIDWithOptions(ctx, workspaceID, nil)\n}\n\n// ReadByIDWithOptions reads a workspace by its ID with the given options.\nfunc (s *workspaces) ReadByIDWithOptions(ctx context.Context, workspaceID string, options *WorkspaceReadOptions) (*Workspace, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Manually populate the deprecated DataRetentionPolicy field\n\tif w.DataRetentionPolicyChoice != nil {\n\t\tw.DataRetentionPolicy = w.DataRetentionPolicyChoice.ConvertToLegacyStruct()\n\t}\n\n\t// durations come over in ms\n\tw.ApplyDurationAverage *= time.Millisecond\n\tw.PlanDurationAverage *= time.Millisecond\n\n\treturn w, nil\n}\n\n// Readme gets the readme of a workspace by its ID.\nfunc (s *workspaces) Readme(ctx context.Context, workspaceID string) (io.Reader, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s?include=readme\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr := &workspaceWithReadme{}\n\terr = req.Do(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif r.Readme == nil {\n\t\treturn nil, nil\n\t}\n\n\treturn strings.NewReader(r.Readme.RawMarkdown), nil\n}\n\n// Update settings of an existing workspace.\nfunc (s *workspaces) Update(ctx context.Context, organization, workspace string, options WorkspaceUpdateOptions) (*Workspace, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif !validStringID(&workspace) {\n\t\treturn nil, ErrInvalidWorkspaceValue\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/workspaces/%s\",\n\t\turl.PathEscape(organization),\n\t\turl.PathEscape(workspace),\n\t)\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// UpdateByID updates the settings of an existing workspace.\nfunc (s *workspaces) UpdateByID(ctx context.Context, workspaceID string, options WorkspaceUpdateOptions) (*Workspace, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// Delete a workspace by its name.\nfunc (s *workspaces) Delete(ctx context.Context, organization, workspace string) error {\n\tif !validStringID(&organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\tif !validStringID(&workspace) {\n\t\treturn ErrInvalidWorkspaceValue\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/workspaces/%s\",\n\t\turl.PathEscape(organization),\n\t\turl.PathEscape(workspace),\n\t)\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// DeleteByID deletes a workspace by its ID.\nfunc (s *workspaces) DeleteByID(ctx context.Context, workspaceID string) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// SafeDelete a workspace by its name.\nfunc (s *workspaces) SafeDelete(ctx context.Context, organization, workspace string) error {\n\tif !validStringID(&organization) {\n\t\treturn ErrInvalidOrg\n\t}\n\tif !validStringID(&workspace) {\n\t\treturn ErrInvalidWorkspaceValue\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/workspaces/%s/actions/safe-delete\",\n\t\turl.PathEscape(organization),\n\t\turl.PathEscape(workspace),\n\t)\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// SafeDeleteByID safely deletes a workspace by its ID.\nfunc (s *workspaces) SafeDeleteByID(ctx context.Context, workspaceID string) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/actions/safe-delete\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// RemoveVCSConnection from a workspace.\nfunc (s *workspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*Workspace, error) {\n\tif !validStringID(&organization) {\n\t\treturn nil, ErrInvalidOrg\n\t}\n\tif !validStringID(&workspace) {\n\t\treturn nil, ErrInvalidWorkspaceValue\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"organizations/%s/workspaces/%s\",\n\t\turl.PathEscape(organization),\n\t\turl.PathEscape(workspace),\n\t)\n\n\treq, err := s.client.NewRequest(\"PATCH\", u, &workspaceRemoveVCSConnectionOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// RemoveVCSConnectionByID removes a VCS connection from a workspace.\nfunc (s *workspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*Workspace, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s\", url.PathEscape(workspaceID))\n\n\treq, err := s.client.NewRequest(\"PATCH\", u, &workspaceRemoveVCSConnectionOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// Lock a workspace by its ID.\nfunc (s *workspaces) Lock(ctx context.Context, workspaceID string, options WorkspaceLockOptions) (*Workspace, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/actions/lock\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// Unlock a workspace by its ID.\nfunc (s *workspaces) Unlock(ctx context.Context, workspaceID string) (*Workspace, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/actions/unlock\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"latest state version is still pending\") {\n\t\t\treturn nil, ErrWorkspaceLockedStateVersionStillPending\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// ForceUnlock a workspace by its ID.\nfunc (s *workspaces) ForceUnlock(ctx context.Context, workspaceID string) (*Workspace, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/actions/force-unlock\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"POST\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// AssignSSHKey to a workspace.\nfunc (s *workspaces) AssignSSHKey(ctx context.Context, workspaceID string, options WorkspaceAssignSSHKeyOptions) (*Workspace, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/relationships/ssh-key\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// UnassignSSHKey from a workspace.\nfunc (s *workspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*Workspace, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/relationships/ssh-key\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, &workspaceUnassignSSHKeyOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &Workspace{}\n\terr = req.Do(ctx, w)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn w, nil\n}\n\n// RemoteStateConsumers returns the remote state consumers for a given workspace.\nfunc (s *workspaces) ListRemoteStateConsumers(ctx context.Context, workspaceID string, options *RemoteStateConsumersListOptions) (*WorkspaceList, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/relationships/remote-state-consumers\", url.PathEscape(workspaceID))\n\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twl := &WorkspaceList{}\n\terr = req.Do(ctx, wl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn wl, nil\n}\n\n// AddRemoteStateConsumere adds the remote state consumers to a given workspace.\nfunc (s *workspaces) AddRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceAddRemoteStateConsumersOptions) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/relationships/remote-state-consumers\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.Workspaces)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// RemoveRemoteStateConsumers removes the remote state consumers for a given workspace.\nfunc (s *workspaces) RemoveRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceRemoveRemoteStateConsumersOptions) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/relationships/remote-state-consumers\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.Workspaces)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// UpdateRemoteStateConsumers removes the remote state consumers for a given workspace.\nfunc (s *workspaces) UpdateRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceUpdateRemoteStateConsumersOptions) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/relationships/remote-state-consumers\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"PATCH\", u, options.Workspaces)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// ListTags returns the tags for a given workspace.\nfunc (s *workspaces) ListTags(ctx context.Context, workspaceID string, options *WorkspaceTagListOptions) (*TagList, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/relationships/tags\", url.PathEscape(workspaceID))\n\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttl := &TagList{}\n\terr = req.Do(ctx, tl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tl, nil\n}\n\n// AddTags adds a list of tags to a workspace.\nfunc (s *workspaces) AddTags(ctx context.Context, workspaceID string, options WorkspaceAddTagsOptions) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/relationships/tags\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"POST\", u, options.Tags)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\n// RemoveTags removes a list of tags from a workspace.\nfunc (s *workspaces) RemoveTags(ctx context.Context, workspaceID string, options WorkspaceRemoveTagsOptions) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/relationships/tags\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"DELETE\", u, options.Tags)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (s *workspaces) ReadDataRetentionPolicy(ctx context.Context, workspaceID string) (*DataRetentionPolicy, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/relationships/data-retention-policy\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdataRetentionPolicy := &DataRetentionPolicy{}\n\terr = req.Do(ctx, dataRetentionPolicy)\n\n\tif err != nil {\n\t\t// try to detect known issue where this function is used with TFE >= 202401,\n\t\t// and direct user towards the V2 function\n\t\tif drpUnmarshalEr.MatchString(err.Error()) {\n\t\t\treturn nil, fmt.Errorf(\"error reading deprecated DataRetentionPolicy, use ReadDataRetentionPolicyChoice instead\")\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn dataRetentionPolicy, nil\n}\n\nfunc (s *workspaces) ReadDataRetentionPolicyChoice(ctx context.Context, workspaceID string) (*DataRetentionPolicyChoice, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\t// The API to read the drp is workspaces/<id>/relationships/data-retention-policy\n\t// However, this API can return multiple \"types\" (e.g. data-retention-policy-delete-olders, or data-retention-policy-dont-deletes)\n\t// Ideally we would deserialize this directly into the choice type (DataRetentionPolicyChoice)...however, there isn't a way to\n\t// tell the current jsonapi implementation that the direct result of an endpoint could be different types. Relationships can be polymorphic,\n\t// but the direct result of an endpoint can't be (as far as the jsonapi implementation is concerned)\n\n\t// Instead, we need to figure out the type of the data retention policy first, and deserialize it into the matching model. We\n\t// can then create a choice type manually\n\tws, err := s.ReadByID(ctx, workspaceID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// there is no drp (of a known type)\n\tif ws.DataRetentionPolicyChoice == nil || !ws.DataRetentionPolicyChoice.IsPopulated() {\n\t\treturn ws.DataRetentionPolicyChoice, nil\n\t}\n\n\tu := s.dataRetentionPolicyLink(workspaceID)\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdataRetentionPolicy := &DataRetentionPolicyChoice{}\n\t// if reading the workspace told us it was a \"delete older policy\" deserialize into the DeleteOlder portion of the choice model\n\tif ws.DataRetentionPolicyChoice.DataRetentionPolicyDeleteOlder != nil {\n\t\tdeleteOlder := &DataRetentionPolicyDeleteOlder{}\n\t\terr = req.Do(ctx, deleteOlder)\n\t\tdataRetentionPolicy.DataRetentionPolicyDeleteOlder = deleteOlder\n\n\t\t// if reading the workspace told us it was a \"delete older policy\" deserialize into the DeleteOlder portion of the choice model\n\t} else if ws.DataRetentionPolicyChoice.DataRetentionPolicyDontDelete != nil {\n\t\tdontDelete := &DataRetentionPolicyDontDelete{}\n\t\terr = req.Do(ctx, dontDelete)\n\t\tdataRetentionPolicy.DataRetentionPolicyDontDelete = dontDelete\n\t} else if ws.DataRetentionPolicyChoice != nil {\n\t\tlegacyDrp := &DataRetentionPolicy{}\n\t\terr = req.Do(ctx, legacyDrp)\n\t\tdataRetentionPolicy.DataRetentionPolicy = legacyDrp\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dataRetentionPolicy, nil\n}\n\nfunc (s *workspaces) SetDataRetentionPolicy(ctx context.Context, workspaceID string, options DataRetentionPolicySetOptions) (*DataRetentionPolicy, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := s.dataRetentionPolicyLink(workspaceID)\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdataRetentionPolicy := &DataRetentionPolicy{}\n\terr = req.Do(ctx, dataRetentionPolicy)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dataRetentionPolicy, nil\n}\n\nfunc (s *workspaces) SetDataRetentionPolicyDeleteOlder(ctx context.Context, workspaceID string, options DataRetentionPolicyDeleteOlderSetOptions) (*DataRetentionPolicyDeleteOlder, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := s.dataRetentionPolicyLink(workspaceID)\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdataRetentionPolicy := &DataRetentionPolicyDeleteOlder{}\n\terr = req.Do(ctx, dataRetentionPolicy)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dataRetentionPolicy, nil\n}\n\nfunc (s *workspaces) SetDataRetentionPolicyDontDelete(ctx context.Context, workspaceID string, options DataRetentionPolicyDontDeleteSetOptions) (*DataRetentionPolicyDontDelete, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := s.dataRetentionPolicyLink(workspaceID)\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdataRetentionPolicy := &DataRetentionPolicyDontDelete{}\n\terr = req.Do(ctx, dataRetentionPolicy)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dataRetentionPolicy, nil\n}\n\nfunc (s *workspaces) DeleteDataRetentionPolicy(ctx context.Context, workspaceID string) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\n\tu := s.dataRetentionPolicyLink(workspaceID)\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o WorkspaceAddTagBindingsOptions) valid() error {\n\tif len(o.TagBindings) == 0 {\n\t\treturn ErrRequiredTagBindings\n\t}\n\n\treturn nil\n}\n\nfunc (o WorkspaceCreateOptions) valid() error {\n\tif !validString(o.Name) {\n\t\treturn ErrRequiredName\n\t}\n\tif !validStringID(o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\tif o.Operations != nil && o.ExecutionMode != nil {\n\t\treturn ErrUnsupportedOperations\n\t}\n\tif o.AgentPoolID != nil && (o.ExecutionMode == nil || *o.ExecutionMode != \"agent\") {\n\t\treturn ErrRequiredAgentMode\n\t}\n\tif o.AgentPoolID == nil && (o.ExecutionMode != nil && *o.ExecutionMode == \"agent\") {\n\t\treturn ErrRequiredAgentPoolID\n\t}\n\tif len(o.TriggerPrefixes) > 0 &&\n\t\to.TriggerPatterns != nil && len(o.TriggerPatterns) > 0 {\n\t\treturn ErrUnsupportedBothTriggerPatternsAndPrefixes\n\t}\n\tif tagRegexDefined(o.VCSRepo) &&\n\t\to.TriggerPatterns != nil && len(o.TriggerPatterns) > 0 {\n\t\treturn ErrUnsupportedBothTagsRegexAndTriggerPatterns\n\t}\n\tif tagRegexDefined(o.VCSRepo) &&\n\t\to.TriggerPrefixes != nil && len(o.TriggerPrefixes) > 0 {\n\t\treturn ErrUnsupportedBothTagsRegexAndTriggerPrefixes\n\t}\n\tif tagRegexDefined(o.VCSRepo) &&\n\t\to.FileTriggersEnabled != nil && *o.FileTriggersEnabled {\n\t\treturn ErrUnsupportedBothTagsRegexAndFileTriggersEnabled\n\t}\n\n\treturn nil\n}\n\nfunc (o WorkspaceUpdateOptions) valid() error {\n\tif o.Name != nil && !validStringID(o.Name) {\n\t\treturn ErrInvalidName\n\t}\n\tif o.Operations != nil && o.ExecutionMode != nil {\n\t\treturn ErrUnsupportedOperations\n\t}\n\tif o.AgentPoolID == nil && (o.ExecutionMode != nil && *o.ExecutionMode == \"agent\") {\n\t\treturn ErrRequiredAgentPoolID\n\t}\n\tif len(o.TriggerPrefixes) > 0 &&\n\t\to.TriggerPatterns != nil && len(o.TriggerPatterns) > 0 {\n\t\treturn ErrUnsupportedBothTriggerPatternsAndPrefixes\n\t}\n\n\tif tagRegexDefined(o.VCSRepo) &&\n\t\to.TriggerPatterns != nil && len(o.TriggerPatterns) > 0 {\n\t\treturn ErrUnsupportedBothTagsRegexAndTriggerPatterns\n\t}\n\tif tagRegexDefined(o.VCSRepo) &&\n\t\to.TriggerPrefixes != nil && len(o.TriggerPrefixes) > 0 {\n\t\treturn ErrUnsupportedBothTagsRegexAndTriggerPrefixes\n\t}\n\tif tagRegexDefined(o.VCSRepo) &&\n\t\to.FileTriggersEnabled != nil && *o.FileTriggersEnabled {\n\t\treturn ErrUnsupportedBothTagsRegexAndFileTriggersEnabled\n\t}\n\n\treturn nil\n}\n\nfunc (o WorkspaceAssignSSHKeyOptions) valid() error {\n\tif !validString(o.SSHKeyID) {\n\t\treturn ErrRequiredSHHKeyID\n\t}\n\tif !validStringID(o.SSHKeyID) {\n\t\treturn ErrInvalidSHHKeyID\n\t}\n\treturn nil\n}\n\nfunc (o WorkspaceAddRemoteStateConsumersOptions) valid() error {\n\tif o.Workspaces == nil {\n\t\treturn ErrWorkspacesRequired\n\t}\n\tif len(o.Workspaces) == 0 {\n\t\treturn ErrWorkspaceMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o WorkspaceRemoveRemoteStateConsumersOptions) valid() error {\n\tif o.Workspaces == nil {\n\t\treturn ErrWorkspacesRequired\n\t}\n\tif len(o.Workspaces) == 0 {\n\t\treturn ErrWorkspaceMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o WorkspaceUpdateRemoteStateConsumersOptions) valid() error {\n\tif o.Workspaces == nil {\n\t\treturn ErrWorkspacesRequired\n\t}\n\tif len(o.Workspaces) == 0 {\n\t\treturn ErrWorkspaceMinLimit\n\t}\n\treturn nil\n}\n\nfunc (o WorkspaceAddTagsOptions) valid() error {\n\tif len(o.Tags) == 0 {\n\t\treturn ErrMissingTagIdentifier\n\t}\n\tfor _, s := range o.Tags {\n\t\tif s.Name == \"\" && s.ID == \"\" {\n\t\t\treturn ErrMissingTagIdentifier\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (o WorkspaceRemoveTagsOptions) valid() error {\n\tif len(o.Tags) == 0 {\n\t\treturn ErrMissingTagIdentifier\n\t}\n\tfor _, s := range o.Tags {\n\t\tif s.Name == \"\" && s.ID == \"\" {\n\t\t\treturn ErrMissingTagIdentifier\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (o *WorkspaceListOptions) valid() error {\n\treturn nil\n}\n\nfunc (o *WorkspaceReadOptions) valid() error {\n\treturn nil\n}\n\nfunc tagRegexDefined(options *VCSRepoOptions) bool {\n\tif options == nil {\n\t\treturn false\n\t}\n\tif options.TagsRegex != nil && *options.TagsRegex != \"\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (s *workspaces) dataRetentionPolicyLink(wsID string) string {\n\treturn fmt.Sprintf(\"workspaces/%s/relationships/data-retention-policy\", url.PathEscape(wsID))\n}\n"
  },
  {
    "path": "workspace_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tretryablehttp \"github.com/hashicorp/go-retryablehttp\"\n\t\"github.com/hashicorp/jsonapi\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype WorkspaceTableOptions struct {\n\tcreateOptions *WorkspaceCreateOptions\n\tupdateOptions *WorkspaceUpdateOptions\n}\n\ntype WorkspaceTableTest struct {\n\tscenario  string\n\toptions   *WorkspaceTableOptions\n\tsetup     func(t *testing.T, options *WorkspaceTableOptions) (w *Workspace, cleanup func())\n\tassertion func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error)\n}\n\nfunc TestWorkspacesList_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest1, wTest1Cleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTest1Cleanup)\n\twTest2, wTest2Cleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTest2Cleanup)\n\twTest3, wTest3Cleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTest3Cleanup)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, wl.Items, wTest1)\n\t\tassert.Contains(t, wl.Items, wTest2)\n\t\tassert.Equal(t, 1, wl.CurrentPage)\n\t\tassert.Equal(t, 3, wl.TotalCount)\n\t})\n\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\t// Request a page number which is out of range. The result should\n\t\t// be successful, but return no results if the paging options are\n\t\t// properly passed along.\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, wl.Items)\n\t\tassert.Equal(t, 999, wl.CurrentPage)\n\t\tassert.Equal(t, 3, wl.TotalCount)\n\t})\n\n\tt.Run(\"when sorting by workspace names\", func(t *testing.T) {\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tSort: \"name\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, wl.Items)\n\t\trequire.GreaterOrEqual(t, len(wl.Items), 2)\n\t\tassert.Equal(t, wl.Items[0].Name < wl.Items[1].Name, true)\n\t})\n\n\tt.Run(\"when sorting workspaces on current-run.created-at\", func(t *testing.T) {\n\t\t_, unappliedCleanup1 := createRunUnapplied(t, client, wTest2)\n\t\tt.Cleanup(unappliedCleanup1)\n\n\t\t_, unappliedCleanup2 := createRunUnapplied(t, client, wTest3)\n\t\tt.Cleanup(unappliedCleanup2)\n\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tInclude: []WSIncludeOpt{WSCurrentRun},\n\t\t\tSort:    \"current-run.created-at\",\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, wl.Items)\n\t\trequire.GreaterOrEqual(t, len(wl.Items), 2)\n\t\tassert.True(t, wl.Items[1].CurrentRun.CreatedAt.After(wl.Items[0].CurrentRun.CreatedAt))\n\t})\n\n\tt.Run(\"when searching a known workspace\", func(t *testing.T) {\n\t\t// Use a known workspace prefix as search attribute. The result\n\t\t// should be successful and only contain the matching workspace.\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tSearch: wTest1.Name[:len(wTest1.Name)-5],\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, wl.Items, wTest1)\n\t\tassert.NotContains(t, wl.Items, wTest2)\n\t\tassert.Equal(t, 1, wl.CurrentPage)\n\t\tassert.Equal(t, 1, wl.TotalCount)\n\t})\n\n\tt.Run(\"when searching using a tag\", func(t *testing.T) {\n\t\ttagName := \"tagtest\"\n\n\t\t// Add the tag to the first workspace for searching.\n\t\terr := client.Workspaces.AddTags(ctx, wTest1.ID, WorkspaceAddTagsOptions{\n\t\t\tTags: []*Tag{\n\t\t\t\t{\n\t\t\t\t\tName: tagName,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// The result should be successful and only contain the workspace with the\n\t\t// new tag.\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tTags: tagName,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, wl.Items[0].ID, wTest1.ID)\n\t\tassert.Equal(t, 1, wl.CurrentPage)\n\t\tassert.Equal(t, 1, wl.TotalCount)\n\t})\n\n\tt.Run(\"when searching using exclude-tags\", func(t *testing.T) {\n\t\tfor wsID, tag := range map[string]string{wTest1.ID: \"foo\", wTest2.ID: \"bar\", wTest3.ID: \"foo\"} {\n\t\t\terr := client.Workspaces.AddTags(ctx, wsID, WorkspaceAddTagsOptions{\n\t\t\t\tTags: []*Tag{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: tag,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tExcludeTags: \"foo\",\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, wl.Items[0].ID, wTest2.ID)\n\t\tassert.Equal(t, 1, wl.CurrentPage)\n\t\tassert.Equal(t, 1, wl.TotalCount)\n\t})\n\n\tt.Run(\"when searching an unknown workspace\", func(t *testing.T) {\n\t\t// Use a nonexisting workspace name as search attribute. The result\n\t\t// should be successful, but return no results.\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tSearch: \"nonexisting\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, wl.Items)\n\t\tassert.Equal(t, 1, wl.CurrentPage)\n\t\tassert.Equal(t, 0, wl.TotalCount)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\twl, err := client.Workspaces.List(ctx, badIdentifier, nil)\n\t\tassert.Nil(t, wl)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"with organization included\", func(t *testing.T) {\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tInclude: []WSIncludeOpt{WSOrganization},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, wl.Items)\n\t\trequire.NotNil(t, wl.Items[0].Organization)\n\t\tassert.NotEmpty(t, wl.Items[0].Organization.Email)\n\t})\n\n\tt.Run(\"with current-state-version,current-run included\", func(t *testing.T) {\n\t\t_, rCleanup := createRunApply(t, client, wTest1)\n\t\tt.Cleanup(rCleanup)\n\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tInclude: []WSIncludeOpt{WSCurrentStateVer, WSCurrentRun},\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, wl.Items)\n\n\t\tfoundWTest1 := false\n\t\tfor _, ws := range wl.Items {\n\t\t\tif ws.ID != wTest1.ID {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfoundWTest1 = true\n\t\t\trequire.NotNil(t, wl.Items[0].CurrentStateVersion)\n\t\t\tassert.NotEmpty(t, wl.Items[0].CurrentStateVersion.DownloadURL)\n\n\t\t\trequire.NotNil(t, wl.Items[0].CurrentRun)\n\t\t\tassert.NotEmpty(t, wl.Items[0].CurrentRun.Message)\n\t\t}\n\n\t\tassert.True(t, foundWTest1)\n\t})\n\n\tt.Run(\"when searching a known substring\", func(t *testing.T) {\n\t\twildcardSearch := \"*-prod\"\n\t\t// should be successful, and return 1 result\n\t\twTest, wTestCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName: String(\"hashicorp-prod\"),\n\t\t})\n\t\tt.Cleanup(wTestCleanup)\n\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tWildcardName: wildcardSearch,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, wTest.ID)\n\t\tassert.Equal(t, 1, wl.TotalCount)\n\t})\n\n\tt.Run(\"when wildcard match does not exist\", func(t *testing.T) {\n\t\twildcardSearch := \"*-dev\"\n\t\t// should be successful, but return no results\n\t\twTest, wTestCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName: String(\"hashicorp-staging\"),\n\t\t})\n\t\tt.Cleanup(wTestCleanup)\n\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tWildcardName: wildcardSearch,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, wTest.ID)\n\t\tassert.Equal(t, 0, wl.TotalCount)\n\t})\n\n\tt.Run(\"when using a tags filter\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\tw1, wTestCleanup1 := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName: String(randomString(t)),\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key1\", Value: \"value1\"},\n\t\t\t\t{Key: \"key2\", Value: \"value2a\"},\n\t\t\t},\n\t\t})\n\t\tw2, wTestCleanup2 := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName: String(randomString(t)),\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key2\", Value: \"value2b\"},\n\t\t\t\t{Key: \"key3\", Value: \"value3\"},\n\t\t\t},\n\t\t})\n\t\tt.Cleanup(wTestCleanup1)\n\t\tt.Cleanup(wTestCleanup2)\n\n\t\t// List all the workspaces under the given tag\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key1\"},\n\t\t\t},\n\t\t})\n\t\tassert.NoError(t, err)\n\t\tassert.Len(t, wl.Items, 1)\n\t\tassert.Contains(t, wl.Items, w1)\n\n\t\twl2, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key2\"},\n\t\t\t},\n\t\t})\n\t\tassert.NoError(t, err)\n\t\tassert.Len(t, wl2.Items, 2)\n\t\tassert.Contains(t, wl2.Items, w1, w2)\n\n\t\twl3, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key2\", Value: \"value2b\"},\n\t\t\t},\n\t\t})\n\t\tassert.NoError(t, err)\n\t\tassert.Len(t, wl3.Items, 1)\n\t\tassert.Contains(t, wl3.Items, w2)\n\t})\n\n\tt.Run(\"when including effective tag bindings\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\torgTest2, orgTest2Cleanup := createOrganization(t, client)\n\t\tt.Cleanup(orgTest2Cleanup)\n\n\t\tprj, pTestCleanup1 := createProjectWithOptions(t, client, orgTest2, ProjectCreateOptions{\n\t\t\tName: randomStringWithoutSpecialChar(t),\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key3\", Value: \"value3\"},\n\t\t\t},\n\t\t})\n\t\tt.Cleanup(pTestCleanup1)\n\n\t\t_, wTestCleanup1 := createWorkspaceWithOptions(t, client, orgTest2, WorkspaceCreateOptions{\n\t\t\tName:    String(randomString(t)),\n\t\t\tProject: prj,\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"key1\", Value: \"value1\"},\n\t\t\t\t{Key: \"key2\", Value: \"value2a\"},\n\t\t\t},\n\t\t})\n\t\tt.Cleanup(wTestCleanup1)\n\n\t\twl, err := client.Workspaces.List(ctx, orgTest2.Name, &WorkspaceListOptions{\n\t\t\tInclude: []WSIncludeOpt{WSEffectiveTagBindings},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, wl.Items, 1)\n\t\trequire.Len(t, wl.Items[0].EffectiveTagBindings, 3)\n\t\tassert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[0].Key)\n\t\tassert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[0].Value)\n\t\tassert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[1].Key)\n\t\tassert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[1].Value)\n\t\tassert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[2].Key)\n\t\tassert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[2].Value)\n\n\t\tinheritedTagsFound := 0\n\t\tfor _, tag := range wl.Items[0].EffectiveTagBindings {\n\t\t\tif tag.Links[\"inherited-from\"] != nil {\n\t\t\t\tinheritedTagsFound += 1\n\t\t\t}\n\t\t}\n\n\t\tif inheritedTagsFound != 1 {\n\t\t\tt.Fatalf(\"Expected 1 inherited tag, got %d\", inheritedTagsFound)\n\t\t}\n\t})\n\n\tt.Run(\"when using project id filter and project contains workspaces\", func(t *testing.T) {\n\t\t// create a project in the orgTest\n\t\tp, pTestCleanup := createProject(t, client, orgTest)\n\t\tdefer pTestCleanup()\n\t\t// create a workspace with project\n\t\tw, wTestCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName:    String(randomString(t)),\n\t\t\tProject: p,\n\t\t})\n\t\tdefer wTestCleanup()\n\n\t\t// List all the workspaces under the given ProjectID\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tProjectID: p.ID,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, wl.Items, w)\n\t})\n\n\tt.Run(\"when using project id filter but project contains no workspaces\", func(t *testing.T) {\n\t\t// create a project in the orgTest\n\t\tp, pTestCleanup := createProject(t, client, orgTest)\n\t\tdefer pTestCleanup()\n\n\t\t// List all the workspaces under the given ProjectID\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tProjectID: p.ID,\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, wl.Items)\n\t})\n\n\tt.Run(\"when filter workspaces by current run status\", func(t *testing.T) {\n\t\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(wTestCleanup)\n\n\t\trn, appliedCleanup := createRunApply(t, client, wTest)\n\t\tt.Cleanup(appliedCleanup)\n\n\t\twl, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{\n\t\t\tCurrentRunStatus: string(RunApplied),\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, wl.Items)\n\t\trequire.GreaterOrEqual(t, len(wl.Items), 1)\n\n\t\tfound := false\n\t\tfor _, ws := range wl.Items {\n\t\t\tif ws.ID != wTest.ID {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tassert.Equal(t, ws.CurrentRun.ID, rn.ID)\n\t\t\tfound = true\n\t\t}\n\n\t\tassert.True(t, found)\n\t})\n}\n\nfunc TestWorkspacesCreateTableDriven(t *testing.T) {\n\tt.Parallel()\n\tt.Skip(\"Skipping due to persistent failures - see TF-31172\")\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\toc, oaCleanup := createOAuthToken(t, client, orgTest)\n\tt.Cleanup(oaCleanup)\n\n\tworkspaceTableTests := []WorkspaceTableTest{\n\t\t{\n\t\t\tscenario: \"when options include vcs-repo\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tcreateOptions: &WorkspaceCreateOptions{\n\t\t\t\t\tName: String(\"foobar\"),\n\t\t\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\t\t\tIdentifier:   String(\"hashicorp/terraform-random-module\"),\n\t\t\t\t\t\tOAuthTokenID: &oc.ID,\n\t\t\t\t\t\tBranch:       String(\"main\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, w)\n\t\t\t\trequire.NotEmpty(t, w.VCSRepo.Identifier)\n\t\t\t\trequire.NotEmpty(t, w.VCSRepo.OAuthTokenID)\n\t\t\t\trequire.NotEmpty(t, w.VCSRepo.Branch)\n\n\t\t\t\twRead, err := client.Workspaces.ReadByID(ctx, w.ID)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, w.VCSRepo.Identifier, wRead.VCSRepo.Identifier)\n\t\t\t\trequire.Equal(t, w.VCSRepo.OAuthTokenID, wRead.VCSRepo.OAuthTokenID)\n\t\t\t\trequire.Equal(t, w.VCSRepo.Branch, wRead.VCSRepo.Branch)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscenario: \"when options include tags-regex\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tcreateOptions: &WorkspaceCreateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\t\t\tTagsRegex: String(\"barfoo\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetup: func(t *testing.T, options *WorkspaceTableOptions) (w *Workspace, cleanup func()) {\n\t\t\t\t// Remove the below organization creation and use the one from the outer scope once the feature flag is removed\n\t\t\t\torgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\t\t\t\tName:  String(\"tst-\" + randomString(t)[0:20]),\n\t\t\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t\t\t})\n\n\t\t\t\tw, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, *options.createOptions)\n\n\t\t\t\treturn w, func() {\n\t\t\t\t\tt.Cleanup(orgTestCleanup)\n\t\t\t\t\tt.Cleanup(wTestCleanup)\n\t\t\t\t}\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\tassert.Equal(t, *options.createOptions.VCSRepo.TagsRegex, w.VCSRepo.TagsRegex)\n\n\t\t\t\t// Get a refreshed view from the API.\n\t\t\t\trefreshed, readErr := client.Workspaces.Read(ctx, w.Organization.Name, *options.createOptions.Name)\n\t\t\t\trequire.NoError(t, readErr)\n\n\t\t\t\tfor _, item := range []*Workspace{\n\t\t\t\t\tw,\n\t\t\t\t\trefreshed,\n\t\t\t\t} {\n\t\t\t\t\tassert.Equal(t, *options.createOptions.VCSRepo.TagsRegex, item.VCSRepo.TagsRegex)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscenario: \"when options include both non-empty tags-regex and trigger-patterns error is returned\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tcreateOptions: &WorkspaceCreateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tVCSRepo:             &VCSRepoOptions{TagsRegex: String(\"foobar\")},\n\t\t\t\t\tTriggerPatterns:     []string{\"/module-1/**/*\", \"/**/networking/*\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\tassert.Nil(t, w)\n\t\t\t\tassert.EqualError(t, err, ErrUnsupportedBothTagsRegexAndTriggerPatterns.Error())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscenario: \"when options include both non-empty tags-regex and trigger-prefixes error is returned\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tcreateOptions: &WorkspaceCreateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tVCSRepo:             &VCSRepoOptions{TagsRegex: String(\"foobar\")},\n\t\t\t\t\tTriggerPrefixes:     []string{\"/module-1\", \"/module-2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\tassert.Nil(t, w)\n\t\t\t\tassert.EqualError(t, err, ErrUnsupportedBothTagsRegexAndTriggerPrefixes.Error())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscenario: \"when options include both non-empty tags-regex and file-triggers-enabled as true an error is returned\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tcreateOptions: &WorkspaceCreateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(true),\n\t\t\t\t\tVCSRepo:             &VCSRepoOptions{TagsRegex: String(\"foobar\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\tassert.Nil(t, w)\n\t\t\t\tassert.EqualError(t, err, ErrUnsupportedBothTagsRegexAndFileTriggersEnabled.Error())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscenario: \"when options include both non-empty tags-regex and file-triggers-enabled as false an error is not returned\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tcreateOptions: &WorkspaceCreateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tVCSRepo:             &VCSRepoOptions{TagsRegex: String(\"foobar\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetup: func(t *testing.T, options *WorkspaceTableOptions) (w *Workspace, cleanup func()) {\n\t\t\t\tw, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, *options.createOptions)\n\n\t\t\t\treturn w, func() {\n\t\t\t\t\tt.Cleanup(wTestCleanup)\n\t\t\t\t}\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\trequire.NotNil(t, w)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tableTest := range workspaceTableTests {\n\t\tt.Run(tableTest.scenario, func(t *testing.T) {\n\t\t\tvar workspace *Workspace\n\t\t\tvar cleanup func()\n\t\t\tvar err error\n\t\t\tif tableTest.setup != nil {\n\t\t\t\tworkspace, cleanup = tableTest.setup(t, tableTest.options)\n\t\t\t\tdefer cleanup()\n\t\t\t} else {\n\t\t\t\tworkspace, err = client.Workspaces.Create(ctx, orgTest.Name, *tableTest.options.createOptions)\n\t\t\t}\n\t\t\ttableTest.assertion(t, workspace, tableTest.options, err)\n\t\t})\n\t}\n}\n\nfunc TestWorkspacesCreateTableDrivenWithGithubApp(t *testing.T) {\n\tt.Parallel()\n\tgHAInstallationID := os.Getenv(\"GITHUB_APP_INSTALLATION_ID\")\n\n\tif gHAInstallationID == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_APP_INSTALLATION_ID before running this test!\")\n\t}\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest1, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tworkspaceTableTests := []WorkspaceTableTest{\n\t\t{\n\t\t\tscenario: \"when options include tags-regex\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tcreateOptions: &WorkspaceCreateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\t\t\tTagsRegex: String(\"barfoo\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetup: func(t *testing.T, options *WorkspaceTableOptions) (w *Workspace, cleanup func()) {\n\t\t\t\t// Remove the below organization creation and use the one from the outer scope once the feature flag is removed\n\t\t\t\torgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\t\t\t\tName:  String(\"tst-\" + randomString(t)[0:20]),\n\t\t\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t\t\t})\n\n\t\t\t\tw, wTestCleanup := createWorkspaceWithGithubApp(t, client, orgTest, *options.createOptions)\n\n\t\t\t\treturn w, func() {\n\t\t\t\t\tt.Cleanup(orgTestCleanup)\n\t\t\t\t\tt.Cleanup(wTestCleanup)\n\t\t\t\t}\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\tassert.Equal(t, *options.createOptions.VCSRepo.TagsRegex, w.VCSRepo.TagsRegex)\n\n\t\t\t\t// Get a refreshed view from the API.\n\t\t\t\trefreshed, readErr := client.Workspaces.Read(ctx, w.Organization.Name, *options.createOptions.Name)\n\t\t\t\trequire.NoError(t, readErr)\n\n\t\t\t\tfor _, item := range []*Workspace{\n\t\t\t\t\tw,\n\t\t\t\t\trefreshed,\n\t\t\t\t} {\n\t\t\t\t\tassert.Equal(t, *options.createOptions.VCSRepo.TagsRegex, item.VCSRepo.TagsRegex)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tableTest := range workspaceTableTests {\n\t\tt.Run(tableTest.scenario, func(t *testing.T) {\n\t\t\tvar workspace *Workspace\n\t\t\tvar cleanup func()\n\t\t\tvar err error\n\t\t\tif tableTest.setup != nil {\n\t\t\t\tworkspace, cleanup = tableTest.setup(t, tableTest.options)\n\t\t\t\tdefer cleanup()\n\t\t\t} else {\n\t\t\t\tworkspace, err = client.Workspaces.Create(ctx, orgTest1.Name, *tableTest.options.createOptions)\n\t\t\t}\n\t\t\ttableTest.assertion(t, workspace, tableTest.options, err)\n\t\t})\n\t}\n}\n\nfunc TestWorkspacesCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tt.Run(\"with valid project option\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName:                       String(fmt.Sprintf(\"foo-%s\", randomString(t))),\n\t\t\tAllowDestroyPlan:           Bool(false),\n\t\t\tAutoApply:                  Bool(true),\n\t\t\tDescription:                String(\"qux\"),\n\t\t\tAssessmentsEnabled:         Bool(false),\n\t\t\tFileTriggersEnabled:        Bool(true),\n\t\t\tOperations:                 Bool(true),\n\t\t\tQueueAllRuns:               Bool(true),\n\t\t\tSpeculativeEnabled:         Bool(true),\n\t\t\tSourceName:                 String(\"my-app\"),\n\t\t\tSourceURL:                  String(\"http://my-app-hostname.io\"),\n\t\t\tStructuredRunOutputEnabled: Bool(true),\n\t\t\tTerraformVersion:           String(\"0.11.0\"),\n\t\t\tTriggerPrefixes:            []string{\"/modules\", \"/shared\"},\n\t\t\tWorkingDirectory:           String(\"bar/\"),\n\t\t\tProject:                    orgTest.DefaultProject,\n\t\t\tTags: []*Tag{\n\t\t\t\t{\n\t\t\t\t\tName: \"tag1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"tag2\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Workspace{\n\t\t\tw,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, options.Project.ID, item.Project.ID)\n\t\t}\n\t})\n\n\tt.Run(\"with valid auto-apply-run-trigger option\", func(t *testing.T) {\n\t\tskipIfEnterprise(t)\n\t\t// FEATURE FLAG: auto-apply-run-trigger\n\t\t// Once un-flagged, delete this test and add an AutoApplyRunTrigger field\n\t\t// to the basic \"with valid options\" test below.\n\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName:                String(fmt.Sprintf(\"foo-%s\", randomString(t))),\n\t\t\tAutoApplyRunTrigger: Bool(true),\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Workspace{\n\t\t\tw,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, *options.AutoApplyRunTrigger, item.AutoApplyRunTrigger)\n\t\t}\n\t})\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName:                       String(fmt.Sprintf(\"foo-%s\", randomString(t))),\n\t\t\tAllowDestroyPlan:           Bool(true),\n\t\t\tAutoApply:                  Bool(true),\n\t\t\tAutoDestroyAt:              NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),\n\t\t\tDescription:                String(\"qux\"),\n\t\t\tAssessmentsEnabled:         Bool(false),\n\t\t\tFileTriggersEnabled:        Bool(true),\n\t\t\tOperations:                 Bool(true),\n\t\t\tQueueAllRuns:               Bool(true),\n\t\t\tSpeculativeEnabled:         Bool(true),\n\t\t\tSourceName:                 String(\"my-app\"),\n\t\t\tSourceURL:                  String(\"http://my-app-hostname.io\"),\n\t\t\tStructuredRunOutputEnabled: Bool(true),\n\t\t\tTerraformVersion:           String(\"0.11.0\"),\n\t\t\tTriggerPrefixes:            []string{\"/modules\", \"/shared\"},\n\t\t\tWorkingDirectory:           String(\"bar/\"),\n\t\t\tTags: []*Tag{\n\t\t\t\t{\n\t\t\t\t\tName: \"tag1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"tag2\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Workspace{\n\t\t\tw,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.NotEmpty(t, item.ID)\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, *options.Description, item.Description)\n\t\t\tassert.Equal(t, *options.AllowDestroyPlan, item.AllowDestroyPlan)\n\t\t\tassert.Equal(t, *options.AutoApply, item.AutoApply)\n\t\t\tassert.Equal(t, options.AutoDestroyAt, item.AutoDestroyAt)\n\t\t\tassert.Equal(t, *options.AssessmentsEnabled, item.AssessmentsEnabled)\n\t\t\tassert.Equal(t, *options.FileTriggersEnabled, item.FileTriggersEnabled)\n\t\t\tassert.Equal(t, *options.Operations, item.Operations)\n\t\t\tassert.Equal(t, *options.QueueAllRuns, item.QueueAllRuns)\n\t\t\tassert.Equal(t, *options.SpeculativeEnabled, item.SpeculativeEnabled)\n\t\t\tassert.Equal(t, *options.SourceName, item.SourceName)\n\t\t\tassert.Equal(t, *options.SourceURL, item.SourceURL)\n\t\t\tassert.Equal(t, *options.StructuredRunOutputEnabled, item.StructuredRunOutputEnabled)\n\t\t\tassert.Equal(t, options.Tags[0].Name, item.TagNames[0])\n\t\t\tassert.Equal(t, options.Tags[1].Name, item.TagNames[1])\n\t\t\tassert.Equal(t, *options.TerraformVersion, item.TerraformVersion)\n\t\t\tassert.Equal(t, options.TriggerPrefixes, item.TriggerPrefixes)\n\t\t\tassert.Equal(t, *options.WorkingDirectory, item.WorkingDirectory)\n\t\t}\n\t})\n\n\tt.Run(\"when options is missing name\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Create(ctx, \"foo\", WorkspaceCreateOptions{})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrRequiredName.Error())\n\t})\n\n\tt.Run(\"when options has an invalid name\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Create(ctx, \"foo\", WorkspaceCreateOptions{\n\t\t\tName: String(badIdentifier),\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidName.Error())\n\t})\n\n\tt.Run(\"when options has an invalid organization\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Create(ctx, badIdentifier, WorkspaceCreateOptions{\n\t\t\tName: String(fmt.Sprintf(\"foo-%s\", randomString(t))),\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when options includes both an operations value and an enforcement mode value\", func(t *testing.T) {\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName:          String(fmt.Sprintf(\"foo-%s\", randomString(t))),\n\t\t\tExecutionMode: String(\"remote\"),\n\t\t\tOperations:    Bool(true),\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, orgTest.Name, options)\n\t\tassert.Nil(t, w)\n\t\tassert.Equal(t, err, ErrUnsupportedOperations)\n\t})\n\n\tt.Run(\"when an agent pool ID is specified without 'agent' execution mode\", func(t *testing.T) {\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName:        String(fmt.Sprintf(\"foo-%s\", randomString(t))),\n\t\t\tAgentPoolID: String(\"apool-xxxxx\"),\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, orgTest.Name, options)\n\t\tassert.Nil(t, w)\n\t\tassert.Equal(t, err, ErrRequiredAgentMode)\n\t})\n\n\tt.Run(\"when 'agent' execution mode is specified without an an agent pool ID\", func(t *testing.T) {\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName:          String(fmt.Sprintf(\"foo-%s\", randomString(t))),\n\t\t\tExecutionMode: String(\"agent\"),\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, orgTest.Name, options)\n\t\tassert.Nil(t, w)\n\t\tassert.Equal(t, err, ErrRequiredAgentPoolID)\n\t})\n\n\tt.Run(\"when no execution mode is specified, in an organization with local as default execution mode\", func(t *testing.T) {\n\t\t// Remove the below organization creation and use the one from the outer scope once the feature flag is removed\n\t\torgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\t\tName:                 String(\"tst-\" + randomString(t)[0:20] + \"-ff-on\"),\n\t\t\tEmail:                String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t\tDefaultExecutionMode: String(\"local\"),\n\t\t})\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName: String(fmt.Sprintf(\"foo-%s\", randomString(t))),\n\t\t\tSettingOverwrites: &WorkspaceSettingOverwritesOptions{\n\t\t\t\tExecutionMode: Bool(false),\n\t\t\t},\n\t\t}\n\n\t\t_, err := client.Workspaces.Create(ctx, orgTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, \"local\", refreshed.ExecutionMode)\n\t})\n\n\tt.Run(\"when an error is returned from the API\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Create(ctx, \"bar\", WorkspaceCreateOptions{\n\t\t\tName:             String(fmt.Sprintf(\"bar-%s\", randomString(t))),\n\t\t\tTerraformVersion: String(\"nonexisting\"),\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"when options include trigger-patterns (behind a feature flag)\", func(t *testing.T) {\n\t\t// Remove the below organization creation and use the one from the outer scope once the feature flag is removed\n\t\torgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\t\tName:  String(\"tst-\" + randomString(t)[0:20] + \"-ff-on\"),\n\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t})\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName:                String(\"foobar\"),\n\t\t\tFileTriggersEnabled: Bool(true),\n\t\t\tTriggerPatterns:     []string{\"/module-1/**/*\", \"/**/networking/*\"},\n\t\t}\n\t\tw, err := client.Workspaces.Create(ctx, orgTest.Name, options)\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, options.TriggerPatterns, w.TriggerPatterns)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Workspace{\n\t\t\tw,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.Equal(t, options.TriggerPatterns, item.TriggerPatterns)\n\t\t}\n\t})\n\n\tt.Run(\"when options include both non-empty trigger-patterns and trigger-paths error is returned\", func(t *testing.T) {\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName:                String(fmt.Sprintf(\"foobar-%s\", randomString(t))),\n\t\t\tFileTriggersEnabled: Bool(true),\n\t\t\tTriggerPrefixes:     []string{\"/module-1\", \"/module-2\"},\n\t\t\tTriggerPatterns:     []string{\"/module-1/**/*\", \"/**/networking/*\"},\n\t\t}\n\t\tw, err := client.Workspaces.Create(ctx, orgTest.Name, options)\n\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrUnsupportedBothTriggerPatternsAndPrefixes.Error())\n\t})\n\n\tt.Run(\"when options include trigger-patterns populated and empty trigger-paths workspace is created\", func(t *testing.T) {\n\t\t// Remove the below organization creation and use the one from the outer scope once the feature flag is removed\n\t\torgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\t\tName:  String(\"tst-\" + randomString(t)[0:20] + \"-ff-on\"),\n\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t})\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName:                String(fmt.Sprintf(\"foobar-%s\", randomString(t))),\n\t\t\tFileTriggersEnabled: Bool(true),\n\t\t\tTriggerPrefixes:     []string{},\n\t\t\tTriggerPatterns:     []string{\"/module-1/**/*\", \"/**/networking/*\"},\n\t\t}\n\t\tw, err := client.Workspaces.Create(ctx, orgTest.Name, options)\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, options.TriggerPatterns, w.TriggerPatterns)\n\t})\n\n\tt.Run(\"when organization has a default execution mode\", func(t *testing.T) {\n\t\tdefaultExecutionOrgTest, defaultExecutionOrgTestCleanup := createOrganizationWithDefaultAgentPool(t, client)\n\t\tt.Cleanup(defaultExecutionOrgTestCleanup)\n\n\t\tt.Run(\"with setting overwrites set to false, workspace inherits the default execution mode\", func(t *testing.T) {\n\t\t\toptions := WorkspaceCreateOptions{\n\t\t\t\tName: String(fmt.Sprintf(\"tst-agent-cody-banks-%s\", randomString(t))),\n\t\t\t\tSettingOverwrites: &WorkspaceSettingOverwritesOptions{\n\t\t\t\t\tExecutionMode: Bool(false),\n\t\t\t\t\tAgentPool:     Bool(false),\n\t\t\t\t},\n\t\t\t}\n\t\t\tw, err := client.Workspaces.Create(ctx, defaultExecutionOrgTest.Name, options)\n\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, \"agent\", w.ExecutionMode)\n\t\t})\n\n\t\tt.Run(\"with setting overwrites set to true, workspace ignores the default execution mode\", func(t *testing.T) {\n\t\t\toptions := WorkspaceCreateOptions{\n\t\t\t\tName:          String(fmt.Sprintf(\"tst-agent-tony-tanks-%s\", randomString(t))),\n\t\t\t\tExecutionMode: String(\"local\"),\n\t\t\t\tSettingOverwrites: &WorkspaceSettingOverwritesOptions{\n\t\t\t\t\tExecutionMode: Bool(true),\n\t\t\t\t\tAgentPool:     Bool(true),\n\t\t\t\t},\n\t\t\t}\n\t\t\tw, err := client.Workspaces.Create(ctx, defaultExecutionOrgTest.Name, options)\n\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, \"local\", w.ExecutionMode)\n\t\t})\n\n\t\tt.Run(\"when explicitly setting execution mode, workspace ignores the default execution mode\", func(t *testing.T) {\n\t\t\toptions := WorkspaceCreateOptions{\n\t\t\t\tName:          String(fmt.Sprintf(\"tst-remotely-interesting-workspace-%s\", randomString(t))),\n\t\t\t\tExecutionMode: String(\"remote\"),\n\t\t\t}\n\t\t\tw, err := client.Workspaces.Create(ctx, defaultExecutionOrgTest.Name, options)\n\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, \"remote\", w.ExecutionMode)\n\t\t})\n\t})\n\n\tt.Run(\"create workspace with hyok enabled set to false\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name that has HYOK permissions\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\t\tworkspaceCreateOptions := WorkspaceCreateOptions{\n\t\t\tName:        String(\"go-tfe-test-hyok-enabled-false\"),\n\t\t\tHYOKEnabled: Bool(false),\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, hyokOrganizationName, workspaceCreateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, w.HYOKEnabled)\n\t\tassert.False(t, *w.HYOKEnabled)\n\n\t\terr = client.Workspaces.Delete(ctx, hyokOrganizationName, *workspaceCreateOptions.Name)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"create workspace with hyok enabled set to true\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name that has HYOK permissions\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\tworkspaceCreateOptions := WorkspaceCreateOptions{\n\t\t\tName:        String(\"go-tfe-test-hyok-enabled-true\"),\n\t\t\tHYOKEnabled: Bool(true),\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, hyokOrganizationName, workspaceCreateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, w.HYOKEnabled)\n\t\tassert.True(t, *w.HYOKEnabled)\n\n\t\terr = client.Workspaces.Delete(ctx, hyokOrganizationName, *workspaceCreateOptions.Name)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestWorkspacesRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tt.Run(\"when the workspace exists\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, wTest, w)\n\n\t\tassert.True(t, w.Permissions.CanDestroy)\n\t\tassert.NotEmpty(t, w.Actions)\n\t\tassert.Equal(t, orgTest.Name, w.Organization.Name)\n\t\tassert.NotEmpty(t, w.CreatedAt)\n\t\tassert.NotEmpty(t, wTest.SettingOverwrites)\n\t})\n\n\tt.Run(\"links are properly decoded\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\tassert.NotEmpty(t, w.Links[\"self-html\"])\n\t\tassert.Contains(t, w.Links[\"self-html\"], fmt.Sprintf(\"/app/%s/workspaces/%s\", orgTest.Name, wTest.Name))\n\n\t\tassert.NotEmpty(t, w.Links[\"self\"])\n\t\tassert.Contains(t, w.Links[\"self\"], fmt.Sprintf(\"/api/v2/organizations/%s/workspaces/%s\", orgTest.Name, wTest.Name))\n\t})\n\n\tt.Run(\"when the workspace does not exist\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Read(ctx, orgTest.Name, \"nonexisting\")\n\t\tassert.Nil(t, w)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"when the organization does not exist\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Read(ctx, \"nonexisting\", \"nonexisting\")\n\t\tassert.Nil(t, w)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"without a valid organization\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Read(ctx, badIdentifier, wTest.Name)\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"without a valid workspace\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Read(ctx, orgTest.Name, badIdentifier)\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceValue.Error())\n\t})\n\n\tt.Run(\"when workspace is inheriting the default execution mode\", func(t *testing.T) {\n\t\tdefaultExecutionOrgTest, defaultExecutionOrgTestCleanup := createOrganizationWithDefaultAgentPool(t, client)\n\t\tt.Cleanup(defaultExecutionOrgTestCleanup)\n\n\t\toptions := WorkspaceCreateOptions{\n\t\t\tName: String(fmt.Sprintf(\"tst-agent-cody-banks-%s\", randomString(t))),\n\t\t\tSettingOverwrites: &WorkspaceSettingOverwritesOptions{\n\t\t\t\tExecutionMode: Bool(false),\n\t\t\t\tAgentPool:     Bool(false),\n\t\t\t},\n\t\t}\n\n\t\twDefaultTest, wDefaultTestCleanup := createWorkspaceWithOptions(t, client, defaultExecutionOrgTest, options)\n\t\tt.Cleanup(wDefaultTestCleanup)\n\n\t\tt.Run(\"and workspace execution mode is default\", func(t *testing.T) {\n\t\t\tw, err := client.Workspaces.Read(ctx, defaultExecutionOrgTest.Name, wDefaultTest.Name)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.NotEmpty(t, w)\n\n\t\t\tassert.Equal(t, defaultExecutionOrgTest.DefaultExecutionMode, w.ExecutionMode)\n\t\t\tassert.NotEmpty(t, w.SettingOverwrites)\n\t\t\tassert.Equal(t, false, *w.SettingOverwrites.ExecutionMode)\n\t\t\tassert.Equal(t, false, *w.SettingOverwrites.ExecutionMode)\n\t\t})\n\t})\n\n\tt.Run(\"read hyok enabled of a workspace\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name that has HYOK permissions\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\t// replace the environment variable with a valid workspace name that has hyok enabled set to true or false\n\t\thyokWorkspaceName := os.Getenv(\"HYOK_WORKSPACE_NAME\")\n\t\tif hyokWorkspaceName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_WORKSPACE_NAME before running this test!\")\n\t\t}\n\n\t\tw, err := client.Workspaces.Read(ctx, hyokOrganizationName, hyokWorkspaceName)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, w.HYOKEnabled)\n\t})\n\n\tt.Run(\"read hyok encrypted data key of a workspace\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name that has HYOK permissions\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\t// replace the environment variable with a valid workspace name that has hyok encrypted data key\n\t\thyokWorkspaceName := os.Getenv(\"HYOK_WORKSPACE_NAME\")\n\t\tif hyokWorkspaceName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_WORKSPACE_NAME before running this test!\")\n\t\t}\n\n\t\tw, err := client.Workspaces.Read(ctx, hyokOrganizationName, hyokWorkspaceName)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, w.HYOKEncryptedDataKey)\n\t})\n}\n\nfunc TestWorkspacesReadSource(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tw, err := client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, WorkspaceSourceAPI, w.Source)\n}\n\nfunc TestWorkspacesReadWithOptions(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tsvTest, svTestCleanup := createStateVersion(t, client, 0, wTest)\n\tt.Cleanup(svTestCleanup)\n\n\t// give HCP Terraform some time to process the statefile and extract the outputs.\n\twaitForSVOutputs(t, client, svTest.ID)\n\n\tt.Run(\"when options to include resource\", func(t *testing.T) {\n\t\topts := &WorkspaceReadOptions{\n\t\t\tInclude: []WSIncludeOpt{WSOutputs},\n\t\t}\n\t\tw, err := client.Workspaces.ReadWithOptions(ctx, orgTest.Name, wTest.Name, opts)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, wTest.ID, w.ID)\n\t\tassert.NotEmpty(t, w.Outputs)\n\n\t\tsvOutputs, err := client.StateVersions.ListOutputs(ctx, svTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, w.Outputs, len(svOutputs.Items))\n\n\t\twsOutputsSensitive := map[string]bool{}\n\t\twsOutputsTypes := map[string]string{}\n\t\tfor _, op := range w.Outputs {\n\t\t\twsOutputsSensitive[op.Name] = op.Sensitive\n\t\t\twsOutputsTypes[op.Name] = op.Type\n\t\t}\n\t\tfor _, svop := range svOutputs.Items {\n\t\t\tvalSensitive, ok := wsOutputsSensitive[svop.Name]\n\t\t\tassert.True(t, ok)\n\t\t\tassert.Equal(t, svop.Sensitive, valSensitive)\n\n\t\t\tvalType, ok := wsOutputsTypes[svop.Name]\n\t\t\tassert.True(t, ok)\n\t\t\tassert.Equal(t, svop.Type, valType)\n\t\t}\n\t})\n}\n\nfunc TestWorkspacesReadWithHistory_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\t_, rCleanup := createRunApply(t, client, wTest)\n\tt.Cleanup(rCleanup)\n\n\t_, err := retry(func() (interface{}, error) {\n\t\tw, err := client.Workspaces.Read(context.Background(), orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\tif w.RunsCount != 1 {\n\t\t\treturn nil, fmt.Errorf(\"expected %d runs but found %d\", 1, w.RunsCount)\n\t\t}\n\n\t\tif w.ResourceCount != 1 {\n\t\t\treturn nil, fmt.Errorf(\"expected %d resources but found %d\", 1, w.ResourceCount)\n\t\t}\n\n\t\treturn w, nil\n\t})\n\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\n// If you've set your own GITHUB_POLICY_SET_IDENTIFIER, make sure the readme\n// starts with the string: This is a simple test\n// Otherwise the test will not pass\nfunc TestWorkspacesReadReadme_RunDependent(t *testing.T) {\n\tt.Skip(\"Skipping due to persistent failures - see TF-31172\")\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, WorkspaceCreateOptions{})\n\tt.Cleanup(wTestCleanup)\n\n\t_, rCleanup := createRunApply(t, client, wTest)\n\tt.Cleanup(rCleanup)\n\n\tt.Run(\"when the readme exists\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Readme(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, w)\n\n\t\treadme, err := io.ReadAll(w)\n\t\trequire.NoError(t, err)\n\t\trequire.True(\n\t\t\tt,\n\t\t\tstrings.HasPrefix(string(readme), `This is a simple test`),\n\t\t\t\"got: %s\", readme,\n\t\t)\n\t})\n\n\tt.Run(\"when the readme does not exist\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Readme(ctx, \"nonexisting\")\n\t\tassert.Nil(t, w)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Readme(ctx, badIdentifier)\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspacesReadByID(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tt.Run(\"when the workspace exists\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.ReadByID(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, wTest, w)\n\n\t\tassert.True(t, w.Permissions.CanDestroy)\n\t\tassert.Equal(t, orgTest.Name, w.Organization.Name)\n\t\tassert.NotEmpty(t, w.CreatedAt)\n\t\tassert.NotEmpty(t, w.Actions)\n\t})\n\n\tt.Run(\"when the workspace does not exist\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.ReadByID(ctx, \"nonexisting\")\n\t\tassert.Nil(t, w)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.ReadByID(ctx, badIdentifier)\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspacesAddTagBindings(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wCleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wCleanup)\n\n\tt.Run(\"when adding tag bindings to a workspace\", func(t *testing.T) {\n\t\ttagBindings := []*TagBinding{\n\t\t\t{Key: \"foo\", Value: \"bar\"},\n\t\t\t{Key: \"baz\", Value: \"qux\"},\n\t\t}\n\n\t\tbindings, err := client.Workspaces.AddTagBindings(ctx, wTest.ID, WorkspaceAddTagBindingsOptions{\n\t\t\tTagBindings: tagBindings,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, bindings, 2)\n\t\tassert.Equal(t, tagBindings[0].Key, bindings[0].Key)\n\t\tassert.Equal(t, tagBindings[0].Value, bindings[0].Value)\n\t\tassert.Equal(t, tagBindings[1].Key, bindings[1].Key)\n\t\tassert.Equal(t, tagBindings[1].Value, bindings[1].Value)\n\t})\n\n\tt.Run(\"when adding 26 tags\", func(t *testing.T) {\n\t\ttagBindings := []*TagBinding{\n\t\t\t{Key: \"alpha\"},\n\t\t\t{Key: \"bravo\"},\n\t\t\t{Key: \"charlie\"},\n\t\t\t{Key: \"delta\"},\n\t\t\t{Key: \"echo\"},\n\t\t\t{Key: \"foxtrot\"},\n\t\t\t{Key: \"golf\"},\n\t\t\t{Key: \"hotel\"},\n\t\t\t{Key: \"india\"},\n\t\t\t{Key: \"juliet\"},\n\t\t\t{Key: \"kilo\"},\n\t\t\t{Key: \"lima\"},\n\t\t\t{Key: \"mike\"},\n\t\t\t{Key: \"november\"},\n\t\t\t{Key: \"oscar\"},\n\t\t\t{Key: \"papa\"},\n\t\t\t{Key: \"quebec\"},\n\t\t\t{Key: \"romeo\"},\n\t\t\t{Key: \"sierra\"},\n\t\t\t{Key: \"tango\"},\n\t\t\t{Key: \"uniform\"},\n\t\t\t{Key: \"victor\"},\n\t\t\t{Key: \"whiskey\"},\n\t\t\t{Key: \"xray\"},\n\t\t\t{Key: \"yankee\"},\n\t\t\t{Key: \"zulu\"},\n\t\t}\n\n\t\t_, err := client.Workspaces.AddTagBindings(ctx, wTest.ID, WorkspaceAddTagBindingsOptions{\n\t\t\tTagBindings: tagBindings,\n\t\t})\n\t\trequire.Error(t, err, \"cannot exceed 10 bindings per resource\")\n\t})\n}\n\nfunc TestWorkspaces_DeleteAllTagBindings(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wCleanup := createWorkspace(t, client, nil)\n\tt.Cleanup(wCleanup)\n\n\ttagBindings := []*TagBinding{\n\t\t{Key: \"foo\", Value: \"bar\"},\n\t\t{Key: \"baz\", Value: \"qux\"},\n\t}\n\n\t_, err := client.Workspaces.AddTagBindings(ctx, wTest.ID, WorkspaceAddTagBindingsOptions{\n\t\tTagBindings: tagBindings,\n\t})\n\trequire.NoError(t, err)\n\n\terr = client.Workspaces.DeleteAllTagBindings(ctx, wTest.ID)\n\trequire.NoError(t, err)\n\n\tbindings, err := client.Workspaces.ListTagBindings(ctx, wTest.ID)\n\trequire.NoError(t, err)\n\trequire.Empty(t, bindings)\n}\n\nfunc TestWorkspacesUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twTest, wCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wCleanup)\n\n\tt.Run(\"when updating a subset of values\", func(t *testing.T) {\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tName:               String(wTest.Name),\n\t\t\tAllowDestroyPlan:   Bool(false),\n\t\t\tAutoApply:          Bool(true),\n\t\t\tOperations:         Bool(true),\n\t\t\tQueueAllRuns:       Bool(true),\n\t\t\tAssessmentsEnabled: Bool(true),\n\t\t\tTerraformVersion:   String(\"0.15.4\"),\n\t\t}\n\n\t\twAfter, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, wTest.Name, wAfter.Name)\n\t\tassert.NotEqual(t, wTest.AllowDestroyPlan, wAfter.AllowDestroyPlan)\n\t\tassert.NotEqual(t, wTest.AutoApply, wAfter.AutoApply)\n\t\tassert.NotEqual(t, wTest.QueueAllRuns, wAfter.QueueAllRuns)\n\t\tassert.NotEqual(t, wTest.AssessmentsEnabled, wAfter.AssessmentsEnabled)\n\t\tassert.NotEqual(t, wTest.TerraformVersion, wAfter.TerraformVersion)\n\t\tassert.Equal(t, wTest.WorkingDirectory, wAfter.WorkingDirectory)\n\t})\n\n\tt.Run(\"when updating auto-apply-run-trigger\", func(t *testing.T) {\n\t\tskipIfEnterprise(t)\n\t\t// Feature flag: auto-apply-run-trigger. Once flag is removed, delete\n\t\t// this test and add the attribute to one generic update test.\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tAutoApplyRunTrigger: Bool(true),\n\t\t}\n\n\t\twAfter, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, wTest.Name, wAfter.Name)\n\t\tassert.NotEqual(t, wTest.AutoApplyRunTrigger, wAfter.AutoApplyRunTrigger)\n\t})\n\n\tt.Run(\"when updating project\", func(t *testing.T) {\n\t\tskipUnlessBeta(t)\n\n\t\tkBefore, kTestCleanup := createProject(t, client, orgTest)\n\t\tdefer kTestCleanup()\n\n\t\twBefore, wBeforeCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName:    String(randomString(t)),\n\t\t\tProject: kBefore,\n\t\t})\n\t\tdefer wBeforeCleanup()\n\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tName:               String(wBefore.Name),\n\t\t\tAllowDestroyPlan:   Bool(false),\n\t\t\tAutoApply:          Bool(true),\n\t\t\tOperations:         Bool(true),\n\t\t\tQueueAllRuns:       Bool(true),\n\t\t\tAssessmentsEnabled: Bool(true),\n\t\t\tTerraformVersion:   String(\"0.15.4\"),\n\t\t\tProject:            orgTest.DefaultProject,\n\t\t}\n\n\t\twAfter, err := client.Workspaces.Update(ctx, orgTest.Name, wBefore.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\trequire.NotNil(t, wAfter.Project)\n\t\trequire.NotNil(t, orgTest.DefaultProject)\n\n\t\tassert.Equal(t, wBefore.Name, wAfter.Name)\n\t\tassert.Equal(t, wAfter.Project.ID, orgTest.DefaultProject.ID)\n\t})\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tName:                       String(randomString(t)),\n\t\t\tAllowDestroyPlan:           Bool(true),\n\t\t\tAutoApply:                  Bool(false),\n\t\t\tAutoDestroyAt:              NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),\n\t\t\tFileTriggersEnabled:        Bool(true),\n\t\t\tOperations:                 Bool(false),\n\t\t\tQueueAllRuns:               Bool(false),\n\t\t\tSpeculativeEnabled:         Bool(true),\n\t\t\tDescription:                String(\"updated description\"),\n\t\t\tStructuredRunOutputEnabled: Bool(true),\n\t\t\tTerraformVersion:           String(\"0.11.1\"),\n\t\t\tTriggerPrefixes:            []string{\"/modules\", \"/shared\"},\n\t\t\tWorkingDirectory:           String(\"baz/\"),\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{Key: \"foo\", Value: \"bar\"},\n\t\t\t},\n\t\t}\n\n\t\tw, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view of the workspace from the API\n\t\trefreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Workspace{\n\t\t\tw,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, *options.AllowDestroyPlan, item.AllowDestroyPlan)\n\t\t\tassert.Equal(t, *options.AutoApply, item.AutoApply)\n\t\t\tassert.Equal(t, options.AutoDestroyAt, item.AutoDestroyAt)\n\t\t\tassert.Equal(t, *options.FileTriggersEnabled, item.FileTriggersEnabled)\n\t\t\tassert.Equal(t, *options.Description, item.Description)\n\t\t\tassert.Equal(t, *options.Operations, item.Operations)\n\t\t\tassert.Equal(t, *options.QueueAllRuns, item.QueueAllRuns)\n\t\t\tassert.Equal(t, *options.SpeculativeEnabled, item.SpeculativeEnabled)\n\t\t\tassert.Equal(t, *options.StructuredRunOutputEnabled, item.StructuredRunOutputEnabled)\n\t\t\tassert.Equal(t, *options.TerraformVersion, item.TerraformVersion)\n\t\t\tassert.Equal(t, options.TriggerPrefixes, item.TriggerPrefixes)\n\t\t\tassert.Equal(t, *options.WorkingDirectory, item.WorkingDirectory)\n\t\t}\n\n\t\tif betaFeaturesEnabled() {\n\t\t\tbindings, err := client.Workspaces.ListTagBindings(ctx, wTest.ID)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.Len(t, bindings, 1)\n\t\t\tassert.Equal(t, \"foo\", bindings[0].Key)\n\t\t\tassert.Equal(t, \"bar\", bindings[0].Value)\n\n\t\t\teffectiveBindings, err := client.Workspaces.ListEffectiveTagBindings(ctx, wTest.ID)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.Len(t, effectiveBindings, 1)\n\t\t\tassert.Equal(t, \"foo\", effectiveBindings[0].Key)\n\t\t\tassert.Equal(t, \"bar\", effectiveBindings[0].Value)\n\t\t}\n\t})\n\n\tt.Run(\"when options includes both an operations value and an enforcement mode value\", func(t *testing.T) {\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tExecutionMode: String(\"remote\"),\n\t\t\tOperations:    Bool(true),\n\t\t}\n\n\t\twAfter, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\t\tassert.Nil(t, wAfter)\n\t\tassert.Equal(t, err, ErrUnsupportedOperations)\n\t})\n\n\tt.Run(\"when 'agent' execution mode is specified without an agent pool ID\", func(t *testing.T) {\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tExecutionMode: String(\"agent\"),\n\t\t}\n\n\t\twAfter, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\t\tassert.Nil(t, wAfter)\n\t\tassert.Equal(t, err, ErrRequiredAgentPoolID)\n\t})\n\n\tt.Run(\"when an error is returned from the api\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{\n\t\t\tTerraformVersion: String(\"nonexisting\"),\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"when options has an invalid name\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Update(ctx, orgTest.Name, badIdentifier, WorkspaceUpdateOptions{})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceValue.Error())\n\t})\n\n\tt.Run(\"when options has an invalid organization\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Update(ctx, badIdentifier, wTest.Name, WorkspaceUpdateOptions{})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when options include trigger-patterns (behind a feature flag)\", func(t *testing.T) {\n\t\t// Remove the below organization and workspace creation and use the one from the outer scope once the feature flag is removed\n\t\torgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\t\tName:  String(\"tst-\" + randomString(t)[0:20] + \"-ff-on\"),\n\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t})\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\twTest, wCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName:            String(randomString(t)),\n\t\t\tTriggerPrefixes: []string{\"/prefix-1/\", \"/prefix-2/\"},\n\t\t})\n\t\tt.Cleanup(wCleanup)\n\t\tassert.Equal(t, wTest.TriggerPrefixes, []string{\"/prefix-1/\", \"/prefix-2/\"}) // Sanity test\n\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tName:                String(\"foobar\"),\n\t\t\tFileTriggersEnabled: Bool(true),\n\t\t\tTriggerPatterns:     []string{\"/module-1/**/*\", \"/**/networking/*\"},\n\t\t}\n\t\tw, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Workspace{\n\t\t\tw,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.Empty(t, options.TriggerPrefixes)\n\t\t\tassert.Equal(t, options.TriggerPatterns, item.TriggerPatterns)\n\t\t}\n\t})\n\n\tt.Run(\"when options include both trigger-patterns and trigger-paths error is returned\", func(t *testing.T) {\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tName:                String(\"foobar\"),\n\t\t\tFileTriggersEnabled: Bool(true),\n\t\t\tTriggerPrefixes:     []string{\"/module-1\", \"/module-2\"},\n\t\t\tTriggerPatterns:     []string{\"/module-1/**/*\", \"/**/networking/*\"},\n\t\t}\n\t\tw, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrUnsupportedBothTriggerPatternsAndPrefixes.Error())\n\t})\n\n\tt.Run(\"when options include trigger-patterns populated and empty trigger-paths workspace is updated\", func(t *testing.T) {\n\t\t// Remove the below organization creation and use the one from the outer scope once the feature flag is removed\n\t\torgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\t\tName:  String(\"tst-\" + randomString(t)[0:20] + \"-ff-on\"),\n\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t})\n\t\tt.Cleanup(orgTestCleanup)\n\n\t\twTest, wCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName:            String(randomString(t)),\n\t\t\tTriggerPatterns: []string{\"/pattern-1/**/*\", \"/pattern-2/**/*\"},\n\t\t})\n\t\tt.Cleanup(wCleanup)\n\t\tassert.Equal(t, wTest.TriggerPatterns, []string{\"/pattern-1/**/*\", \"/pattern-2/**/*\"}) // Sanity test\n\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tName:                String(\"foobar\"),\n\t\t\tFileTriggersEnabled: Bool(true),\n\t\t\tTriggerPrefixes:     []string{},\n\t\t\tTriggerPatterns:     []string{\"/module-1/**/*\", \"/**/networking/*\"},\n\t\t}\n\t\tw, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view from the API.\n\t\trefreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Workspace{\n\t\t\tw,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.Empty(t, options.TriggerPrefixes)\n\t\t\tassert.Equal(t, options.TriggerPatterns, item.TriggerPatterns)\n\t\t}\n\t})\n\n\tt.Run(\"update hyok enabled of a workspace from false to false\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name that has HYOK permissions\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\tworkspaceCreateOptions := WorkspaceCreateOptions{\n\t\t\tName:        String(\"go-tfe-test-hyok-enabled-false\"),\n\t\t\tHYOKEnabled: Bool(false),\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, hyokOrganizationName, workspaceCreateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, w.HYOKEnabled)\n\t\tassert.False(t, *w.HYOKEnabled)\n\n\t\tworkspaceUpdateOptions := WorkspaceUpdateOptions{\n\t\t\tHYOKEnabled: Bool(false),\n\t\t}\n\n\t\tw, err = client.Workspaces.Update(ctx, hyokOrganizationName, w.Name, workspaceUpdateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, w.HYOKEnabled)\n\t\tassert.False(t, *w.HYOKEnabled)\n\n\t\terr = client.Workspaces.Delete(ctx, hyokOrganizationName, *workspaceCreateOptions.Name)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"update hyok enabled of a workspace from false to true\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name that has HYOK permissions\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\tworkspaceCreateOptions := WorkspaceCreateOptions{\n\t\t\tName:        String(\"go-tfe-test-hyok-enabled-false\"),\n\t\t\tHYOKEnabled: Bool(false),\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, hyokOrganizationName, workspaceCreateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, w.HYOKEnabled)\n\t\tassert.False(t, *w.HYOKEnabled)\n\n\t\tworkspaceUpdateOptions := WorkspaceUpdateOptions{\n\t\t\tHYOKEnabled: Bool(true),\n\t\t}\n\n\t\tw, err = client.Workspaces.Update(ctx, hyokOrganizationName, w.Name, workspaceUpdateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, w.HYOKEnabled)\n\t\tassert.True(t, *w.HYOKEnabled)\n\n\t\terr = client.Workspaces.Delete(ctx, hyokOrganizationName, *workspaceCreateOptions.Name)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"update hyok enabled of a workspace from true to true\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name that has HYOK permissions\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\tworkspaceCreateOptions := WorkspaceCreateOptions{\n\t\t\tName:        String(\"go-tfe-test-hyok-enabled-true\"),\n\t\t\tHYOKEnabled: Bool(true),\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, hyokOrganizationName, workspaceCreateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, w.HYOKEnabled)\n\t\tassert.True(t, *w.HYOKEnabled)\n\n\t\tworkspaceUpdateOptions := WorkspaceUpdateOptions{\n\t\t\tHYOKEnabled: Bool(true),\n\t\t}\n\n\t\tw, err = client.Workspaces.Update(ctx, hyokOrganizationName, w.Name, workspaceUpdateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, w.HYOKEnabled)\n\t\tassert.True(t, *w.HYOKEnabled)\n\n\t\terr = client.Workspaces.Delete(ctx, hyokOrganizationName, *workspaceCreateOptions.Name)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"update hyok enabled of a workspace from true to false\", func(t *testing.T) {\n\t\tskipHYOKIntegrationTests(t)\n\n\t\t// replace the environment variable with a valid organization name that has HYOK permissions\n\t\thyokOrganizationName := os.Getenv(\"HYOK_ORGANIZATION_NAME\")\n\t\tif hyokOrganizationName == \"\" {\n\t\t\tt.Fatal(\"Export a valid HYOK_ORGANIZATION_NAME before running this test!\")\n\t\t}\n\n\t\tworkspaceCreateOptions := WorkspaceCreateOptions{\n\t\t\tName:        String(\"go-tfe-test-hyok-enabled-true\"),\n\t\t\tHYOKEnabled: Bool(true),\n\t\t}\n\n\t\tw, err := client.Workspaces.Create(ctx, hyokOrganizationName, workspaceCreateOptions)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, w.HYOKEnabled)\n\t\tassert.True(t, *w.HYOKEnabled)\n\n\t\tworkspaceUpdateOptions := WorkspaceUpdateOptions{\n\t\t\tHYOKEnabled: Bool(false),\n\t\t}\n\n\t\t_, err = client.Workspaces.Update(ctx, hyokOrganizationName, w.Name, workspaceUpdateOptions)\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrHYOKCannotBeDisabled.Error())\n\n\t\terr = client.Workspaces.Delete(ctx, hyokOrganizationName, *workspaceCreateOptions.Name)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestWorkspacesUpdateTableDriven(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wCleanup)\n\n\tworkspaceTableTests := []WorkspaceTableTest{\n\t\t{\n\t\t\tscenario: \"when options include VCSRepo tags-regex\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tcreateOptions: &WorkspaceCreateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\t\t\tTagsRegex: String(\"barfoo\")},\n\t\t\t\t},\n\t\t\t\tupdateOptions: &WorkspaceUpdateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tVCSRepo:             &VCSRepoOptions{TagsRegex: String(\"foobar\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetup: func(t *testing.T, options *WorkspaceTableOptions) (w *Workspace, cleanup func()) {\n\t\t\t\torgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\t\t\t\tName:  String(\"tst-\" + randomString(t)[0:20]),\n\t\t\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t\t\t})\n\n\t\t\t\twTest, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, *options.createOptions)\n\t\t\t\treturn wTest, func() {\n\t\t\t\t\tt.Cleanup(orgTestCleanup)\n\t\t\t\t\tt.Cleanup(wTestCleanup)\n\t\t\t\t}\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, workspace *Workspace, options *WorkspaceTableOptions, _ error) {\n\t\t\t\tassert.Equal(t, *options.createOptions.VCSRepo.TagsRegex, workspace.VCSRepo.TagsRegex)\n\t\t\t\tassert.Equal(t, workspace.VCSRepo.TagsRegex, *String(\"barfoo\")) // Sanity test\n\n\t\t\t\tw, err := client.Workspaces.Update(ctx, workspace.Organization.Name, workspace.Name, *options.updateOptions)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tassert.Equal(t, w.VCSRepo.TagsRegex, *String(\"foobar\")) // Sanity test\n\n\t\t\t\t// Get a refreshed view from the API.\n\t\t\t\trefreshed, err := client.Workspaces.Read(ctx, workspace.Organization.Name, *options.updateOptions.Name)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tfor _, item := range []*Workspace{\n\t\t\t\t\tw,\n\t\t\t\t\trefreshed,\n\t\t\t\t} {\n\t\t\t\t\tassert.Empty(t, options.updateOptions.TriggerPrefixes)\n\t\t\t\t\tassert.Empty(t, options.updateOptions.TriggerPatterns, item.TriggerPatterns)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscenario: \"when options include tags-regex and file-triggers-enabled is true an error is returned\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tupdateOptions: &WorkspaceUpdateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(true),\n\t\t\t\t\tVCSRepo:             &VCSRepoOptions{TagsRegex: String(\"foobar\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\tassert.Nil(t, w)\n\t\t\t\tassert.EqualError(t, err, ErrUnsupportedBothTagsRegexAndFileTriggersEnabled.Error())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscenario: \"when options include both non-empty tags-regex and file-triggers-enabled an error is returned\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tupdateOptions: &WorkspaceUpdateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(true),\n\t\t\t\t\tVCSRepo:             &VCSRepoOptions{TagsRegex: String(\"foobar\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\tassert.Nil(t, w)\n\t\t\t\tassert.EqualError(t, err, ErrUnsupportedBothTagsRegexAndFileTriggersEnabled.Error())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscenario: \"when options include both tags-regex and trigger-prefixes an error is returned\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tupdateOptions: &WorkspaceUpdateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tTriggerPrefixes:     []string{\"/module-1\", \"/module-2\"},\n\t\t\t\t\tVCSRepo:             &VCSRepoOptions{TagsRegex: String(\"foobar\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\tassert.Nil(t, w)\n\t\t\t\tassert.EqualError(t, err, ErrUnsupportedBothTagsRegexAndTriggerPrefixes.Error())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscenario: \"when options include both tags-regex and trigger-patterns error is returned\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tupdateOptions: &WorkspaceUpdateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tTriggerPatterns:     []string{\"/module-1/**/*\", \"/**/networking/*\"},\n\t\t\t\t\tVCSRepo:             &VCSRepoOptions{TagsRegex: String(\"foobar\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, w *Workspace, options *WorkspaceTableOptions, err error) {\n\t\t\t\tassert.Nil(t, w)\n\t\t\t\tassert.EqualError(t, err, ErrUnsupportedBothTagsRegexAndTriggerPatterns.Error())\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tableTest := range workspaceTableTests {\n\t\tt.Run(tableTest.scenario, func(t *testing.T) {\n\t\t\tvar workspace *Workspace\n\t\t\tvar cleanup func()\n\t\t\tvar err error\n\t\t\tif tableTest.setup != nil {\n\t\t\t\tworkspace, cleanup = tableTest.setup(t, tableTest.options)\n\t\t\t\tdefer cleanup()\n\t\t\t} else {\n\t\t\t\tworkspace, err = client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, *tableTest.options.updateOptions)\n\t\t\t}\n\t\t\ttableTest.assertion(t, workspace, tableTest.options, err)\n\t\t})\n\t}\n}\n\nfunc TestWorkspacesUpdateTableDrivenWithGithubApp(t *testing.T) {\n\tt.Parallel()\n\tgHAInstallationID := os.Getenv(\"GITHUB_APP_INSTALLATION_ID\")\n\n\tif gHAInstallationID == \"\" {\n\t\tt.Skip(\"Export a valid GITHUB_APP_INSTALLATION_ID before running this test!\")\n\t}\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wCleanup)\n\n\tworkspaceTableTests := []WorkspaceTableTest{\n\t\t{\n\t\t\tscenario: \"when options include VCSRepo tags-regex\",\n\t\t\toptions: &WorkspaceTableOptions{\n\t\t\t\tcreateOptions: &WorkspaceCreateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\t\t\tTagsRegex: String(\"barfoo\")},\n\t\t\t\t},\n\t\t\t\tupdateOptions: &WorkspaceUpdateOptions{\n\t\t\t\t\tName:                String(\"foobar\"),\n\t\t\t\t\tFileTriggersEnabled: Bool(false),\n\t\t\t\t\tVCSRepo: &VCSRepoOptions{\n\t\t\t\t\t\tTagsRegex: String(\"foobar\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetup: func(t *testing.T, options *WorkspaceTableOptions) (w *Workspace, cleanup func()) {\n\t\t\t\torgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{\n\t\t\t\t\tName:  String(\"tst-\" + randomString(t)[0:20]),\n\t\t\t\t\tEmail: String(fmt.Sprintf(\"%s@tfe.local\", randomString(t))),\n\t\t\t\t})\n\n\t\t\t\twTest, wTestCleanup := createWorkspaceWithGithubApp(t, client, orgTest, *options.createOptions)\n\t\t\t\treturn wTest, func() {\n\t\t\t\t\tt.Cleanup(orgTestCleanup)\n\t\t\t\t\tt.Cleanup(wTestCleanup)\n\t\t\t\t}\n\t\t\t},\n\t\t\tassertion: func(t *testing.T, workspace *Workspace, options *WorkspaceTableOptions, _ error) {\n\t\t\t\tassert.Equal(t, *options.createOptions.VCSRepo.TagsRegex, workspace.VCSRepo.TagsRegex)\n\t\t\t\tassert.Equal(t, workspace.VCSRepo.TagsRegex, *String(\"barfoo\")) // Sanity test\n\n\t\t\t\tw, err := client.Workspaces.Update(ctx, workspace.Organization.Name, workspace.Name, *options.updateOptions)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, w.VCSRepo.TagsRegex, *String(\"foobar\")) // Sanity test\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tableTest := range workspaceTableTests {\n\t\tt.Run(tableTest.scenario, func(t *testing.T) {\n\t\t\tvar workspace *Workspace\n\t\t\tvar cleanup func()\n\t\t\tvar err error\n\t\t\tif tableTest.setup != nil {\n\t\t\t\tworkspace, cleanup = tableTest.setup(t, tableTest.options)\n\t\t\t\tdefer cleanup()\n\t\t\t} else {\n\t\t\t\tworkspace, err = client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, *tableTest.options.updateOptions)\n\t\t\t}\n\t\t\ttableTest.assertion(t, workspace, tableTest.options, err)\n\t\t})\n\t}\n}\n\nfunc TestWorkspacesUpdateByID(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wCleanup)\n\n\tt.Run(\"when updating a subset of values\", func(t *testing.T) {\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tName:             String(wTest.Name),\n\t\t\tAllowDestroyPlan: Bool(false),\n\t\t\tAutoApply:        Bool(true),\n\t\t\tOperations:       Bool(true),\n\t\t\tQueueAllRuns:     Bool(true),\n\t\t\tTerraformVersion: String(\"0.10.0\"),\n\t\t}\n\n\t\twAfter, err := client.Workspaces.UpdateByID(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, wTest.Name, wAfter.Name)\n\t\tassert.NotEqual(t, wTest.AllowDestroyPlan, wAfter.AllowDestroyPlan)\n\t\tassert.NotEqual(t, wTest.AutoApply, wAfter.AutoApply)\n\t\tassert.NotEqual(t, wTest.QueueAllRuns, wAfter.QueueAllRuns)\n\t\tassert.NotEqual(t, wTest.TerraformVersion, wAfter.TerraformVersion)\n\t\tassert.Equal(t, wTest.WorkingDirectory, wAfter.WorkingDirectory)\n\t})\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tName:                       String(randomString(t)),\n\t\t\tAllowDestroyPlan:           Bool(true),\n\t\t\tAutoApply:                  Bool(false),\n\t\t\tFileTriggersEnabled:        Bool(true),\n\t\t\tOperations:                 Bool(false),\n\t\t\tQueueAllRuns:               Bool(false),\n\t\t\tSpeculativeEnabled:         Bool(true),\n\t\t\tStructuredRunOutputEnabled: Bool(true),\n\t\t\tTerraformVersion:           String(\"0.11.1\"),\n\t\t\tTriggerPrefixes:            []string{\"/modules\", \"/shared\"},\n\t\t\tWorkingDirectory:           String(\"baz/\"),\n\t\t}\n\n\t\tw, err := client.Workspaces.UpdateByID(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\t// Get a refreshed view of the workspace from the API\n\t\trefreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, item := range []*Workspace{\n\t\t\tw,\n\t\t\trefreshed,\n\t\t} {\n\t\t\tassert.Equal(t, *options.Name, item.Name)\n\t\t\tassert.Equal(t, *options.AllowDestroyPlan, item.AllowDestroyPlan)\n\t\t\tassert.Equal(t, *options.AutoApply, item.AutoApply)\n\t\t\tassert.Equal(t, *options.FileTriggersEnabled, item.FileTriggersEnabled)\n\t\t\tassert.Equal(t, *options.Operations, item.Operations)\n\t\t\tassert.Equal(t, *options.QueueAllRuns, item.QueueAllRuns)\n\t\t\tassert.Equal(t, *options.SpeculativeEnabled, item.SpeculativeEnabled)\n\t\t\tassert.Equal(t, *options.StructuredRunOutputEnabled, item.StructuredRunOutputEnabled)\n\t\t\tassert.Equal(t, *options.TerraformVersion, item.TerraformVersion)\n\t\t\tassert.Equal(t, options.TriggerPrefixes, item.TriggerPrefixes)\n\t\t\tassert.Equal(t, *options.WorkingDirectory, item.WorkingDirectory)\n\t\t}\n\t})\n\n\tt.Run(\"when an error is returned from the api\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.UpdateByID(ctx, wTest.ID, WorkspaceUpdateOptions{\n\t\t\tTerraformVersion: String(\"nonexisting\"),\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.UpdateByID(ctx, badIdentifier, WorkspaceUpdateOptions{})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspacesUpdateWithDefaultExecutionMode(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\tdefaultExecutionOrgTest, defaultExecutionOrgTestCleanup := createOrganizationWithDefaultAgentPool(t, client)\n\tt.Cleanup(defaultExecutionOrgTestCleanup)\n\n\twTest, wCleanup := createWorkspace(t, client, defaultExecutionOrgTest)\n\tt.Cleanup(wCleanup)\n\n\tt.Run(\"when explicitly setting execution mode, workspace ignores the default execution mode\", func(t *testing.T) {\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tExecutionMode: String(\"remote\"),\n\t\t}\n\t\tw, err := client.Workspaces.Update(ctx, defaultExecutionOrgTest.Name, wTest.Name, options)\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"remote\", w.ExecutionMode)\n\t})\n\n\tt.Run(\"with setting overwrites set to true, workspace ignores the default execution mode\", func(t *testing.T) {\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tExecutionMode: String(\"local\"),\n\t\t\tSettingOverwrites: &WorkspaceSettingOverwritesOptions{\n\t\t\t\tExecutionMode: Bool(true),\n\t\t\t\tAgentPool:     Bool(true),\n\t\t\t},\n\t\t}\n\t\tw, err := client.Workspaces.Update(ctx, defaultExecutionOrgTest.Name, wTest.Name, options)\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"local\", w.ExecutionMode)\n\t})\n\n\tt.Run(\"with setting overwrites set to false, workspace inherits the default execution mode\", func(t *testing.T) {\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tSettingOverwrites: &WorkspaceSettingOverwritesOptions{\n\t\t\t\tExecutionMode: Bool(false),\n\t\t\t\tAgentPool:     Bool(false),\n\t\t\t},\n\t\t}\n\t\tw, err := client.Workspaces.Update(ctx, defaultExecutionOrgTest.Name, wTest.Name, options)\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"agent\", w.ExecutionMode)\n\t})\n}\n\nfunc TestWorkspacesDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\t// ignore workspace cleanup b/c it will be destroyed during tests\n\twTest, _ := createWorkspace(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.Workspaces.Delete(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the workspace - it should fail.\n\t\t_, err = client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"when organization is invalid\", func(t *testing.T) {\n\t\terr := client.Workspaces.Delete(ctx, badIdentifier, wTest.Name)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when workspace is invalid\", func(t *testing.T) {\n\t\terr := client.Workspaces.Delete(ctx, orgTest.Name, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceValue.Error())\n\t})\n}\n\nfunc TestWorkspacesDeleteByID(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\t// ignore workspace cleanup b/c it will be destroyed during tests\n\twTest, _ := createWorkspace(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.Workspaces.DeleteByID(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the workspace - it should fail.\n\t\t_, err = client.Workspaces.ReadByID(ctx, wTest.ID)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\terr := client.Workspaces.DeleteByID(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestCanForceDeletePermission(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wCleanup)\n\n\tt.Run(\"workspace permission set includes can-force-delete\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.ReadByID(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, wTest, w)\n\t\trequire.NotNil(t, w.Permissions)\n\t\trequire.NotNil(t, w.Permissions.CanForceDelete)\n\t\tassert.True(t, *w.Permissions.CanForceDelete)\n\t})\n}\n\nfunc TestWorkspacesSafeDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\t// ignore workspace cleanup b/c it will be destroyed during tests\n\twTest, _ := createWorkspace(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.Workspaces.SafeDelete(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the workspace - it should fail.\n\t\t_, err = client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"when organization is invalid\", func(t *testing.T) {\n\t\terr := client.Workspaces.SafeDelete(ctx, badIdentifier, wTest.Name)\n\t\tassert.EqualError(t, err, ErrInvalidOrg.Error())\n\t})\n\n\tt.Run(\"when workspace is invalid\", func(t *testing.T) {\n\t\terr := client.Workspaces.SafeDelete(ctx, orgTest.Name, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceValue.Error())\n\t})\n\n\tt.Run(\"when workspace is locked\", func(t *testing.T) {\n\t\twTest, workspaceCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceCleanup)\n\t\tw, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, w.Locked)\n\n\t\terr = client.Workspaces.SafeDelete(ctx, orgTest.Name, wTest.Name)\n\t\tassert.True(t, errors.Is(err, ErrWorkspaceLockedCannotDelete))\n\t})\n\n\tt.Run(\"when workspace has resources under management\", func(t *testing.T) {\n\t\twTest, workspaceCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceCleanup)\n\t\t_, svTestCleanup := createStateVersion(t, client, 0, wTest)\n\t\tt.Cleanup(svTestCleanup)\n\n\t\t_, err := retry(func() (interface{}, error) {\n\t\t\terr := client.Workspaces.SafeDelete(ctx, orgTest.Name, wTest.Name)\n\t\t\tif errors.Is(err, ErrWorkspaceStillProcessing) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn nil, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Workspace still processing after retrying: %s\", err)\n\t\t}\n\n\t\terr = client.Workspaces.SafeDelete(ctx, orgTest.Name, wTest.Name)\n\t\tassert.True(t, errors.Is(err, ErrWorkspaceNotSafeToDelete))\n\t})\n}\n\nfunc TestWorkspacesSafeDeleteByID(t *testing.T) {\n\tt.Parallel()\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\t// ignore workspace cleanup b/c it will be destroyed during tests\n\twTest, _ := createWorkspace(t, client, orgTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.Workspaces.SafeDeleteByID(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Try loading the workspace - it should fail.\n\t\t_, err = client.Workspaces.ReadByID(ctx, wTest.ID)\n\t\tassert.Equal(t, ErrResourceNotFound, err)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\terr := client.Workspaces.SafeDeleteByID(ctx, badIdentifier)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n\n\tt.Run(\"when workspace is locked\", func(t *testing.T) {\n\t\twTest, workspaceCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceCleanup)\n\t\tw, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, w.Locked)\n\n\t\terr = client.Workspaces.SafeDeleteByID(ctx, wTest.ID)\n\t\tassert.True(t, errors.Is(err, ErrWorkspaceLockedCannotDelete))\n\t})\n\n\tt.Run(\"when workspace has resources under management\", func(t *testing.T) {\n\t\twTest, workspaceCleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(workspaceCleanup)\n\t\t_, svTestCleanup := createStateVersion(t, client, 0, wTest)\n\t\tt.Cleanup(svTestCleanup)\n\n\t\t_, err := retryPatiently(func() (interface{}, error) {\n\t\t\terr := client.Workspaces.SafeDeleteByID(ctx, wTest.ID)\n\t\t\tif errors.Is(err, ErrWorkspaceStillProcessing) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn nil, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Workspace still processing after retrying: %s\", err)\n\t\t}\n\n\t\terr = client.Workspaces.SafeDeleteByID(ctx, wTest.ID)\n\t\tassert.True(t, errors.Is(err, ErrWorkspaceNotSafeToDelete))\n\t})\n}\n\nfunc TestWorkspacesRemoveVCSConnection(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, WorkspaceCreateOptions{})\n\tt.Cleanup(wTestCleanup)\n\n\tt.Run(\"remove vcs integration\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.RemoveVCSConnection(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, (*VCSRepo)(nil), w.VCSRepo)\n\t})\n}\n\nfunc TestWorkspacesRemoveVCSConnectionByID(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, WorkspaceCreateOptions{})\n\tt.Cleanup(wTestCleanup)\n\n\tt.Run(\"remove vcs integration\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.RemoveVCSConnectionByID(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, (*VCSRepo)(nil), w.VCSRepo)\n\t})\n}\n\nfunc TestWorkspacesLock(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\trequire.Empty(t, wTest.LockedBy)\n\n\t\tw, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, w.Locked)\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, w.LockedBy)\n\t\trequireExactlyOneNotEmpty(t, w.LockedBy.Run, w.LockedBy.Team, w.LockedBy.User)\n\t})\n\n\tt.Run(\"when workspace is already locked\", func(t *testing.T) {\n\t\t_, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\t\tassert.Equal(t, ErrWorkspaceLocked, err)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Lock(ctx, badIdentifier, WorkspaceLockOptions{})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspacesUnlock_RunDependent(t *testing.T) {\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tw, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\tif err != nil {\n\t\torgTestCleanup()\n\t}\n\trequire.NoError(t, err)\n\trequire.True(t, w.Locked)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Unlock(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, w.Locked)\n\t})\n\n\tt.Run(\"when workspace is already unlocked\", func(t *testing.T) {\n\t\t_, err := client.Workspaces.Unlock(ctx, wTest.ID)\n\t\tassert.Equal(t, ErrWorkspaceNotLocked, err)\n\t})\n\n\tt.Run(\"when a workspace is locked by a run\", func(t *testing.T) {\n\t\twTest2, wTest2Cleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(wTest2Cleanup)\n\n\t\t_, rTestCleanup := createRun(t, client, wTest2)\n\t\tt.Cleanup(rTestCleanup)\n\n\t\t// Wait for wTest2 to be locked by a run\n\t\twaitForRunLock(t, client, wTest2.ID)\n\n\t\t_, err = client.Workspaces.Unlock(ctx, wTest2.ID)\n\t\tassert.Equal(t, ErrWorkspaceLockedByRun, err)\n\t})\n\n\tt.Run(\"when a workspace is locked by a team\", func(t *testing.T) {\n\t\twTest2, wTest2Cleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(wTest2Cleanup)\n\n\t\t// Create a new team to lock the workspace\n\t\ttmTest, tmTestCleanup := createTeam(t, client, orgTest)\n\t\tdefer tmTestCleanup()\n\t\tta, err := client.TeamAccess.Add(ctx, TeamAccessAddOptions{\n\t\t\tAccess:    Access(AccessAdmin),\n\t\t\tTeam:      tmTest,\n\t\t\tWorkspace: wTest2,\n\t\t})\n\t\tassert.Nil(t, err)\n\t\tdefer func() {\n\t\t\terr := client.TeamAccess.Remove(ctx, ta.ID)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"error removing team access (%s): %s\", ta.ID, err)\n\t\t\t}\n\t\t}()\n\t\ttt, ttTestCleanup := createTeamToken(t, client, tmTest)\n\t\tdefer ttTestCleanup()\n\n\t\t// Create a new client with the team token\n\t\tteamClient := testClient(t)\n\t\tteamClient.token = tt.Token\n\n\t\t// Lock the workspace with the team client\n\t\t_, err = teamClient.Workspaces.Lock(ctx, wTest2.ID, WorkspaceLockOptions{})\n\t\tassert.Nil(t, err)\n\n\t\t// Attempt to unlock the workspace with the original client\n\t\t_, err = client.Workspaces.Unlock(ctx, wTest2.ID)\n\t\tassert.Equal(t, ErrWorkspaceLockedByTeam, err)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Unlock(ctx, badIdentifier)\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspacesForceUnlock(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tw, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})\n\tif err != nil {\n\t\torgTestCleanup()\n\t}\n\trequire.NoError(t, err)\n\trequire.True(t, w.Locked)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.ForceUnlock(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.False(t, w.Locked)\n\t})\n\n\tt.Run(\"when workspace is already unlocked\", func(t *testing.T) {\n\t\t_, err := client.Workspaces.ForceUnlock(ctx, wTest.ID)\n\t\tassert.Equal(t, ErrWorkspaceNotLocked, err)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.ForceUnlock(ctx, badIdentifier)\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspacesAssignSSHKey(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tsshKeyTest, sshKeyTestCleanup := createSSHKey(t, client, orgTest)\n\tt.Cleanup(sshKeyTestCleanup)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.AssignSSHKey(ctx, wTest.ID, WorkspaceAssignSSHKeyOptions{\n\t\t\tSSHKeyID: String(sshKeyTest.ID),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, w.SSHKey)\n\t\tassert.Equal(t, w.SSHKey.ID, sshKeyTest.ID)\n\t})\n\n\tt.Run(\"without an SSH key ID\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.AssignSSHKey(ctx, wTest.ID, WorkspaceAssignSSHKeyOptions{})\n\t\tassert.Nil(t, w)\n\t\tassert.Equal(t, err, ErrRequiredSHHKeyID)\n\t})\n\n\tt.Run(\"without a valid SSH key ID\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.AssignSSHKey(ctx, wTest.ID, WorkspaceAssignSSHKeyOptions{\n\t\t\tSSHKeyID: String(badIdentifier),\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.Equal(t, err, ErrInvalidSHHKeyID)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.AssignSSHKey(ctx, badIdentifier, WorkspaceAssignSSHKeyOptions{\n\t\t\tSSHKeyID: String(sshKeyTest.ID),\n\t\t})\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspacesUnassignSSHKey(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tsshKeyTest, sshKeyTestCleanup := createSSHKey(t, client, orgTest)\n\tt.Cleanup(sshKeyTestCleanup)\n\n\tw, err := client.Workspaces.AssignSSHKey(ctx, wTest.ID, WorkspaceAssignSSHKeyOptions{\n\t\tSSHKeyID: String(sshKeyTest.ID),\n\t})\n\tif err != nil {\n\t\torgTestCleanup()\n\t}\n\trequire.NoError(t, err)\n\trequire.NotNil(t, w.SSHKey)\n\trequire.Equal(t, w.SSHKey.ID, sshKeyTest.ID)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.UnassignSSHKey(ctx, wTest.ID)\n\t\tassert.Nil(t, err)\n\t\tassert.Nil(t, w.SSHKey)\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.UnassignSSHKey(ctx, badIdentifier)\n\t\tassert.Nil(t, w)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspaces_AddRemoteStateConsumers(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\t// Update workspace to not allow global remote state\n\toptions := WorkspaceUpdateOptions{\n\t\tGlobalRemoteState: Bool(false),\n\t}\n\twTest, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\trequire.NoError(t, err)\n\n\tt.Run(\"successfully adds a remote state consumer\", func(t *testing.T) {\n\t\twTestConsumer1, wTestCleanupConsumer1 := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(wTestCleanupConsumer1)\n\t\twTestConsumer2, wTestCleanupConsumer2 := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(wTestCleanupConsumer2)\n\n\t\terr := client.Workspaces.AddRemoteStateConsumers(ctx, wTest.ID, WorkspaceAddRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{wTestConsumer1, wTestConsumer2},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\trsc, err := client.Workspaces.ListRemoteStateConsumers(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 2, len(rsc.Items))\n\t\tassert.Contains(t, rsc.Items, wTestConsumer1)\n\t\tassert.Contains(t, rsc.Items, wTestConsumer2)\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\terr := client.Workspaces.AddRemoteStateConsumers(ctx, wTest.ID, WorkspaceAddRemoteStateConsumersOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrWorkspacesRequired.Error())\n\n\t\terr = client.Workspaces.AddRemoteStateConsumers(ctx, wTest.ID, WorkspaceAddRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{},\n\t\t})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrWorkspaceMinLimit.Error())\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\terr := client.Workspaces.AddRemoteStateConsumers(ctx, badIdentifier, WorkspaceAddRemoteStateConsumersOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspaces_RemoveRemoteStateConsumers(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\t// Update workspace to not allow global remote state\n\toptions := WorkspaceUpdateOptions{\n\t\tGlobalRemoteState: Bool(false),\n\t}\n\twTest, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\trequire.NoError(t, err)\n\n\tt.Run(\"successfully removes a remote state consumer\", func(t *testing.T) {\n\t\twTestConsumer1, wTestCleanupConsumer1 := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(wTestCleanupConsumer1)\n\t\twTestConsumer2, wTestCleanupConsumer2 := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(wTestCleanupConsumer2)\n\n\t\terr := client.Workspaces.AddRemoteStateConsumers(ctx, wTest.ID, WorkspaceAddRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{wTestConsumer1, wTestConsumer2},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trsc, err := client.Workspaces.ListRemoteStateConsumers(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 2, len(rsc.Items))\n\t\tassert.Contains(t, rsc.Items, wTestConsumer1)\n\t\tassert.Contains(t, rsc.Items, wTestConsumer2)\n\n\t\terr = client.Workspaces.RemoveRemoteStateConsumers(ctx, wTest.ID, WorkspaceRemoveRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{wTestConsumer1},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\trsc, err = client.Workspaces.ListRemoteStateConsumers(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Contains(t, rsc.Items, wTestConsumer2)\n\t\tassert.Equal(t, 1, len(rsc.Items))\n\n\t\terr = client.Workspaces.RemoveRemoteStateConsumers(ctx, wTest.ID, WorkspaceRemoveRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{wTestConsumer2},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trsc, err = client.Workspaces.ListRemoteStateConsumers(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, len(rsc.Items))\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\terr := client.Workspaces.RemoveRemoteStateConsumers(ctx, wTest.ID, WorkspaceRemoveRemoteStateConsumersOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrWorkspacesRequired.Error())\n\n\t\terr = client.Workspaces.RemoveRemoteStateConsumers(ctx, wTest.ID, WorkspaceRemoveRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{},\n\t\t})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrWorkspaceMinLimit.Error())\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\terr := client.Workspaces.RemoveRemoteStateConsumers(ctx, badIdentifier, WorkspaceRemoveRemoteStateConsumersOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspaces_UpdateRemoteStateConsumers(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\t// Update workspace to not allow global remote state\n\toptions := WorkspaceUpdateOptions{\n\t\tGlobalRemoteState: Bool(false),\n\t}\n\twTest, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\trequire.NoError(t, err)\n\n\tt.Run(\"successfully updates a remote state consumer\", func(t *testing.T) {\n\t\twTestConsumer1, wTestCleanupConsumer1 := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(wTestCleanupConsumer1)\n\t\twTestConsumer2, wTestCleanupConsumer2 := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(wTestCleanupConsumer2)\n\n\t\terr := client.Workspaces.AddRemoteStateConsumers(ctx, wTest.ID, WorkspaceAddRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{wTestConsumer1},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trsc, err := client.Workspaces.ListRemoteStateConsumers(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, len(rsc.Items))\n\t\tassert.Contains(t, rsc.Items, wTestConsumer1)\n\n\t\terr = client.Workspaces.UpdateRemoteStateConsumers(ctx, wTest.ID, WorkspaceUpdateRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{wTestConsumer2},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trsc, err = client.Workspaces.ListRemoteStateConsumers(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, len(rsc.Items))\n\t\tassert.Contains(t, rsc.Items, wTestConsumer2)\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\terr := client.Workspaces.UpdateRemoteStateConsumers(ctx, wTest.ID, WorkspaceUpdateRemoteStateConsumersOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrWorkspacesRequired.Error())\n\n\t\terr = client.Workspaces.UpdateRemoteStateConsumers(ctx, wTest.ID, WorkspaceUpdateRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{},\n\t\t})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrWorkspaceMinLimit.Error())\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\terr := client.Workspaces.UpdateRemoteStateConsumers(ctx, badIdentifier, WorkspaceUpdateRemoteStateConsumersOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspaces_AddTags(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\toptions := WorkspaceAddTagsOptions{\n\t\tTags: []*Tag{\n\t\t\t{\n\t\t\t\tName: \"tag1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: \"tag2\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: \"tag3\",\n\t\t\t},\n\t\t},\n\t}\n\n\tt.Run(\"successfully adds tags\", func(t *testing.T) {\n\t\terr := client.Workspaces.AddTags(ctx, wTest.ID, options)\n\t\trequire.NoError(t, err)\n\n\t\tw, err := client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 3, len(w.TagNames))\n\t\tassert.Equal(t, w.TagNames, []string{\"tag1\", \"tag2\", \"tag3\"})\n\n\t\terr = client.Workspaces.AddTags(ctx, wTest.ID, WorkspaceAddTagsOptions{\n\t\t\tTags: []*Tag{\n\t\t\t\t{\n\t\t\t\t\tName: \"tag4\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tw, err = client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 4, len(w.TagNames))\n\t\tsort.Strings(w.TagNames)\n\t\tassert.EqualValues(t, w.TagNames, []string{\"tag1\", \"tag2\", \"tag3\", \"tag4\"})\n\n\t\twt, err := client.Workspaces.ListTags(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 4, len(wt.Items))\n\t\tassert.Equal(t, wt.Items[3].Name, \"tag4\")\n\t})\n\n\tt.Run(\"successfully adds tags by id and name\", func(t *testing.T) {\n\t\twTest2, wTest2Cleanup := createWorkspace(t, client, orgTest)\n\t\tt.Cleanup(wTest2Cleanup)\n\n\t\t// add a tag to another workspace\n\t\terr := client.Workspaces.AddTags(ctx, wTest2.ID, WorkspaceAddTagsOptions{\n\t\t\tTags: []*Tag{\n\t\t\t\t{\n\t\t\t\t\tName: \"tagbyid\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// get the id of the new tag (may take a moment to show up)\n\t\tcreatedTags, err := retryPatientlyIf(\n\t\t\tfunc() (any, error) {\n\t\t\t\treturn client.Workspaces.ListTags(ctx, wTest2.ID, nil)\n\t\t\t},\n\t\t\tfunc(tl *TagList) bool {\n\t\t\t\treturn tl == nil || len(tl.Items) == 0\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, createdTags)\n\t\trequire.NotEmpty(t, createdTags.Items)\n\t\ttagID := createdTags.Items[0].ID\n\n\t\t// add the tag to our workspace by id\n\t\terr = client.Workspaces.AddTags(ctx, wTest.ID, WorkspaceAddTagsOptions{\n\t\t\tTags: []*Tag{\n\t\t\t\t{\n\t\t\t\t\tID: tagID,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\t// tag is now in our tag list\n\t\twt, err := retryPatientlyIf(\n\t\t\tfunc() (any, error) {\n\t\t\t\treturn client.Workspaces.ListTags(ctx, wTest.ID, nil)\n\t\t\t},\n\t\t\tfunc(tl *TagList) bool {\n\t\t\t\t// wait for the tag to appear\n\t\t\t\tif tl == nil {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tfor _, tag := range tl.Items {\n\t\t\t\t\tif tag.ID == tagID {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, wt)\n\n\t\t// find the tag we added\n\t\tvar addedTag *Tag\n\t\tfor _, tag := range wt.Items {\n\t\t\tif tag.ID == tagID {\n\t\t\t\taddedTag = tag\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\trequire.NotNil(t, addedTag)\n\t\tassert.Equal(t, \"tagbyid\", addedTag.Name)\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\terr := client.Workspaces.AddTags(ctx, wTest.ID, WorkspaceAddTagsOptions{\n\t\t\tTags: []*Tag{},\n\t\t})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrMissingTagIdentifier.Error())\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\terr := client.Workspaces.AddTags(ctx, badIdentifier, WorkspaceAddTagsOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspaces_RemoveTags(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\ttags := []*Tag{\n\t\t{\n\t\t\tName: \"tag1\",\n\t\t},\n\t\t{\n\t\t\tName: \"tag2\",\n\t\t},\n\t\t{\n\t\t\tName: \"tag3\",\n\t\t},\n\t}\n\taddOptions := WorkspaceAddTagsOptions{\n\t\tTags: tags,\n\t}\n\tremoveOptions := WorkspaceRemoveTagsOptions{\n\t\tTags: tags[0:2],\n\t}\n\n\tt.Run(\"successfully removes tags\", func(t *testing.T) {\n\t\terr := client.Workspaces.AddTags(ctx, wTest.ID, addOptions)\n\t\trequire.NoError(t, err)\n\n\t\tw, err := client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 3, len(w.TagNames))\n\t\tassert.Equal(t, w.TagNames, []string{\"tag1\", \"tag2\", \"tag3\"})\n\n\t\terr = client.Workspaces.RemoveTags(ctx, wTest.ID, removeOptions)\n\t\trequire.NoError(t, err)\n\n\t\tw, err = client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, len(w.TagNames))\n\t\tassert.Equal(t, w.TagNames, []string{\"tag3\"})\n\n\t\twt, err := client.Workspaces.ListTags(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, len(wt.Items))\n\t\tassert.EqualValues(t, wt.Items[0].Name, \"tag3\")\n\t})\n\n\tt.Run(\"attempts to remove a tag that doesn't exist\", func(t *testing.T) {\n\t\terr := client.Workspaces.RemoveTags(ctx, wTest.ID, WorkspaceRemoveTagsOptions{\n\t\t\tTags: []*Tag{\n\t\t\t\t{\n\t\t\t\t\tName: \"NonExistentTag\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\terr := client.Workspaces.RemoveTags(ctx, wTest.ID, WorkspaceRemoveTagsOptions{\n\t\t\tTags: []*Tag{},\n\t\t})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrMissingTagIdentifier.Error())\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\terr := client.Workspaces.RemoveTags(ctx, badIdentifier, WorkspaceRemoveTagsOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n\nfunc TestWorkspace_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tdata := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"type\": \"workspaces\",\n\t\t\t\"id\":   \"ws-1234\",\n\t\t\t\"attributes\": map[string]interface{}{\n\t\t\t\t\"name\":           \"my-workspace\",\n\t\t\t\t\"auto-apply\":     true,\n\t\t\t\t\"created-at\":     \"2020-07-15T23:38:43.821Z\",\n\t\t\t\t\"resource-count\": 2,\n\t\t\t\t\"permissions\": map[string]interface{}{\n\t\t\t\t\t\"can-update\": true,\n\t\t\t\t\t\"can-lock\":   true,\n\t\t\t\t},\n\t\t\t\t\"vcs-repo\": map[string]interface{}{\n\t\t\t\t\t\"branch\":              \"main\",\n\t\t\t\t\t\"display-identifier\":  \"repo-name\",\n\t\t\t\t\t\"identifier\":          \"hashicorp/repo-name\",\n\t\t\t\t\t\"ingress-submodules\":  true,\n\t\t\t\t\t\"oauth-token-id\":      \"token\",\n\t\t\t\t\t\"repository-http-url\": \"github.com\",\n\t\t\t\t\t\"service-provider\":    \"github\",\n\t\t\t\t\t\"webhook-url\":         \"https://app.terraform.io/webhooks/vcs/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n\t\t\t\t},\n\t\t\t\t\"actions\": map[string]interface{}{\n\t\t\t\t\t\"is-destroyable\": true,\n\t\t\t\t},\n\t\t\t\t\"trigger-prefixes\": []string{\"prefix-\"},\n\t\t\t\t\"trigger-patterns\": []string{\"pattern1/**/*\", \"pattern2/**/submodule/*\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tbyteData, err := json.Marshal(data)\n\trequire.NoError(t, err)\n\n\tresponseBody := bytes.NewReader(byteData)\n\tws := &Workspace{}\n\terr = unmarshalResponse(responseBody, ws)\n\trequire.NoError(t, err)\n\n\tiso8601TimeFormat := \"2006-01-02T15:04:05Z\"\n\tparsedTime, err := time.Parse(iso8601TimeFormat, \"2020-07-15T23:38:43.821Z\")\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, ws.ID, \"ws-1234\")\n\tassert.Equal(t, ws.Name, \"my-workspace\")\n\tassert.Equal(t, ws.AutoApply, true)\n\tassert.Equal(t, ws.CreatedAt, parsedTime)\n\tassert.Equal(t, ws.ResourceCount, 2)\n\tassert.Equal(t, ws.Permissions.CanUpdate, true)\n\tassert.Equal(t, ws.Permissions.CanLock, true)\n\tassert.Equal(t, ws.VCSRepo.Branch, \"main\")\n\tassert.Equal(t, ws.VCSRepo.DisplayIdentifier, \"repo-name\")\n\tassert.Equal(t, ws.VCSRepo.Identifier, \"hashicorp/repo-name\")\n\tassert.Equal(t, ws.VCSRepo.IngressSubmodules, true)\n\tassert.Equal(t, ws.VCSRepo.OAuthTokenID, \"token\")\n\tassert.Equal(t, ws.VCSRepo.RepositoryHTTPURL, \"github.com\")\n\tassert.Equal(t, ws.VCSRepo.ServiceProvider, \"github\")\n\tassert.Equal(t, ws.VCSRepo.WebhookURL, \"https://app.terraform.io/webhooks/vcs/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\")\n\tassert.Equal(t, ws.Actions.IsDestroyable, true)\n\tassert.Equal(t, ws.TriggerPrefixes, []string{\"prefix-\"})\n\tassert.Equal(t, ws.TriggerPatterns, []string{\"pattern1/**/*\", \"pattern2/**/submodule/*\"})\n}\n\nfunc TestWorkspaceCreateOptions_Marshal(t *testing.T) {\n\tt.Parallel()\n\topts := WorkspaceCreateOptions{\n\t\tAllowDestroyPlan: Bool(true),\n\t\tName:             String(\"my-workspace\"),\n\t\tTriggerPrefixes:  []string{\"prefix-\"},\n\t\tTriggerPatterns:  []string{\"pattern1/**/*\", \"pattern2/**/*\"},\n\t\tVCSRepo: &VCSRepoOptions{\n\t\t\tIdentifier:   String(\"id\"),\n\t\t\tOAuthTokenID: String(\"token\"),\n\t\t},\n\t}\n\n\treqBody, err := serializeRequestBody(&opts)\n\trequire.NoError(t, err)\n\treq, err := retryablehttp.NewRequest(\"POST\", \"url\", reqBody)\n\trequire.NoError(t, err)\n\tbodyBytes, err := req.BodyBytes()\n\trequire.NoError(t, err)\n\n\texpectedBody := `{\"data\":{\"type\":\"workspaces\",\"attributes\":{\"allow-destroy-plan\":true,\"name\":\"my-workspace\",\"trigger-patterns\":[\"pattern1/**/*\",\"pattern2/**/*\"],\"trigger-prefixes\":[\"prefix-\"],\"vcs-repo\":{\"identifier\":\"id\",\"oauth-token-id\":\"token\"}}}}\n`\n\tassert.Equal(t, expectedBody, string(bodyBytes))\n}\n\nfunc TestWorkspacesRunTasksPermission(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tt.Run(\"when the workspace exists\", func(t *testing.T) {\n\t\tw, err := client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, wTest, w)\n\t\tassert.True(t, w.Permissions.CanManageRunTasks)\n\t})\n}\n\nfunc TestWorkspacesProjects(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wTestCleanup()\n\n\tt.Run(\"created workspace includes default organization project\", func(t *testing.T) {\n\t\trequire.NotNil(t, orgTest.DefaultProject)\n\t\trequire.NotNil(t, wTest.Project)\n\t\tassert.Equal(t, wTest.Project.ID, orgTest.DefaultProject.ID)\n\t})\n\n\tt.Run(\"created workspace includes project ID\", func(t *testing.T) {\n\t\tassert.NotNil(t, wTest.Project.ID)\n\t})\n\n\tt.Run(\"read workspace includes project ID\", func(t *testing.T) {\n\t\tworkspace, err := client.Workspaces.ReadByID(ctx, wTest.ID)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, workspace.Project.ID)\n\t})\n\n\tt.Run(\"list workspace includes project ID\", func(t *testing.T) {\n\t\tworkspaces, err := client.Workspaces.List(ctx, orgTest.Name, &WorkspaceListOptions{})\n\t\tassert.NoError(t, err)\n\t\tfor idx, item := range workspaces.Items {\n\t\t\tassert.NotNil(t, item.Project.ID, \"No project ID set on workspace %s at idx %d\", item.ID, idx)\n\t\t}\n\t})\n}\n\nfunc TestWorkspace_DataRetentionPolicy(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\twTest, wTestCleanup := createWorkspace(t, client, nil)\n\tdefer wTestCleanup()\n\n\tdataRetentionPolicy, err := client.Workspaces.ReadDataRetentionPolicyChoice(ctx, wTest.ID)\n\tassert.Nil(t, err)\n\trequire.Nil(t, dataRetentionPolicy)\n\n\tworkspace, err := client.Workspaces.ReadByID(ctx, wTest.ID)\n\trequire.NoError(t, err)\n\trequire.Nil(t, workspace.DataRetentionPolicy)\n\trequire.Nil(t, workspace.DataRetentionPolicyChoice)\n\n\tt.Run(\"set and update data retention policy to delete older\", func(t *testing.T) {\n\t\tcreatedDataRetentionPolicy, err := client.Workspaces.SetDataRetentionPolicyDeleteOlder(ctx, wTest.ID, DataRetentionPolicyDeleteOlderSetOptions{DeleteOlderThanNDays: 33})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 33, createdDataRetentionPolicy.DeleteOlderThanNDays)\n\t\trequire.Contains(t, createdDataRetentionPolicy.ID, \"drp-\")\n\n\t\tdataRetentionPolicy, err = client.Workspaces.ReadDataRetentionPolicyChoice(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder)\n\n\t\trequire.Equal(t, 33, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.DeleteOlderThanNDays)\n\t\trequire.Equal(t, createdDataRetentionPolicy.ID, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.ID)\n\t\trequire.Contains(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.ID, \"drp-\")\n\n\t\tworkspace, err := client.Workspaces.ReadByID(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.ID, workspace.DataRetentionPolicyChoice.DataRetentionPolicyDeleteOlder.ID)\n\n\t\t// deprecated DataRetentionPolicy field should also have been populated\n\t\trequire.NotNil(t, workspace.DataRetentionPolicy)\n\t\trequire.Equal(t, workspace.DataRetentionPolicy.ID, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.ID)\n\n\t\t// try updating the number of days\n\t\tcreatedDataRetentionPolicy, err = client.Workspaces.SetDataRetentionPolicyDeleteOlder(ctx, wTest.ID, DataRetentionPolicyDeleteOlderSetOptions{DeleteOlderThanNDays: 1})\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, createdDataRetentionPolicy.DeleteOlderThanNDays)\n\n\t\tdataRetentionPolicy, err = client.Workspaces.ReadDataRetentionPolicyChoice(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder)\n\t\trequire.Equal(t, 1, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.DeleteOlderThanNDays)\n\t\trequire.Equal(t, createdDataRetentionPolicy.ID, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.ID)\n\t})\n\n\tt.Run(\"set data retention policy to not delete\", func(t *testing.T) {\n\t\tcreatedDataRetentionPolicy, err := client.Workspaces.SetDataRetentionPolicyDontDelete(ctx, wTest.ID, DataRetentionPolicyDontDeleteSetOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, createdDataRetentionPolicy.ID, \"drp-\")\n\n\t\tdataRetentionPolicy, err = client.Workspaces.ReadDataRetentionPolicyChoice(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDontDelete)\n\t\trequire.Equal(t, createdDataRetentionPolicy.ID, dataRetentionPolicy.DataRetentionPolicyDontDelete.ID)\n\n\t\t// dont delete policies should leave the legacy DataRetentionPolicy field on workspaces empty\n\t\tworkspace, err := client.Workspaces.ReadByID(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, workspace.DataRetentionPolicy)\n\t})\n\n\tt.Run(\"change data retention policy type\", func(t *testing.T) {\n\t\t_, err = client.Workspaces.SetDataRetentionPolicyDeleteOlder(ctx, wTest.ID, DataRetentionPolicyDeleteOlderSetOptions{DeleteOlderThanNDays: 45})\n\t\trequire.NoError(t, err)\n\n\t\tdataRetentionPolicy, err = client.Workspaces.ReadDataRetentionPolicyChoice(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder)\n\t\trequire.Equal(t, 45, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.DeleteOlderThanNDays)\n\t\trequire.Nil(t, dataRetentionPolicy.DataRetentionPolicyDontDelete)\n\n\t\t_, err = client.Workspaces.SetDataRetentionPolicyDontDelete(ctx, wTest.ID, DataRetentionPolicyDontDeleteSetOptions{})\n\t\trequire.NoError(t, err)\n\t\tdataRetentionPolicy, err = client.Workspaces.ReadDataRetentionPolicyChoice(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDontDelete)\n\n\t\t_, err = client.Workspaces.SetDataRetentionPolicyDeleteOlder(ctx, wTest.ID, DataRetentionPolicyDeleteOlderSetOptions{DeleteOlderThanNDays: 20})\n\t\trequire.NoError(t, err)\n\n\t\tdataRetentionPolicy, err = client.Workspaces.ReadDataRetentionPolicyChoice(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dataRetentionPolicy.DataRetentionPolicyDeleteOlder)\n\t\trequire.Equal(t, 20, dataRetentionPolicy.DataRetentionPolicyDeleteOlder.DeleteOlderThanNDays)\n\t\trequire.Nil(t, dataRetentionPolicy.DataRetentionPolicyDontDelete)\n\t})\n\n\tt.Run(\"delete data retention policy\", func(t *testing.T) {\n\t\terr = client.Workspaces.DeleteDataRetentionPolicy(ctx, wTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tdataRetentionPolicy, err = client.Workspaces.ReadDataRetentionPolicyChoice(ctx, wTest.ID)\n\t\tassert.Nil(t, err)\n\t\trequire.Nil(t, dataRetentionPolicy)\n\t})\n}\n\nfunc TestWorkspacesAutoDestroy(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tautoDestroyAt := NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC))\n\twTest, wCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\tName:          String(randomString(t)),\n\t\tAutoDestroyAt: autoDestroyAt,\n\t})\n\tt.Cleanup(wCleanup)\n\n\trequire.Equal(t, autoDestroyAt, wTest.AutoDestroyAt)\n\n\t// respect default omitempty\n\tw, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{\n\t\tAutoDestroyAt: nil,\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, w.AutoDestroyAt)\n\n\t// explicitly update the value of auto_destroy_at\n\tw, err = client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{\n\t\tAutoDestroyAt: NullableTime(time.Date(2025, 1, 2, 0, 0, 0, 0, time.UTC)),\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, w.AutoDestroyAt)\n\trequire.NotEqual(t, autoDestroyAt, w.AutoDestroyAt)\n\n\t// disable auto destroy\n\tw, err = client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{\n\t\tAutoDestroyAt: NullTime(),\n\t})\n\n\trequire.NoError(t, err)\n\trequire.Nil(t, w.AutoDestroyAt)\n}\n\nfunc TestWorkspacesAutoDestroyDuration(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tnewSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)\n\n\tt.Run(\"when creating a new workspace with standalone auto destroy settings\", func(t *testing.T) {\n\t\tduration := jsonapi.NewNullableAttrWithValue(\"14d\")\n\t\tnilDuration := jsonapi.NewNullNullableAttr[string]()\n\t\tnilAutoDestroy := jsonapi.NewNullNullableAttr[time.Time]()\n\t\twTest, wCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName:                        String(randomString(t)),\n\t\t\tAutoDestroyActivityDuration: duration,\n\t\t\tInheritsProjectAutoDestroy:  Bool(false),\n\t\t})\n\t\tt.Cleanup(wCleanup)\n\n\t\trequire.Equal(t, duration, wTest.AutoDestroyActivityDuration)\n\t\trequire.NotEqual(t, nilAutoDestroy, wTest.AutoDestroyAt)\n\t\trequire.Equal(t, wTest.InheritsProjectAutoDestroy, false)\n\n\t\tw, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{\n\t\t\tAutoDestroyActivityDuration: nilDuration,\n\t\t\tInheritsProjectAutoDestroy:  Bool(false),\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\trequire.False(t, w.AutoDestroyActivityDuration.IsSpecified())\n\t\trequire.False(t, w.AutoDestroyAt.IsSpecified())\n\t\trequire.Equal(t, wTest.InheritsProjectAutoDestroy, false)\n\t})\n}\n\nfunc TestWorkspaces_effectiveTagBindingsInheritedFrom(t *testing.T) {\n\tt.Parallel()\n\tskipUnlessBeta(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\tprojTest, projTestCleanup := createProject(t, client, orgTest)\n\tt.Cleanup(projTestCleanup)\n\n\tws, wsCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\tName:    String(\"mycoolworkspace\"),\n\t\tProject: projTest,\n\t})\n\tt.Cleanup(wsCleanup)\n\n\t_, err := client.Workspaces.AddTagBindings(ctx, ws.ID, WorkspaceAddTagBindingsOptions{\n\t\tTagBindings: []*TagBinding{\n\t\t\t{\n\t\t\t\tKey:   \"a\",\n\t\t\t\tValue: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   \"b\",\n\t\t\t\tValue: \"2\",\n\t\t\t},\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\tt.Run(\"when no tags are inherited from the project\", func(t *testing.T) {\n\t\teffectiveBindings, err := client.Workspaces.ListEffectiveTagBindings(ctx, ws.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, binding := range effectiveBindings {\n\t\t\trequire.Nil(t, binding.Links)\n\t\t}\n\t})\n\n\tt.Run(\"when tags are inherited from the project\", func(t *testing.T) {\n\t\t_, err := client.Projects.AddTagBindings(ctx, projTest.ID, ProjectAddTagBindingsOptions{\n\t\t\tTagBindings: []*TagBinding{\n\t\t\t\t{\n\t\t\t\t\tKey:   \"inherited\",\n\t\t\t\t\tValue: \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\teffectiveBindings, err := client.Workspaces.ListEffectiveTagBindings(ctx, ws.ID)\n\t\trequire.NoError(t, err)\n\n\t\tfor _, binding := range effectiveBindings {\n\t\t\tif binding.Key == \"inherited\" {\n\t\t\t\trequire.NotNil(t, binding.Links)\n\t\t\t\trequire.NotNil(t, binding.Links[\"inherited-from\"])\n\t\t\t} else {\n\t\t\t\trequire.Nil(t, binding.Links)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestWorkspacesProjectRemoteState(t *testing.T) {\n\tskipUnlessEnterprise(t)\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\t// create project\n\tprojTest, projTestCleanup := createProject(t, client, orgTest)\n\tt.Cleanup(projTestCleanup)\n\n\t// create workspace in first project\n\twTest, wTestCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\tName:              String(randomString(t)),\n\t\tProject:           projTest,\n\t\tGlobalRemoteState: Bool(false),\n\t})\n\tt.Cleanup(wTestCleanup)\n\n\tt.Run(\"successfully returns remote state consumer list\", func(t *testing.T) {\n\t\t// create consumer workspace in the test project\n\t\twTestConsumer1, wTestCleanupConsumer1 := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName:    String(randomString(t)),\n\t\t\tProject: projTest,\n\t\t})\n\t\tt.Cleanup(wTestCleanupConsumer1)\n\n\t\t// create another project\n\t\tprojTest2, projTest2Cleanup := createProject(t, client, orgTest)\n\t\tt.Cleanup(projTest2Cleanup)\n\n\t\t// create consumer workspace in the other project\n\t\twTestConsumer2, wTestCleanupConsumer2 := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{\n\t\t\tName:    String(randomString(t)),\n\t\t\tProject: projTest2,\n\t\t})\n\t\tt.Cleanup(wTestCleanupConsumer2)\n\n\t\terr := client.Workspaces.AddRemoteStateConsumers(ctx, wTest.ID, WorkspaceAddRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{wTestConsumer1, wTestConsumer2},\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\trsc, err := client.Workspaces.ListRemoteStateConsumers(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\t// all the consumer workspaces are in the list\n\t\tassert.Equal(t, 2, len(rsc.Items))\n\t\tassert.Contains(t, rsc.Items, wTestConsumer1, wTestConsumer2)\n\n\t\t// Update workspace to allow project remote state sharing\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tProjectRemoteState: Bool(true),\n\t\t}\n\t\twTest, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)\n\t\trequire.NoError(t, err)\n\n\t\trsc, err = client.Workspaces.ListRemoteStateConsumers(ctx, wTest.ID, nil)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, 1, len(rsc.Items))\n\t\tassert.Contains(t, rsc.Items, wTestConsumer1)\n\t\tassert.NotContains(t, rsc.Items, wTestConsumer2)\n\t})\n\n\tt.Run(\"with invalid options\", func(t *testing.T) {\n\t\terr := client.Workspaces.AddRemoteStateConsumers(ctx, wTest.ID, WorkspaceAddRemoteStateConsumersOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrWorkspacesRequired.Error())\n\n\t\terr = client.Workspaces.AddRemoteStateConsumers(ctx, wTest.ID, WorkspaceAddRemoteStateConsumersOptions{\n\t\t\tWorkspaces: []*Workspace{},\n\t\t})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrWorkspaceMinLimit.Error())\n\n\t\t// Update workspace to allow project and global remote state sharing\n\t\toptions := WorkspaceUpdateOptions{\n\t\t\tProjectRemoteState: Bool(true),\n\t\t\tGlobalRemoteState:  Bool(true),\n\t\t}\n\n\t\t_, err = client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)\n\t\trequire.Error(t, err)\n\t\tassert.ErrorContains(t, err, ErrInvalidRemoteStateOptions.Error())\n\t})\n\n\tt.Run(\"without a valid workspace ID\", func(t *testing.T) {\n\t\terr := client.Workspaces.AddRemoteStateConsumers(ctx, badIdentifier, WorkspaceAddRemoteStateConsumersOptions{})\n\t\trequire.Error(t, err)\n\t\tassert.EqualError(t, err, ErrInvalidWorkspaceID.Error())\n\t})\n}\n"
  },
  {
    "path": "workspace_resources.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation.\nvar _ WorkspaceResources = (*workspaceResources)(nil)\n\n// WorkspaceResources describes all the workspace resources related methods that the Terraform\n// Enterprise API supports.\n//\n// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspace-resources\ntype WorkspaceResources interface {\n\t// List all the workspaces resources within a workspace\n\tList(ctx context.Context, workspaceID string, options *WorkspaceResourceListOptions) (*WorkspaceResourcesList, error)\n}\n\n// workspaceResources implements WorkspaceResources.\ntype workspaceResources struct {\n\tclient *Client\n}\n\n// WorkspaceResourcesList represents a list of workspace resources.\ntype WorkspaceResourcesList struct {\n\t*Pagination\n\tItems []*WorkspaceResource\n}\n\n// WorkspaceResource represents a Terraform Enterprise workspace resource.\ntype WorkspaceResource struct {\n\tID                       string  `jsonapi:\"primary,resources\"`\n\tAddress                  string  `jsonapi:\"attr,address\"`\n\tName                     string  `jsonapi:\"attr,name\"`\n\tCreatedAt                string  `jsonapi:\"attr,created-at\"`\n\tUpdatedAt                string  `jsonapi:\"attr,updated-at\"`\n\tModule                   string  `jsonapi:\"attr,module\"`\n\tProvider                 string  `jsonapi:\"attr,provider\"`\n\tProviderType             string  `jsonapi:\"attr,provider-type\"`\n\tModifiedByStateVersionID string  `jsonapi:\"attr,modified-by-state-version-id\"`\n\tNameIndex                *string `jsonapi:\"attr,name-index\"`\n}\n\n// WorkspaceResourceListOptions represents the options for listing workspace resources.\ntype WorkspaceResourceListOptions struct {\n\tListOptions\n}\n\n// List all the workspaces resources within a workspace\nfunc (s *workspaceResources) List(ctx context.Context, workspaceID string, options *WorkspaceResourceListOptions) (*WorkspaceResourcesList, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/resources\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twl := &WorkspaceResourcesList{}\n\terr = req.Do(ctx, wl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn wl, nil\n}\n\nfunc (o *WorkspaceResourceListOptions) valid() error {\n\treturn nil\n}\n"
  },
  {
    "path": "workspace_resources_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestWorkspaceResourcesList(t *testing.T) {\n\tt.Parallel()\n\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tt.Cleanup(orgTestCleanup)\n\n\twTest, wTestCleanup := createWorkspace(t, client, orgTest)\n\tt.Cleanup(wTestCleanup)\n\n\tsvTest, svTestCleanup := createStateVersion(t, client, 0, wTest)\n\tt.Cleanup(svTestCleanup)\n\n\t// give HCP Terraform some time to process the statefile and extract the outputs.\n\twaitForSVOutputs(t, client, svTest.ID)\n\n\tt.Run(\"without list options\", func(t *testing.T) {\n\t\t// Retry while waiting for workspace resources to be populated.\n\t\t// This can take some time after the state version is created, so we\n\t\t// retry the list call until we get non-empty results.\n\t\trs, err := retryPatientlyIf(\n\t\t\tfunc() (any, error) {\n\t\t\t\treturn client.WorkspaceResources.List(ctx, wTest.ID, nil)\n\t\t\t},\n\t\t\tfunc(rs *WorkspaceResourcesList) bool {\n\t\t\t\treturn len(rs.Items) == 0\n\t\t\t},\n\t\t)\n\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rs)\n\t\trequire.NotNil(t, rs.Items)\n\t\trequire.NotEmpty(t, rs.Items)\n\t\tassert.Equal(t, 1, len(rs.Items))\n\t\tassert.Equal(t, 1, rs.CurrentPage)\n\t\tassert.Equal(t, 1, rs.TotalCount)\n\n\t\tassert.Equal(t, \"media_bucket.aws_s3_bucket_public_access_block.this[0]\", rs.Items[0].Address)\n\t\tassert.Equal(t, \"this\", rs.Items[0].Name)\n\t\tassert.Equal(t, \"media_bucket\", rs.Items[0].Module)\n\t\tassert.Equal(t, \"hashicorp/aws\", rs.Items[0].Provider)\n\t})\n\tt.Run(\"with list options\", func(t *testing.T) {\n\t\trs, err := client.WorkspaceResources.List(ctx, wTest.ID, &WorkspaceResourceListOptions{\n\t\t\tListOptions: ListOptions{\n\t\t\t\tPageNumber: 999,\n\t\t\t\tPageSize:   100,\n\t\t\t},\n\t\t})\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, rs.Items)\n\t\tassert.Equal(t, 999, rs.CurrentPage)\n\t\tassert.Equal(t, 1, rs.TotalCount)\n\t})\n}\n"
  },
  {
    "path": "workspace_run_task.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// Compile-time proof of interface implementation\nvar _ WorkspaceRunTasks = (*workspaceRunTasks)(nil)\n\n// WorkspaceRunTasks represent all the run task related methods in the context of a workspace that the HCP Terraform and Terraform Enterprise API supports.\ntype WorkspaceRunTasks interface {\n\t// Add a run task to a workspace\n\tCreate(ctx context.Context, workspaceID string, options WorkspaceRunTaskCreateOptions) (*WorkspaceRunTask, error)\n\n\t// List all run tasks for a workspace\n\tList(ctx context.Context, workspaceID string, options *WorkspaceRunTaskListOptions) (*WorkspaceRunTaskList, error)\n\n\t// Read a workspace run task by ID\n\tRead(ctx context.Context, workspaceID string, workspaceTaskID string) (*WorkspaceRunTask, error)\n\n\t// Update a workspace run task by ID\n\tUpdate(ctx context.Context, workspaceID string, workspaceTaskID string, options WorkspaceRunTaskUpdateOptions) (*WorkspaceRunTask, error)\n\n\t// Delete a workspace's run task by ID\n\tDelete(ctx context.Context, workspaceID string, workspaceTaskID string) error\n}\n\n// workspaceRunTasks implements WorkspaceRunTasks\ntype workspaceRunTasks struct {\n\tclient *Client\n}\n\n// WorkspaceRunTask represents a HCP Terraform or Terraform Enterprise run task that belongs to a workspace\ntype WorkspaceRunTask struct {\n\tID               string               `jsonapi:\"primary,workspace-tasks\"`\n\tEnforcementLevel TaskEnforcementLevel `jsonapi:\"attr,enforcement-level\"`\n\t// Deprecated: Use Stages property instead.\n\tStage  Stage   `jsonapi:\"attr,stage\"`\n\tStages []Stage `jsonapi:\"attr,stages\"`\n\n\tRunTask   *RunTask   `jsonapi:\"relation,task\"`\n\tWorkspace *Workspace `jsonapi:\"relation,workspace\"`\n}\n\n// WorkspaceRunTaskList represents a list of workspace run tasks\ntype WorkspaceRunTaskList struct {\n\t*Pagination\n\tItems []*WorkspaceRunTask\n}\n\n// WorkspaceRunTaskListOptions represents the set of options for listing workspace run tasks\ntype WorkspaceRunTaskListOptions struct {\n\tListOptions\n}\n\n// WorkspaceRunTaskCreateOptions represents the set of options for creating a workspace run task\ntype WorkspaceRunTaskCreateOptions struct {\n\tType string `jsonapi:\"primary,workspace-tasks\"`\n\t// Required: The enforcement level for a run task\n\tEnforcementLevel TaskEnforcementLevel `jsonapi:\"attr,enforcement-level\"`\n\t// Required: The run task to attach to the workspace\n\tRunTask *RunTask `jsonapi:\"relation,task\"`\n\t// Deprecated: Use Stages property instead.\n\tStage *Stage `jsonapi:\"attr,stage,omitempty\"`\n\t// Optional: The stage to run the task in\n\tStages *[]Stage `jsonapi:\"attr,stages,omitempty\"`\n}\n\n// WorkspaceRunTaskUpdateOptions represent the set of options for updating a workspace run task.\ntype WorkspaceRunTaskUpdateOptions struct {\n\tType             string               `jsonapi:\"primary,workspace-tasks\"`\n\tEnforcementLevel TaskEnforcementLevel `jsonapi:\"attr,enforcement-level,omitempty\"`\n\t// Deprecated: Use Stages property instead.\n\tStage *Stage `jsonapi:\"attr,stage,omitempty\"`\n\t// Optional: The stage to run the task in\n\tStages *[]Stage `jsonapi:\"attr,stages,omitempty\"`\n}\n\n// List all run tasks attached to a workspace\nfunc (s *workspaceRunTasks) List(ctx context.Context, workspaceID string, options *WorkspaceRunTaskListOptions) (*WorkspaceRunTaskList, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/tasks\", url.PathEscape(workspaceID))\n\treq, err := s.client.NewRequest(\"GET\", u, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trl := &internalWorkspaceRunTaskList{}\n\terr = req.Do(ctx, rl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rl.ToWorkspaceRunTaskList(), nil\n}\n\n// Read a workspace run task by ID\nfunc (s *workspaceRunTasks) Read(ctx context.Context, workspaceID, workspaceTaskID string) (*WorkspaceRunTask, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tif !validStringID(&workspaceTaskID) {\n\t\treturn nil, ErrInvalidWorkspaceRunTaskID\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"workspaces/%s/tasks/%s\",\n\t\turl.PathEscape(workspaceID),\n\t\turl.PathEscape(workspaceTaskID),\n\t)\n\treq, err := s.client.NewRequest(\"GET\", u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twr := &internalWorkspaceRunTask{}\n\terr = req.Do(ctx, wr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn wr.ToWorkspaceRunTask(), nil\n}\n\n// Create is used to attach a run task to a workspace, or in other words: create a workspace run task. The run task must exist in the workspace's organization.\nfunc (s *workspaceRunTasks) Create(ctx context.Context, workspaceID string, options WorkspaceRunTaskCreateOptions) (*WorkspaceRunTask, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tif err := options.valid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := fmt.Sprintf(\"workspaces/%s/tasks\", workspaceID)\n\treq, err := s.client.NewRequest(\"POST\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twr := &internalWorkspaceRunTask{}\n\terr = req.Do(ctx, wr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn wr.ToWorkspaceRunTask(), nil\n}\n\n// Update an existing workspace run task by ID\nfunc (s *workspaceRunTasks) Update(ctx context.Context, workspaceID, workspaceTaskID string, options WorkspaceRunTaskUpdateOptions) (*WorkspaceRunTask, error) {\n\tif !validStringID(&workspaceID) {\n\t\treturn nil, ErrInvalidWorkspaceID\n\t}\n\n\tif !validStringID(&workspaceTaskID) {\n\t\treturn nil, ErrInvalidWorkspaceRunTaskID\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"workspaces/%s/tasks/%s\",\n\t\turl.PathEscape(workspaceID),\n\t\turl.PathEscape(workspaceTaskID),\n\t)\n\treq, err := s.client.NewRequest(\"PATCH\", u, &options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twr := &internalWorkspaceRunTask{}\n\terr = req.Do(ctx, wr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn wr.ToWorkspaceRunTask(), nil\n}\n\n// Delete a workspace run task by ID\nfunc (s *workspaceRunTasks) Delete(ctx context.Context, workspaceID, workspaceTaskID string) error {\n\tif !validStringID(&workspaceID) {\n\t\treturn ErrInvalidWorkspaceID\n\t}\n\n\tif !validStringID(&workspaceTaskID) {\n\t\treturn ErrInvalidWorkspaceRunTaskType\n\t}\n\n\tu := fmt.Sprintf(\n\t\t\"workspaces/%s/tasks/%s\",\n\t\turl.PathEscape(workspaceID),\n\t\turl.PathEscape(workspaceTaskID),\n\t)\n\treq, err := s.client.NewRequest(\"DELETE\", u, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn req.Do(ctx, nil)\n}\n\nfunc (o *WorkspaceRunTaskCreateOptions) valid() error {\n\tif o.RunTask.ID == \"\" {\n\t\treturn ErrInvalidRunTaskID\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "workspace_run_task_integration_test.go",
    "content": "// Copyright IBM Corp. 2018, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage tfe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestWorkspaceRunTasksCreate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\tt.Run(\"attach run task to workspace\", func(t *testing.T) {\n\t\ts := []Stage{PrePlan, PostPlan}\n\t\twr, err := client.WorkspaceRunTasks.Create(ctx, wkspaceTest.ID, WorkspaceRunTaskCreateOptions{\n\t\t\tEnforcementLevel: Mandatory,\n\t\t\tStages:           &s,\n\t\t\tRunTask:          runTaskTest,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\terr = client.WorkspaceRunTasks.Delete(ctx, wkspaceTest.ID, wr.ID)\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\n\t\tassert.NotEmpty(t, wr.ID)\n\t\tassert.Equal(t, Mandatory, wr.EnforcementLevel)\n\t\tassert.Equal(t, s[0], wr.Stage)\n\t\tassert.Equal(t, s, wr.Stages)\n\n\t\tt.Run(\"ensure run task is deserialized properly\", func(t *testing.T) {\n\t\t\tassert.NotNil(t, wr.RunTask)\n\t\t\tassert.NotEmpty(t, wr.RunTask.ID)\n\t\t})\n\t})\n}\n\nfunc TestWorkspaceRunTasksCreateDeprecated(t *testing.T) {\n\tt.Parallel()\n\t// This test uses the deprecate `stage` attribute\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\tt.Run(\"attach run task to workspace\", func(t *testing.T) {\n\t\ts := PrePlan\n\t\twr, err := client.WorkspaceRunTasks.Create(ctx, wkspaceTest.ID, WorkspaceRunTaskCreateOptions{\n\t\t\tEnforcementLevel: Mandatory,\n\t\t\tStage:            &s,\n\t\t\tRunTask:          runTaskTest,\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\terr = client.WorkspaceRunTasks.Delete(ctx, wkspaceTest.ID, wr.ID)\n\t\t\trequire.NoError(t, err)\n\t\t}()\n\n\t\tassert.NotEmpty(t, wr.ID)\n\t\tassert.Equal(t, wr.EnforcementLevel, Mandatory)\n\t\tassert.Equal(t, wr.Stage, s)\n\n\t\tt.Run(\"ensure run task is deserialized properly\", func(t *testing.T) {\n\t\t\tassert.NotNil(t, wr.RunTask)\n\t\t\tassert.NotEmpty(t, wr.RunTask.ID)\n\t\t})\n\t})\n}\n\nfunc TestWorkspaceRunTasksList(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\trunTaskTest1, runTaskTest1Cleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTest1Cleanup()\n\n\trunTaskTest2, runTaskTest2Cleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTest2Cleanup()\n\n\t_, wrTaskTest1Cleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest1)\n\tdefer wrTaskTest1Cleanup()\n\n\t_, wrTaskTest2Cleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest2)\n\tdefer wrTaskTest2Cleanup()\n\n\tt.Run(\"with no params\", func(t *testing.T) {\n\t\twrTaskList, err := client.WorkspaceRunTasks.List(ctx, wkspaceTest.ID, nil)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, wrTaskList.Items)\n\t\tassert.Equal(t, len(wrTaskList.Items), 2)\n\t\tassert.NotEmpty(t, wrTaskList.Items[0].ID)\n\t\tassert.NotEmpty(t, wrTaskList.Items[0].EnforcementLevel)\n\t})\n}\n\nfunc TestWorkspaceRunTasksRead(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\twrTaskTest, wrTaskTestCleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest)\n\tdefer wrTaskTestCleanup()\n\n\tt.Run(\"by ID\", func(t *testing.T) {\n\t\twr, err := client.WorkspaceRunTasks.Read(ctx, wkspaceTest.ID, wrTaskTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, wrTaskTest.ID, wr.ID)\n\t\tassert.Equal(t, wrTaskTest.EnforcementLevel, wr.EnforcementLevel)\n\n\t\tt.Run(\"ensure run task is deserialized\", func(t *testing.T) {\n\t\t\tassert.Equal(t, wr.RunTask.ID, runTaskTest.ID)\n\t\t})\n\n\t\tt.Run(\"ensure workspace is deserialized\", func(t *testing.T) {\n\t\t\tassert.Equal(t, wr.Workspace.ID, wkspaceTest.ID)\n\t\t})\n\t})\n}\n\nfunc TestWorkspaceRunTasksUpdate(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\twrTaskTest, wrTaskTestCleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest)\n\tdefer wrTaskTestCleanup()\n\n\tt.Run(\"update task\", func(t *testing.T) {\n\t\tstages := []Stage{PrePlan, PostPlan}\n\t\twr, err := client.WorkspaceRunTasks.Update(ctx, wkspaceTest.ID, wrTaskTest.ID, WorkspaceRunTaskUpdateOptions{\n\t\t\tEnforcementLevel: Mandatory,\n\t\t\tStages:           &stages,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\twr, err = client.WorkspaceRunTasks.Read(ctx, wkspaceTest.ID, wr.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, Mandatory, wr.EnforcementLevel)\n\t\tassert.Equal(t, stages, wr.Stages)\n\t\tassert.Equal(t, PrePlan, wr.Stage)\n\t})\n}\n\nfunc TestWorkspaceRunTasksUpdateDeprecated(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\twrTaskTest, wrTaskTestCleanup := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest)\n\tdefer wrTaskTestCleanup()\n\n\tt.Run(\"update task\", func(t *testing.T) {\n\t\tstage := PrePlan\n\t\twr, err := client.WorkspaceRunTasks.Update(ctx, wkspaceTest.ID, wrTaskTest.ID, WorkspaceRunTaskUpdateOptions{\n\t\t\tEnforcementLevel: Mandatory,\n\t\t\tStage:            &stage,\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\twr, err = client.WorkspaceRunTasks.Read(ctx, wkspaceTest.ID, wr.ID)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, wr.EnforcementLevel, Mandatory)\n\t\tassert.Equal(t, wr.Stage, PrePlan)\n\t})\n}\n\nfunc TestWorkspaceRunTasksDelete(t *testing.T) {\n\tt.Parallel()\n\tclient := testClient(t)\n\tctx := context.Background()\n\n\torgTest, orgTestCleanup := createOrganization(t, client)\n\tdefer orgTestCleanup()\n\n\tupgradeOrganizationSubscription(t, client, orgTest)\n\n\twkspaceTest, wkspaceTestCleanup := createWorkspace(t, client, orgTest)\n\tdefer wkspaceTestCleanup()\n\n\trunTaskTest, runTaskTestCleanup := createRunTask(t, client, orgTest)\n\tdefer runTaskTestCleanup()\n\n\twrTaskTest, _ := createWorkspaceRunTask(t, client, wkspaceTest, runTaskTest)\n\n\tt.Run(\"with valid options\", func(t *testing.T) {\n\t\terr := client.WorkspaceRunTasks.Delete(ctx, wkspaceTest.ID, wrTaskTest.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = client.WorkspaceRunTasks.Read(ctx, wkspaceTest.ID, wrTaskTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the workspace run task does not exist\", func(t *testing.T) {\n\t\terr := client.WorkspaceRunTasks.Delete(ctx, wkspaceTest.ID, wrTaskTest.ID)\n\t\tassert.Equal(t, err, ErrResourceNotFound)\n\t})\n\n\tt.Run(\"when the workspace does not exist\", func(t *testing.T) {\n\t\terr := client.WorkspaceRunTasks.Delete(ctx, \"does-not-exist\", wrTaskTest.ID)\n\t\tassert.EqualError(t, err, ErrResourceNotFound.Error())\n\t})\n}\n"
  }
]