├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── renovate.json5 ├── stale.yml └── workflows │ ├── ci.yml │ ├── publish-provider-packages.yaml │ ├── stale.yml │ ├── tag.yaml │ └── uptest-trigger.yaml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── NOTICE ├── OWNERS.md ├── README.md ├── apis ├── generate.go ├── terraform.go └── v1beta1 │ ├── doc.go │ ├── register.go │ ├── storeconfig_types.go │ ├── types.go │ ├── workspace_types.go │ ├── zz_generated.deepcopy.go │ ├── zz_generated.managed.go │ ├── zz_generated.managedlist.go │ ├── zz_generated.pc.go │ ├── zz_generated.pcu.go │ └── zz_generated.pculist.go ├── catalog-info.yaml ├── cluster ├── images │ └── provider-terraform │ │ ├── .gitconfig │ │ ├── Dockerfile │ │ └── Makefile └── test │ └── setup.sh ├── cmd └── provider │ └── main.go ├── docs └── monolith │ ├── Configuration.md │ ├── Quickstart.md │ └── index.json ├── examples ├── .terraformrc ├── aws-eks-irsa-setup.yaml ├── creationblocker │ ├── README.md │ ├── composition.yaml │ ├── definition.yaml │ ├── xsubnetblocking.yaml │ └── xsubnetnotblocking.yaml ├── environment │ ├── envconfigmap.yaml │ ├── envsecret.yaml │ └── workspace-inline-env-aws.yaml ├── importer │ ├── README.md │ ├── composition.yaml │ ├── definition.yaml │ └── xsubnet.yaml ├── install.yaml ├── observe-only-composition │ ├── README.md │ ├── composition.yaml │ ├── definition.yaml │ └── xsubnet.yaml ├── providerconfig-aws.yaml ├── providerconfig-azure.yaml ├── providerconfig-backend-file.yaml ├── providerconfig-terraformrc.yaml ├── providerconfig.yaml ├── transition │ ├── 00-mr-tf-workspace │ │ ├── workspace-inline.yaml │ │ └── workspace-remote.yaml │ ├── 01-composition-tf-only │ │ ├── composition.yaml │ │ └── subnet-tf.yaml │ ├── 02-composition-tf-and-native │ │ ├── composition.yaml │ │ └── subnet-mixed.yaml │ ├── 03-composition-native-only │ │ ├── composition.yaml │ │ └── subnet-native.yaml │ ├── README.md │ └── definition.yaml ├── workspace-enable-logging.yaml ├── workspace-inline-aws.yaml ├── workspace-inline.yaml ├── workspace-random-generator.yaml └── workspace-remote.yaml ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── internal ├── controller │ ├── config │ │ └── config.go │ ├── doc.go │ ├── features │ │ └── features.go │ ├── terraform.go │ └── workspace │ │ ├── workspace.go │ │ └── workspace_test.go ├── terraform │ ├── terraform.go │ ├── terraform_harness_test.go │ ├── terraform_test.go │ └── testdata │ │ ├── invalidmodule │ │ └── main.tf │ │ ├── nullmodule │ │ ├── main.tf │ │ └── terraform.tfstate │ │ ├── outputmodule │ │ ├── main.tf │ │ └── terraform.tfstate │ │ └── validmodule │ │ └── main.tf └── workdir │ ├── workdir.go │ └── workdir_test.go ├── package ├── crds │ ├── tf.upbound.io_providerconfigs.yaml │ ├── tf.upbound.io_providerconfigusages.yaml │ ├── tf.upbound.io_storeconfigs.yaml │ └── tf.upbound.io_workspaces.yaml └── crossplane.yaml └── scripts └── check-examples.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Help us diagnose and fix bugs in this provider 4 | labels: bug,needs:triage 5 | title: 6 | --- 7 | 12 | 13 | ### What happened? 14 | 18 | 19 | ### How can we reproduce it? 20 | 25 | 26 | ### What environment did it happen in? 27 | 28 | * Crossplane Version: 29 | * Provider Version: 30 | * Kubernetes Version: 31 | * Kubernetes Distribution: 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Help us make this provider more useful 4 | labels: enhancement,needs:triage 5 | --- 6 | 7 | 13 | 14 | ### What problem are you facing? 15 | 16 | 21 | 22 | ### What could help solve your problem? 23 | 24 | 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ### Description of your changes 8 | 9 | 17 | 18 | Fixes # 19 | 20 | I have: 21 | 22 | - [ ] Read and followed Crossplane's [contribution process]. 23 | - [ ] Run `make reviewable` to ensure this PR is ready for review. 24 | 25 | ### How has this code been tested 26 | 27 | 32 | 33 | [contribution process]: https://git.io/fj2m9 34 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: 'https://docs.renovatebot.com/renovate-schema.json', 3 | extends: [ 4 | 'config:recommended', 5 | 'helpers:pinGitHubActionDigests', 6 | ':semanticCommits', 7 | ], 8 | rebaseWhen: 'conflicted', 9 | prConcurrentLimit: 5, 10 | baseBranches: [ 11 | 'main', 12 | ], 13 | ignorePaths: [ 14 | 'design/**', 15 | ], 16 | postUpdateOptions: [ 17 | 'gomodTidy', 18 | ], 19 | labels: [ 20 | 'automated', 21 | ], 22 | customManagers: [ 23 | { 24 | customType: 'regex', 25 | description: 'Bump Go version used in workflows', 26 | fileMatch: [ 27 | '^\\.github\\/workflows\\/[^/]+\\.ya?ml$', 28 | ], 29 | matchStrings: [ 30 | "GO_VERSION: '(?.*?)'\\n", 31 | ], 32 | datasourceTemplate: 'golang-version', 33 | depNameTemplate: 'golang', 34 | }, 35 | { 36 | customType: 'regex', 37 | description: 'Bump golangci-lint version in workflows and the Makefile', 38 | fileMatch: [ 39 | '^\\.github\\/workflows\\/[^/]+\\.ya?ml$', 40 | '^Makefile$', 41 | ], 42 | matchStrings: [ 43 | "GOLANGCI_VERSION: 'v(?.*?)'\\n", 44 | 'GOLANGCILINT_VERSION = (?.*?)\\n', 45 | ], 46 | datasourceTemplate: 'github-tags', 47 | depNameTemplate: 'golangci/golangci-lint', 48 | extractVersionTemplate: '^v(?.*)$', 49 | }, 50 | { 51 | customType: 'regex', 52 | description: 'Bump helm version in the Makefile', 53 | fileMatch: [ 54 | '^Makefile$', 55 | ], 56 | matchStrings: [ 57 | 'HELM3_VERSION = (?.*?)\\n', 58 | ], 59 | datasourceTemplate: 'github-tags', 60 | depNameTemplate: 'helm/helm', 61 | }, 62 | { 63 | customType: 'regex', 64 | description: 'Bump kind version in the Makefile', 65 | fileMatch: [ 66 | '^Makefile$', 67 | ], 68 | matchStrings: [ 69 | 'KIND_VERSION = (?.*?)\\n', 70 | ], 71 | datasourceTemplate: 'github-tags', 72 | depNameTemplate: 'kubernetes-sigs/kind', 73 | }, 74 | ], 75 | vulnerabilityAlerts: { 76 | enabled: true, 77 | }, 78 | osvVulnerabilityAlerts: true, 79 | packageRules: [ 80 | { 81 | description: 'Only get Docker image updates every 2 weeks to reduce noise', 82 | matchDatasources: [ 83 | 'docker', 84 | ], 85 | schedule: [ 86 | 'every 2 week on monday', 87 | ], 88 | enabled: true, 89 | }, 90 | { 91 | description: 'Ignore k8s.io/client-go older versions, they switched to semantic version and old tags are still available in the repo', 92 | matchDatasources: [ 93 | 'go', 94 | ], 95 | matchDepNames: [ 96 | 'k8s.io/client-go', 97 | ], 98 | allowedVersions: '<1.0', 99 | }, 100 | { 101 | description: 'Ignore k8s dependencies, should be updated on crossplane-runtime', 102 | matchDatasources: [ 103 | 'go', 104 | ], 105 | enabled: false, 106 | matchPackageNames: [ 107 | 'k8s.io{/,}**', 108 | 'sigs.k8s.io{/,}**', 109 | ], 110 | }, 111 | { 112 | description: 'Only get dependency digest updates every month to reduce noise, except crossplane-runtime', 113 | matchDatasources: [ 114 | 'go', 115 | ], 116 | matchUpdateTypes: [ 117 | 'digest', 118 | ], 119 | extends: [ 120 | 'schedule:monthly', 121 | ], 122 | matchPackageNames: [ 123 | '!github.com/crossplane/crossplane-runtime', 124 | ], 125 | }, 126 | { 127 | description: "Ignore oss-fuzz, it's not using tags, we'll stick to master", 128 | matchDepTypes: [ 129 | 'action', 130 | ], 131 | matchDepNames: [ 132 | 'google/oss-fuzz', 133 | ], 134 | enabled: false, 135 | }, 136 | ], 137 | } 138 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 90 5 | 6 | # Number of days of inactivity before a stale Issue or Pull Request is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 7 9 | 10 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 11 | exemptLabels: 12 | - security 13 | 14 | # Set to true to ignore issues in a project (defaults to false) 15 | exemptProjects: false 16 | 17 | # Set to true to ignore issues in a milestone (defaults to false) 18 | exemptMilestones: false 19 | 20 | # Label to use when marking as stale 21 | staleLabel: wontfix 22 | 23 | # Comment to post when marking as stale. Set to `false` to disable 24 | markComment: > 25 | This issue has been automatically marked as stale because it has not had 26 | recent activity. It will be closed if no further activity occurs. Thank you 27 | for your contributions. 28 | 29 | # Comment to post when closing a stale Issue or Pull Request. 30 | closeComment: > 31 | This issue has been automatically closed due to inactivity. Please re-open 32 | if this still requires investigation. 33 | 34 | # Limit the number of actions per hour, from 1-30. Default is 30 35 | limitPerRun: 30 36 | 37 | # Limit to only `issues` or `pulls` 38 | only: issues 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 The Crossplane Authors 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | name: CI 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | - release-* 12 | pull_request: {} 13 | workflow_dispatch: {} 14 | 15 | env: 16 | GO_VERSION: "1.22.12" 17 | 18 | jobs: 19 | detect-noop: 20 | runs-on: ubuntu-latest 21 | outputs: 22 | noop: ${{ steps.noop.outputs.should_skip }} 23 | steps: 24 | - name: Detect No-op Changes 25 | id: noop 26 | uses: fkirc/skip-duplicate-actions@f75f66ce1886f00957d99748a42c724f4330bdcf # v5.3.1 27 | with: 28 | paths_ignore: '["**.md", "**.png", "**.jpg"]' 29 | do_not_skip: '["workflow_dispatch", "schedule", "push"]' 30 | 31 | report-breaking-changes: 32 | runs-on: ubuntu-latest 33 | needs: detect-noop 34 | if: needs.detect-noop.outputs.noop != 'true' 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 38 | with: 39 | submodules: true 40 | 41 | - name: Get modified CRDs 42 | id: modified-crds 43 | uses: tj-actions/changed-files@823fcebdb31bb35fdf2229d9f769b400309430d0 # v46.0.3 (breaks the 'Report native schema version changes' step on newer versions) 44 | with: 45 | files: | 46 | package/crds/** 47 | 48 | - name: Report breaking CRD OpenAPI v3 schema changes 49 | if: steps.modified-crds.outputs.any_changed == 'true' 50 | env: 51 | MODIFIED_CRD_LIST: ${{ steps.modified-crds.outputs.all_changed_files }} 52 | run: | 53 | make crddiff 54 | 55 | lint: 56 | runs-on: ubuntu-latest 57 | needs: detect-noop 58 | if: needs.detect-noop.outputs.noop != 'true' 59 | steps: 60 | - name: Cleanup Disk 61 | uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 62 | with: 63 | large-packages: false 64 | swap-storage: false 65 | 66 | - name: Checkout 67 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 68 | with: 69 | submodules: true 70 | 71 | - name: Setup Go 72 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 73 | with: 74 | go-version: ${{ env.GO_VERSION }} 75 | 76 | - name: Find the Analysis Cache 77 | id: analysis_cache 78 | run: | 79 | echo "analysis_cache=$HOME/.cache/golangci-lint" >> $GITHUB_OUTPUT && \ 80 | echo "analysis_cache_key=$(make go.lint.analysiskey)" >> $GITHUB_OUTPUT && \ 81 | echo "analysis_cache_key_int=$(make go.lint.analysiskey-interval)" >> $GITHUB_OUTPUT 82 | 83 | - name: Cache Linter Analysis 84 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 85 | id: cache-analysis 86 | with: 87 | path: ${{ steps.analysis_cache.outputs.analysis_cache }} 88 | key: ${{ steps.analysis_cache.outputs.analysis_cache_key }} 89 | restore-keys: | 90 | ${{ steps.analysis_cache.outputs.analysis_cache_key_int }} 91 | 92 | - name: Vendor Dependencies 93 | run: make vendor vendor.check 94 | 95 | - name: Lint 96 | env: 97 | GOLANGCI_LINT_CACHE: ${{ steps.go_cache.outputs.analysis_cache }} 98 | SKIP_LINTER_ANALYSIS: false 99 | RUN_BUILDTAGGER: true 100 | GOGC: "50" 101 | run: make lint 102 | 103 | check-diff: 104 | runs-on: ubuntu-latest 105 | needs: detect-noop 106 | if: needs.detect-noop.outputs.noop != 'true' 107 | steps: 108 | - name: Cleanup Disk 109 | uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 110 | with: 111 | large-packages: false 112 | swap-storage: false 113 | 114 | - name: Checkout 115 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 116 | with: 117 | submodules: true 118 | 119 | - name: Setup Go 120 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 121 | with: 122 | go-version: ${{ env.GO_VERSION }} 123 | 124 | - name: Install goimports 125 | run: go install golang.org/x/tools/cmd/goimports 126 | 127 | - name: Vendor Dependencies 128 | run: make vendor vendor.check 129 | 130 | - name: Check Diff 131 | id: check-diff 132 | run: | 133 | mkdir _output 134 | make check-diff 135 | env: 136 | # check-diff depends on the generate Make target, and we would like 137 | # to save a skipped resource list 138 | SKIPPED_RESOURCES_CSV: ../_output/skipped_resources.csv 139 | 140 | - name: Show diff 141 | if: failure() && steps.check-diff.outcome == 'failure' 142 | run: git diff 143 | 144 | - name: Publish skipped resources CSV to Github 145 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 146 | with: 147 | name: skipped_resources 148 | path: _output/skipped_resources.csv 149 | 150 | unit-tests: 151 | runs-on: ubuntu-latest 152 | needs: detect-noop 153 | if: needs.detect-noop.outputs.noop != 'true' 154 | steps: 155 | - name: Cleanup Disk 156 | uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 157 | with: 158 | large-packages: false 159 | swap-storage: false 160 | 161 | - name: Checkout 162 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 163 | with: 164 | submodules: true 165 | 166 | - name: Fetch History 167 | run: git fetch --prune --unshallow 168 | 169 | - name: Setup Go 170 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 171 | with: 172 | go-version: ${{ env.GO_VERSION }} 173 | 174 | - name: Vendor Dependencies 175 | run: make vendor vendor.check 176 | 177 | - name: Run Unit Tests 178 | run: make -j2 test 179 | 180 | local-deploy: 181 | runs-on: ubuntu-latest 182 | needs: detect-noop 183 | if: needs.detect-noop.outputs.noop != 'true' 184 | steps: 185 | - name: Cleanup Disk 186 | uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 187 | with: 188 | large-packages: false 189 | swap-storage: false 190 | 191 | - name: Checkout 192 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 193 | with: 194 | submodules: true 195 | 196 | - name: Fetch History 197 | run: git fetch --prune --unshallow 198 | 199 | - name: Setup Go 200 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 201 | with: 202 | go-version: ${{ env.GO_VERSION }} 203 | 204 | - name: Vendor Dependencies 205 | run: make vendor vendor.check 206 | 207 | - name: Deploying locally built provider package 208 | run: make local-deploy 209 | 210 | check-examples: 211 | runs-on: ubuntu-latest 212 | needs: detect-noop 213 | if: ${{ needs.detect-noop.outputs.noop != 'true' }} 214 | 215 | steps: 216 | - name: Checkout 217 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 218 | with: 219 | submodules: true 220 | 221 | - name: Check Example Manifests 222 | run: | 223 | ./scripts/check-examples.py package/crds examples -------------------------------------------------------------------------------- /.github/workflows/publish-provider-packages.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Provider Package 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: "Version string to use while publishing the package (e.g. v1.0.0-alpha.1)" 8 | default: '' 9 | required: false 10 | go-version: 11 | description: 'Go version to use if building needs to be done' 12 | default: '1.23' 13 | required: false 14 | 15 | jobs: 16 | publish-provider-package: 17 | uses: crossplane-contrib/provider-workflows/.github/workflows/publish-provider-non-family.yml@main 18 | with: 19 | repository: provider-terraform 20 | version: ${{ github.event.inputs.version }} 21 | go-version: ${{ github.event.inputs.go-version }} 22 | cleanup-disk: true 23 | secrets: 24 | GHCR_PAT: ${{ secrets.GITHUB_TOKEN }} 25 | XPKG_UPBOUND_TOKEN: ${{ secrets.XPKG_UPBOUND_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale Issues and PRs 2 | on: 3 | schedule: 4 | # Process new stale issues once a day. Folks can use /fresh for a fast un-stale 5 | # per the commands workflow. Run at 4:15 mostly as a somewhat unique time to 6 | # help correlate any issues with this workflow. 7 | - cron: '15 4 * * *' 8 | workflow_dispatch: {} 9 | 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | 14 | jobs: 15 | stale: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9 19 | with: 20 | # This action uses ~2 operations per stale issue per run to determine 21 | # whether it's still stale. It also uses 2-3 operations to mark an issue 22 | # stale or not. During steady state (no issues to mark stale, check, or 23 | # close) we seem to use less than 10 operations with ~350 issues and PRs 24 | # open. 25 | # 26 | # Our hourly rate-limit budget for all workflows that use GITHUB_TOKEN 27 | # is 1,000 requests per the below docs. 28 | # https://docs.github.com/en/rest/overview/resources-in-the-rest-api#requests-from-github-actions 29 | operations-per-run: 100 30 | # For issues: post a "warning" message after 90 days, then close if another 14 days pass without a response. 31 | days-before-issue-stale: 90 32 | days-before-issue-close: 14 33 | stale-issue-label: stale 34 | exempt-issue-labels: exempt-from-stale 35 | stale-issue-message: > 36 | This provider repo does not have enough maintainers to address every 37 | issue. Since there has been no activity in the last 90 days it is now marked 38 | as `stale`. It will be closed in 14 days if no further activity occurs. 39 | Leaving a comment **starting with** `/fresh` will mark this issue as not 40 | stale. 41 | close-issue-message: > 42 | This issue is being closed since there has been no activity for 14 days 43 | since marking it as `stale`. If you still need help, feel free to 44 | comment or reopen the issue! 45 | # For PRs: post a "warning" message after 90 days, then close if another 14 days pass without a response. 46 | days-before-pr-stale: 90 47 | days-before-pr-close: 14 48 | stale-pr-label: stale 49 | exempt-pr-labels: exempt-from-stale 50 | stale-pr-message: > 51 | This provider repo does not have enough maintainers to address every 52 | pull request. Since there has been no activity in the last 90 days it is 53 | now marked as `stale`. It will be closed in 14 days if no further activity 54 | occurs. Leaving a comment **starting with** `/fresh` will mark this issue 55 | as not stale. 56 | close-pr-message: > 57 | This pull request is being closed since there has been no activity for 14 days 58 | since marking it as `stale`. If you're still working on this, feel free 59 | to reopen the PR or create a new one! 60 | -------------------------------------------------------------------------------- /.github/workflows/tag.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 The Crossplane Authors 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | name: Tag 6 | 7 | on: 8 | workflow_dispatch: 9 | inputs: 10 | version: 11 | description: 'Release version (e.g. v0.1.0)' 12 | required: true 13 | message: 14 | description: 'Tag message' 15 | required: true 16 | 17 | jobs: 18 | create-tag: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 24 | 25 | - name: Create Tag 26 | uses: negz/create-tag@39bae1e0932567a58c20dea5a1a0d18358503320 # v1 27 | with: 28 | version: ${{ github.event.inputs.version }} 29 | message: ${{ github.event.inputs.message }} 30 | token: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/uptest-trigger.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 The Crossplane Authors 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | name: End to End Testing 6 | 7 | on: 8 | issue_comment: 9 | types: [created] 10 | 11 | env: 12 | GO_VERSION: "1.23.6" 13 | 14 | jobs: 15 | check-permissions: 16 | runs-on: ubuntu-latest 17 | outputs: 18 | permission: ${{ steps.check-permissions.outputs.permission }} 19 | steps: 20 | - name: Get Commenter Permissions 21 | id: check-permissions 22 | run: | 23 | echo "Trigger keyword: '/test-examples'" 24 | echo "Go version: ${{ env.GO_VERSION }}" 25 | 26 | REPO=${{ github.repository }} 27 | COMMENTER=${{ github.event.comment.user.login }} 28 | 29 | # Fetch the commenter's repo-level permission grant 30 | GRANTED=$(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ 31 | -H "Accept: application/vnd.github.v3+json" \ 32 | "https://api.github.com/repos/$REPO/collaborators/$COMMENTER/permission" | jq -r .permission) 33 | 34 | # Make it accessible in the workflow via a job output -- cannot use env 35 | echo "User $COMMENTER has $GRANTED permissions" 36 | echo "permission=$GRANTED" >> "$GITHUB_OUTPUT" 37 | 38 | get-example-list: 39 | needs: check-permissions 40 | if: ${{ (needs.check-permissions.outputs.permission == 'admin' || needs.check-permissions.outputs.permission == 'write') && github.event.issue.pull_request != null && contains(github.event.comment.body, '/test-examples')}} 41 | runs-on: ubuntu-latest 42 | outputs: 43 | example_list: ${{ steps.get-example-list-name.outputs.example-list }} 44 | example_hash: ${{ steps.get-example-list-name.outputs.example-hash }} 45 | 46 | steps: 47 | - name: Checkout 48 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 49 | with: 50 | submodules: true 51 | 52 | - name: Checkout PR 53 | id: checkout-pr 54 | env: 55 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | run: | 57 | gh pr checkout ${{ github.event.issue.number }} 58 | git submodule update --init --recursive 59 | OUTPUT=$(git log -1 --format='%H') 60 | echo "commit-sha=$OUTPUT" >> $GITHUB_OUTPUT 61 | 62 | - name: Prepare The Example List 63 | env: 64 | COMMENT: ${{ github.event.comment.body }} 65 | id: get-example-list-name 66 | run: | 67 | PATHS=$(echo $COMMENT | sed 's/^.*\/test-examples="//g' | cut -d '"' -f 1 | sed 's/,/ /g') 68 | EXAMPLE_LIST="" 69 | for P in $PATHS; do EXAMPLE_LIST="${EXAMPLE_LIST},$(find $P -name '*.yaml' | tr '\n' ',')"; done 70 | 71 | sudo apt-get -y install coreutils 72 | COUNT=$(echo ${EXAMPLE_LIST:1} | grep -o ".yaml" | wc -l) 73 | if [ $COUNT -gt 1 ]; then EXAMPLE_HASH=$(echo ${EXAMPLE_LIST} | md5sum | cut -f1 -d" "); else EXAMPLE_HASH=$(echo ${EXAMPLE_LIST:1} | sed 's/.$//'); fi 74 | 75 | echo "Examples: ${EXAMPLE_LIST:1}" 76 | echo "Example Hash: ${EXAMPLE_HASH}" 77 | 78 | echo "example-list=${EXAMPLE_LIST:1}" >> $GITHUB_OUTPUT 79 | echo "example-hash=${EXAMPLE_HASH}" >> $GITHUB_OUTPUT 80 | 81 | - name: Create Pending Status Check 82 | env: 83 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 84 | run: | 85 | gh api \ 86 | --method POST \ 87 | -H "Accept: application/vnd.github+json" \ 88 | /repos/${{ github.repository }}/statuses/${{ steps.checkout-pr.outputs.commit-sha }} \ 89 | -f state='pending' \ 90 | -f target_url='https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' \ 91 | -f description='Running...' \ 92 | -f context="Uptest-${{ steps.get-example-list-name.outputs.example-hash }}" 93 | 94 | uptest: 95 | needs: 96 | - check-permissions 97 | - get-example-list 98 | if: ${{ (needs.check-permissions.outputs.permission == 'admin' || needs.check-permissions.outputs.permission == 'write') && github.event.issue.pull_request != null && contains(github.event.comment.body, '/test-examples')}} 99 | runs-on: ubuntu-latest 100 | steps: 101 | - name: Cleanup Disk 102 | uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 103 | with: 104 | large-packages: false 105 | swap-storage: false 106 | 107 | - name: Setup QEMU 108 | uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 109 | with: 110 | platforms: all 111 | 112 | - name: Checkout 113 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 114 | with: 115 | submodules: true 116 | 117 | - name: Setup Go 118 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 119 | with: 120 | go-version: ${{ env.GO_VERSION }} 121 | 122 | - name: Checkout PR 123 | id: checkout-pr 124 | env: 125 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 126 | run: | 127 | gh pr checkout ${{ github.event.issue.number }} 128 | git submodule update --init --recursive 129 | OUTPUT=$(git log -1 --format='%H') 130 | echo "commit-sha=$OUTPUT" >> $GITHUB_OUTPUT 131 | 132 | - name: Vendor Dependencies 133 | run: make vendor vendor.check 134 | 135 | - name: Run Uptest 136 | id: run-uptest 137 | env: 138 | UPTEST_CLOUD_CREDENTIALS: ${{ secrets.UPTEST_CLOUD_CREDENTIALS }} 139 | UPTEST_EXAMPLE_LIST: ${{ needs.get-example-list.outputs.example_list }} 140 | UPTEST_TEST_DIR: ./_output/controlplane-dump 141 | UPTEST_DATASOURCE_PATH: .work/uptest-datasource.yaml 142 | UPTEST_UPDATE_PARAMETER: "" 143 | run: | 144 | mkdir -p .work && echo "${{ secrets.UPTEST_DATASOURCE }}" > .work/uptest-datasource.yaml 145 | make e2e 146 | 147 | - name: Create Successful Status Check 148 | env: 149 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 150 | EXAMPLE_HASH: ${{ needs.get-example-list.outputs.example_hash }} 151 | run: | 152 | gh api \ 153 | --method POST \ 154 | -H "Accept: application/vnd.github+json" \ 155 | /repos/${{ github.repository }}/statuses/${{ steps.checkout-pr.outputs.commit-sha }} \ 156 | -f state='success' \ 157 | -f target_url='https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' \ 158 | -f description='Passed' \ 159 | -f context="Uptest-${EXAMPLE_HASH}" 160 | 161 | - name: Collect Cluster Dump 162 | if: always() 163 | run: | 164 | make controlplane.dump 165 | 166 | - name: Upload Cluster Dump 167 | if: always() 168 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 169 | with: 170 | name: controlplane-dump 171 | path: ./_output/controlplane-dump 172 | 173 | - name: Cleanup 174 | if: always() 175 | run: | 176 | eval $(make --no-print-directory build.vars) 177 | ${KUBECTL} delete managed --all || true 178 | 179 | - name: Create Unsuccessful Status Check 180 | if: failure() 181 | env: 182 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 183 | EXAMPLE_HASH: ${{ needs.get-example-list.outputs.example_hash }} 184 | run: | 185 | gh api \ 186 | --method POST \ 187 | -H "Accept: application/vnd.github+json" \ 188 | /repos/${{ github.repository }}/statuses/${{ steps.checkout-pr.outputs.commit-sha }} \ 189 | -f state='failure' \ 190 | -f target_url='https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' \ 191 | -f description='Failed' \ 192 | -f context="Uptest-${EXAMPLE_HASH}" 193 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.cache 2 | /.work 3 | /_output 4 | cover.out 5 | /vendor 6 | /.vendor-new 7 | .registry 8 | 9 | # ignore GoLand files (debug config etc...) 10 | /.idea 11 | 12 | # ignore vscode files (debug config etc...) 13 | /.vscode 14 | 15 | # ignore asdf local versions 16 | /.tool-versions 17 | 18 | # local tf dir 19 | /tf 20 | 21 | # common credentials 22 | kubeconfig 23 | credentials.json -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "build"] 2 | path = build 3 | url = https://github.com/crossplane/build 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 30m 3 | 4 | output: 5 | # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" 6 | formats: 7 | - format: colored-line-number 8 | print-linter-name: true 9 | show-stats: true 10 | 11 | linters-settings: 12 | errcheck: 13 | # report about not checking of errors in type assetions: `a := b.(MyStruct)`; 14 | # default is false: such cases aren't reported by default. 15 | check-type-assertions: false 16 | 17 | # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; 18 | # default is false: such cases aren't reported by default. 19 | check-blank: true 20 | 21 | # Updated to use exclude-functions instead of ignore 22 | exclude-functions: fmt:.*,io/ioutil:^Read.* 23 | 24 | govet: 25 | # report about shadowed variables 26 | check-shadowing: false 27 | 28 | revive: 29 | # confidence for issues, default is 0.8 30 | confidence: 0.8 31 | 32 | gofmt: 33 | # simplify code: gofmt with `-s` option, true by default 34 | simplify: true 35 | 36 | goimports: 37 | # put imports beginning with prefix after 3rd-party packages; 38 | # it's a comma-separated list of prefixes 39 | local-prefixes: github.com/upbound/provider-aws 40 | 41 | gocyclo: 42 | # minimal code complexity to report, 30 by default (but we recommend 10-20) 43 | min-complexity: 10 44 | 45 | dupl: 46 | # tokens count to trigger issue, 150 by default 47 | threshold: 100 48 | 49 | goconst: 50 | # minimal length of string constant, 3 by default 51 | min-len: 3 52 | # minimal occurrences count to trigger, 3 by default 53 | min-occurrences: 5 54 | 55 | lll: 56 | # tab width in spaces. Default to 1. 57 | tab-width: 1 58 | 59 | unparam: 60 | # Inspect exported functions, default is false. Set to true if no external program/library imports your code. 61 | # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: 62 | # if it's called for subdir of a project it can't find external interfaces. All text editor integrations 63 | # with golangci-lint call it on a directory with the changed file. 64 | check-exported: false 65 | 66 | nakedret: 67 | # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 68 | max-func-lines: 30 69 | 70 | prealloc: 71 | # XXX: we don't recommend using this linter before doing performance profiling. 72 | # For most programs usage of prealloc will be a premature optimization. 73 | 74 | # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. 75 | # True by default. 76 | simple: true 77 | range-loops: true # Report preallocation suggestions on range loops, true by default 78 | for-loops: false # Report preallocation suggestions on for loops, false by default 79 | 80 | gocritic: 81 | # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint` run to see all tags and checks. 82 | # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". 83 | enabled-tags: 84 | - performance 85 | 86 | settings: # settings passed to gocritic 87 | captLocal: # must be valid enabled check name 88 | paramsOnly: true 89 | rangeValCopy: 90 | sizeThreshold: 32 91 | 92 | linters: 93 | enable: 94 | - govet 95 | - gocyclo 96 | - gocritic 97 | - goconst 98 | - goimports 99 | - gofmt # We enable this as well as goimports for its simplify mode. 100 | - gosimple 101 | - prealloc 102 | - revive 103 | - staticcheck 104 | - unconvert 105 | - unused 106 | - misspell 107 | - nakedret 108 | 109 | presets: 110 | - bugs 111 | - unused 112 | fast: false 113 | 114 | 115 | issues: 116 | # Added the exclude-files configuration here to replace run.skip-files 117 | exclude-files: 118 | - "zz_generated\\..+\\.go$" 119 | 120 | # Excluding configuration per-path and per-linter 121 | exclude-rules: 122 | # Exclude some linters from running on tests files. 123 | - path: _test(ing)?\.go 124 | linters: 125 | - gocyclo 126 | - errcheck 127 | - dupl 128 | - gosec 129 | - scopelint 130 | - unparam 131 | 132 | # Ease some gocritic warnings on test files. 133 | - path: _test\.go 134 | text: "(unnamedResult|exitAfterDefer)" 135 | linters: 136 | - gocritic 137 | 138 | # These are performance optimisations rather than style issues per se. 139 | # They warn when function arguments or range values copy a lot of memory 140 | # rather than using a pointer. 141 | - text: "(hugeParam|rangeValCopy):" 142 | linters: 143 | - gocritic 144 | 145 | # This "TestMain should call os.Exit to set exit code" warning is not clever 146 | # enough to notice that we call a helper method that calls os.Exit. 147 | - text: "SA3000:" 148 | linters: 149 | - staticcheck 150 | 151 | - text: "k8s.io/api/core/v1" 152 | linters: 153 | - goimports 154 | 155 | # This is a "potential hardcoded credentials" warning. It's triggered by 156 | # any variable with 'secret' in the same, and thus hits a lot of false 157 | # positives in Kubernetes land where a Secret is an object type. 158 | - text: "G101:" 159 | linters: 160 | - gosec 161 | - gas 162 | 163 | # This is an 'errors unhandled' warning that duplicates errcheck. 164 | - text: "G104:" 165 | linters: 166 | - gosec 167 | - gas 168 | 169 | # Independently from option `exclude` we use default exclude patterns, 170 | # it can be disabled by this option. To list all 171 | # excluded by default patterns execute `golangci-lint run --help`. 172 | # Default value for this option is true. 173 | exclude-use-default: false 174 | 175 | # Show only new issues: if there are unstaged changes or untracked files, 176 | # only those changes are analyzed, else only changes in HEAD~ are analyzed. 177 | # It's a super-useful option for integration of golangci-lint into existing 178 | # large codebase. It's not practical to fix all existing issues at the moment 179 | # of integration: much better don't allow issues in new code. 180 | # Default is false. 181 | new: false 182 | 183 | # Maximum issues count per one linter. Set to 0 to disable. Default is 50. 184 | max-per-linter: 0 185 | 186 | # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. 187 | max-same-issues: 0 188 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file controls automatic PR reviewer assignment. See the following docs: 2 | # 3 | # * https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 4 | # * https://docs.github.com/en/organizations/organizing-members-into-teams/managing-code-review-settings-for-your-team 5 | # 6 | # The goal of this file is for most PRs to automatically and fairly have one 7 | # maintainer set as PR reviewers. All maintainers have permission to approve 8 | # and merge PRs. All PRs must be approved by at least one maintainer before being merged. 9 | # 10 | # Where possible, prefer explicitly specifying a maintainer who is a subject 11 | # matter expert for a particular part of the codebase rather than using the 12 | # @upbound/team-extensions group. 13 | # 14 | # See also OWNERS.md for governance details 15 | 16 | # Fallback owners 17 | * @bobh66 @ytsarev @negz @sergenyalcin @turkenf 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to upbound/provider-terraform 2 | 3 | Welcome, and thank you for considering contributing to `provider-terraform`. 4 | We encourage you to help out by raising issues, improving documentation, 5 | fixing bugs, or adding new features. 6 | 7 | If you're interested in contributing please start by reading this document. 8 | If you have any questions at all, or don't know where to start, please reach 9 | out to us in the [`#providers`](https://crossplane.slack.com/archives/C01TRKD4623) 10 | channel in [Crossplane Community Slack]. 11 | 12 | ## Contributing Code 13 | 14 | To contribute bug fixes or features to `provider-terraform`: 15 | 16 | 1. Communicate your intent. 17 | 1. Make your changes. 18 | 1. Test your changes. 19 | 1. Update documentation and examples where appropriate. 20 | 1. Open a Pull Request (PR). 21 | 22 | Communicating your intent lets the [maintainers] know that you intend 23 | to contribute, and how. This sets you up for success - you can avoid duplicating 24 | an effort that may already be underway, adding a feature that may be rejected, 25 | or heading down a path that you would be steered away from at review time. The 26 | best way to communicate your intent is via a detailed GitHub issue. Take a look 27 | first to see if there's already an issue relating to the thing you'd like to 28 | contribute. If there isn't, please raise a new one! Let us know what you'd like 29 | to work on, and why. The `provider-terraform` maintainers can't always triage new issues 30 | immediately, but we encourage you to bring them to our attention via [Slack]. 31 | 32 | Be sure to practice [good git commit hygiene] as you make your changes. All but 33 | the smallest changes should be broken up into a few commits that tell a story. 34 | Use your git commits to provide context for the folks who will review PR, and 35 | the folks who will be spelunking the codebase in the months and years to come. 36 | Ensure each of your commits is signed-off in compliance with the [Developer 37 | Certificate of Origin] by using `git commit -s` and follow the CLA instructions. 38 | The `provider-terraform` project highly values readable, idiomatic Go code. Familiarise 39 | yourself with the [Coding Style] and try to preempt any comments your reviewers 40 | would otherwise leave. Run `make reviewable` to lint your change. 41 | 42 | All `provider-terraform` code must be covered by tests. `provider-terraform` does not use Ginkgo tests and 43 | will request changes to any PR that uses Ginkgo or any third party testing 44 | library, per the common Go [test review comments]. `provider-terraform` encourages the use 45 | of table driven unit tests. 46 | 47 | Note that when opening a PR your reviewer will expect you to detail how you've 48 | tested your work. For all but the smallest changes some manual testing is 49 | encouraged in addition to unit tests. 50 | 51 | All `provider-terraform` documentation is under revision control; see the [docs] directory 52 | of this repository. Any change that introduces new behaviour or changes existing 53 | behaviour must include updates to any relevant documentation. Please keep 54 | documentation changes in distinct commits. 55 | 56 | Once your change is written, tested, and documented the final step is to have it 57 | reviewed! You'll be presented with a template and a small checklist when you 58 | open a PR. Please read the template and fill out the checklist. Please make all 59 | requested changes in subsequent commits. This allows your reviewers to see what 60 | has changed as you address their comments. Be mindful of your commit history as 61 | you do this - avoid commit messages like "Address review feedback" if possible. 62 | If doing so is difficult a good alternative is to rewrite your commit history to 63 | clean them up after your PR is approved but before it is merged. 64 | 65 | In summary, please: 66 | 67 | * Discuss your change in a GitHub issue before you start. 68 | * Use your Git commit messages to communicate your intent to your reviewers. 69 | * Sign-off on all Git commits by running `git commit -s` 70 | * Add or update tests for all changes. 71 | * Preempt [Coding Style] review comments. 72 | * Update all relevant documentation. 73 | * Don't force push to address review feedback. Your commits should tell a story. 74 | * If necessary, tidy up your git commit history once your PR is approved. 75 | 76 | Thank you for reading through our contributing guide! We appreciate you taking 77 | the time to ensure your contributions are high quality and easy for our 78 | community to review and accept. Please don't hesitate to [reach out to 79 | us][Slack] if you have any questions about contributing! 80 | 81 | ## Establishing a Development Environment 82 | 83 | The `provider-terraform` project uses the Upbound [build submodule]; a library of 84 | common Makefiles. Establishing a development environment typically requires: 85 | 86 | 1. Forking and cloning the repository you wish to work on. 87 | 1. Installing development dependencies. 88 | 1. Running `make` to establish the build submodule. 89 | 90 | Run `make help` for information on the available Make targets. Useful targets 91 | include: 92 | 93 | * `make` - Synchronize the build submodule and build provider-terraform. 94 | * `make reviewable` - Run code generation, linters, and unit tests. 95 | * `make e2e` - Run end-to-end tests. 96 | 97 | Once you've built provider-terraform you can deploy it to a Kubernetes cluster of your 98 | choice. [`kind`] (Kubernetes in Docker) is a good choice for development. 99 | 100 | When iterating rapidly on a change it can be faster to run provider-terraform as a local 101 | process. For example: 102 | 103 | ```bash 104 | # Run provider-terraform locally; it should connect to your kind cluster if said cluster 105 | # is your active kubectl context. You can also go run cmd/provider-terraform/main.go. 106 | make run 107 | ``` 108 | 109 | ## Code Review Process 110 | 111 | All Pull Requests (PR), whether written by a maintainer or a 112 | community member, must go through code review. This ensures that the code is 113 | correct, maintainable, and secure. 114 | 115 | We encourage anyone in the community to conduct a code review on a PR, 116 | however, we require the following approvals before merging a PR: 117 | - At least one approval from [Maintainers] 118 | 119 | The PR author should notify one of the [Maintainers], when the PR is ready for 120 | review. 121 | 122 | [Crossplane Community Slack]: https://slack.crossplane.io/ 123 | [Slack]: https://crossplane.slack.com/archives/C01TRKD4623 124 | [maintainers]: https://github.com/upbound/provider-terraform/blob/main/OWNERS.md 125 | [code of conduct]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md 126 | [good git commit hygiene]: https://www.futurelearn.com/info/blog/telling-stories-with-your-git-history 127 | [Developer Certificate of Origin]: https://github.com/apps/dco 128 | [test review comments]: https://github.com/golang/go/wiki/TestComments 129 | [docs]: docs/ 130 | [build submodule]: https://github.com/upbound/build/ 131 | [Coding Style]: https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md#coding-style 132 | 133 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ==================================================================================== 2 | # Setup Project 3 | PROJECT_NAME := provider-terraform 4 | PROJECT_REPO := github.com/upbound/$(PROJECT_NAME) 5 | 6 | PLATFORMS ?= linux_amd64 linux_arm64 7 | -include build/makelib/common.mk 8 | 9 | # Setup Output 10 | -include build/makelib/output.mk 11 | 12 | # Setup Go 13 | GO_REQUIRED_VERSION ?= 1.23 14 | NPROCS ?= 1 15 | # GOLANGCILINT_VERSION is inherited from build submodule by default. 16 | # Uncomment below if you need to override the version. 17 | # GOLANGCILINT_VERSION ?= 1.50.0 18 | GO_TEST_PARALLEL := $(shell echo $$(( $(NPROCS) / 2 ))) 19 | GO_STATIC_PACKAGES = $(GO_PROJECT)/cmd/provider 20 | GO_LDFLAGS += -X $(GO_PROJECT)/pkg/version.Version=$(VERSION) 21 | GO_SUBDIRS += cmd internal apis 22 | GO111MODULE = on 23 | 24 | -include build/makelib/golang.mk 25 | 26 | # ==================================================================================== 27 | # Setup Kubernetes tools 28 | 29 | # Uncomment below to override the versions from the build module 30 | #KIND_VERSION = v0.27.0 31 | UP_VERSION = v0.34.2 32 | UP_CHANNEL = stable 33 | UPTEST_VERSION = v1.1.2 34 | UPTEST_LOCAL_VERSION = v0.13.0 35 | UPTEST_LOCAL_CHANNEL = stable 36 | KUSTOMIZE_VERSION = v5.3.0 37 | YQ_VERSION = v4.40.5 38 | CROSSPLANE_VERSION = 1.17.1 39 | CRDDIFF_VERSION = v0.12.1 40 | 41 | export UP_VERSION := $(UP_VERSION) 42 | export UP_CHANNEL := $(UP_CHANNEL) 43 | 44 | -include build/makelib/k8s_tools.mk 45 | 46 | # uptest download and install 47 | UPTEST_LOCAL := $(TOOLS_HOST_DIR)/uptest-$(UPTEST_LOCAL_VERSION) 48 | 49 | $(UPTEST_LOCAL): 50 | @$(INFO) installing uptest $(UPTEST_LOCAL) 51 | @mkdir -p $(TOOLS_HOST_DIR) 52 | @curl -fsSLo $(UPTEST_LOCAL) https://s3.us-west-2.amazonaws.com/crossplane.uptest.releases/$(UPTEST_LOCAL_CHANNEL)/$(UPTEST_LOCAL_VERSION)/bin/$(SAFEHOST_PLATFORM)/uptest || $(FAIL) 53 | @chmod +x $(UPTEST_LOCAL) 54 | @$(OK) installing uptest $(UPTEST_LOCAL) 55 | 56 | # Setup Images 57 | REGISTRY_ORGS ?= xpkg.upbound.io/upbound 58 | IMAGES = provider-terraform 59 | BATCH_PLATFORMS ?= linux_amd64,linux_arm64 60 | export BATCH_PLATFORMS := $(BATCH_PLATFORMS) 61 | 62 | -include build/makelib/imagelight.mk 63 | 64 | # ==================================================================================== 65 | # Targets 66 | 67 | # run `make help` to see the targets and options 68 | 69 | # We want submodules to be set up the first time `make` is run. 70 | # We manage the build/ folder and its Makefiles as a submodule. 71 | # The first time `make` is run, the includes of build/*.mk files will 72 | # all fail, and this target will be run. The next time, the default as defined 73 | # by the includes will be run instead. 74 | fallthrough: submodules 75 | @echo Initial setup complete. Running make again . . . 76 | @make 77 | 78 | # Update the submodules, such as the common build scripts. 79 | submodules: 80 | @git submodule sync 81 | @git submodule update --init --recursive 82 | 83 | # ==================================================================================== 84 | # Setup XPKG 85 | 86 | XPKG_REG_ORGS ?= xpkg.upbound.io/upbound 87 | # NOTE(hasheddan): skip promoting on xpkg.upbound.io as channel tags are 88 | # inferred. 89 | XPKG_REG_ORGS_NO_PROMOTE ?= xpkg.upbound.io/upbound 90 | XPKGS = provider-terraform 91 | -include build/makelib/xpkg.mk 92 | 93 | # NOTE(hasheddan): we force image building to happen prior to xpkg build so that 94 | # we ensure image is present in daemon. 95 | xpkg.build.provider-terraform: do.build.images 96 | 97 | # NOTE(hasheddan): we must ensure up is installed in tool cache prior to build 98 | # as including the k8s_tools machinery prior to the xpkg machinery sets UP to 99 | # point to tool cache. 100 | build.init: $(UP) 101 | 102 | # This is for running out-of-cluster locally, and is for convenience. Running 103 | # this make target will print out the command which was used. For more control, 104 | # try running the binary directly with different arguments. 105 | run: go.build 106 | @$(INFO) Running Crossplane locally out-of-cluster . . . 107 | @# To see other arguments that can be provided, run the command with --help instead 108 | @# KUBE_CONFIG_PATH explained at https://developer.hashicorp.com/terraform/language/settings/backends/kubernetes 109 | @# XP_TF_DIR is to override default tf work dir which is usually /tf and unreadable locally 110 | KUBE_CONFIG_PATH=~/.kube/config XP_TF_DIR=./tf $(GO_OUT_DIR)/provider --debug 111 | 112 | dev: $(KIND) $(KUBECTL) 113 | @$(INFO) Creating kind cluster 114 | @$(KIND) create cluster --name=$(PROJECT_NAME)-dev 115 | @$(KUBECTL) cluster-info --context kind-$(PROJECT_NAME)-dev 116 | @$(INFO) Installing Crossplane CRDs 117 | @$(KUBECTL) apply -k https://github.com/crossplane/crossplane/cluster?ref=master 118 | @$(INFO) Installing Provider CRDs 119 | @$(KUBECTL) apply -R -f package/crds 120 | @$(INFO) Starting Provider controllers 121 | @$(GO) run cmd/provider/main.go --debug 122 | 123 | dev-clean: $(KIND) $(KUBECTL) 124 | @$(INFO) Deleting kind cluster 125 | @$(KIND) delete cluster --name=$(PROJECT_NAME)-dev 126 | 127 | .PHONY: reviewable submodules fallthrough test-integration run dev dev-clean 128 | 129 | # ==================================================================================== 130 | # End to End Testing 131 | CROSSPLANE_NAMESPACE = upbound-system 132 | -include build/makelib/local.xpkg.mk 133 | -include build/makelib/controlplane.mk 134 | 135 | # This target requires the following environment variables to be set: 136 | # - UPTEST_EXAMPLE_LIST, a comma-separated list of examples to test 137 | # - UPTEST_CLOUD_CREDENTIALS (optional), cloud credentials for the provider being tested, e.g. export UPTEST_CLOUD_CREDENTIALS=$(cat ~/.aws/credentials) 138 | # - UPTEST_DATASOURCE_PATH (optional), see https://github.com/upbound/uptest#injecting-dynamic-values-and-datasource 139 | uptest: $(UPTEST) $(KUBECTL) $(CHAINSAW) $(CROSSPLANE_CLI) 140 | @$(INFO) running automated tests 141 | @KUBECTL=$(KUBECTL) CHAINSAW=$(CHAINSAW) CROSSPLANE_CLI=$(CROSSPLANE_CLI) CROSSPLANE_NAMESPACE=$(CROSSPLANE_NAMESPACE) $(UPTEST) e2e "${UPTEST_EXAMPLE_LIST}" --data-source="${UPTEST_DATASOURCE_PATH}" --setup-script=cluster/test/setup.sh --skip-import || $(FAIL) 142 | @$(OK) running automated tests 143 | 144 | local-deploy: build controlplane.up local.xpkg.deploy.provider.$(PROJECT_NAME) 145 | @$(INFO) running locally built provider 146 | @$(KUBECTL) wait provider.pkg $(PROJECT_NAME) --for condition=Healthy --timeout 5m 147 | @$(KUBECTL) -n upbound-system wait --for=condition=Available deployment --all --timeout=5m 148 | @$(OK) running locally built provider 149 | 150 | # This target requires the following environment variables to be set: 151 | # - UPTEST_CLOUD_CREDENTIALS, cloud credentials for the provider being tested, e.g. export UPTEST_CLOUD_CREDENTIALS=$(cat ~/.aws/credentials) 152 | # - UPTEST_EXAMPLE_LIST, a comma-separated list of examples to test 153 | # - UPTEST_DATASOURCE_PATH, see https://github.com/upbound/uptest#injecting-dynamic-values-and-datasource 154 | e2e: local-deploy uptest 155 | 156 | # TODO: please move this to the common build submodule 157 | # once the use cases mature 158 | crddiff: $(UPTEST) 159 | @$(INFO) Checking breaking CRD schema changes 160 | @for crd in $${MODIFIED_CRD_LIST}; do \ 161 | if ! git cat-file -e "$${GITHUB_BASE_REF}:$${crd}" 2>/dev/null; then \ 162 | echo "CRD $${crd} does not exist in the $${GITHUB_BASE_REF} branch. Skipping..." ; \ 163 | continue ; \ 164 | fi ; \ 165 | echo "Checking $${crd} for breaking API changes..." ; \ 166 | changes_detected=$$($(UPTEST) crddiff revision <(git cat-file -p "$${GITHUB_BASE_REF}:$${crd}") "$${crd}" 2>&1) ; \ 167 | if [[ $$? != 0 ]] ; then \ 168 | printf "\033[31m"; echo "Breaking change detected!"; printf "\033[0m" ; \ 169 | echo "$${changes_detected}" ; \ 170 | echo ; \ 171 | fi ; \ 172 | done 173 | @$(OK) Checking breaking CRD schema changes 174 | 175 | go.lint.analysiskey-interval: 176 | @# cache is invalidated at least every 7 days 177 | @echo -n golangci-lint.cache-$$(( $$(date +%s) / (7 * 86400) ))- 178 | 179 | go.lint.analysiskey: 180 | @echo $$(make go.lint.analysiskey-interval)$$(sha1sum go.sum | cut -d' ' -f1) 181 | 182 | .PHONY: uptest e2e 183 | # ==================================================================================== 184 | # Special Targets 185 | 186 | define CROSSPLANE_MAKE_HELP 187 | Crossplane Targets: 188 | submodules Update the submodules, such as the common build scripts. 189 | run Run crossplane locally, out-of-cluster. Useful for development. 190 | 191 | endef 192 | # The reason CROSSPLANE_MAKE_HELP is used instead of CROSSPLANE_HELP is because the crossplane 193 | # binary will try to use CROSSPLANE_HELP if it is set, and this is for something different. 194 | export CROSSPLANE_MAKE_HELP 195 | 196 | crossplane.help: 197 | @echo "$$CROSSPLANE_MAKE_HELP" 198 | 199 | help-special: crossplane.help 200 | 201 | .PHONY: crossplane.help help-special 202 | 203 | go.cachedir: 204 | @go env GOCACHE 205 | 206 | .PHONY: go.cachedir 207 | 208 | go.mod.cachedir: 209 | @go env GOMODCACHE 210 | 211 | .PHONY: go.mod.cachedir 212 | 213 | vendor: modules.download 214 | vendor.check: modules.check 215 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This project is a larger work that combines with software written by third 2 | parties, licensed under their own terms. 3 | 4 | Notably, this larger work combines with the following Terraform components, 5 | which are licensed under the Mozilla Public License 2.0 (see 6 | or the individual projects listed 7 | below). 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /OWNERS.md: -------------------------------------------------------------------------------- 1 | # OWNERS 2 | 3 | This page lists all maintainers for **this** repository. Each repository in the [Upbound 4 | organization](https://github.com/upbound/) will list their repository maintainers in their own 5 | `OWNERS.md` file. 6 | 7 | ## Maintainers 8 | 9 | * Bob Haddleton ([bobh66](https://github.com/bobh66)) 10 | * Yury Tsarev ([ytsarev](https://github.com/ytsarev)) 11 | * Nic Cope ([negz](https://github.com/negz)) 12 | * Sergen Yalcin ([sergenyalcin](https://github.com/sergenyalcin)) 13 | * Fatih Turken ([turkenf](https://github.com/turkenf)) 14 | 15 | See [CODEOWNERS](./CODEOWNERS) for automatic PR assignment. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform Provider 2 | 3 | > [!IMPORTANT] 4 | > This provider is frozen at Terraform 1.5.7 and will not adopt any Terraform versions released under the BSL license. For newer capabilities, consider [provider-opentofu](https://github.com/upbound/provider-opentofu) instead. 5 | 6 |
7 | 8 | ![CI](https://github.com/upbound/provider-terraform/workflows/CI/badge.svg) [![GitHub release](https://img.shields.io/github/release/upbound/provider-terraform/all.svg?style=flat-square)](https://github.com/upbound/provider-terraform/releases) [![Go Report Card](https://goreportcard.com/badge/github.com/upbound/provider-terraform)](https://goreportcard.com/report/github.com/upbound/provider-terraform) [![Slack](https://slack.crossplane.io/badge.svg)](https://crossplane.slack.com/archives/C01TRKD4623) [![Twitter Follow](https://img.shields.io/twitter/follow/upbound_io.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=upbound_io&user_id=788180534543339520) 9 | 10 |
11 | 12 | Provider Terraform is a [Crossplane](https://crossplane.io/) provider that 13 | can run Terraform code and enables defining new Crossplane Composite Resources (XRs) 14 | that are composed of a mix of 'native' Crossplane managed resources and your 15 | existing Terraform modules. 16 | 17 | The Terraform provider adds support for a `Workspace` managed resource that 18 | represents a Terraform workspace. The configuration of each workspace may be 19 | either fetched from a remote source (e.g. git), or simply specified inline. 20 | 21 | ```yaml 22 | apiVersion: tf.upbound.io/v1beta1 23 | kind: Workspace 24 | metadata: 25 | name: example-inline 26 | annotations: 27 | # The terraform workspace will be named 'coolbucket'. If you omitted this 28 | # annotation it would be derived from metadata.name - i.e. 'example-inline'. 29 | crossplane.io/external-name: coolbucket 30 | spec: 31 | forProvider: 32 | # For simple cases you can use an inline source to specify the content of 33 | # main.tf as opaque, inline HCL. 34 | source: Inline 35 | module: | 36 | // All outputs are written to the connection secret. Non-sensitive outputs 37 | // are stored in the status.atProvider.outputs object. 38 | output "url" { 39 | value = google_storage_bucket.example.self_link 40 | } 41 | 42 | resource "random_id" "example" { 43 | byte_length = 4 44 | } 45 | 46 | // The google provider and remote state are configured by the provider 47 | // config - see examples/providerconfig.yaml. 48 | resource "google_storage_bucket" "example" { 49 | name = "crossplane-example-${terraform.workspace}-${random_id.example.hex}" 50 | } 51 | writeConnectionSecretToRef: 52 | namespace: default 53 | name: terraform-workspace-example-inline 54 | ``` 55 | 56 | ```yaml 57 | apiVersion: tf.upbound.io/v1beta1 58 | kind: Workspace 59 | metadata: 60 | name: example-remote 61 | annotations: 62 | crossplane.io/external-name: myworkspace 63 | spec: 64 | forProvider: 65 | # Use any module source supported by terraform init -from-module. 66 | source: Remote 67 | module: https://github.com/crossplane/tf 68 | # Environment variables can be passed through 69 | env: 70 | - name: TF_VAR_varFromValue 71 | value: 'value' 72 | - name: ENV_FROM_CONFIGMAP 73 | configMapKeyRef: 74 | namespace: my-namespace 75 | name: my-config-map 76 | key: target-key 77 | - name: ENV_FROM_SECRET 78 | secretKeyRef: 79 | namespace: my-namespace 80 | name: my-secret 81 | key: target-key 82 | # Variables can be specified inline as a list of key-value pairs or as an json object, or loaded from a ConfigMap or Secret. 83 | vars: 84 | - key: region 85 | value: us-west-1 86 | varmap: 87 | account: 88 | region: us-west-1 89 | owners: 90 | - example-owner-1 91 | - example-owner-2 92 | varFiles: 93 | - source: SecretKey 94 | secretKeyRef: 95 | namespace: default 96 | name: terraform 97 | key: example.tfvar.json 98 | # All Terraform outputs are written to the connection secret. 99 | writeConnectionSecretToRef: 100 | namespace: default 101 | name: terraform-workspace-example-inline 102 | ``` 103 | 104 | ## Getting Started 105 | 106 | Follow the quick start guide [here](https://marketplace.upbound.io/providers/upbound/provider-terraform/latest/docs/quickstart). 107 | 108 | You can find a detailed API reference for all the managed resources with examples in the [Upbound Marketplace](https://marketplace.upbound.io/providers/upbound/provider-terraform/latest/managed-resources). 109 | 110 | ## Further Configuration 111 | 112 | You can find more information about configuring the provider further [here](https://marketplace.upbound.io/providers/upbound/provider-terraform/latest/docs/configuration). 113 | 114 | ### Polling Interval 115 | The default polling interval has been updated to 10 minutes from 1 minute. 116 | This affects how often the provider will run `terraform plan` on existing 117 | `Workspaces` to determine if there are any resources out of sync and whether 118 | `terraform apply` needs to be re-executed to recover the desired state. 119 | A 1 minute polling interval is often too short when the time required for 120 | running `terrform init`, `terraform plan` and `terraform apply` is taken 121 | into account. Workspaces with large numbers of resources can take longer 122 | than 1 minute to run `terraform plan`. Changes to the `Workspace` object 123 | `spec` will still be reconciled immediately. The poll interval is 124 | configurable using `ControllerConfig`. 125 | 126 | ## Known limitations: 127 | 128 | * You must either use remote state or ensure the provider container's `/tf` 129 | directory is not lost. `provider-terraform` __does not persist state__; 130 | consider using the [Kubernetes](https://www.terraform.io/docs/language/settings/backends/kubernetes.html) remote state backend. 131 | * If the module takes longer than the value of `--timeout` (default is 20m) to apply the 132 | underlying `terraform` process will be killed. You will potentially lose state 133 | and leak resources. The workspace lock will also likely be left in place and need to be manually removed 134 | before the Workspace can be reconciled again. 135 | * The provider won't emit an event until _after_ it has successfully applied the 136 | Terraform module, which can take a long time. 137 | * Setting `--max-reconcile-rate` to a value greater than 1 will potentially cause the provider 138 | to use up to the same number of CPUs. Add a resources section to the ControllerConfig to restrict 139 | CPU usage as needed. 140 | 141 | ## Report a Bug 142 | 143 | For filing bugs, suggesting improvements, or requesting new features, please 144 | open an [issue](https://github.com/upbound/provider-terraform/issues). 145 | 146 | ## Contact 147 | 148 | Please open a Github issue for all requests. If you need to reach out to Upbound, 149 | you can do so via the following channels: 150 | * Slack: [#upbound](https://crossplane.slack.com/archives/C01TRKD4623) channel in [Crossplane Slack](https://slack.crossplane.io) 151 | * Twitter: [@upbound_io](https://twitter.com/upbound_io) 152 | * Email: [support@upbound.io](mailto:support@upbound.io) 153 | 154 | ## Licensing 155 | 156 | Provider Terraform is under [the Apache 2.0 license](LICENSE) with [notice](NOTICE). 157 | -------------------------------------------------------------------------------- /apis/generate.go: -------------------------------------------------------------------------------- 1 | //go:build generate 2 | // +build generate 3 | 4 | /* 5 | Copyright 2020 The Crossplane Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // NOTE: See the below link for details on what is happening here. 21 | // https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 22 | 23 | // Remove existing CRDs 24 | //go:generate rm -rf ../package/crds 25 | 26 | // Generate deepcopy methodsets and CRD manifests 27 | //go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=./... crd:crdVersions=v1 output:artifacts:config=../package/crds 28 | 29 | // Generate crossplane-runtime methodsets (resource.Claim, etc) 30 | //go:generate go run -tags generate github.com/crossplane/crossplane-tools/cmd/angryjet generate-methodsets --header-file=../hack/boilerplate.go.txt ./... 31 | 32 | package apis 33 | 34 | import ( 35 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen" //nolint:typecheck 36 | 37 | _ "github.com/crossplane/crossplane-tools/cmd/angryjet" //nolint:typecheck 38 | ) 39 | -------------------------------------------------------------------------------- /apis/terraform.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package apis contains Kubernetes API for the terraform provider. 18 | package apis 19 | 20 | import ( 21 | "k8s.io/apimachinery/pkg/runtime" 22 | 23 | "github.com/upbound/provider-terraform/apis/v1beta1" 24 | ) 25 | 26 | func init() { 27 | // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back 28 | AddToSchemes = append(AddToSchemes, 29 | v1beta1.SchemeBuilder.AddToScheme, 30 | ) 31 | } 32 | 33 | // AddToSchemes may be used to add all resources defined in the project to a Scheme 34 | var AddToSchemes runtime.SchemeBuilder 35 | 36 | // AddToScheme adds all Resources to the Scheme 37 | func AddToScheme(s *runtime.Scheme) error { 38 | return AddToSchemes.AddToScheme(s) 39 | } 40 | -------------------------------------------------------------------------------- /apis/v1beta1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1beta1 contains the core resources of the Terraform provider. 18 | // +kubebuilder:object:generate=true 19 | // +groupName=tf.upbound.io 20 | // +versionName=v1beta1 21 | package v1beta1 22 | -------------------------------------------------------------------------------- /apis/v1beta1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | "reflect" 21 | 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | "sigs.k8s.io/controller-runtime/pkg/scheme" 24 | ) 25 | 26 | // Package type metadata. 27 | const ( 28 | Group = "tf.upbound.io" 29 | Version = "v1beta1" 30 | ) 31 | 32 | var ( 33 | // SchemeGroupVersion is group version used to register these objects 34 | SchemeGroupVersion = schema.GroupVersion{Group: Group, Version: Version} 35 | 36 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 37 | SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} 38 | ) 39 | 40 | // ProviderConfig type metadata. 41 | var ( 42 | ProviderConfigKind = reflect.TypeOf(ProviderConfig{}).Name() 43 | ProviderConfigGroupKind = schema.GroupKind{Group: Group, Kind: ProviderConfigKind}.String() 44 | ProviderConfigKindAPIVersion = ProviderConfigKind + "." + SchemeGroupVersion.String() 45 | ProviderConfigGroupVersionKind = SchemeGroupVersion.WithKind(ProviderConfigKind) 46 | ) 47 | 48 | // ProviderConfigUsage type metadata. 49 | var ( 50 | ProviderConfigUsageKind = reflect.TypeOf(ProviderConfigUsage{}).Name() 51 | ProviderConfigUsageGroupKind = schema.GroupKind{Group: Group, Kind: ProviderConfigUsageKind}.String() 52 | ProviderConfigUsageKindAPIVersion = ProviderConfigUsageKind + "." + SchemeGroupVersion.String() 53 | ProviderConfigUsageGroupVersionKind = SchemeGroupVersion.WithKind(ProviderConfigUsageKind) 54 | 55 | ProviderConfigUsageListKind = reflect.TypeOf(ProviderConfigUsageList{}).Name() 56 | ProviderConfigUsageListGroupKind = schema.GroupKind{Group: Group, Kind: ProviderConfigUsageListKind}.String() 57 | ProviderConfigUsageListKindAPIVersion = ProviderConfigUsageListKind + "." + SchemeGroupVersion.String() 58 | ProviderConfigUsageListGroupVersionKind = SchemeGroupVersion.WithKind(ProviderConfigUsageListKind) 59 | ) 60 | 61 | // Workspace type metadata. 62 | var ( 63 | WorkspaceKind = reflect.TypeOf(Workspace{}).Name() 64 | WorkspaceGroupKind = schema.GroupKind{Group: Group, Kind: WorkspaceKind}.String() 65 | WorkspaceKindAPIVersion = WorkspaceKind + "." + SchemeGroupVersion.String() 66 | WorkspaceGroupVersionKind = SchemeGroupVersion.WithKind(WorkspaceKind) 67 | ) 68 | 69 | func init() { 70 | SchemeBuilder.Register(&ProviderConfig{}, &ProviderConfigList{}) 71 | SchemeBuilder.Register(&ProviderConfigUsage{}, &ProviderConfigUsageList{}) 72 | SchemeBuilder.Register(&Workspace{}, &WorkspaceList{}) 73 | } 74 | -------------------------------------------------------------------------------- /apis/v1beta1/storeconfig_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | "reflect" 21 | 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | 25 | xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" 26 | ) 27 | 28 | // A StoreConfigSpec defines the desired state of a ProviderConfig. 29 | type StoreConfigSpec struct { 30 | xpv1.SecretStoreConfig `json:",inline"` 31 | } 32 | 33 | // A StoreConfigStatus represents the status of a StoreConfig. 34 | type StoreConfigStatus struct { 35 | xpv1.ConditionedStatus `json:",inline"` 36 | } 37 | 38 | // GetCondition of this StoreConfigStatus. 39 | func (s *StoreConfigStatus) GetCondition(ct xpv1.ConditionType) xpv1.Condition { 40 | return s.ConditionedStatus.GetCondition(ct) 41 | } 42 | 43 | // SetConditions of this StoreConfigStatus. 44 | func (s *StoreConfigStatus) SetConditions(c ...xpv1.Condition) { 45 | s.ConditionedStatus.SetConditions(c...) 46 | } 47 | 48 | // +kubebuilder:object:root=true 49 | 50 | // A StoreConfig configures how GCP controller should store connection details. 51 | // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" 52 | // +kubebuilder:printcolumn:name="TYPE",type="string",JSONPath=".spec.type" 53 | // +kubebuilder:printcolumn:name="DEFAULT-SCOPE",type="string",JSONPath=".spec.defaultScope" 54 | // +kubebuilder:resource:scope=Cluster,categories={crossplane,store,gcp} 55 | // +kubebuilder:subresource:status 56 | type StoreConfig struct { 57 | metav1.TypeMeta `json:",inline"` 58 | metav1.ObjectMeta `json:"metadata,omitempty"` 59 | 60 | Spec StoreConfigSpec `json:"spec"` 61 | Status StoreConfigStatus `json:"status,omitempty"` 62 | } 63 | 64 | // +kubebuilder:object:root=true 65 | 66 | // StoreConfigList contains a list of StoreConfig 67 | type StoreConfigList struct { 68 | metav1.TypeMeta `json:",inline"` 69 | metav1.ListMeta `json:"metadata,omitempty"` 70 | Items []StoreConfig `json:"items"` 71 | } 72 | 73 | // Note(turkenh): To be generated with AngryJet 74 | 75 | // GetStoreConfig returns SecretStoreConfig 76 | func (in *StoreConfig) GetStoreConfig() xpv1.SecretStoreConfig { 77 | return in.Spec.SecretStoreConfig 78 | } 79 | 80 | // GetCondition of this StoreConfig. 81 | func (in *StoreConfig) GetCondition(ct xpv1.ConditionType) xpv1.Condition { 82 | return in.Status.GetCondition(ct) 83 | } 84 | 85 | // SetConditions of this StoreConfig. 86 | func (in *StoreConfig) SetConditions(c ...xpv1.Condition) { 87 | in.Status.SetConditions(c...) 88 | } 89 | 90 | // StoreConfig type metadata. 91 | var ( 92 | StoreConfigKind = reflect.TypeOf(StoreConfig{}).Name() 93 | StoreConfigGroupKind = schema.GroupKind{Group: Group, Kind: StoreConfigKind}.String() 94 | StoreConfigKindAPIVersion = StoreConfigKind + "." + SchemeGroupVersion.String() 95 | StoreConfigGroupVersionKind = SchemeGroupVersion.WithKind(StoreConfigKind) 96 | ) 97 | 98 | func init() { 99 | SchemeBuilder.Register(&StoreConfig{}, &StoreConfigList{}) 100 | } 101 | -------------------------------------------------------------------------------- /apis/v1beta1/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | 22 | xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" 23 | ) 24 | 25 | // A ProviderConfigSpec defines the desired state of a ProviderConfig. 26 | type ProviderConfigSpec struct { 27 | // Credentials required to authenticate to this provider. 28 | // +optional 29 | Credentials []ProviderCredentials `json:"credentials"` 30 | 31 | // Configuration that should be injected into all workspaces that use 32 | // this provider config, expressed as inline HCL. This can be used to 33 | // automatically inject Terraform provider configuration blocks. 34 | // +optional 35 | Configuration *string `json:"configuration,omitempty"` 36 | 37 | // Terraform backend file configuration content, 38 | // it has the contents of the backend block as top-level attributes, 39 | // without the need to wrap it in another terraform or backend block. 40 | // More details at https://developer.hashicorp.com/terraform/language/settings/backends/configuration#file. 41 | // +optional 42 | BackendFile *string `json:"backendFile,omitempty"` 43 | 44 | // PluginCache enables terraform provider plugin caching mechanism 45 | // https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache 46 | // +optional 47 | // +kubebuilder:default=true 48 | PluginCache *bool `json:"pluginCache,omitempty"` 49 | } 50 | 51 | // ProviderCredentials required to authenticate. 52 | type ProviderCredentials struct { 53 | // Filename (relative to main.tf) to which these provider credentials 54 | // should be written. 55 | Filename string `json:"filename"` 56 | 57 | // Source of the provider credentials. 58 | // +kubebuilder:validation:Enum=None;Secret;Environment;Filesystem 59 | Source xpv1.CredentialsSource `json:"source"` 60 | 61 | xpv1.CommonCredentialSelectors `json:",inline"` 62 | } 63 | 64 | // A ProviderConfigStatus reflects the observed state of a ProviderConfig. 65 | type ProviderConfigStatus struct { 66 | xpv1.ProviderConfigStatus `json:",inline"` 67 | } 68 | 69 | // +kubebuilder:object:root=true 70 | 71 | // A ProviderConfig configures a Terraform provider. 72 | // +kubebuilder:subresource:status 73 | // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" 74 | // +kubebuilder:printcolumn:name="SECRET-NAME",type="string",JSONPath=".spec.credentials.secretRef.name",priority=1 75 | // +kubebuilder:resource:scope=Cluster,categories={crossplane,provider,terraform} 76 | type ProviderConfig struct { 77 | metav1.TypeMeta `json:",inline"` 78 | metav1.ObjectMeta `json:"metadata,omitempty"` 79 | 80 | Spec ProviderConfigSpec `json:"spec"` 81 | Status ProviderConfigStatus `json:"status,omitempty"` 82 | } 83 | 84 | // +kubebuilder:object:root=true 85 | 86 | // ProviderConfigList contains a list of ProviderConfig. 87 | type ProviderConfigList struct { 88 | metav1.TypeMeta `json:",inline"` 89 | metav1.ListMeta `json:"metadata,omitempty"` 90 | Items []ProviderConfig `json:"items"` 91 | } 92 | 93 | // +kubebuilder:object:root=true 94 | 95 | // A ProviderConfigUsage indicates that a resource is using a ProviderConfig. 96 | // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" 97 | // +kubebuilder:printcolumn:name="CONFIG-NAME",type="string",JSONPath=".providerConfigRef.name" 98 | // +kubebuilder:printcolumn:name="RESOURCE-KIND",type="string",JSONPath=".resourceRef.kind" 99 | // +kubebuilder:printcolumn:name="RESOURCE-NAME",type="string",JSONPath=".resourceRef.name" 100 | // +kubebuilder:resource:scope=Cluster,categories={crossplane,provider,terraform} 101 | type ProviderConfigUsage struct { 102 | metav1.TypeMeta `json:",inline"` 103 | metav1.ObjectMeta `json:"metadata,omitempty"` 104 | 105 | xpv1.ProviderConfigUsage `json:",inline"` 106 | } 107 | 108 | // +kubebuilder:object:root=true 109 | 110 | // ProviderConfigUsageList contains a list of ProviderConfigUsage 111 | type ProviderConfigUsageList struct { 112 | metav1.TypeMeta `json:",inline"` 113 | metav1.ListMeta `json:"metadata,omitempty"` 114 | Items []ProviderConfigUsage `json:"items"` 115 | } 116 | -------------------------------------------------------------------------------- /apis/v1beta1/workspace_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" 21 | extensionsV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | ) 25 | 26 | // A Var represents a Terraform configuration variable. 27 | type Var struct { 28 | Key string `json:"key"` 29 | Value string `json:"value"` 30 | } 31 | 32 | // A VarFileSource specifies the source of a Terraform vars file. 33 | // +kubebuilder:validation:Enum=ConfigMapKey;SecretKey 34 | type VarFileSource string 35 | 36 | // Vars file sources. 37 | const ( 38 | VarFileSourceConfigMapKey VarFileSource = "ConfigMapKey" 39 | VarFileSourceSecretKey VarFileSource = "SecretKey" 40 | ) 41 | 42 | // A FileFormat specifies the format of a Terraform file. 43 | // +kubebuilder:validation:Enum=HCL;JSON 44 | type FileFormat string 45 | 46 | // Vars file formats. 47 | var ( 48 | FileFormatHCL FileFormat = "HCL" 49 | FileFormatJSON FileFormat = "JSON" 50 | ) 51 | 52 | // A VarFile is a file containing many Terraform variables. 53 | type VarFile struct { 54 | // Source of this vars file. 55 | Source VarFileSource `json:"source"` 56 | 57 | // Format of this vars file. 58 | // +kubebuilder:default=HCL 59 | // +optional 60 | Format *FileFormat `json:"format,omitempty"` 61 | 62 | // A ConfigMap key containing the vars file. 63 | // +optional 64 | ConfigMapKeyReference *KeyReference `json:"configMapKeyRef,omitempty"` 65 | 66 | // A Secret key containing the vars file. 67 | // +optional 68 | SecretKeyReference *KeyReference `json:"secretKeyRef,omitempty"` 69 | } 70 | 71 | // An EnvVar specifies an environment variable to be set for the workspace. 72 | type EnvVar struct { 73 | Name string `json:"name"` 74 | Value string `json:"value,omitempty"` 75 | 76 | // A ConfigMap key containing the desired env var value. 77 | ConfigMapKeyReference *KeyReference `json:"configMapKeyRef,omitempty"` 78 | 79 | // A Secret key containing the desired env var value. 80 | SecretKeyReference *KeyReference `json:"secretKeyRef,omitempty"` 81 | } 82 | 83 | // A KeyReference references a key within a Secret or a ConfigMap. 84 | type KeyReference struct { 85 | // Namespace of the referenced resource. 86 | Namespace string `json:"namespace"` 87 | 88 | // Name of the referenced resource. 89 | Name string `json:"name"` 90 | 91 | // Key within the referenced resource. 92 | Key string `json:"key"` 93 | } 94 | 95 | // A ModuleSource represents the source of a Terraform module. 96 | // +kubebuilder:validation:Enum=Remote;Inline;Flux 97 | type ModuleSource string 98 | 99 | // Module sources. 100 | const ( 101 | ModuleSourceRemote ModuleSource = "Remote" 102 | ModuleSourceInline ModuleSource = "Inline" 103 | ModuleSourceFlux ModuleSource = "Flux" 104 | ) 105 | 106 | // WorkspaceParameters are the configurable fields of a Workspace. 107 | type WorkspaceParameters struct { 108 | // The root module of this workspace; i.e. the module containing its main.tf 109 | // file. When the workspace's source is 'Remote' (the default) this can be 110 | // any address supported by terraform init -from-module, for example a git 111 | // repository or an S3 bucket. When the workspace's source is 'Inline' the 112 | // content of a simple main.tf or main.tf.json file may be written inline. 113 | // When the workspace's source is 'Flux', use the FluxSourceKind::namespace/name format. 114 | // Example: 115 | // Module: "GitRepository::my-namespace/my-repo" 116 | Module string `json:"module"` 117 | 118 | // Specifies the format of the inline Terraform content 119 | // if Source is 'Inline' 120 | InlineFormat FileFormat `json:"inlineFormat,omitempty"` 121 | 122 | // Source of the root module of this workspace. 123 | Source ModuleSource `json:"source"` 124 | 125 | // Entrypoint for `terraform init` within the module 126 | // +kubebuilder:default="" 127 | // +optional 128 | Entrypoint string `json:"entrypoint"` 129 | 130 | // Environment variables. 131 | // +optional 132 | Env []EnvVar `json:"env,omitempty"` 133 | 134 | // Configuration variables. 135 | // +optional 136 | Vars []Var `json:"vars,omitempty"` 137 | 138 | // Terraform Variable Map. Should be a valid JSON representation of the input vars 139 | // +optional 140 | VarMap *runtime.RawExtension `json:"varmap,omitempty"` 141 | 142 | // Files of configuration variables. Explicitly declared vars take 143 | // precedence. 144 | // +optional 145 | VarFiles []VarFile `json:"varFiles,omitempty"` 146 | 147 | // Arguments to be included in the terraform init CLI command 148 | InitArgs []string `json:"initArgs,omitempty"` 149 | 150 | // Arguments to be included in the terraform plan CLI command 151 | PlanArgs []string `json:"planArgs,omitempty"` 152 | 153 | // Arguments to be included in the terraform apply CLI command 154 | ApplyArgs []string `json:"applyArgs,omitempty"` 155 | 156 | // Arguments to be included in the terraform destroy CLI command 157 | DestroyArgs []string `json:"destroyArgs,omitempty"` 158 | 159 | // Boolean value to indicate CLI logging of terraform execution is enabled or not 160 | // +optional 161 | EnableTerraformCLILogging bool `json:"enableTerraformCLILogging,omitempty"` 162 | } 163 | 164 | // WorkspaceObservation are the observable fields of a Workspace. 165 | type WorkspaceObservation struct { 166 | Checksum string `json:"checksum,omitempty"` 167 | Outputs map[string]extensionsV1.JSON `json:"outputs,omitempty"` 168 | } 169 | 170 | // A WorkspaceSpec defines the desired state of a Workspace. 171 | type WorkspaceSpec struct { 172 | xpv1.ResourceSpec `json:",inline"` 173 | ForProvider WorkspaceParameters `json:"forProvider"` 174 | } 175 | 176 | // A WorkspaceStatus represents the observed state of a Workspace. 177 | type WorkspaceStatus struct { 178 | xpv1.ResourceStatus `json:",inline"` 179 | AtProvider WorkspaceObservation `json:"atProvider,omitempty"` 180 | } 181 | 182 | // +kubebuilder:object:root=true 183 | 184 | // A Workspace of Terraform Configuration. 185 | // +kubebuilder:subresource:status 186 | // +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" 187 | // +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" 188 | // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" 189 | // +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,terraform} 190 | type Workspace struct { 191 | metav1.TypeMeta `json:",inline"` 192 | metav1.ObjectMeta `json:"metadata,omitempty"` 193 | 194 | Spec WorkspaceSpec `json:"spec"` 195 | Status WorkspaceStatus `json:"status,omitempty"` 196 | } 197 | 198 | // +kubebuilder:object:root=true 199 | 200 | // WorkspaceList contains a list of Workspace 201 | type WorkspaceList struct { 202 | metav1.TypeMeta `json:",inline"` 203 | metav1.ListMeta `json:"metadata,omitempty"` 204 | Items []Workspace `json:"items"` 205 | } 206 | -------------------------------------------------------------------------------- /apis/v1beta1/zz_generated.managed.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by angryjet. DO NOT EDIT. 17 | 18 | package v1beta1 19 | 20 | import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" 21 | 22 | // GetCondition of this Workspace. 23 | func (mg *Workspace) GetCondition(ct xpv1.ConditionType) xpv1.Condition { 24 | return mg.Status.GetCondition(ct) 25 | } 26 | 27 | // GetDeletionPolicy of this Workspace. 28 | func (mg *Workspace) GetDeletionPolicy() xpv1.DeletionPolicy { 29 | return mg.Spec.DeletionPolicy 30 | } 31 | 32 | // GetManagementPolicies of this Workspace. 33 | func (mg *Workspace) GetManagementPolicies() xpv1.ManagementPolicies { 34 | return mg.Spec.ManagementPolicies 35 | } 36 | 37 | // GetProviderConfigReference of this Workspace. 38 | func (mg *Workspace) GetProviderConfigReference() *xpv1.Reference { 39 | return mg.Spec.ProviderConfigReference 40 | } 41 | 42 | // GetPublishConnectionDetailsTo of this Workspace. 43 | func (mg *Workspace) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { 44 | return mg.Spec.PublishConnectionDetailsTo 45 | } 46 | 47 | // GetWriteConnectionSecretToReference of this Workspace. 48 | func (mg *Workspace) GetWriteConnectionSecretToReference() *xpv1.SecretReference { 49 | return mg.Spec.WriteConnectionSecretToReference 50 | } 51 | 52 | // SetConditions of this Workspace. 53 | func (mg *Workspace) SetConditions(c ...xpv1.Condition) { 54 | mg.Status.SetConditions(c...) 55 | } 56 | 57 | // SetDeletionPolicy of this Workspace. 58 | func (mg *Workspace) SetDeletionPolicy(r xpv1.DeletionPolicy) { 59 | mg.Spec.DeletionPolicy = r 60 | } 61 | 62 | // SetManagementPolicies of this Workspace. 63 | func (mg *Workspace) SetManagementPolicies(r xpv1.ManagementPolicies) { 64 | mg.Spec.ManagementPolicies = r 65 | } 66 | 67 | // SetProviderConfigReference of this Workspace. 68 | func (mg *Workspace) SetProviderConfigReference(r *xpv1.Reference) { 69 | mg.Spec.ProviderConfigReference = r 70 | } 71 | 72 | // SetPublishConnectionDetailsTo of this Workspace. 73 | func (mg *Workspace) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { 74 | mg.Spec.PublishConnectionDetailsTo = r 75 | } 76 | 77 | // SetWriteConnectionSecretToReference of this Workspace. 78 | func (mg *Workspace) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { 79 | mg.Spec.WriteConnectionSecretToReference = r 80 | } 81 | -------------------------------------------------------------------------------- /apis/v1beta1/zz_generated.managedlist.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by angryjet. DO NOT EDIT. 17 | 18 | package v1beta1 19 | 20 | import resource "github.com/crossplane/crossplane-runtime/pkg/resource" 21 | 22 | // GetItems of this WorkspaceList. 23 | func (l *WorkspaceList) GetItems() []resource.Managed { 24 | items := make([]resource.Managed, len(l.Items)) 25 | for i := range l.Items { 26 | items[i] = &l.Items[i] 27 | } 28 | return items 29 | } 30 | -------------------------------------------------------------------------------- /apis/v1beta1/zz_generated.pc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by angryjet. DO NOT EDIT. 17 | 18 | package v1beta1 19 | 20 | import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" 21 | 22 | // GetCondition of this ProviderConfig. 23 | func (p *ProviderConfig) GetCondition(ct xpv1.ConditionType) xpv1.Condition { 24 | return p.Status.GetCondition(ct) 25 | } 26 | 27 | // GetUsers of this ProviderConfig. 28 | func (p *ProviderConfig) GetUsers() int64 { 29 | return p.Status.Users 30 | } 31 | 32 | // SetConditions of this ProviderConfig. 33 | func (p *ProviderConfig) SetConditions(c ...xpv1.Condition) { 34 | p.Status.SetConditions(c...) 35 | } 36 | 37 | // SetUsers of this ProviderConfig. 38 | func (p *ProviderConfig) SetUsers(i int64) { 39 | p.Status.Users = i 40 | } 41 | -------------------------------------------------------------------------------- /apis/v1beta1/zz_generated.pcu.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by angryjet. DO NOT EDIT. 17 | 18 | package v1beta1 19 | 20 | import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" 21 | 22 | // GetProviderConfigReference of this ProviderConfigUsage. 23 | func (p *ProviderConfigUsage) GetProviderConfigReference() xpv1.Reference { 24 | return p.ProviderConfigReference 25 | } 26 | 27 | // GetResourceReference of this ProviderConfigUsage. 28 | func (p *ProviderConfigUsage) GetResourceReference() xpv1.TypedReference { 29 | return p.ResourceReference 30 | } 31 | 32 | // SetProviderConfigReference of this ProviderConfigUsage. 33 | func (p *ProviderConfigUsage) SetProviderConfigReference(r xpv1.Reference) { 34 | p.ProviderConfigReference = r 35 | } 36 | 37 | // SetResourceReference of this ProviderConfigUsage. 38 | func (p *ProviderConfigUsage) SetResourceReference(r xpv1.TypedReference) { 39 | p.ResourceReference = r 40 | } 41 | -------------------------------------------------------------------------------- /apis/v1beta1/zz_generated.pculist.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by angryjet. DO NOT EDIT. 17 | 18 | package v1beta1 19 | 20 | import resource "github.com/crossplane/crossplane-runtime/pkg/resource" 21 | 22 | // GetItems of this ProviderConfigUsageList. 23 | func (p *ProviderConfigUsageList) GetItems() []resource.ProviderConfigUsage { 24 | items := make([]resource.ProviderConfigUsage, len(p.Items)) 25 | for i := range p.Items { 26 | items[i] = &p.Items[i] 27 | } 28 | return items 29 | } 30 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: provider-terraform 5 | description: "Terraform Provider for Crossplane" 6 | links: 7 | - url: https://marketplace.upbound.io/providers/upbound/provider-terraform/latest/docs/quickstart 8 | title: Provider terraform Quickstart 9 | annotations: 10 | github.com/project-slug: upbound/provider-terraform 11 | spec: 12 | type: service 13 | lifecycle: production 14 | owner: team-extensions 15 | -------------------------------------------------------------------------------- /cluster/images/provider-terraform/.gitconfig: -------------------------------------------------------------------------------- 1 | [credential] 2 | helper = store --file=$GIT_CRED_DIR/.git-credentials 3 | -------------------------------------------------------------------------------- /cluster/images/provider-terraform/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.21.3 2 | RUN apk --no-cache add ca-certificates bash git curl 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | ENV TERRAFORM_VERSION=1.5.5 7 | ENV TF_IN_AUTOMATION=1 8 | ENV TF_PLUGIN_CACHE_DIR=/tf/plugin-cache 9 | 10 | ADD "bin/${TARGETOS}_${TARGETARCH}/provider" /usr/local/bin/crossplane-terraform-provider 11 | ADD .gitconfig .gitconfig 12 | 13 | # Do not change the URL from which the Terraform CLI is downloaded. 14 | # We are using an MPL-2.0 licensed version of the CLI and 15 | # we must make sure that this holds true. 16 | RUN curl -s -L https://github.com/upbound/terraform/releases/download/v${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_${TARGETOS}_${TARGETARCH}.zip -o terraform.zip \ 17 | && unzip -d /usr/local/bin terraform.zip \ 18 | && rm terraform.zip \ 19 | && chmod +x /usr/local/bin/terraform \ 20 | && mkdir -p ${TF_PLUGIN_CACHE_DIR} \ 21 | && chown -R 2000 /tf 22 | # As of Crossplane v1.3.0 provider controllers run as UID 2000. 23 | # https://github.com/crossplane/crossplane/blob/v1.3.0/internal/controller/pkg/revision/deployment.go#L32 24 | 25 | USER 65532 26 | ENTRYPOINT ["crossplane-terraform-provider"] 27 | -------------------------------------------------------------------------------- /cluster/images/provider-terraform/Makefile: -------------------------------------------------------------------------------- 1 | # ==================================================================================== 2 | # Setup Project 3 | 4 | include ../../../build/makelib/common.mk 5 | 6 | # ==================================================================================== 7 | # Options 8 | 9 | include ../../../build/makelib/imagelight.mk 10 | 11 | # ==================================================================================== 12 | # Targets 13 | 14 | img.build: 15 | @$(INFO) docker build $(BUILD_ARGS) $(IMAGE) 16 | @$(MAKE) BUILD_ARGS="$(BUILD_ARGS) --load" img.build.shared 17 | @$(OK) docker build $(IMAGE) 18 | 19 | img.publish: 20 | @$(INFO) Skipping image publish for $(IMAGE) 21 | @echo Publish is deferred to xpkg machinery 22 | @$(OK) Image publish skipped for $(IMAGE) 23 | 24 | img.build.shared: 25 | @cp Dockerfile $(IMAGE_TEMP_DIR) || $(FAIL) 26 | @cp .gitconfig $(IMAGE_TEMP_DIR) || $(FAIL) 27 | @cp -r $(OUTPUT_DIR)/bin/ $(IMAGE_TEMP_DIR)/bin || $(FAIL) 28 | @docker buildx build $(BUILD_ARGS) \ 29 | --platform $(IMAGE_PLATFORMS) \ 30 | -t $(IMAGE) \ 31 | $(IMAGE_TEMP_DIR) || $(FAIL) 32 | 33 | img.promote: 34 | @$(INFO) Skipping image promotion from $(FROM_IMAGE) to $(TO_IMAGE) 35 | @echo Promote is deferred to xpkg machinery 36 | @$(OK) Image promotion skipped for $(FROM_IMAGE) to $(TO_IMAGE) 37 | -------------------------------------------------------------------------------- /cluster/test/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -aeuo pipefail 3 | 4 | scriptdir="$( dirname "${BASH_SOURCE[0]}")" 5 | 6 | echo "Running setup.sh" 7 | 8 | if [[ -n "${UPTEST_CLOUD_CREDENTIALS:-}" ]]; then 9 | # NOTE(turkenh): UPTEST_CLOUD_CREDENTIALS may contain more than one cloud credentials that we expect to be provided 10 | # in a single GitHub secret. We expect them provided as key=value pairs separated by newlines. Currently we expect 11 | # AWS and GCP credentials to be provided. For example: 12 | # AWS='[default] 13 | # aws_access_key_id = REDACTED 14 | # aws_secret_access_key = REDACTED' 15 | # GCP='{ 16 | # "type": "service_account", 17 | # "project_id": "REDACTED", 18 | # "private_key_id": "REDACTED", 19 | # "private_key": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n", 20 | # "client_email": "REDACTED", 21 | # "client_id": "REDACTED", 22 | # "auth_uri": "https://accounts.google.com/o/oauth2/auth", 23 | # "token_uri": "https://oauth2.googleapis.com/token", 24 | # "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 25 | # "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/official-provider-testing%40official-provider-testing.iam.gserviceaccount.com" 26 | # }' 27 | eval "${UPTEST_CLOUD_CREDENTIALS}" 28 | 29 | if [[ -n "${AWS:-}" ]]; then 30 | echo "Creating cloud credentials secret for AWS..." 31 | ${KUBECTL} -n upbound-system create secret generic aws-creds --from-literal=credentials="${AWS}" --dry-run=client -o yaml | ${KUBECTL} apply -f - 32 | ${KUBECTL} apply -f "${scriptdir}/../../examples/providerconfig-aws.yaml" 33 | fi 34 | 35 | if [[ -n "${GCP:-}" ]]; then 36 | echo "Creating cloud credentials secret for GCP..." 37 | ${KUBECTL} -n upbound-system create secret generic gcp-creds --from-literal=credentials="${GCP}" --dry-run=client -o yaml | ${KUBECTL} apply -f - 38 | ${KUBECTL} apply -f "${scriptdir}/../../examples/providerconfig.yaml" 39 | fi 40 | fi -------------------------------------------------------------------------------- /cmd/provider/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "os" 22 | "path/filepath" 23 | "time" 24 | 25 | "github.com/crossplane/crossplane-runtime/pkg/certificates" 26 | "github.com/crossplane/crossplane-runtime/pkg/controller" 27 | "github.com/crossplane/crossplane-runtime/pkg/feature" 28 | "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" 29 | "github.com/crossplane/crossplane-runtime/pkg/statemetrics" 30 | zapuber "go.uber.org/zap" 31 | "go.uber.org/zap/zapcore" 32 | kingpin "gopkg.in/alecthomas/kingpin.v2" 33 | 34 | "k8s.io/client-go/tools/leaderelection/resourcelock" 35 | 36 | kerrors "k8s.io/apimachinery/pkg/api/errors" 37 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 38 | ctrl "sigs.k8s.io/controller-runtime" 39 | "sigs.k8s.io/controller-runtime/pkg/cache" 40 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 41 | "sigs.k8s.io/controller-runtime/pkg/metrics" 42 | 43 | xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" 44 | "github.com/crossplane/crossplane-runtime/pkg/logging" 45 | "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" 46 | "github.com/crossplane/crossplane-runtime/pkg/resource" 47 | 48 | sourcev1 "github.com/fluxcd/source-controller/api/v1" 49 | sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" 50 | "github.com/upbound/provider-terraform/apis" 51 | "github.com/upbound/provider-terraform/apis/v1beta1" 52 | workspace "github.com/upbound/provider-terraform/internal/controller" 53 | "github.com/upbound/provider-terraform/internal/controller/features" 54 | ) 55 | 56 | func main() { 57 | var ( 58 | app = kingpin.New(filepath.Base(os.Args[0]), "Terraform support for Crossplane.").DefaultEnvars() 59 | debug = app.Flag("debug", "Run with debug logging.").Short('d').Bool() 60 | syncInterval = app.Flag("sync", "Sync interval controls how often all resources will be double checked for drift.").Short('s').Default("1h").Duration() 61 | pollInterval = app.Flag("poll", "Poll interval controls how often an individual resource should be checked for drift.").Default("10m").Duration() 62 | pollStateMetricInterval = app.Flag("poll-state-metric", "State metric recording interval").Default("5s").Duration() 63 | pollJitter = app.Flag("poll-jitter", "If non-zero, varies the poll interval by a random amount up to plus-or-minus this value.").Default("1m").Duration() 64 | timeout = app.Flag("timeout", "Controls how long Terraform processes may run before they are killed.").Default("20m").Duration() 65 | leaderElection = app.Flag("leader-election", "Use leader election for the controller manager.").Short('l').Default("false").Envar("LEADER_ELECTION").Bool() 66 | maxReconcileRate = app.Flag("max-reconcile-rate", "The maximum number of concurrent reconciliation operations.").Default("1").Int() 67 | namespace = app.Flag("namespace", "Namespace used to set as default scope in default secret store config.").Default("crossplane-system").Envar("POD_NAMESPACE").String() 68 | enableExternalSecretStores = app.Flag("enable-external-secret-stores", "Enable support for ExternalSecretStores.").Default("false").Envar("ENABLE_EXTERNAL_SECRET_STORES").Bool() 69 | enableManagementPolicies = app.Flag("enable-management-policies", "Enable support for Management Policies.").Default("true").Envar("ENABLE_MANAGEMENT_POLICIES").Bool() 70 | essTLSCertsPath = app.Flag("ess-tls-cert-dir", "Path of ESS TLS certificates.").Envar("ESS_TLS_CERTS_DIR").String() 71 | logEncoding = app.Flag("log-encoding", "Container logging output ending. Possible values: console, json").Default("console").Enum("console", "json") 72 | ) 73 | kingpin.MustParse(app.Parse(os.Args[1:])) 74 | 75 | var logEncoder zap.Opts 76 | switch *logEncoding { 77 | case "json": 78 | logEncoder = UseJSON() 79 | case "console": 80 | logEncoder = UseISO8601() 81 | default: 82 | kingpin.Fatalf("Unknown --log-encoding value: %s. Supported values are 'console' and 'json'", *logEncoding) 83 | } 84 | zl := zap.New(zap.UseDevMode(*debug), logEncoder) 85 | log := logging.NewLogrLogger(zl.WithName("provider-terraform")) 86 | // SetLogger is required starting in controller-runtime 0.15.0. 87 | // https://github.com/kubernetes-sigs/controller-runtime/pull/2317 88 | ctrl.SetLogger(zl) 89 | 90 | log.Debug("Starting", 91 | "sync-period", syncInterval.String(), 92 | "poll-interval", pollInterval.String(), 93 | "poll-jitter", pollJitter.String(), 94 | "max-reconcile-rate", *maxReconcileRate) 95 | 96 | cfg, err := ctrl.GetConfig() 97 | kingpin.FatalIfError(err, "Cannot get API server rest config") 98 | 99 | mgr, err := ctrl.NewManager(ratelimiter.LimitRESTConfig(cfg, *maxReconcileRate), ctrl.Options{ 100 | Cache: cache.Options{ 101 | SyncPeriod: syncInterval, 102 | }, 103 | 104 | // controller-runtime uses both ConfigMaps and Leases for leader 105 | // election by default. Leases expire after 15 seconds, with a 106 | // 10 second renewal deadline. We've observed leader loss due to 107 | // renewal deadlines being exceeded when under high load - i.e. 108 | // hundreds of reconciles per second and ~200rps to the API 109 | // server. Switching to Leases only and longer leases appears to 110 | // alleviate this. 111 | LeaderElection: *leaderElection, 112 | LeaderElectionID: "crossplane-leader-election-provider-terraform", 113 | LeaderElectionResourceLock: resourcelock.LeasesResourceLock, 114 | LeaseDuration: func() *time.Duration { d := 60 * time.Second; return &d }(), 115 | RenewDeadline: func() *time.Duration { d := 50 * time.Second; return &d }(), 116 | }) 117 | kingpin.FatalIfError(err, "Cannot create controller manager") 118 | 119 | kingpin.FatalIfError(apis.AddToScheme(mgr.GetScheme()), "Cannot add terraform APIs to scheme") 120 | kingpin.FatalIfError(sourcev1.AddToScheme(mgr.GetScheme()), "Cannot add flux gitrepository APIs to scheme") 121 | kingpin.FatalIfError(sourcev1beta2.AddToScheme(mgr.GetScheme()), "Cannot add flux ocirepository APIs to scheme") 122 | 123 | metricRecorder := managed.NewMRMetricRecorder() 124 | stateMetrics := statemetrics.NewMRStateMetrics() 125 | 126 | metrics.Registry.MustRegister(metricRecorder) 127 | metrics.Registry.MustRegister(stateMetrics) 128 | 129 | o := controller.Options{ 130 | Logger: log, 131 | MaxConcurrentReconciles: *maxReconcileRate, 132 | PollInterval: *pollInterval, 133 | GlobalRateLimiter: ratelimiter.NewGlobal(*maxReconcileRate), 134 | Features: &feature.Flags{}, 135 | MetricOptions: &controller.MetricOptions{ 136 | PollStateMetricInterval: *pollStateMetricInterval, 137 | MRMetrics: metricRecorder, 138 | MRStateMetrics: stateMetrics, 139 | }, 140 | } 141 | 142 | if *enableManagementPolicies { 143 | o.Features.Enable(features.EnableBetaManagementPolicies) 144 | log.Info("Beta feature enabled", "flag", features.EnableBetaManagementPolicies) 145 | } 146 | 147 | if *enableExternalSecretStores { 148 | o.Features.Enable(features.EnableAlphaExternalSecretStores) 149 | log.Info("Alpha feature enabled", "flag", features.EnableAlphaExternalSecretStores) 150 | o.ESSOptions = &controller.ESSOptions{} 151 | if *essTLSCertsPath != "" { 152 | log.Info("ESS TLS certificates path is set. Loading mTLS configuration.") 153 | tCfg, err := certificates.LoadMTLSConfig(filepath.Join(*essTLSCertsPath, "ca.crt"), filepath.Join(*essTLSCertsPath, "tls.crt"), filepath.Join(*essTLSCertsPath, "tls.key"), false) 154 | kingpin.FatalIfError(err, "Cannot load ESS TLS config.") 155 | 156 | o.ESSOptions.TLSConfig = tCfg 157 | } 158 | 159 | // Ensure default store config exists. 160 | kingpin.FatalIfError(resource.Ignore(kerrors.IsAlreadyExists, mgr.GetClient().Create(context.Background(), &v1beta1.StoreConfig{ 161 | ObjectMeta: metav1.ObjectMeta{ 162 | Name: "default", 163 | }, 164 | Spec: v1beta1.StoreConfigSpec{ 165 | // NOTE(turkenh): We only set required spec and expect optional 166 | // ones to properly be initialized with CRD level default values. 167 | SecretStoreConfig: xpv1.SecretStoreConfig{ 168 | DefaultScope: *namespace, 169 | }, 170 | }, 171 | })), "cannot create default store config") 172 | } 173 | 174 | kingpin.FatalIfError(workspace.Setup(mgr, o, *timeout, *pollJitter), "Cannot setup Workspace controllers") 175 | kingpin.FatalIfError(mgr.Start(ctrl.SetupSignalHandler()), "Cannot start controller manager") 176 | } 177 | 178 | // UseISO8601 sets the logger to use ISO8601 timestamp format 179 | func UseISO8601() zap.Opts { 180 | return func(o *zap.Options) { 181 | o.TimeEncoder = zapcore.ISO8601TimeEncoder 182 | } 183 | } 184 | 185 | // UseJSON sets the logger to use JSON encoding 186 | func UseJSON() zap.Opts { 187 | return func(o *zap.Options) { 188 | encoderConfig := zapuber.NewProductionEncoderConfig() 189 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 190 | o.Encoder = zapcore.NewJSONEncoder(encoderConfig) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /docs/monolith/index.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Quickstart", 4 | "location": "Quickstart.md" 5 | }, 6 | { 7 | "name": "Configuration", 8 | "location": "Configuration.md" 9 | }, 10 | { 11 | "name": "Migration Guide", 12 | "location": "MigrationGuide.md" 13 | } 14 | ] -------------------------------------------------------------------------------- /examples/.terraformrc: -------------------------------------------------------------------------------- 1 | provider_installation { 2 | filesystem_mirror { 3 | path = "/usr/share/terraform/providers" 4 | include = ["registry.terraform.io/hashicorp/*"] 5 | } 6 | direct { 7 | exclude = ["example.com/*/*"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/aws-eks-irsa-setup.yaml: -------------------------------------------------------------------------------- 1 | # NOTE: You need a trust policy associated with the role matching the Service Account and OIDC provider 2 | # Something like the following: 3 | # { 4 | # "Version": "2012-10-17", 5 | # "Statement": [ 6 | # { 7 | # "Effect": "Allow", 8 | # "Principal": { 9 | # "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}" 10 | # }, 11 | # "Action": "sts:AssumeRoleWithWebIdentity", 12 | # "Condition": { 13 | # "StringLike": { 14 | # "${OIDC_PROVIDER}:sub": "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:provider-terraform-*" 15 | # } 16 | # } 17 | # } 18 | # ] 19 | # } 20 | # 21 | # Where: 22 | # OIDC_PROVIDER=$(aws eks describe-cluster --name "${CLUSTER_NAME}" --region "${AWS_REGION}" --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///") 23 | --- 24 | apiVersion: pkg.crossplane.io/v1alpha1 25 | kind: ControllerConfig 26 | metadata: 27 | name: terraform-config 28 | labels: 29 | app: crossplane-provider-terraform 30 | annotations: 31 | eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNTID:role/provider-terraform 32 | spec: 33 | image: crossplane/provider-terraform-controller:v0.2.0 34 | args: 35 | - "--debug" 36 | podSecurityContext: 37 | fsGroup: 2000 38 | --- 39 | apiVersion: pkg.crossplane.io/v1 40 | kind: Provider 41 | metadata: 42 | name: crossplane-provider-terraform 43 | spec: 44 | package: crossplane/provider-terraform:v0.2.0 45 | controllerConfigRef: 46 | name: terraform-config 47 | --- 48 | apiVersion: tf.upbound.io/v1beta1 49 | kind: ProviderConfig 50 | metadata: 51 | annotations: {} 52 | name: aws-irsa 53 | spec: 54 | configuration: | 55 | terraform { 56 | backend "kubernetes" { 57 | secret_suffix = "providerconfig-tf-aws" 58 | namespace = "crossplane-system" 59 | in_cluster_config = true 60 | } 61 | } 62 | provider "aws" {} 63 | credentials: 64 | # Terraform natively supports IRSA auth so we can use None here 65 | - filename: aws.json 66 | source: None 67 | -------------------------------------------------------------------------------- /examples/creationblocker/README.md: -------------------------------------------------------------------------------- 1 | # Blocking condition PoC 2 | 3 | A very special edge case workaround, use it at your own risk :) 4 | 5 | This example Configuration(Composition+XRD) demonstrates a blocking condition 6 | PoC for blocking the resource creation according to the result of some special 7 | condition. 8 | 9 | We use provider-terraform Workspace to create the special condition and check if 10 | VPC alredy exists and in case it does it fully blocks the further execution by 11 | intentionally violating the RFC1123 in metada.name of composed VPC resource. 12 | -------------------------------------------------------------------------------- /examples/creationblocker/composition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: Composition 3 | metadata: 4 | name: xsubnets.aws.platformref.upbound.io 5 | labels: 6 | provider: aws 7 | spec: 8 | compositeTypeRef: 9 | apiVersion: aws.platformref.upbound.io/v1alpha1 10 | kind: XSubnet 11 | resources: 12 | - name: vpc-data-source 13 | base: 14 | apiVersion: tf.upbound.io/v1beta1 15 | kind: Workspace 16 | spec: 17 | forProvider: 18 | source: Inline 19 | module: | 20 | data "aws_vpcs" "exists" { 21 | tags = { 22 | Name = var.vpcName 23 | } 24 | } 25 | output "vpc_exists" { 26 | description = "VPC name" 27 | value = length(data.aws_vpcs.exists.ids) > 0 ? "-block.me" : "blockmenot" 28 | } 29 | 30 | variable "vpcName" { 31 | description = "VPC name" 32 | type = string 33 | } 34 | vars: 35 | - key: vpcName 36 | patches: 37 | - fromFieldPath: spec.vpcName 38 | toFieldPath: spec.forProvider.vars[0].value 39 | - type: ToCompositeFieldPath 40 | fromFieldPath: status.atProvider.outputs.vpc_exists 41 | toFieldPath: status.block 42 | policy: 43 | fromFieldPath: Optional 44 | - name: vpc-blocked 45 | base: 46 | apiVersion: ec2.aws.upbound.io/v1beta1 47 | kind: VPC 48 | spec: 49 | forProvider: 50 | region: eu-central-1 51 | cidrBlock: 10.0.0.0/24 52 | patches: 53 | - type: CombineFromComposite 54 | combine: 55 | variables: 56 | - fromFieldPath: status.block 57 | - fromFieldPath: spec.vpcName 58 | strategy: string 59 | string: 60 | fmt: "%s%s" 61 | toFieldPath: metadata.name 62 | policy: 63 | fromFieldPath: Required 64 | - name: subnet 65 | base: 66 | apiVersion: ec2.aws.upbound.io/v1beta1 67 | kind: Subnet 68 | spec: 69 | forProvider: 70 | region: eu-central-1 71 | cidrBlock: 10.0.0.0/25 72 | vpcIdSelector: 73 | matchControllerRef: true 74 | -------------------------------------------------------------------------------- /examples/creationblocker/definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: CompositeResourceDefinition 3 | metadata: 4 | name: xsubnets.aws.platformref.upbound.io 5 | spec: 6 | group: aws.platformref.upbound.io 7 | names: 8 | kind: XSubnet 9 | plural: xsubnets 10 | versions: 11 | - name: v1alpha1 12 | served: true 13 | referenceable: true 14 | schema: 15 | openAPIV3Schema: 16 | type: object 17 | properties: 18 | spec: 19 | type: object 20 | properties: 21 | vpcName: 22 | type: string 23 | required: 24 | - vpcName 25 | status: 26 | description: A Status represents the observed state 27 | properties: 28 | block: 29 | description: block arbitrary resource creation 30 | type: string 31 | #default: -blockcreation 32 | type: object 33 | -------------------------------------------------------------------------------- /examples/creationblocker/xsubnetblocking.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: aws.platformref.upbound.io/v1alpha1 2 | kind: XSubnet 3 | metadata: 4 | name: subnet-with-blocking-vpc 5 | spec: 6 | vpcName: blockingvpc 7 | -------------------------------------------------------------------------------- /examples/creationblocker/xsubnetnotblocking.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: aws.platformref.upbound.io/v1alpha1 2 | kind: XSubnet 3 | metadata: 4 | name: subnet-with-non-blocking-vpc 5 | spec: 6 | vpcName: nonblockingvpc 7 | -------------------------------------------------------------------------------- /examples/environment/envconfigmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | envfromconfigmap: EnvFromConfigMapValue 4 | kind: ConfigMap 5 | metadata: 6 | name: provider-terraform-test 7 | namespace: default 8 | -------------------------------------------------------------------------------- /examples/environment/envsecret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | envfromsecret: c3VwZXJzZWNyZXQ= 4 | kind: Secret 5 | metadata: 6 | name: provider-terraform-test 7 | namespace: default 8 | type: Opaque 9 | -------------------------------------------------------------------------------- /examples/environment/workspace-inline-env-aws.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: Workspace 3 | metadata: 4 | name: sample-inline-with-env 5 | spec: 6 | forProvider: 7 | source: Inline 8 | env: 9 | - name: TF_VAR_vpcName 10 | value: 'sample-tf-inline-with-env' 11 | - name: TF_VAR_EnvFromConfigMap 12 | configMapKeyRef: 13 | namespace: default 14 | name: provider-terraform-test 15 | key: envfromconfigmap 16 | - name: TF_VAR_EnvFromSecret 17 | secretKeyRef: 18 | namespace: default 19 | name: provider-terraform-test 20 | key: envfromsecret 21 | module: | 22 | resource "aws_vpc" "main" { 23 | cidr_block = "10.0.0.0/16" 24 | tags = { 25 | Name = var.vpcName 26 | EnvFromConfigMap = var.EnvFromConfigMap 27 | EnvFromSecret = var.EnvFromSecret 28 | } 29 | } 30 | resource "aws_subnet" "main" { 31 | vpc_id = aws_vpc.main.id 32 | cidr_block = "10.0.1.0/24" 33 | } 34 | output "vpc_id" { 35 | value = aws_vpc.main.id 36 | } 37 | output "subnet_data" { 38 | value = { 39 | "id" = aws_subnet.main.id 40 | "arn" = aws_subnet.main.arn 41 | } 42 | } 43 | variable "vpcName" { 44 | description = "VPC name" 45 | type = string 46 | } 47 | variable "EnvFromConfigMap" { 48 | description = "Environment Value From ConfigMap" 49 | type = string 50 | } 51 | variable "EnvFromSecret" { 52 | description = "Environment Value From Secret" 53 | type = string 54 | } 55 | -------------------------------------------------------------------------------- /examples/importer/README.md: -------------------------------------------------------------------------------- 1 | # Workaround for Importing Resources Functionality 2 | 3 | This example Configuration(Composition+XRD) demonstrates a temporary workaround 4 | for Importing Resources functionality before it is fully automated in the core Crossplane. 5 | 6 | In a nutshell it is a well-known 7 | https://docs.crossplane.io/knowledge-base/guides/import-existing-resources/#import-resources-manually 8 | process that is automated via the Composition. 9 | 10 | The workaround consists of a `Composition` that provides a mix of provider-terraform 11 | `Workspace` with the 12 | [aws_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) 13 | **data** resource as Inline module. 14 | 15 | It publishes the discovered imported `vpcdata` to the `XSubnet` XR status. 16 | 17 | The `vpcdata.id` and other data from the status is getting eventually consumed by `VPC` 18 | provider-aws resource following which is a part of the same `Composition`. `VPC` 19 | is eventually used as a reference in a new standard `Subnet` creation. 20 | -------------------------------------------------------------------------------- /examples/importer/composition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: Composition 3 | metadata: 4 | name: xsubnets.aws.platformref.upbound.io 5 | labels: 6 | provider: aws 7 | spec: 8 | compositeTypeRef: 9 | apiVersion: aws.platformref.upbound.io/v1alpha1 10 | kind: XSubnet 11 | resources: 12 | - name: vpc-data-source 13 | base: 14 | apiVersion: tf.upbound.io/v1beta1 15 | kind: Workspace 16 | spec: 17 | forProvider: 18 | source: Inline 19 | module: | 20 | data "aws_vpc" "import" { 21 | tags = { 22 | Name = var.vpcName 23 | } 24 | } 25 | output "vpc_data" { 26 | description = "Imported VPC data" 27 | value = { 28 | "id" = try(data.aws_vpc.import.id, "") 29 | "cidrBlock" = try(data.aws_vpc.import.cidr_block, "") 30 | "enableDnsSupport" = try(data.aws_vpc.import.enable_dns_support, "") 31 | "enableDnsHostnames" = try(data.aws_vpc.import.enable_dns_hostnames, "") 32 | } 33 | } 34 | variable "vpcName" { 35 | description = "VPC name" 36 | type = string 37 | } 38 | vars: 39 | - key: vpcName 40 | patches: 41 | - fromFieldPath: spec.vpcName 42 | toFieldPath: spec.forProvider.vars[0].value 43 | - type: ToCompositeFieldPath 44 | fromFieldPath: status.atProvider.outputs.vpc_data 45 | toFieldPath: status.share.vpcData 46 | policy: 47 | fromFieldPath: Optional 48 | - name: vpc-imported 49 | base: 50 | apiVersion: ec2.aws.upbound.io/v1beta1 51 | kind: VPC 52 | spec: 53 | deletionPolicy: Orphan 54 | forProvider: 55 | region: eu-central-1 56 | patches: 57 | - fromFieldPath: spec.vpcName 58 | toFieldPath: spec.forProvider.tags.Name 59 | - fromFieldPath: status.share.vpcData.id 60 | toFieldPath: metadata.annotations[crossplane.io/external-name] 61 | policy: 62 | fromFieldPath: Required 63 | - fromFieldPath: status.share.vpcData.cidrBlock 64 | toFieldPath: spec.forProvider.cidrBlock 65 | policy: 66 | fromFieldPath: Required 67 | - fromFieldPath: status.share.vpcData.enableDnsSupport 68 | toFieldPath: spec.forProvider.enableDnsSupport 69 | policy: 70 | fromFieldPath: Required 71 | - fromFieldPath: status.share.vpcData.enableDnsHostnames 72 | toFieldPath: spec.forProvider.enableDnsHostnames 73 | policy: 74 | fromFieldPath: Required 75 | - name: subnet 76 | base: 77 | apiVersion: ec2.aws.upbound.io/v1beta1 78 | kind: Subnet 79 | spec: 80 | forProvider: 81 | region: eu-central-1 82 | cidrBlock: 10.0.0.0/25 83 | vpcIdSelector: 84 | matchControllerRef: true 85 | -------------------------------------------------------------------------------- /examples/importer/definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: CompositeResourceDefinition 3 | metadata: 4 | name: xsubnets.aws.platformref.upbound.io 5 | spec: 6 | group: aws.platformref.upbound.io 7 | names: 8 | kind: XSubnet 9 | plural: xsubnets 10 | versions: 11 | - name: v1alpha1 12 | served: true 13 | referenceable: true 14 | schema: 15 | openAPIV3Schema: 16 | type: object 17 | properties: 18 | spec: 19 | type: object 20 | properties: 21 | vpcName: 22 | type: string 23 | required: 24 | - vpcName 25 | status: 26 | description: A Status represents the observed state 27 | properties: 28 | share: 29 | description: Freeform field containing status information 30 | type: object 31 | x-kubernetes-preserve-unknown-fields: true 32 | type: object 33 | -------------------------------------------------------------------------------- /examples/importer/xsubnet.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: aws.platformref.upbound.io/v1alpha1 2 | kind: XSubnet 3 | metadata: 4 | name: subnet-with-imported-vpc 5 | spec: 6 | vpcName: importvpc 7 | -------------------------------------------------------------------------------- /examples/install.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: pkg.crossplane.io/v1alpha1 3 | kind: ControllerConfig 4 | metadata: 5 | name: terraform-config 6 | labels: 7 | app: crossplane-provider-terraform 8 | spec: 9 | args: ["-d"] 10 | --- 11 | apiVersion: pkg.crossplane.io/v1 12 | kind: Provider 13 | metadata: 14 | name: crossplane-provider-terraform 15 | spec: 16 | package: xpkg.upbound.io/upbound/provider-terraform:v0.2.0 17 | controllerConfigRef: 18 | name: terraform-config 19 | -------------------------------------------------------------------------------- /examples/observe-only-composition/README.md: -------------------------------------------------------------------------------- 1 | # Workaround for Observe-Only Resources Functionality 2 | 3 | This example Configuration(Composition+XRD) demonstrates a temporary workaround 4 | for Observe-Only Resources functionality before it is [properly 5 | implemented](https://github.com/crossplane/crossplane/issues/1722) 6 | the core Crossplane. 7 | 8 | The workaround consists of a `Composition` that provides a mix of provider-terraform 9 | `Workspace` with the 10 | [aws_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) 11 | **data** resource as Inline module. 12 | 13 | It publishes the discovered observe-only `vpcId` to the `XSubnet` XR status. 14 | 15 | The `vpcId` from the status is getting eventually consumed by the native `Subnet` 16 | provider-aws resource which is a part of the same `Composition`. 17 | -------------------------------------------------------------------------------- /examples/observe-only-composition/composition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: Composition 3 | metadata: 4 | name: xsubnets.aws.platformref.upbound.io 5 | labels: 6 | provider: aws 7 | spec: 8 | compositeTypeRef: 9 | apiVersion: aws.platformref.upbound.io/v1alpha1 10 | kind: XSubnet 11 | resources: 12 | - name: observe-only-vpc 13 | base: 14 | apiVersion: tf.upbound.io/v1beta1 15 | kind: Workspace 16 | metadata: 17 | name: observe-only-vpc 18 | spec: 19 | forProvider: 20 | source: Inline 21 | module: | 22 | data "aws_vpc" "observe_only" { 23 | tags = { 24 | Name = var.vpcName 25 | } 26 | } 27 | output "vpc_id" { 28 | description = "Observe Only VPC ID" 29 | value = try(data.aws_vpc.observe_only.id, "") 30 | } 31 | variable "vpcName" { 32 | description = "VPC name" 33 | type = string 34 | } 35 | vars: 36 | - key: vpcName 37 | patches: 38 | - fromFieldPath: spec.vpcName 39 | toFieldPath: spec.forProvider.vars[0].value 40 | - type: ToCompositeFieldPath 41 | fromFieldPath: status.atProvider.outputs.vpc_id 42 | toFieldPath: status.share.vpcId 43 | policy: 44 | fromFieldPath: Optional 45 | - name: subnet 46 | base: 47 | apiVersion: ec2.aws.upbound.io/v1beta1 48 | kind: Subnet 49 | spec: 50 | forProvider: 51 | region: eu-central-1 52 | cidrBlock: 10.0.0.0/25 53 | patches: 54 | - fromFieldPath: status.share.vpcId 55 | toFieldPath: spec.forProvider.vpcId 56 | policy: 57 | fromFieldPath: Required 58 | -------------------------------------------------------------------------------- /examples/observe-only-composition/definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: CompositeResourceDefinition 3 | metadata: 4 | name: xsubnets.aws.platformref.upbound.io 5 | spec: 6 | group: aws.platformref.upbound.io 7 | names: 8 | kind: XSubnet 9 | plural: xsubnets 10 | versions: 11 | - name: v1alpha1 12 | served: true 13 | referenceable: true 14 | schema: 15 | openAPIV3Schema: 16 | type: object 17 | properties: 18 | spec: 19 | type: object 20 | properties: 21 | vpcName: 22 | type: string 23 | required: 24 | - vpcName 25 | status: 26 | description: A Status represents the observed state 27 | properties: 28 | share: 29 | description: Freeform field containing status information 30 | type: object 31 | x-kubernetes-preserve-unknown-fields: true 32 | type: object 33 | -------------------------------------------------------------------------------- /examples/observe-only-composition/xsubnet.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: aws.platformref.upbound.io/v1alpha1 2 | kind: XSubnet 3 | metadata: 4 | name: subnet-with-observe-only-vpc 5 | spec: 6 | vpcName: observeonly 7 | -------------------------------------------------------------------------------- /examples/providerconfig-aws.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: tf.upbound.io/v1beta1 3 | kind: ProviderConfig 4 | metadata: 5 | name: aws-eu-west-1 6 | spec: 7 | credentials: 8 | - filename: aws-creds.ini 9 | source: Secret 10 | secretRef: 11 | namespace: upbound-system 12 | name: aws-creds 13 | key: credentials 14 | configuration: | 15 | terraform { 16 | required_providers { 17 | aws = { 18 | source = "hashicorp/aws" 19 | version = "5.6.1" 20 | } 21 | } 22 | backend "kubernetes" { 23 | secret_suffix = "providerconfig-aws-eu-west-1" 24 | namespace = "upbound-system" 25 | in_cluster_config = true 26 | } 27 | } 28 | provider "aws" { 29 | shared_credentials_files = ["${path.module}/aws-creds.ini"] 30 | region = "us-east-2" 31 | } 32 | -------------------------------------------------------------------------------- /examples/providerconfig-azure.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: azure-creds 6 | type: Opaque 7 | stringData: 8 | "credentials": |- 9 | { 10 | "clientId": "wwwww~wwwwwwwww", 11 | "clientSecret": "xxxx-xxxx-xxxx-xxxx-xxxx", 12 | "tenantId": "yyyy-yyyy-yyyy-yyyy-yyyy", 13 | "subscriptionId": "zzzz-zzzz-zzzz-zzzz-zzzz" 14 | } 15 | --- 16 | apiVersion: tf.upbound.io/v1beta1 17 | kind: ProviderConfig 18 | metadata: 19 | name: azure-westeurope 20 | spec: 21 | credentials: 22 | # Filename has to comply with below naming convention: 23 | # - Files named exactly terraform.tfvars or terraform.tfvars.json. 24 | # - Any files with names ending in .auto.tfvars or .auto.tfvars.json. 25 | - filename: terraform.tfvars.json 26 | source: Secret 27 | secretRef: 28 | namespace: upbound-system 29 | name: azure-creds 30 | key: credentials 31 | configuration: | 32 | terraform { 33 | required_providers { 34 | azurerm = { 35 | source = "hashicorp/azurerm" 36 | version = "3.78.0" 37 | } 38 | } 39 | 40 | backend "kubernetes" { 41 | secret_suffix = "providerconfig-azure-westeurope" 42 | namespace = "upbound-system" 43 | in_cluster_config = true 44 | } 45 | } 46 | 47 | variable "subscriptionId" { 48 | type = string 49 | } 50 | 51 | variable "tenantId" { 52 | type = string 53 | } 54 | 55 | variable "clientId" { 56 | type = string 57 | } 58 | 59 | variable "clientSecret" { 60 | type = string 61 | } 62 | 63 | provider "azurerm" { 64 | subscription_id = var.subscriptionId 65 | tenant_id = var.tenantId 66 | client_id = var.clientId 67 | client_secret = var.clientSecret 68 | features {} 69 | } -------------------------------------------------------------------------------- /examples/providerconfig-backend-file.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: ProviderConfig 3 | metadata: 4 | name: default 5 | spec: 6 | # Note that unlike most provider configs this one supports an array of 7 | # credentials. This is because each Terraform workspace uses a single 8 | # Crossplane provider config, but could use multiple Terraform providers each 9 | # with their own credentials. 10 | credentials: 11 | - filename: gcp-credentials.json 12 | source: Secret 13 | secretRef: 14 | namespace: upbound-system 15 | name: gcp-creds 16 | key: credentials 17 | # This optional configuration block can be used to inject HCL into any 18 | # workspace that uses this provider config, for example to setup Terraform 19 | # providers. 20 | configuration: | 21 | provider "google" { 22 | credentials = "gcp-credentials.json" 23 | project = "official-provider-testing" 24 | } 25 | 26 | // Defining partial backend configuration as documented at 27 | // https://developer.hashicorp.com/terraform/language/settings/backends/configuration#partial-configuration 28 | terraform { 29 | backend "kubernetes" {} 30 | } 31 | # Using backend configuration file as documented at 32 | # https://developer.hashicorp.com/terraform/language/settings/backends/configuration#file 33 | backendFile: | 34 | secret_suffix = "providerconfig-default" 35 | namespace = "upbound-system" 36 | in_cluster_config = true -------------------------------------------------------------------------------- /examples/providerconfig-terraformrc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: ProviderConfig 3 | metadata: 4 | name: default 5 | spec: 6 | credentials: 7 | - filename: aws-creds.ini 8 | source: Secret 9 | secretRef: 10 | namespace: upbound-system 11 | name: aws-creds 12 | key: credentials 13 | # kubectl -n upbound-system create secret generic terraformrc --from-file=examples/.terraformrc 14 | - filename: .terraformrc 15 | source: Secret 16 | secretRef: 17 | namespace: upbound-system 18 | name: terraformrc 19 | key: .terraformrc 20 | configuration: | 21 | terraform { 22 | backend "kubernetes" { 23 | secret_suffix = "providerconfig-aws-eu-west-1" 24 | namespace = "upbound-system" 25 | in_cluster_config = true 26 | } 27 | } 28 | provider "aws" { 29 | shared_credentials_file = "${path.module}/aws-creds.ini" 30 | region = "eu-west-1" 31 | } 32 | -------------------------------------------------------------------------------- /examples/providerconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: ProviderConfig 3 | metadata: 4 | name: default 5 | spec: 6 | # Note that unlike most provider configs this one supports an array of 7 | # credentials. This is because each Terraform workspace uses a single 8 | # Crossplane provider config, but could use multiple Terraform providers each 9 | # with their own credentials. 10 | credentials: 11 | - filename: gcp-credentials.json 12 | source: Secret 13 | secretRef: 14 | namespace: upbound-system 15 | name: gcp-creds 16 | key: credentials 17 | # This optional configuration block can be used to inject HCL into any 18 | # workspace that uses this provider config, for example to setup Terraform 19 | # providers. 20 | configuration: | 21 | provider "google" { 22 | credentials = "gcp-credentials.json" 23 | project = "official-provider-testing" 24 | } 25 | 26 | // Modules _must_ use remote state. The provider does not persist state. 27 | terraform { 28 | backend "kubernetes" { 29 | secret_suffix = "providerconfig-default" 30 | namespace = "upbound-system" 31 | in_cluster_config = true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/transition/00-mr-tf-workspace/workspace-inline.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: Workspace 3 | metadata: 4 | name: sample-inline 5 | spec: 6 | forProvider: 7 | source: Inline 8 | module: | 9 | resource "aws_vpc" "main" { 10 | cidr_block = "10.0.0.0/16" 11 | tags = { 12 | Name = var.vpcName 13 | } 14 | } 15 | resource "aws_subnet" "main" { 16 | vpc_id = aws_vpc.main.id 17 | cidr_block = "10.0.1.0/24" 18 | } 19 | output "vpc_id" { 20 | value = aws_vpc.main.id 21 | } 22 | output "subnet_id" { 23 | value = aws_subnet.main.id 24 | } 25 | variable "vpcName" { 26 | description = "VPC name" 27 | type = string 28 | } 29 | vars: 30 | - key: vpcName 31 | value: sample-tf-inline 32 | -------------------------------------------------------------------------------- /examples/transition/00-mr-tf-workspace/workspace-remote.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: Workspace 3 | metadata: 4 | name: sample-remote 5 | spec: 6 | forProvider: 7 | source: Remote 8 | module: git::https://github.com/ytsarev/provider-terraform-test-module.git//transition?ref=main 9 | vars: 10 | - key: vpcName 11 | value: sample-tf-remote 12 | -------------------------------------------------------------------------------- /examples/transition/01-composition-tf-only/composition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: Composition 3 | metadata: 4 | name: tf-xsubnets.aws.demo.upbound.io 5 | labels: 6 | provider: aws 7 | implementation: terraform 8 | spec: 9 | compositeTypeRef: 10 | apiVersion: aws.demo.upbound.io/v1alpha1 11 | kind: XSubnet 12 | resources: 13 | - name: tf-vpc-and-subnet 14 | base: 15 | apiVersion: tf.upbound.io/v1beta1 16 | kind: Workspace 17 | spec: 18 | forProvider: 19 | source: Inline 20 | module: | 21 | resource "aws_vpc" "main" { 22 | cidr_block = "10.0.0.0/16" 23 | tags = { 24 | Name = var.vpcName 25 | } 26 | } 27 | resource "aws_subnet" "main" { 28 | vpc_id = aws_vpc.main.id 29 | cidr_block = "10.0.1.0/24" 30 | } 31 | output "vpc_id" { 32 | value = aws_vpc.main.id 33 | } 34 | output "subnet_id" { 35 | value = aws_subnet.main.id 36 | } 37 | variable "vpcName" { 38 | description = "VPC name" 39 | type = string 40 | } 41 | vars: 42 | - key: vpcName 43 | patches: 44 | - fromFieldPath: spec.vpcName 45 | toFieldPath: spec.forProvider.vars[0].value 46 | - type: ToCompositeFieldPath 47 | fromFieldPath: status.atProvider.outputs.vpc_id 48 | toFieldPath: status.share.vpcId 49 | policy: 50 | fromFieldPath: Optional 51 | - type: ToCompositeFieldPath 52 | fromFieldPath: status.atProvider.outputs.subnet_id 53 | toFieldPath: status.share.subnetId 54 | policy: 55 | fromFieldPath: Optional 56 | -------------------------------------------------------------------------------- /examples/transition/01-composition-tf-only/subnet-tf.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: aws.demo.upbound.io/v1alpha1 2 | kind: Subnet 3 | metadata: 4 | name: subnet-tf 5 | spec: 6 | vpcName: provider-terraform-demo-tf 7 | compositionSelector: 8 | matchLabels: 9 | implementation: terraform 10 | -------------------------------------------------------------------------------- /examples/transition/02-composition-tf-and-native/composition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: Composition 3 | metadata: 4 | name: mixed-xsubnets.aws.demo.upbound.io 5 | labels: 6 | provider: aws 7 | implementation: mixed 8 | spec: 9 | compositeTypeRef: 10 | apiVersion: aws.demo.upbound.io/v1alpha1 11 | kind: XSubnet 12 | resources: 13 | - name: vpc 14 | base: 15 | apiVersion: tf.upbound.io/v1beta1 16 | kind: Workspace 17 | spec: 18 | forProvider: 19 | source: Inline 20 | module: | 21 | resource "aws_vpc" "main" { 22 | cidr_block = "10.0.0.0/16" 23 | tags = { 24 | Name = var.vpcName 25 | } 26 | } 27 | output "vpc_id" { 28 | value = aws_vpc.main.id 29 | } 30 | variable "vpcName" { 31 | description = "VPC name" 32 | type = string 33 | } 34 | vars: 35 | - key: vpcName 36 | patches: 37 | - fromFieldPath: spec.vpcName 38 | toFieldPath: spec.forProvider.vars[0].value 39 | - type: ToCompositeFieldPath 40 | fromFieldPath: status.atProvider.outputs.vpc_id 41 | toFieldPath: status.share.vpcId 42 | policy: 43 | fromFieldPath: Optional 44 | - name: subnet 45 | base: 46 | apiVersion: ec2.aws.upbound.io/v1beta1 47 | kind: Subnet 48 | spec: 49 | forProvider: 50 | region: eu-central-1 51 | cidrBlock: 10.0.1.0/24 52 | patches: 53 | - fromFieldPath: status.share.vpcId 54 | toFieldPath: spec.forProvider.vpcId 55 | policy: 56 | fromFieldPath: Required 57 | -------------------------------------------------------------------------------- /examples/transition/02-composition-tf-and-native/subnet-mixed.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: aws.demo.upbound.io/v1alpha1 2 | kind: Subnet 3 | metadata: 4 | name: subnet-mixed 5 | spec: 6 | vpcName: provider-terraform-demo-mixed 7 | compositionSelector: 8 | matchLabels: 9 | implementation: mixed 10 | -------------------------------------------------------------------------------- /examples/transition/03-composition-native-only/composition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: Composition 3 | metadata: 4 | name: native-xsubnets.aws.demo.upbound.io 5 | labels: 6 | provider: aws 7 | implementation: native 8 | spec: 9 | compositeTypeRef: 10 | apiVersion: aws.demo.upbound.io/v1alpha1 11 | kind: XSubnet 12 | resources: 13 | - name: vpc 14 | base: 15 | apiVersion: ec2.aws.upbound.io/v1beta1 16 | kind: VPC 17 | spec: 18 | forProvider: 19 | region: eu-central-1 20 | cidrBlock: 10.0.0.0/16 21 | patches: 22 | - fromFieldPath: spec.vpcName 23 | toFieldPath: spec.forProvider.tags.Name 24 | - name: subnet 25 | base: 26 | apiVersion: ec2.aws.upbound.io/v1beta1 27 | kind: Subnet 28 | spec: 29 | forProvider: 30 | region: eu-central-1 31 | cidrBlock: 10.0.1.0/24 32 | vpcIdSelector: 33 | matchControllerRef: true 34 | -------------------------------------------------------------------------------- /examples/transition/03-composition-native-only/subnet-native.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: aws.demo.upbound.io/v1alpha1 2 | kind: Subnet 3 | metadata: 4 | name: subnet-native 5 | spec: 6 | vpcName: provider-terraform-demo-native 7 | compositionSelector: 8 | matchLabels: 9 | implementation: native 10 | -------------------------------------------------------------------------------- /examples/transition/README.md: -------------------------------------------------------------------------------- 1 | # provider-terraform demo sample 2 | 3 | This example demonstrates `XSubnet` consistent XRD abstraction with multiple 4 | `Composition` implementations. 5 | 6 | It illustrates the following transition: 7 | 8 | 1. Barebone Managed Resource of [Terraform Workspace](00-mr-tf-workspace/workspace-inline.yaml). 9 | 2. [Terraform Workspace within a Composition](01-composition-tf-only/composition.yaml). 10 | 3. Mixed scenario of [Terraform Workspace and Crossplane-native Resource within the Composition](02-composition-tf-and-native/composition.yaml). 11 | 4. Full migration to [Crossplane-native Managed Resources](03-composition-native-only/composition.yaml). 12 | 13 | All Composition-based steps are backed by the stable custom API defined by [XRD](definition.yaml). 14 | 15 | All Composition-based transition stages can be end-to-end tested by associated 16 | Claim instantiation. 17 | -------------------------------------------------------------------------------- /examples/transition/definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: CompositeResourceDefinition 3 | metadata: 4 | name: xsubnets.aws.demo.upbound.io 5 | spec: 6 | group: aws.demo.upbound.io 7 | names: 8 | kind: XSubnet 9 | plural: xsubnets 10 | claimNames: 11 | kind: Subnet 12 | plural: subnets 13 | versions: 14 | - name: v1alpha1 15 | served: true 16 | referenceable: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | properties: 21 | spec: 22 | type: object 23 | properties: 24 | vpcName: 25 | type: string 26 | required: 27 | - vpcName 28 | status: 29 | description: A Status represents the observed state 30 | properties: 31 | share: 32 | description: Freeform field containing status information 33 | type: object 34 | x-kubernetes-preserve-unknown-fields: true 35 | type: object 36 | -------------------------------------------------------------------------------- /examples/workspace-enable-logging.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: Workspace 3 | metadata: 4 | name: example-random-generator 5 | annotations: 6 | meta.upbound.io/example-id: tf/v1beta1/workspace 7 | # The terraform workspace will be named 'random'. If you omit this 8 | # annotation it would be derived from metadata.name - e.g. 'example-random-generator. 9 | crossplane.io/external-name: random 10 | spec: 11 | forProvider: 12 | enableTerraformCLILogging: true 13 | source: Inline 14 | module: | 15 | resource "random_id" "example_id" { 16 | byte_length = 8 17 | } 18 | resource "random_password" "password" { 19 | length = 16 20 | special = true 21 | } 22 | // Non-sensitive Outputs are written to status.atProvider.outputs and to the connection secret. 23 | output "random_id_hex" { 24 | value = random_id.example_id.hex 25 | } 26 | // Sensitive Outputs are only written to the connection secret 27 | output "random_password" { 28 | value = random_password.password 29 | sensitive = true 30 | } 31 | // Terraform has several other random resources, see the random provider for details 32 | writeConnectionSecretToRef: 33 | namespace: default 34 | name: terraform-workspace-example-random-generator 35 | -------------------------------------------------------------------------------- /examples/workspace-inline-aws.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: Workspace 3 | metadata: 4 | name: sample-inline 5 | spec: 6 | providerConfigRef: 7 | name: aws-eu-west-1 8 | forProvider: 9 | source: Inline 10 | module: | 11 | resource "aws_vpc" "main" { 12 | cidr_block = "10.0.0.0/16" 13 | tags = { 14 | Name = var.vpcName 15 | } 16 | } 17 | resource "aws_subnet" "main" { 18 | vpc_id = aws_vpc.main.id 19 | cidr_block = "10.0.1.0/24" 20 | } 21 | output "vpc_id" { 22 | value = aws_vpc.main.id 23 | } 24 | output "subnet_data" { 25 | value = { 26 | "id" = aws_subnet.main.id 27 | "arn" = aws_subnet.main.arn 28 | } 29 | } 30 | variable "vpcName" { 31 | description = "VPC name" 32 | type = string 33 | } 34 | vars: 35 | - key: vpcName 36 | value: sample-tf-inline 37 | -------------------------------------------------------------------------------- /examples/workspace-inline.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: Workspace 3 | metadata: 4 | name: example-inline 5 | annotations: 6 | meta.upbound.io/example-id: tf/v1beta1/workspace 7 | # The terraform workspace will be named 'coolbucket'. If you omit this 8 | # annotation it would be derived from metadata.name - e.g. 'example-inline'. 9 | crossplane.io/external-name: coolbucket 10 | spec: 11 | forProvider: 12 | # Workspaces default to using a remote source - like workspace-remote.yaml. 13 | # For simple cases you can use an inline source to specify the content of 14 | # main.tf as opaque, inline HCL. 15 | source: Inline 16 | module: | 17 | // Outputs are written to the connection secret. 18 | output "url" { 19 | value = google_storage_bucket.example.self_link 20 | } 21 | 22 | resource "random_id" "example" { 23 | byte_length = 4 24 | } 25 | 26 | // The google provider and remote state are configured by the provider 27 | // config - see providerconfig.yaml. 28 | resource "google_storage_bucket" "example" { 29 | name = "crossplane-example-${terraform.workspace}-${random_id.example.hex}" 30 | location = "US" 31 | force_destroy = true 32 | 33 | public_access_prevention = "enforced" 34 | } 35 | writeConnectionSecretToRef: 36 | namespace: default 37 | name: terraform-workspace-example-inline 38 | -------------------------------------------------------------------------------- /examples/workspace-random-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: Workspace 3 | metadata: 4 | name: example-random-generator 5 | annotations: 6 | meta.upbound.io/example-id: tf/v1beta1/workspace 7 | # The terraform workspace will be named 'random'. If you omit this 8 | # annotation it would be derived from metadata.name - e.g. 'example-random-generator. 9 | crossplane.io/external-name: random 10 | spec: 11 | forProvider: 12 | source: Inline 13 | module: | 14 | resource "random_id" "example_id" { 15 | byte_length = 4 16 | } 17 | resource "random_password" "password" { 18 | length = 16 19 | special = true 20 | } 21 | // Non-sensitive Outputs are written to status.atProvider.outputs and to the connection secret. 22 | output "random_id_hex" { 23 | value = random_id.example_id.hex 24 | } 25 | // Sensitive Outputs are only written to the connection secret 26 | output "random_password" { 27 | value = random_password.password 28 | sensitive = true 29 | } 30 | // Terraform has several other random resources, see the random provider for details 31 | 32 | writeConnectionSecretToRef: 33 | namespace: default 34 | name: terraform-workspace-example-random-generator 35 | -------------------------------------------------------------------------------- /examples/workspace-remote.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tf.upbound.io/v1beta1 2 | kind: Workspace 3 | metadata: 4 | name: example-remote 5 | annotations: 6 | meta.upbound.io/example-id: tf/v1beta1/workspace 7 | # The terraform workspace will be named 'myworkspace'. If you omit this 8 | # annotation it would be derived from metadata.name - e.g. 'example-remote'. 9 | crossplane.io/external-name: myworkspace 10 | spec: 11 | forProvider: 12 | # Git based remote module is supported. 13 | # See https://www.terraform.io/language/modules/sources#generic-git-repository 14 | # and https://www.terraform.io/language/modules/sources#modules-in-package-sub-directories 15 | # for URL structure. 16 | # You can also specify a simple main.tf inline; see 17 | # workspace-inline.yaml. 18 | source: Remote 19 | module: git::https://github.com/crossplane/tf?ref=main 20 | # Variables can be specified inline. 21 | vars: 22 | - key: region 23 | value: us-west-1 24 | # Variable files can be loaded from a ConfigMap or a Secret. 25 | varFiles: 26 | - source: ConfigMapKey 27 | configMapKeyRef: 28 | namespace: default 29 | name: terraform 30 | key: example.tfvars 31 | - source: SecretKey 32 | secretKeyRef: 33 | namespace: default 34 | name: terraform 35 | key: example.tfvar.json 36 | # Variables are expected to be in HCL '.tfvars' format by default. Use 37 | # the JSON format if your variables are in the JSON '.tfvars.json' format. 38 | format: JSON 39 | # All Terraform outputs are written to the connection secret. 40 | writeConnectionSecretToRef: 41 | namespace: default 42 | name: terraform-workspace-example-remote 43 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/upbound/provider-terraform 2 | 3 | go 1.23.8 4 | 5 | require ( 6 | github.com/MakeNowJust/heredoc v1.0.0 7 | github.com/crossplane/crossplane-runtime v1.16.0 8 | github.com/crossplane/crossplane-tools v0.0.0-20230925130601-628280f8bf79 9 | github.com/fluxcd/source-controller/api v1.2.5 10 | github.com/google/go-cmp v0.7.0 11 | github.com/google/uuid v1.6.0 12 | github.com/hashicorp/go-getter v1.7.8 13 | github.com/pkg/errors v0.9.1 14 | github.com/spf13/afero v1.14.0 15 | go.uber.org/zap v1.27.0 16 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 17 | k8s.io/api v0.29.4 18 | k8s.io/apiextensions-apiserver v0.29.4 19 | k8s.io/apimachinery v0.29.4 20 | k8s.io/client-go v0.29.4 21 | sigs.k8s.io/controller-runtime v0.17.3 22 | sigs.k8s.io/controller-tools v0.14.0 23 | ) 24 | 25 | require ( 26 | cloud.google.com/go v0.110.10 // indirect 27 | cloud.google.com/go/compute/metadata v0.3.0 // indirect 28 | cloud.google.com/go/iam v1.1.5 // indirect 29 | cloud.google.com/go/storage v1.35.1 // indirect 30 | dario.cat/mergo v1.0.0 // indirect 31 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 32 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect 33 | github.com/aws/aws-sdk-go v1.44.122 // indirect 34 | github.com/beorn7/perks v1.0.1 // indirect 35 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect 36 | github.com/blang/semver/v4 v4.0.0 // indirect 37 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 38 | github.com/dave/jennifer v1.4.1 // indirect 39 | github.com/davecgh/go-spew v1.1.1 // indirect 40 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 41 | github.com/evanphx/json-patch v5.6.0+incompatible // indirect 42 | github.com/evanphx/json-patch/v5 v5.8.0 // indirect 43 | github.com/fatih/color v1.16.0 // indirect 44 | github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect 45 | github.com/fluxcd/pkg/apis/meta v1.3.0 // indirect 46 | github.com/fsnotify/fsnotify v1.7.0 // indirect 47 | github.com/go-logr/logr v1.4.1 // indirect 48 | github.com/go-logr/zapr v1.3.0 // indirect 49 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 50 | github.com/go-openapi/jsonreference v0.20.2 // indirect 51 | github.com/go-openapi/swag v0.22.3 // indirect 52 | github.com/gobuffalo/flect v1.0.2 // indirect 53 | github.com/gogo/protobuf v1.3.2 // indirect 54 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 55 | github.com/golang/protobuf v1.5.4 // indirect 56 | github.com/google/gnostic-models v0.6.8 // indirect 57 | github.com/google/gofuzz v1.2.0 // indirect 58 | github.com/google/s2a-go v0.1.7 // indirect 59 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 60 | github.com/googleapis/gax-go/v2 v2.12.0 // indirect 61 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 62 | github.com/hashicorp/go-safetemp v1.0.0 // indirect 63 | github.com/hashicorp/go-version v1.6.0 // indirect 64 | github.com/imdario/mergo v0.3.16 // indirect 65 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 66 | github.com/jmespath/go-jmespath v0.4.0 // indirect 67 | github.com/josharian/intern v1.0.0 // indirect 68 | github.com/json-iterator/go v1.1.12 // indirect 69 | github.com/klauspost/compress v1.17.4 // indirect 70 | github.com/mailru/easyjson v0.7.7 // indirect 71 | github.com/mattn/go-colorable v0.1.13 // indirect 72 | github.com/mattn/go-isatty v0.0.20 // indirect 73 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 74 | github.com/mitchellh/go-homedir v1.1.0 // indirect 75 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 76 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 77 | github.com/modern-go/reflect2 v1.0.2 // indirect 78 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 79 | github.com/prometheus/client_golang v1.18.0 // indirect 80 | github.com/prometheus/client_model v0.5.0 // indirect 81 | github.com/prometheus/common v0.45.0 // indirect 82 | github.com/prometheus/procfs v0.12.0 // indirect 83 | github.com/spf13/cobra v1.8.0 // indirect 84 | github.com/spf13/pflag v1.0.5 // indirect 85 | github.com/ulikunitz/xz v0.5.10 // indirect 86 | go.opencensus.io v0.24.0 // indirect 87 | go.uber.org/multierr v1.11.0 // indirect 88 | golang.org/x/crypto v0.36.0 // indirect 89 | golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect 90 | golang.org/x/mod v0.17.0 // indirect 91 | golang.org/x/net v0.38.0 // indirect 92 | golang.org/x/oauth2 v0.27.0 // indirect 93 | golang.org/x/sync v0.12.0 // indirect 94 | golang.org/x/sys v0.31.0 // indirect 95 | golang.org/x/term v0.30.0 // indirect 96 | golang.org/x/text v0.23.0 // indirect 97 | golang.org/x/time v0.5.0 // indirect 98 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 99 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect 100 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 101 | google.golang.org/api v0.152.0 // indirect 102 | google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect 103 | google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect 104 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect 105 | google.golang.org/grpc v1.61.0 // indirect 106 | google.golang.org/protobuf v1.33.0 // indirect 107 | gopkg.in/inf.v0 v0.9.1 // indirect 108 | gopkg.in/yaml.v2 v2.4.0 // indirect 109 | gopkg.in/yaml.v3 v3.0.1 // indirect 110 | k8s.io/component-base v0.29.4 // indirect 111 | k8s.io/klog/v2 v2.110.1 // indirect 112 | k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect 113 | k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect 114 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 115 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 116 | sigs.k8s.io/yaml v1.4.0 // indirect 117 | ) 118 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /internal/controller/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "time" 21 | 22 | ctrl "sigs.k8s.io/controller-runtime" 23 | 24 | "github.com/crossplane/crossplane-runtime/pkg/controller" 25 | "github.com/crossplane/crossplane-runtime/pkg/event" 26 | "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" 27 | "github.com/crossplane/crossplane-runtime/pkg/reconciler/providerconfig" 28 | "github.com/crossplane/crossplane-runtime/pkg/resource" 29 | 30 | "github.com/upbound/provider-terraform/apis/v1beta1" 31 | ) 32 | 33 | // Setup adds a controller that reconciles ProviderConfigs by accounting for 34 | // their current usage. 35 | func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration) error { 36 | name := providerconfig.ControllerName(v1beta1.ProviderConfigGroupKind) 37 | 38 | of := resource.ProviderConfigKinds{ 39 | Config: v1beta1.ProviderConfigGroupVersionKind, 40 | UsageList: v1beta1.ProviderConfigUsageListGroupVersionKind, 41 | } 42 | 43 | r := providerconfig.NewReconciler(mgr, of, 44 | providerconfig.WithLogger(o.Logger.WithValues("controller", name)), 45 | providerconfig.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name)))) 46 | 47 | return ctrl.NewControllerManagedBy(mgr). 48 | Named(name). 49 | WithOptions(o.ForControllerRuntime()). 50 | For(&v1beta1.ProviderConfig{}). 51 | Watches(&v1beta1.ProviderConfigUsage{}, &resource.EnqueueRequestForProviderConfig{}). 52 | Complete(ratelimiter.NewReconciler(name, r, o.GlobalRateLimiter)) 53 | } 54 | -------------------------------------------------------------------------------- /internal/controller/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | -------------------------------------------------------------------------------- /internal/controller/features/features.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package features 18 | 19 | import "github.com/crossplane/crossplane-runtime/pkg/feature" 20 | 21 | // Feature flags. 22 | const ( 23 | // EnableAlphaExternalSecretStores enables alpha support for 24 | // External Secret Stores. See the below design for more details. 25 | // https://github.com/crossplane/crossplane/blob/390ddd/design/design-doc-external-secret-stores.md 26 | EnableAlphaExternalSecretStores feature.Flag = "EnableAlphaExternalSecretStores" 27 | 28 | // EnableBetaManagementPolicies enables beta support for 29 | // Management Policies. See the below design for more details. 30 | // https://github.com/crossplane/crossplane/pull/3531 31 | EnableBetaManagementPolicies feature.Flag = "EnableBetaManagementPolicies" 32 | ) 33 | -------------------------------------------------------------------------------- /internal/controller/terraform.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "time" 21 | 22 | ctrl "sigs.k8s.io/controller-runtime" 23 | 24 | "github.com/crossplane/crossplane-runtime/pkg/controller" 25 | 26 | "github.com/upbound/provider-terraform/internal/controller/config" 27 | "github.com/upbound/provider-terraform/internal/controller/workspace" 28 | ) 29 | 30 | // Setup creates all terraform controllers with the supplied options and adds 31 | // them to the supplied manager. 32 | func Setup(mgr ctrl.Manager, o controller.Options, timeout, pollJitter time.Duration) error { 33 | if err := config.Setup(mgr, o, timeout); err != nil { 34 | return err 35 | } 36 | if err := workspace.Setup(mgr, o, timeout, pollJitter); err != nil { 37 | return err 38 | } 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/terraform/terraform_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package terraform 18 | 19 | import ( 20 | "os/exec" 21 | "testing" 22 | 23 | "github.com/MakeNowJust/heredoc" 24 | "github.com/google/go-cmp/cmp" 25 | "github.com/pkg/errors" 26 | ) 27 | 28 | func TestOutputStringValue(t *testing.T) { 29 | cases := map[string]struct { 30 | o Output 31 | want string 32 | }{ 33 | "ValueIsString": { 34 | o: Output{value: "imastring!"}, 35 | want: "imastring!", 36 | }, 37 | "ValueIsNotString": { 38 | o: Output{value: 42}, 39 | want: "", 40 | }, 41 | } 42 | 43 | for name, tc := range cases { 44 | t.Run(name, func(t *testing.T) { 45 | got := tc.o.StringValue() 46 | if diff := cmp.Diff(tc.want, got); diff != "" { 47 | t.Errorf("\no.StringValue(): -want, +got:\n%s", diff) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func TestOutputNumberValue(t *testing.T) { 54 | cases := map[string]struct { 55 | o Output 56 | want float64 57 | }{ 58 | "ValueIsFloat": { 59 | o: Output{value: float64(42.0)}, 60 | want: 42.0, 61 | }, 62 | // We create outputs by decoding from JSON, so numbers should always be 63 | // a float64. 64 | "ValueIsNotFloat": { 65 | o: Output{value: 42}, 66 | want: 0, 67 | }, 68 | } 69 | 70 | for name, tc := range cases { 71 | t.Run(name, func(t *testing.T) { 72 | got := tc.o.NumberValue() 73 | if diff := cmp.Diff(tc.want, got); diff != "" { 74 | t.Errorf("\no.NumberValue(): -want, +got:\n%s", diff) 75 | } 76 | }) 77 | } 78 | } 79 | 80 | func TestOutputBoolValue(t *testing.T) { 81 | cases := map[string]struct { 82 | o Output 83 | want bool 84 | }{ 85 | "ValueIsBool": { 86 | o: Output{value: true}, 87 | want: true, 88 | }, 89 | "ValueIsNotBool": { 90 | o: Output{value: "DEFINITELY!"}, 91 | want: false, 92 | }, 93 | } 94 | 95 | for name, tc := range cases { 96 | t.Run(name, func(t *testing.T) { 97 | got := tc.o.BoolValue() 98 | if diff := cmp.Diff(tc.want, got); diff != "" { 99 | t.Errorf("\no.BoolValue(): -want, +got:\n%s", diff) 100 | } 101 | }) 102 | } 103 | } 104 | 105 | func TestOutputJSONValue(t *testing.T) { 106 | type want struct { 107 | j []byte 108 | err error 109 | } 110 | cases := map[string]struct { 111 | o Output 112 | want want 113 | }{ 114 | "ValueIsString": { 115 | o: Output{value: "imastring!"}, 116 | want: want{ 117 | j: []byte(`"imastring!"`), 118 | }, 119 | }, 120 | "ValueIsNumber": { 121 | o: Output{value: 42.0}, 122 | want: want{ 123 | j: []byte(`42`), 124 | }, 125 | }, 126 | "ValueIsBool": { 127 | o: Output{value: true}, 128 | want: want{ 129 | j: []byte(`true`), 130 | }, 131 | }, 132 | "ValueIsTuple": { 133 | o: Output{value: []any{"imastring", 42, true}}, 134 | want: want{ 135 | j: []byte(`["imastring",42,true]`), 136 | }, 137 | }, 138 | "ValueIsObject": { 139 | o: Output{value: map[string]any{ 140 | "cool": 42, 141 | }}, 142 | want: want{ 143 | j: []byte(`{"cool":42}`), 144 | }, 145 | }, 146 | } 147 | 148 | for name, tc := range cases { 149 | t.Run(name, func(t *testing.T) { 150 | got, err := tc.o.JSONValue() 151 | if diff := cmp.Diff(tc.want.err, err); diff != "" { 152 | t.Errorf("\no.JSONValue(): -want error, +got error:\n%s", diff) 153 | } 154 | if diff := cmp.Diff(tc.want.j, got); diff != "" { 155 | t.Errorf("\no.JSONValue(): -want, +got:\n%s", diff) 156 | } 157 | }) 158 | } 159 | } 160 | 161 | func TestClassify(t *testing.T) { 162 | tferrs := make(map[string]error) 163 | expectedOutput := make(map[string]error) 164 | 165 | tferrs["unexpectedName"] = &exec.ExitError{ 166 | Stderr: []byte(heredoc.Doc(` 167 | │ Error: Unsupported argument 168 | │ 169 | │ on test.tf line 10, in resource "aws_s3_bucket" "example": 170 | │ 10: name = "cp-example-${terraform.workspace}-${random_id.example.hex}" 171 | │ 172 | │ An argument named "name" is not expected here. 173 | `)), 174 | } 175 | 176 | expectedOutput["unexpectedName"] = errors.New( 177 | heredoc.Doc( 178 | `Terraform encountered an error. Summary: Unsupported argument. To see the full error run: echo "H4sIAAAAAAAA/zyPMWoDMRBFe5/iI1xmhU26hRQpcoTUi6L9jhdbIzEa4QXjxmfwCX2SsGFxMUzzeLz/fNzxpZq1x7fUVkpW44igvy1RbPN83JcDkAXGat4OOE9C7HdvmATKmptGwoVLHer78NPiiebgOIdUznT9KtjvegASEvEBF0u3At32alQNh6zJX7KeagmRt2571SBjTsM0+hX1R84394r6lFfov3eEW57DVCHZwLkwLnOOVPrNHwAAAP//AQAA//+ac3Cu7AAAAA==" | base64 -d | gunzip`, 179 | ), 180 | ) 181 | 182 | tferrs["tooManyListItems"] = &exec.ExitError{ 183 | Stderr: []byte(heredoc.Doc(` 184 | │ Error: Too many list items 185 | │ 186 | │ with aws_cognito_user_pool_client.client, 187 | │ on main.tf line 21, in resource "aws_cognito_user_pool_client" "client": 188 | │ 21: allowed_oauth_flows = jsondecode(base64decode("ICBbIkFMTE9XX0FETUlOX1VTRVJfUEFTU1dPUkRfQVVUSCIsICJBTExPV19SRUZSRVNIX1RPS0VOX0FVVEgiLCAiQUxMT1dfVVNFUl9QQVNTV09SRF9BVVRIIiwgIkFMTE9XX1VTRVJfU1JQX0FVVEgiXQo=")) 189 | │ 190 | │ Attribute allowed_oauth_flows supports 3 item maximum, but config has 4 declared. 191 | `)), 192 | } 193 | 194 | expectedOutput["tooManyListItems"] = errors.New( 195 | heredoc.Doc( 196 | `Terraform encountered an error. Summary: Too many list items. To see the full error run: echo "H4sIAAAAAAAA/3yRwWrbQBBA7/mKQacGjNGmoSBDDrGQQKZxLGl3Eb2IlbSSt1ntmN0Vcq/5hnyhv6S0lX0qOQwzA8N7zMzl4x0Sa9FugCLCKMwv0Mp5UF6O7u7y8f4nAGBW/ghidnWLg1Ee68lJW58Qdd1qJY1f/0urZR4NjEKZte9BKyPhgaxAGbDS4WRbCcFnrACCpdgsuAeyAQChNc6yq1FM/lj3GmcHT/DToelki5380ggnvz0uTZDF2yZ7S19oElVVmCaU6deKcFrwXc+SlDLSHdhb0eecszLOXBbvtjQ5HziJyoL9KAu+zypSHMqQv1ZhynkyqO/xs8rZ+YWSrud8nzId5TnfUx5GZZFGW86LLFPzcPNefWSXXxlVjk/B/f3tus/eW9VMXv53QTedTmi9g69/nwKjOKtxGlfQTB5aNL0a4CgcPEInWy2s7NZ3vwEAAP//AQAA//9AvYb+1wEAAA==" | base64 -d | gunzip`, 197 | ), 198 | ) 199 | 200 | output := Classify(tferrs["unexpectedName"]) 201 | 202 | if output.Error() != expectedOutput["unexpectedName"].Error() { 203 | t.Errorf("Unexpected error classification got:\n`%s`\nexpected:\n`%s`", output, expectedOutput["unexpectedName"]) 204 | } 205 | 206 | output = Classify(tferrs["tooManyListItems"]) 207 | 208 | if output.Error() != expectedOutput["tooManyListItems"].Error() { 209 | t.Errorf("Unexpected error classification got:\n`%s`\nexpected:\n`%s`", output, expectedOutput["tooManyListItems"]) 210 | } 211 | } 212 | 213 | func TestFormatTerraformErrorOutput(t *testing.T) { 214 | tferrs := make(map[string]string) 215 | expectedOutput := make(map[string]map[string]string) 216 | 217 | tferrs["unexpectedName"] = heredoc.Doc(` 218 | │ Error: Unsupported argument 219 | │ 220 | │ on test.tf line 10, in resource "aws_s3_bucket" "example": 221 | │ 10: name = "cp-example-${terraform.workspace}-${random_id.example.hex}" 222 | │ 223 | │ An argument named "name" is not expected here. 224 | `) 225 | 226 | expectedOutput["unexpectedName"] = make(map[string]string) 227 | expectedOutput["unexpectedName"]["summary"] = heredoc.Doc(` 228 | Unsupported argument`) 229 | 230 | expectedOutput["unexpectedName"]["base64full"] = "H4sIAAAAAAAA/zyPMWoDMRBFe5/iI1xmhU26hRQpcoTUi6L9jhdbIzEa4QXjxmfwCX2SsGFxMUzzeLz/fNzxpZq1x7fUVkpW44igvy1RbPN83JcDkAXGat4OOE9C7HdvmATKmptGwoVLHer78NPiiebgOIdUznT9KtjvegASEvEBF0u3At32alQNh6zJX7KeagmRt2571SBjTsM0+hX1R84394r6lFfov3eEW57DVCHZwLkwLnOOVPrNHwAAAP//AQAA//+ac3Cu7AAAAA==" 231 | 232 | tferrs["tooManyListItems"] = heredoc.Doc(` 233 | │ Error: Too many list items 234 | │ 235 | │ with aws_cognito_user_pool_client.client, 236 | │ on main.tf line 21, in resource "aws_cognito_user_pool_client" "client": 237 | │ 21: allowed_oauth_flows = jsondecode(base64decode("ICBbIkFMTE9XX0FETUlOX1VTRVJfUEFTU1dPUkRfQVVUSCIsICJBTExPV19SRUZSRVNIX1RPS0VOX0FVVEgiLCAiQUxMT1dfVVNFUl9QQVNTV09SRF9BVVRIIiwgIkFMTE9XX1VTRVJfU1JQX0FVVEgiXQo=")) 238 | │ 239 | │ Attribute allowed_oauth_flows supports 3 item maximum, but config has 4 declared. 240 | `) 241 | 242 | expectedOutput["tooManyListItems"] = make(map[string]string) 243 | expectedOutput["tooManyListItems"]["summary"] = heredoc.Doc(` 244 | Too many list items`) 245 | 246 | expectedOutput["tooManyListItems"]["base64full"] = "H4sIAAAAAAAA/3yRwWrbQBBA7/mKQacGjNGmoSBDDrGQQKZxLGl3Eb2IlbSSt1ntmN0Vcq/5hnyhv6S0lX0qOQwzA8N7zMzl4x0Sa9FugCLCKMwv0Mp5UF6O7u7y8f4nAGBW/ghidnWLg1Ee68lJW58Qdd1qJY1f/0urZR4NjEKZte9BKyPhgaxAGbDS4WRbCcFnrACCpdgsuAeyAQChNc6yq1FM/lj3GmcHT/DToelki5380ggnvz0uTZDF2yZ7S19oElVVmCaU6deKcFrwXc+SlDLSHdhb0eecszLOXBbvtjQ5HziJyoL9KAu+zypSHMqQv1ZhynkyqO/xs8rZ+YWSrud8nzId5TnfUx5GZZFGW86LLFPzcPNefWSXXxlVjk/B/f3tus/eW9VMXv53QTedTmi9g69/nwKjOKtxGlfQTB5aNL0a4CgcPEInWy2s7NZ3vwEAAP//AQAA//9AvYb+1wEAAA==" 247 | 248 | summary, base64FullErr, err := formatTerraformErrorOutput(tferrs["unexpectedName"]) 249 | if err != nil { 250 | t.Errorf("Unexpected error: %s", err) 251 | } 252 | 253 | if summary != expectedOutput["unexpectedName"]["summary"] { 254 | t.Errorf( 255 | "Unexpected error summary value got:`%s`\nexpected: `%s`", 256 | summary, 257 | expectedOutput["unexpectedName"]["summary"], 258 | ) 259 | } 260 | 261 | if base64FullErr != expectedOutput["unexpectedName"]["base64full"] { 262 | t.Errorf( 263 | "Unexpected error base64full got:`%s`\nexpected: `%s`", 264 | base64FullErr, 265 | expectedOutput["unexpectedName"]["base64full"], 266 | ) 267 | } 268 | 269 | summary, base64FullErr, err = formatTerraformErrorOutput(tferrs["tooManyListItems"]) 270 | 271 | if err != nil { 272 | t.Errorf("Unexpected error: %s", err) 273 | } 274 | 275 | if summary != expectedOutput["tooManyListItems"]["summary"] { 276 | t.Errorf( 277 | "Unexpected error classification got:`%s`\nexpected: `%s`", 278 | summary, 279 | expectedOutput["tooManyListItems"]["summary"], 280 | ) 281 | } 282 | 283 | if base64FullErr != expectedOutput["tooManyListItems"]["base64full"] { 284 | t.Errorf( 285 | "Unexpected error base64full got:`%s`\nexpected: `%s`", 286 | base64FullErr, 287 | expectedOutput["tooManyListItems"]["base64full"], 288 | ) 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /internal/terraform/testdata/invalidmodule/main.tf: -------------------------------------------------------------------------------- 1 | variable "coolness" { 2 | default = "very" 3 | description = "How cool is this test?" 4 | } 5 | 6 | // This output block is missing a trailing bracket. 7 | output "coolness" { 8 | value = var.coolness 9 | description = "The coolness of this test." 10 | -------------------------------------------------------------------------------- /internal/terraform/testdata/nullmodule/main.tf: -------------------------------------------------------------------------------- 1 | variable "coolness" { 2 | default = "very" 3 | description = "How cool is this test?" 4 | } 5 | 6 | output "coolness" { 7 | value = var.coolness 8 | description = "The coolness of this test." 9 | } 10 | 11 | output "randomness" { 12 | value = random_id.test.hex 13 | description = "A random string." 14 | } 15 | 16 | terraform { 17 | required_providers { 18 | null = { 19 | version = "3.2.3" 20 | } 21 | random = { 22 | version = "3.7.1" 23 | } 24 | } 25 | } 26 | 27 | resource "random_id" "test" { 28 | byte_length = 4 29 | } 30 | 31 | resource "null_resource" "test" { 32 | triggers = { 33 | trigger = random_id.test.hex 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/terraform/testdata/nullmodule/terraform.tfstate: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "terraform_version": "0.14.7", 4 | "serial": 3, 5 | "lineage": "7b9c5b6b-ce2c-9cb9-f940-b696b4454038", 6 | "outputs": { 7 | "coolness": { 8 | "value": "very", 9 | "type": "string" 10 | }, 11 | "randomness": { 12 | "value": "f33da529", 13 | "type": "string" 14 | } 15 | }, 16 | "resources": [ 17 | { 18 | "mode": "managed", 19 | "type": "null_resource", 20 | "name": "test", 21 | "provider": "provider[\"registry.terraform.io/hashicorp/null\"]", 22 | "instances": [ 23 | { 24 | "schema_version": 0, 25 | "attributes": { 26 | "id": "1790215571976780205", 27 | "triggers": { 28 | "trigger": "f33da529" 29 | } 30 | }, 31 | "sensitive_attributes": [], 32 | "private": "bnVsbA==", 33 | "dependencies": [ 34 | "random_id.test" 35 | ] 36 | } 37 | ] 38 | }, 39 | { 40 | "mode": "managed", 41 | "type": "random_id", 42 | "name": "test", 43 | "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", 44 | "instances": [ 45 | { 46 | "schema_version": 0, 47 | "attributes": { 48 | "b64_std": "8z2lKQ==", 49 | "b64_url": "8z2lKQ", 50 | "byte_length": 4, 51 | "dec": "4080903465", 52 | "hex": "f33da529", 53 | "id": "8z2lKQ", 54 | "keepers": null, 55 | "prefix": null 56 | }, 57 | "sensitive_attributes": [], 58 | "private": "bnVsbA==" 59 | } 60 | ] 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /internal/terraform/testdata/outputmodule/main.tf: -------------------------------------------------------------------------------- 1 | output "string" { 2 | value = "very" 3 | } 4 | 5 | output "sensitive" { 6 | value = "very" 7 | sensitive = true 8 | } 9 | 10 | output "tuple" { 11 | value = ["a", "really", "long", "tuple"] 12 | } 13 | 14 | output "object" { 15 | value = { 16 | wow : "suchobject" 17 | } 18 | } 19 | 20 | output "bool" { 21 | value = true 22 | } 23 | 24 | output "number" { 25 | value = 42 26 | } 27 | -------------------------------------------------------------------------------- /internal/terraform/testdata/outputmodule/terraform.tfstate: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "terraform_version": "0.14.7", 4 | "serial": 4, 5 | "lineage": "cabe0872-bb95-a2aa-53c4-7d034376c1f0", 6 | "outputs": { 7 | "bool": { 8 | "value": true, 9 | "type": "bool" 10 | }, 11 | "number": { 12 | "value": 42, 13 | "type": "number" 14 | }, 15 | "object": { 16 | "value": { 17 | "wow": "suchobject" 18 | }, 19 | "type": [ 20 | "object", 21 | { 22 | "wow": "string" 23 | } 24 | ] 25 | }, 26 | "sensitive": { 27 | "value": "very", 28 | "type": "string", 29 | "sensitive": true 30 | }, 31 | "string": { 32 | "value": "very", 33 | "type": "string" 34 | }, 35 | "tuple": { 36 | "value": [ 37 | "a", 38 | "really", 39 | "long", 40 | "tuple" 41 | ], 42 | "type": [ 43 | "tuple", 44 | [ 45 | "string", 46 | "string", 47 | "string", 48 | "string" 49 | ] 50 | ] 51 | } 52 | }, 53 | "resources": [] 54 | } 55 | -------------------------------------------------------------------------------- /internal/terraform/testdata/validmodule/main.tf: -------------------------------------------------------------------------------- 1 | variable "coolness" { 2 | default = "very" 3 | description = "How cool is this test?" 4 | } 5 | 6 | output "coolness" { 7 | value = var.coolness 8 | description = "The coolness of this test." 9 | } 10 | -------------------------------------------------------------------------------- /internal/workdir/workdir.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package workdir 18 | 19 | import ( 20 | "context" 21 | "path/filepath" 22 | "strings" 23 | "time" 24 | 25 | "github.com/crossplane/crossplane-runtime/pkg/logging" 26 | "github.com/google/uuid" 27 | "github.com/pkg/errors" 28 | "github.com/spf13/afero" 29 | "sigs.k8s.io/controller-runtime/pkg/client" 30 | 31 | "github.com/upbound/provider-terraform/apis/v1beta1" 32 | ) 33 | 34 | // Error strings. 35 | const ( 36 | errListWorkspaces = "cannot list workspaces" 37 | errFmtReadDir = "cannot read directory %q" 38 | ) 39 | 40 | // A GarbageCollector garbage collects the working directories of Terraform 41 | // workspaces that no longer exist. 42 | type GarbageCollector struct { 43 | kube client.Client 44 | parentDir string 45 | fs afero.Afero 46 | interval time.Duration 47 | log logging.Logger 48 | } 49 | 50 | // A GarbageCollectorOption configures a new GarbageCollector. 51 | type GarbageCollectorOption func(*GarbageCollector) 52 | 53 | // WithFs configures the afero filesystem implementation in which work dirs will 54 | // be garbage collected. The default is the real operating system filesystem. 55 | func WithFs(fs afero.Afero) GarbageCollectorOption { 56 | return func(gc *GarbageCollector) { gc.fs = fs } 57 | } 58 | 59 | // WithInterval configures how often garbage collection will run. The default 60 | // interval is one hour. 61 | func WithInterval(i time.Duration) GarbageCollectorOption { 62 | return func(gc *GarbageCollector) { gc.interval = i } 63 | } 64 | 65 | // WithLogger configures the logger that will be used. The default is a no-op 66 | // logger never emits logs. 67 | func WithLogger(l logging.Logger) GarbageCollectorOption { 68 | return func(gc *GarbageCollector) { gc.log = l } 69 | } 70 | 71 | // NewGarbageCollector returns a garbage collector that garbage collects the 72 | // working directories of Terraform workspaces. 73 | func NewGarbageCollector(c client.Client, parentDir string, o ...GarbageCollectorOption) *GarbageCollector { 74 | gc := &GarbageCollector{ 75 | kube: c, 76 | parentDir: parentDir, 77 | fs: afero.Afero{Fs: afero.NewOsFs()}, 78 | interval: 1 * time.Hour, 79 | log: logging.NewNopLogger(), 80 | } 81 | 82 | for _, fn := range o { 83 | fn(gc) 84 | } 85 | 86 | return gc 87 | } 88 | 89 | // Run the garbage collector. Blocks until the supplied context is done. 90 | func (gc *GarbageCollector) Run(ctx context.Context) { 91 | t := time.NewTicker(gc.interval) 92 | for { 93 | select { 94 | case <-ctx.Done(): 95 | return 96 | case <-t.C: 97 | if err := gc.collect(ctx); err != nil { 98 | gc.log.Info("Garbage collection failed", "error", err) 99 | } 100 | } 101 | } 102 | } 103 | 104 | func isUUID(u string) bool { 105 | _, err := uuid.Parse(u) 106 | return err == nil 107 | } 108 | 109 | func (gc *GarbageCollector) collect(ctx context.Context) error { 110 | l := &v1beta1.WorkspaceList{} 111 | if err := gc.kube.List(ctx, l); err != nil { 112 | return errors.Wrap(err, errListWorkspaces) 113 | } 114 | 115 | exists := map[string]bool{} 116 | for _, ws := range l.Items { 117 | exists[string(ws.GetUID())] = true 118 | } 119 | fis, err := gc.fs.ReadDir(gc.parentDir) 120 | if err != nil { 121 | return errors.Wrapf(err, errFmtReadDir, gc.parentDir) 122 | } 123 | 124 | failed := make([]string, 0) 125 | for _, fi := range fis { 126 | if !fi.IsDir() || !isUUID(fi.Name()) { 127 | continue 128 | } 129 | if exists[fi.Name()] { 130 | continue 131 | } 132 | path := filepath.Join(gc.parentDir, fi.Name()) 133 | if err := gc.fs.RemoveAll(path); err != nil { 134 | failed = append(failed, path) 135 | } 136 | } 137 | 138 | if len(failed) > 0 { 139 | return errors.Errorf("could not delete directories: %v", strings.Join(failed, ", ")) 140 | } 141 | 142 | return nil 143 | } 144 | -------------------------------------------------------------------------------- /internal/workdir/workdir_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package workdir 18 | 19 | import ( 20 | "context" 21 | "os" 22 | "path/filepath" 23 | "testing" 24 | 25 | "github.com/google/go-cmp/cmp" 26 | "github.com/google/go-cmp/cmp/cmpopts" 27 | "github.com/pkg/errors" 28 | "github.com/spf13/afero" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | "k8s.io/apimachinery/pkg/types" 31 | "sigs.k8s.io/controller-runtime/pkg/client" 32 | 33 | "github.com/crossplane/crossplane-runtime/pkg/test" 34 | 35 | "github.com/upbound/provider-terraform/apis/v1beta1" 36 | ) 37 | 38 | func withDirs(fs afero.Afero, dir ...string) afero.Afero { 39 | for _, d := range dir { 40 | _ = fs.Mkdir(d, os.ModePerm) 41 | } 42 | return fs 43 | } 44 | 45 | func getDirs(fs afero.Afero, parentDir string) []string { 46 | dirs := make([]string, 0) 47 | fis, _ := fs.ReadDir(parentDir) 48 | for _, fi := range fis { 49 | if !fi.IsDir() { 50 | continue 51 | } 52 | dirs = append(dirs, fi.Name()) 53 | } 54 | return dirs 55 | } 56 | 57 | func TestCollect(t *testing.T) { 58 | parentDir := "/test" 59 | 60 | type fields struct { 61 | kube client.Client 62 | parentdDir string 63 | fs afero.Afero 64 | } 65 | type args struct { 66 | ctx context.Context 67 | } 68 | type want struct { 69 | dirs []string 70 | err error 71 | } 72 | cases := map[string]struct { 73 | reason string 74 | fields fields 75 | args args 76 | want want 77 | }{ 78 | "ErrNoParentDir": { 79 | reason: "Garbage collection should fail when the parent directory does not exist.", 80 | fields: fields{ 81 | kube: &test.MockClient{MockList: test.NewMockListFn(nil)}, 82 | parentdDir: parentDir, 83 | fs: afero.Afero{Fs: afero.NewMemMapFs()}, 84 | }, 85 | want: want{ 86 | err: errors.Wrapf(errors.Errorf("open %s: file does not exist", parentDir), errFmtReadDir, parentDir), 87 | }, 88 | }, 89 | "NoOp": { 90 | reason: "Garbage collection should succeed when there are no workspaces or workdirs.", 91 | fields: fields{ 92 | kube: &test.MockClient{MockList: test.NewMockListFn(nil)}, 93 | parentdDir: parentDir, 94 | fs: withDirs(afero.Afero{Fs: afero.NewMemMapFs()}, parentDir), 95 | }, 96 | want: want{ 97 | err: nil, 98 | }, 99 | }, 100 | "Success": { 101 | reason: "Workdirs belonging to workspaces that no longer exist should be successfully garbage collected.", 102 | fields: fields{ 103 | kube: &test.MockClient{MockList: test.NewMockListFn(nil, func(obj client.ObjectList) error { 104 | *obj.(*v1beta1.WorkspaceList) = v1beta1.WorkspaceList{Items: []v1beta1.Workspace{ 105 | {ObjectMeta: metav1.ObjectMeta{UID: types.UID("8371dd9e-dd3f-4a42-bd8c-340c4744f6de")}}, 106 | {ObjectMeta: metav1.ObjectMeta{UID: types.UID("ebaac629-43a3-4b39-8138-d7ac19cafe11")}}, 107 | }} 108 | return nil 109 | })}, 110 | parentdDir: parentDir, 111 | fs: withDirs(afero.Afero{Fs: afero.NewMemMapFs()}, 112 | parentDir, 113 | filepath.Join(parentDir, "8371dd9e-dd3f-4a42-bd8c-340c4744f6de"), 114 | filepath.Join(parentDir, "ebaac629-43a3-4b39-8138-d7ac19cafe11"), 115 | filepath.Join(parentDir, "0d177133-1a2f-4ce2-93d2-f8212d3344e7"), 116 | filepath.Join(parentDir, "helm"), 117 | filepath.Join(parentDir, "registry.terraform.io"), 118 | ), 119 | }, 120 | want: want{ 121 | dirs: []string{"8371dd9e-dd3f-4a42-bd8c-340c4744f6de", "ebaac629-43a3-4b39-8138-d7ac19cafe11", "helm", "registry.terraform.io"}, 122 | }, 123 | }, 124 | } 125 | 126 | for name, tc := range cases { 127 | t.Run(name, func(t *testing.T) { 128 | gc := NewGarbageCollector(tc.fields.kube, tc.fields.parentdDir, WithFs(tc.fields.fs)) 129 | err := gc.collect(tc.args.ctx) 130 | if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { 131 | t.Errorf("gc.collect(...): -want error, +got error:\n%s", diff) 132 | } 133 | 134 | got := getDirs(tc.fields.fs, tc.fields.parentdDir) 135 | if diff := cmp.Diff(tc.want.dirs, got, cmpopts.EquateEmpty()); diff != "" { 136 | t.Errorf("gc.collect(...): -want dirs, +got dirs:\n%s", diff) 137 | } 138 | }) 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /package/crds/tf.upbound.io_providerconfigs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.14.0 7 | name: providerconfigs.tf.upbound.io 8 | spec: 9 | group: tf.upbound.io 10 | names: 11 | categories: 12 | - crossplane 13 | - provider 14 | - terraform 15 | kind: ProviderConfig 16 | listKind: ProviderConfigList 17 | plural: providerconfigs 18 | singular: providerconfig 19 | scope: Cluster 20 | versions: 21 | - additionalPrinterColumns: 22 | - jsonPath: .metadata.creationTimestamp 23 | name: AGE 24 | type: date 25 | - jsonPath: .spec.credentials.secretRef.name 26 | name: SECRET-NAME 27 | priority: 1 28 | type: string 29 | name: v1beta1 30 | schema: 31 | openAPIV3Schema: 32 | description: A ProviderConfig configures a Terraform provider. 33 | properties: 34 | apiVersion: 35 | description: |- 36 | APIVersion defines the versioned schema of this representation of an object. 37 | Servers should convert recognized schemas to the latest internal value, and 38 | may reject unrecognized values. 39 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 40 | type: string 41 | kind: 42 | description: |- 43 | Kind is a string value representing the REST resource this object represents. 44 | Servers may infer this from the endpoint the client submits requests to. 45 | Cannot be updated. 46 | In CamelCase. 47 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 48 | type: string 49 | metadata: 50 | type: object 51 | spec: 52 | description: A ProviderConfigSpec defines the desired state of a ProviderConfig. 53 | properties: 54 | backendFile: 55 | description: |- 56 | Terraform backend file configuration content, 57 | it has the contents of the backend block as top-level attributes, 58 | without the need to wrap it in another terraform or backend block. 59 | More details at https://developer.hashicorp.com/terraform/language/settings/backends/configuration#file. 60 | type: string 61 | configuration: 62 | description: |- 63 | Configuration that should be injected into all workspaces that use 64 | this provider config, expressed as inline HCL. This can be used to 65 | automatically inject Terraform provider configuration blocks. 66 | type: string 67 | credentials: 68 | description: Credentials required to authenticate to this provider. 69 | items: 70 | description: ProviderCredentials required to authenticate. 71 | properties: 72 | env: 73 | description: |- 74 | Env is a reference to an environment variable that contains credentials 75 | that must be used to connect to the provider. 76 | properties: 77 | name: 78 | description: Name is the name of an environment variable. 79 | type: string 80 | required: 81 | - name 82 | type: object 83 | filename: 84 | description: |- 85 | Filename (relative to main.tf) to which these provider credentials 86 | should be written. 87 | type: string 88 | fs: 89 | description: |- 90 | Fs is a reference to a filesystem location that contains credentials that 91 | must be used to connect to the provider. 92 | properties: 93 | path: 94 | description: Path is a filesystem path. 95 | type: string 96 | required: 97 | - path 98 | type: object 99 | secretRef: 100 | description: |- 101 | A SecretRef is a reference to a secret key that contains the credentials 102 | that must be used to connect to the provider. 103 | properties: 104 | key: 105 | description: The key to select. 106 | type: string 107 | name: 108 | description: Name of the secret. 109 | type: string 110 | namespace: 111 | description: Namespace of the secret. 112 | type: string 113 | required: 114 | - key 115 | - name 116 | - namespace 117 | type: object 118 | source: 119 | description: Source of the provider credentials. 120 | enum: 121 | - None 122 | - Secret 123 | - Environment 124 | - Filesystem 125 | type: string 126 | required: 127 | - filename 128 | - source 129 | type: object 130 | type: array 131 | pluginCache: 132 | default: true 133 | description: |- 134 | PluginCache enables terraform provider plugin caching mechanism 135 | https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache 136 | type: boolean 137 | type: object 138 | status: 139 | description: A ProviderConfigStatus reflects the observed state of a ProviderConfig. 140 | properties: 141 | conditions: 142 | description: Conditions of the resource. 143 | items: 144 | description: A Condition that may apply to a resource. 145 | properties: 146 | lastTransitionTime: 147 | description: |- 148 | LastTransitionTime is the last time this condition transitioned from one 149 | status to another. 150 | format: date-time 151 | type: string 152 | message: 153 | description: |- 154 | A Message containing details about this condition's last transition from 155 | one status to another, if any. 156 | type: string 157 | observedGeneration: 158 | description: |- 159 | ObservedGeneration represents the .metadata.generation that the condition was set based upon. 160 | For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date 161 | with respect to the current state of the instance. 162 | format: int64 163 | type: integer 164 | reason: 165 | description: A Reason for this condition's last transition from 166 | one status to another. 167 | type: string 168 | status: 169 | description: Status of this condition; is it currently True, 170 | False, or Unknown? 171 | type: string 172 | type: 173 | description: |- 174 | Type of this condition. At most one of each condition type may apply to 175 | a resource at any point in time. 176 | type: string 177 | required: 178 | - lastTransitionTime 179 | - reason 180 | - status 181 | - type 182 | type: object 183 | type: array 184 | x-kubernetes-list-map-keys: 185 | - type 186 | x-kubernetes-list-type: map 187 | users: 188 | description: Users of this provider configuration. 189 | format: int64 190 | type: integer 191 | type: object 192 | required: 193 | - spec 194 | type: object 195 | served: true 196 | storage: true 197 | subresources: 198 | status: {} 199 | -------------------------------------------------------------------------------- /package/crds/tf.upbound.io_providerconfigusages.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.14.0 7 | name: providerconfigusages.tf.upbound.io 8 | spec: 9 | group: tf.upbound.io 10 | names: 11 | categories: 12 | - crossplane 13 | - provider 14 | - terraform 15 | kind: ProviderConfigUsage 16 | listKind: ProviderConfigUsageList 17 | plural: providerconfigusages 18 | singular: providerconfigusage 19 | scope: Cluster 20 | versions: 21 | - additionalPrinterColumns: 22 | - jsonPath: .metadata.creationTimestamp 23 | name: AGE 24 | type: date 25 | - jsonPath: .providerConfigRef.name 26 | name: CONFIG-NAME 27 | type: string 28 | - jsonPath: .resourceRef.kind 29 | name: RESOURCE-KIND 30 | type: string 31 | - jsonPath: .resourceRef.name 32 | name: RESOURCE-NAME 33 | type: string 34 | name: v1beta1 35 | schema: 36 | openAPIV3Schema: 37 | description: A ProviderConfigUsage indicates that a resource is using a ProviderConfig. 38 | properties: 39 | apiVersion: 40 | description: |- 41 | APIVersion defines the versioned schema of this representation of an object. 42 | Servers should convert recognized schemas to the latest internal value, and 43 | may reject unrecognized values. 44 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 45 | type: string 46 | kind: 47 | description: |- 48 | Kind is a string value representing the REST resource this object represents. 49 | Servers may infer this from the endpoint the client submits requests to. 50 | Cannot be updated. 51 | In CamelCase. 52 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 53 | type: string 54 | metadata: 55 | type: object 56 | providerConfigRef: 57 | description: ProviderConfigReference to the provider config being used. 58 | properties: 59 | name: 60 | description: Name of the referenced object. 61 | type: string 62 | policy: 63 | description: Policies for referencing. 64 | properties: 65 | resolution: 66 | default: Required 67 | description: |- 68 | Resolution specifies whether resolution of this reference is required. 69 | The default is 'Required', which means the reconcile will fail if the 70 | reference cannot be resolved. 'Optional' means this reference will be 71 | a no-op if it cannot be resolved. 72 | enum: 73 | - Required 74 | - Optional 75 | type: string 76 | resolve: 77 | description: |- 78 | Resolve specifies when this reference should be resolved. The default 79 | is 'IfNotPresent', which will attempt to resolve the reference only when 80 | the corresponding field is not present. Use 'Always' to resolve the 81 | reference on every reconcile. 82 | enum: 83 | - Always 84 | - IfNotPresent 85 | type: string 86 | type: object 87 | required: 88 | - name 89 | type: object 90 | resourceRef: 91 | description: ResourceReference to the managed resource using the provider 92 | config. 93 | properties: 94 | apiVersion: 95 | description: APIVersion of the referenced object. 96 | type: string 97 | kind: 98 | description: Kind of the referenced object. 99 | type: string 100 | name: 101 | description: Name of the referenced object. 102 | type: string 103 | uid: 104 | description: UID of the referenced object. 105 | type: string 106 | required: 107 | - apiVersion 108 | - kind 109 | - name 110 | type: object 111 | required: 112 | - providerConfigRef 113 | - resourceRef 114 | type: object 115 | served: true 116 | storage: true 117 | subresources: {} 118 | -------------------------------------------------------------------------------- /package/crds/tf.upbound.io_storeconfigs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.14.0 7 | name: storeconfigs.tf.upbound.io 8 | spec: 9 | group: tf.upbound.io 10 | names: 11 | categories: 12 | - crossplane 13 | - store 14 | - gcp 15 | kind: StoreConfig 16 | listKind: StoreConfigList 17 | plural: storeconfigs 18 | singular: storeconfig 19 | scope: Cluster 20 | versions: 21 | - additionalPrinterColumns: 22 | - jsonPath: .metadata.creationTimestamp 23 | name: AGE 24 | type: date 25 | - jsonPath: .spec.type 26 | name: TYPE 27 | type: string 28 | - jsonPath: .spec.defaultScope 29 | name: DEFAULT-SCOPE 30 | type: string 31 | name: v1beta1 32 | schema: 33 | openAPIV3Schema: 34 | description: A StoreConfig configures how GCP controller should store connection 35 | details. 36 | properties: 37 | apiVersion: 38 | description: |- 39 | APIVersion defines the versioned schema of this representation of an object. 40 | Servers should convert recognized schemas to the latest internal value, and 41 | may reject unrecognized values. 42 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 43 | type: string 44 | kind: 45 | description: |- 46 | Kind is a string value representing the REST resource this object represents. 47 | Servers may infer this from the endpoint the client submits requests to. 48 | Cannot be updated. 49 | In CamelCase. 50 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 51 | type: string 52 | metadata: 53 | type: object 54 | spec: 55 | description: A StoreConfigSpec defines the desired state of a ProviderConfig. 56 | properties: 57 | defaultScope: 58 | description: |- 59 | DefaultScope used for scoping secrets for "cluster-scoped" resources. 60 | If store type is "Kubernetes", this would mean the default namespace to 61 | store connection secrets for cluster scoped resources. 62 | In case of "Vault", this would be used as the default parent path. 63 | Typically, should be set as Crossplane installation namespace. 64 | type: string 65 | kubernetes: 66 | description: |- 67 | Kubernetes configures a Kubernetes secret store. 68 | If the "type" is "Kubernetes" but no config provided, in cluster config 69 | will be used. 70 | properties: 71 | auth: 72 | description: Credentials used to connect to the Kubernetes API. 73 | properties: 74 | env: 75 | description: |- 76 | Env is a reference to an environment variable that contains credentials 77 | that must be used to connect to the provider. 78 | properties: 79 | name: 80 | description: Name is the name of an environment variable. 81 | type: string 82 | required: 83 | - name 84 | type: object 85 | fs: 86 | description: |- 87 | Fs is a reference to a filesystem location that contains credentials that 88 | must be used to connect to the provider. 89 | properties: 90 | path: 91 | description: Path is a filesystem path. 92 | type: string 93 | required: 94 | - path 95 | type: object 96 | secretRef: 97 | description: |- 98 | A SecretRef is a reference to a secret key that contains the credentials 99 | that must be used to connect to the provider. 100 | properties: 101 | key: 102 | description: The key to select. 103 | type: string 104 | name: 105 | description: Name of the secret. 106 | type: string 107 | namespace: 108 | description: Namespace of the secret. 109 | type: string 110 | required: 111 | - key 112 | - name 113 | - namespace 114 | type: object 115 | source: 116 | description: Source of the credentials. 117 | enum: 118 | - None 119 | - Secret 120 | - Environment 121 | - Filesystem 122 | type: string 123 | required: 124 | - source 125 | type: object 126 | required: 127 | - auth 128 | type: object 129 | plugin: 130 | description: Plugin configures External secret store as a plugin. 131 | properties: 132 | configRef: 133 | description: ConfigRef contains store config reference info. 134 | properties: 135 | apiVersion: 136 | description: APIVersion of the referenced config. 137 | type: string 138 | kind: 139 | description: Kind of the referenced config. 140 | type: string 141 | name: 142 | description: Name of the referenced config. 143 | type: string 144 | required: 145 | - apiVersion 146 | - kind 147 | - name 148 | type: object 149 | endpoint: 150 | description: Endpoint is the endpoint of the gRPC server. 151 | type: string 152 | type: object 153 | type: 154 | default: Kubernetes 155 | description: |- 156 | Type configures which secret store to be used. Only the configuration 157 | block for this store will be used and others will be ignored if provided. 158 | Default is Kubernetes. 159 | enum: 160 | - Kubernetes 161 | - Vault 162 | - Plugin 163 | type: string 164 | required: 165 | - defaultScope 166 | type: object 167 | status: 168 | description: A StoreConfigStatus represents the status of a StoreConfig. 169 | properties: 170 | conditions: 171 | description: Conditions of the resource. 172 | items: 173 | description: A Condition that may apply to a resource. 174 | properties: 175 | lastTransitionTime: 176 | description: |- 177 | LastTransitionTime is the last time this condition transitioned from one 178 | status to another. 179 | format: date-time 180 | type: string 181 | message: 182 | description: |- 183 | A Message containing details about this condition's last transition from 184 | one status to another, if any. 185 | type: string 186 | observedGeneration: 187 | description: |- 188 | ObservedGeneration represents the .metadata.generation that the condition was set based upon. 189 | For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date 190 | with respect to the current state of the instance. 191 | format: int64 192 | type: integer 193 | reason: 194 | description: A Reason for this condition's last transition from 195 | one status to another. 196 | type: string 197 | status: 198 | description: Status of this condition; is it currently True, 199 | False, or Unknown? 200 | type: string 201 | type: 202 | description: |- 203 | Type of this condition. At most one of each condition type may apply to 204 | a resource at any point in time. 205 | type: string 206 | required: 207 | - lastTransitionTime 208 | - reason 209 | - status 210 | - type 211 | type: object 212 | type: array 213 | x-kubernetes-list-map-keys: 214 | - type 215 | x-kubernetes-list-type: map 216 | type: object 217 | required: 218 | - spec 219 | type: object 220 | served: true 221 | storage: true 222 | subresources: 223 | status: {} 224 | -------------------------------------------------------------------------------- /package/crossplane.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: meta.pkg.crossplane.io/v1 2 | kind: Provider 3 | metadata: 4 | name: provider-terraform 5 | annotations: 6 | meta.crossplane.io/maintainer: Upbound 7 | meta.crossplane.io/source: github.com/upbound/provider-terraform 8 | meta.crossplane.io/description: | 9 | Crossplane provider to manage Terraform workspaces. 10 | meta.crossplane.io/readme: | 11 | Provider Terraform is a Crossplane provider that lets you manage resources 12 | using virtual Terraform workspaces. It is developed and supported by 13 | Upbound. Available resources and their fields can be found in the [Upbound 14 | Marketplace](https://marketplace.upbound.io/providers/upbound/provider-terraform). 15 | friendly-name.meta.crossplane.io: Provider Terraform 16 | -------------------------------------------------------------------------------- /scripts/check-examples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import yaml 4 | import os 5 | import sys 6 | 7 | 8 | def load_gvks(path, loader): 9 | types = set() 10 | for root, _, files in os.walk(path): 11 | for f in files: 12 | if f.endswith(".yaml"): 13 | with open(os.path.join(root, f)) as s: 14 | for t in yaml.safe_load_all(s): 15 | for gvk in loader(t): 16 | types.add(gvk) 17 | return types 18 | 19 | 20 | def load_crd_type(t): 21 | kind = t["spec"]["names"]["kind"] 22 | group = t["spec"]["group"] 23 | for v in t["spec"]["versions"]: 24 | yield f'{kind}.{group}/{v["name"]}' 25 | 26 | 27 | exception_set = { 28 | 'ProviderConfigUsage.tf.upbound.io/v1beta1', 29 | 'StoreConfig.tf.upbound.io/v1beta1' 30 | } 31 | 32 | 33 | # Example usage: check-examples.py 34 | if __name__ == "__main__": 35 | if len(sys.argv) != 3: 36 | print("Example usage: check-examples.py ") 37 | sys.exit(1) 38 | known_crd_types = load_gvks(sys.argv[1], load_crd_type) 39 | example_types = load_gvks(sys.argv[2], lambda t: [] if t is None or not {"kind", "apiVersion"}.issubset(t.keys()) 40 | else [f'{t["kind"]}.{t["apiVersion"]}']) 41 | diff = known_crd_types.difference(example_types.union(exception_set)) 42 | if len(diff) == 0: 43 | print("All CRDs have at least one example...") 44 | print(f'Exceptions allowed for: {exception_set}') 45 | sys.exit(0) 46 | print(f'Please add example manifests for the following types: {diff}') 47 | sys.exit(2) 48 | --------------------------------------------------------------------------------