From c67f2e27927aa4b9b872256de8c24b3c6883504a Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Wed, 20 Sep 2023 12:30:39 +0300 Subject: [PATCH] Updates source code for ownership change Signed-off-by: Jean du Plessis --- .github/ISSUE_TEMPLATE/bug_report.md | 5 +- .github/ISSUE_TEMPLATE/bug_report.md.license | 3 + .github/ISSUE_TEMPLATE/feature_request.md | 4 +- .../ISSUE_TEMPLATE/feature_request.md.license | 3 + .github/PULL_REQUEST_TEMPLATE.md | 17 +- .github/PULL_REQUEST_TEMPLATE.md.license | 3 + .github/stale.yml | 4 + .github/workflows/backport.yml | 4 + .github/workflows/ci.yml | 11 +- .github/workflows/codeql.yml | 4 + .github/workflows/commands.yml | 138 +-- .github/workflows/reuse-license-linter.yml | 19 + .github/workflows/tag.yml | 8 +- .gitignore | 6 +- .gitmodules | 8 +- .golangci.yml | 61 +- CODEOWNERS | 14 +- CODE_OF_CONDUCT.md | 10 +- CONTRIBUTING.md | 10 +- LICENSE | 274 ++--- LICENSES/Apache-2.0.txt | 73 ++ LICENSES/CC-BY-4.0.txt | 156 +++ LICENSES/CC0-1.0.txt | 121 +++ Makefile | 15 +- NOTICE | 6 + OWNERS.md | 13 +- README.md | 45 +- catalog-info.yaml | 14 - cmd/scraper/main.go | 9 +- docs/README.md | 75 +- docs/{ => archive}/add-new-resource-long.md | 179 ++-- docs/{ => archive}/add-new-resource-short.md | 87 +- .../adding-support-for-management-policies.md | 27 +- ...design-doc-provider-identity-based-auth.md | 101 +- .../manual-migration-guide-to-op.md | 77 +- .../migrating-from-terrajet-to-upjet.md | 106 +- .../moving-resources-to-v1beta1.md | 8 +- docs/{ => archive}/reference-generation.md | 45 +- docs/{ => archive}/testing-instructions.md | 221 ++-- .../testing-resources-by-using-uptest.md | 8 +- docs/configuring-a-resource.md | 942 ++++++++++++++++++ docs/generating-a-provider.md | 406 ++++---- docs/images/artifacts.png.license | 3 + docs/images/azure-wi.png.license | 3 + docs/images/managed-all.png.license | 3 + docs/images/summary.png.license | 3 + docs/images/upjet-components.png | Bin 0 -> 282066 bytes docs/images/upjet-components.png.license | 3 + .../upjet-externalname.excalidraw.license | 3 + docs/images/upjet-externalname.png.license | 3 + docs/monitoring.md | 82 +- docs/provider_metrics_help.txt | 4 + docs/sizing-guide.md | 108 +- docs/testing-with-uptest.md | 370 +++++++ go.mod | 8 +- go.sum.license | 4 + hack/boilerplate.txt | 14 +- pkg/common.go | 6 +- pkg/config/common.go | 13 +- pkg/config/common_test.go | 9 +- pkg/config/externalname.go | 6 +- pkg/config/externalname_test.go | 10 +- pkg/config/provider.go | 13 +- pkg/config/resource.go | 9 +- pkg/config/resource_test.go | 4 + pkg/controller/api.go | 30 +- pkg/controller/api_test.go | 25 +- pkg/controller/external.go | 27 +- pkg/controller/external_test.go | 36 +- pkg/controller/handler/eventhandler.go | 17 +- pkg/controller/interfaces.go | 12 +- pkg/controller/options.go | 12 +- pkg/examples/example.go | 16 +- pkg/generate.go | 14 +- pkg/metrics/metrics.go | 14 +- pkg/migration/api_steps.go | 27 +- pkg/migration/categorical_steps.go | 14 +- pkg/migration/configurationmetadata_steps.go | 21 +- pkg/migration/configurationpackage_steps.go | 21 +- pkg/migration/converter.go | 34 +- pkg/migration/errors.go | 14 +- pkg/migration/exec_steps.go | 14 +- pkg/migration/fake/mocks/mock.go | 14 +- pkg/migration/fake/objects.go | 18 +- pkg/migration/filesystem.go | 4 + pkg/migration/filesystem_test.go | 4 + pkg/migration/fork_executor.go | 14 +- pkg/migration/fork_executor_test.go | 14 +- pkg/migration/interfaces.go | 15 +- pkg/migration/kubernetes.go | 9 +- pkg/migration/kubernetes_test.go | 4 + pkg/migration/package_lock_steps.go | 14 +- pkg/migration/patches.go | 25 +- pkg/migration/patches_test.go | 14 +- pkg/migration/plan_executor.go | 14 +- pkg/migration/plan_generator.go | 29 +- pkg/migration/plan_generator_test.go | 31 +- pkg/migration/plan_steps.go | 14 +- pkg/migration/provider_package_steps.go | 17 +- pkg/migration/registry.go | 22 +- pkg/migration/testdata/plan/claim.yaml | 4 + pkg/migration/testdata/plan/composition.yaml | 4 + .../testdata/plan/configurationpkgv1.yaml | 6 +- .../testdata/plan/configurationv1.yaml | 6 +- .../testdata/plan/configurationv1alpha1.yaml | 6 +- ...ws-ec2.providers.pkg.crossplane.io_v1.yaml | 4 + ...ws-eks.providers.pkg.crossplane.io_v1.yaml | 4 + ...ly-aws.providers.pkg.crossplane.io_v1.yaml | 4 + .../configurationv1_migration_plan.yaml | 112 ++- .../configurationv1_pkg_migration_plan.yaml | 4 + .../configurationv1alpha1_migration_plan.yaml | 112 ++- .../sample-vpc.vpcs.faketargetapi.yaml | 4 + ...ample-vpc.vpcs.fakesourceapi_v1alpha1.yaml | 6 +- .../sample-vpc.vpcs.fakesourceapi.yaml | 4 + ...ample-vpc.vpcs.fakesourceapi_v1alpha1.yaml | 6 +- ...s.configurations.pkg.crossplane.io_v1.yaml | 4 + .../my-resource.myresources.test.com.yaml | 4 + ...-resource-dwjgh.xmyresources.test.com.yaml | 10 +- ...figurations.meta.pkg.crossplane.io_v1.yaml | 4 + ...tions.meta.pkg.crossplane.io_v1alpha1.yaml | 4 + ...s.configurations.pkg.crossplane.io_v1.yaml | 4 + .../lock.locks.pkg.crossplane.io_v1beta1.yaml | 5 +- ...s.configurations.pkg.crossplane.io_v1.yaml | 4 + .../plan/generated/migration_plan.yaml | 186 ++-- .../generated/migration_plan_filesystem.yaml | 6 +- ...positions.apiextensions.crossplane.io.yaml | 72 +- ...ws-ec2.providers.pkg.crossplane.io_v1.yaml | 4 + ...ws-eks.providers.pkg.crossplane.io_v1.yaml | 4 + ...ly-aws.providers.pkg.crossplane.io_v1.yaml | 4 + ...-resource-dwjgh.xmyresources.test.com.yaml | 4 + .../sample-vpc.vpcs.fakesourceapi.yaml | 4 + .../generated/providerv1_migration_plan.yaml | 204 ++-- .../sample-vpc.vpcs.fakesourceapi.yaml | 5 +- .../plan/generated/sp_migration_plan.yaml | 5 +- ...-resource-dwjgh.xmyresources.test.com.yaml | 4 + .../sample-vpc.vpcs.faketargetapi.yaml | 4 + pkg/migration/testdata/plan/lockv1beta1.yaml | 4 + pkg/migration/testdata/plan/providerv1.yaml | 4 + pkg/migration/testdata/plan/sourcevpc.yaml | 4 + pkg/migration/testdata/plan/sourcevpc2.yaml | 4 + pkg/migration/testdata/plan/xr.yaml | 4 + pkg/migration/testdata/plan/xrd.yaml | 48 +- pkg/migration/testdata/source/awsvpc.yaml | 4 + .../testdata/source/resourcegroup.yaml | 4 + pkg/migration/types.go | 14 +- pkg/pipeline/controller.go | 11 +- pkg/pipeline/crd.go | 14 +- pkg/pipeline/crd_test.go | 7 +- pkg/pipeline/register.go | 9 +- pkg/pipeline/run.go | 14 +- pkg/pipeline/setup.go | 11 +- pkg/pipeline/templates/controller.go.tmpl | 10 +- pkg/pipeline/templates/crd_types.go.tmpl | 4 + pkg/pipeline/templates/embed.go | 8 +- .../templates/groupversion_info.go.tmpl | 4 + pkg/pipeline/templates/register.go.tmpl | 4 + pkg/pipeline/templates/setup.go.tmpl | 8 +- pkg/pipeline/templates/terraformed.go.tmpl | 8 +- pkg/pipeline/terraformed.go | 9 +- pkg/pipeline/version.go | 9 +- pkg/registry/meta.go | 14 +- pkg/registry/meta_test.go | 8 +- pkg/registry/reference/references.go | 17 +- pkg/registry/reference/resolver.go | 16 +- pkg/registry/resource.go | 10 +- pkg/registry/testdata/aws/pm.yaml | 298 +++--- .../r/accessanalyzer_analyzer.html.markdown | 9 +- .../testdata/aws/r/ebs_volume.html.markdown | 15 +- .../aws/r/s3_bucket_acl.html.markdown | 16 +- pkg/registry/testdata/azure/pm.yaml | 674 ++++++------- .../azure/r/aadb2c_directory.html.markdown | 9 +- .../azure/r/attestation.html.markdown | 9 +- .../azure/r/kubernetes_cluster.html.markdown | 12 +- pkg/registry/testdata/gcp/pm.yaml | 4 + ...context_manager_access_level.html.markdown | 6 + .../gcp/r/container_cluster.html.markdown | 6 + .../gcp/r/storage_bucket.html.markdown | 6 + pkg/resource/conditions.go | 9 +- pkg/resource/extractor.go | 6 +- pkg/resource/fake/mocks/mock.go | 16 +- pkg/resource/fake/terraformed.go | 6 +- pkg/resource/ignored.go | 6 +- pkg/resource/ignored_test.go | 9 +- pkg/resource/interfaces.go | 6 +- pkg/resource/json/json.go | 6 +- pkg/resource/json/statev4.go | 6 +- pkg/resource/lateinit.go | 26 +- pkg/resource/lateinit_test.go | 6 +- pkg/resource/sensitive.go | 32 +- pkg/resource/sensitive_test.go | 32 +- pkg/terraform/errors/errors.go | 6 +- pkg/terraform/errors/errors_test.go | 6 +- pkg/terraform/files.go | 19 +- pkg/terraform/files_test.go | 22 +- pkg/terraform/finalizer.go | 9 +- pkg/terraform/finalizer_test.go | 14 +- pkg/terraform/operation.go | 6 +- pkg/terraform/operation_test.go | 6 +- pkg/terraform/provider_runner.go | 17 +- pkg/terraform/provider_runner_test.go | 11 +- pkg/terraform/provider_scheduler.go | 18 +- pkg/terraform/store.go | 28 +- pkg/terraform/timeouts.go | 24 +- pkg/terraform/timeouts_test.go | 20 +- pkg/terraform/workspace.go | 25 +- pkg/terraform/workspace_test.go | 11 +- pkg/types/builder.go | 17 +- pkg/types/builder_test.go | 22 +- pkg/types/comments/comment.go | 8 +- pkg/types/comments/comment_test.go | 10 +- pkg/types/conversion/tfjson/tfjson.go | 18 +- pkg/types/field.go | 19 +- pkg/types/markers/crossplane.go | 6 +- pkg/types/markers/crossplane_test.go | 7 +- pkg/types/markers/kubebuilder.go | 4 + pkg/types/markers/kubebuilder_test.go | 4 + pkg/types/markers/options.go | 4 + pkg/types/markers/terrajet.go | 4 + pkg/types/markers/terrajet_test.go | 7 +- pkg/types/name/name.go | 6 +- pkg/types/name/name_test.go | 6 +- pkg/types/name/reference.go | 6 +- pkg/types/name/reference_test.go | 6 +- pkg/types/reference.go | 11 +- pkg/types/reference_test.go | 31 +- pkg/version/version.go | 4 + 226 files changed, 4598 insertions(+), 2835 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md.license create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md.license create mode 100644 .github/PULL_REQUEST_TEMPLATE.md.license create mode 100644 .github/workflows/reuse-license-linter.yml create mode 100644 LICENSES/Apache-2.0.txt create mode 100644 LICENSES/CC-BY-4.0.txt create mode 100644 LICENSES/CC0-1.0.txt delete mode 100644 catalog-info.yaml rename docs/{ => archive}/add-new-resource-long.md (88%) rename docs/{ => archive}/add-new-resource-short.md (94%) rename docs/{ => archive}/adding-support-for-management-policies.md (97%) rename docs/{ => archive}/design-doc-provider-identity-based-auth.md (93%) rename docs/{ => archive}/manual-migration-guide-to-op.md (91%) rename docs/{ => archive}/migrating-from-terrajet-to-upjet.md (67%) rename docs/{ => archive}/moving-resources-to-v1beta1.md (89%) rename docs/{ => archive}/reference-generation.md (91%) rename docs/{ => archive}/testing-instructions.md (84%) rename docs/{ => archive}/testing-resources-by-using-uptest.md (95%) create mode 100644 docs/configuring-a-resource.md create mode 100644 docs/images/artifacts.png.license create mode 100644 docs/images/azure-wi.png.license create mode 100644 docs/images/managed-all.png.license create mode 100644 docs/images/summary.png.license create mode 100644 docs/images/upjet-components.png create mode 100644 docs/images/upjet-components.png.license create mode 100644 docs/images/upjet-externalname.excalidraw.license create mode 100644 docs/images/upjet-externalname.png.license create mode 100644 docs/testing-with-uptest.md create mode 100644 go.sum.license diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2f87a905..8268b4fd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: Bug Report about: Help us diagnose and fix bugs in Upjet -labels: bug,needs:triage +labels: bug --- - ### How can we reproduce it? +--> \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md.license b/.github/ISSUE_TEMPLATE/bug_report.md.license new file mode 100644 index 00000000..21ad42e1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC0-1.0 diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index b83b710a..a041d487 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,7 @@ --- name: Feature Request about: Help us make Upjet more useful -labels: enhancement,needs:triage +labels: enhancement --- +--> \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md.license b/.github/ISSUE_TEMPLATE/feature_request.md.license new file mode 100644 index 00000000..21ad42e1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC0-1.0 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 09513126..3ac09ef9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,27 +1,28 @@ ### Description of your changes + Fixes # I have: -- [ ] Read and followed Crossplane's [contribution process]. +- [ ] Read and followed Upjet's [contribution process]. - [ ] Run `make reviewable` to ensure this PR is ready for review. - [ ] Added `backport release-x.y` labels to auto-backport this PR if necessary. @@ -33,4 +34,4 @@ needs to tested and shown to be correct. Briefly describe the testing that has already been done or which is planned for this change. --> -[contribution process]: https://git.io/fj2m9 +[contribution process]: https://github.com/crossplane/upjet/blob/master/CONTRIBUTING.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md.license b/.github/PULL_REQUEST_TEMPLATE.md.license new file mode 100644 index 00000000..21ad42e1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC0-1.0 diff --git a/.github/stale.yml b/.github/stale.yml index f6c6e0ac..00406161 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 8b850da2..9554fac6 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + name: Backport on: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22f9ff87..8f3698a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + name: CI on: @@ -10,9 +14,9 @@ on: env: # Common versions - GO_VERSION: '1.20' - GOLANGCI_VERSION: 'v1.53.3' - DOCKER_BUILDX_VERSION: 'v0.8.2' + GO_VERSION: "1.20" + GOLANGCI_VERSION: "v1.53.3" + DOCKER_BUILDX_VERSION: "v0.8.2" # Common users. We can't run a step 'if secrets.AWS_USR != ""' but we can run # a step 'if env.AWS_USR' != ""', so we copy these to succinctly test whether @@ -35,7 +39,6 @@ jobs: do_not_skip: '["workflow_dispatch", "schedule", "push"]' concurrent_skipping: false - lint: runs-on: ubuntu-20.04 needs: detect-noop diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2b51acbd..1a7bee14 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + name: CodeQL on: diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 815838b5..d2fb48c4 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + name: Comment Commands on: issue_comment @@ -8,59 +12,59 @@ jobs: if: startsWith(github.event.comment.body, '/points') steps: - - name: Extract Command - id: command - uses: xt0rted/slash-command-action@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - command: points - reaction: "true" - reaction-type: "eyes" - allow-edits: "false" - permission-level: write - - name: Handle Command - uses: actions/github-script@v4 - env: - POINTS: ${{ steps.command.outputs.command-arguments }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const points = process.env.POINTS + - name: Extract Command + id: command + uses: xt0rted/slash-command-action@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + command: points + reaction: "true" + reaction-type: "eyes" + allow-edits: "false" + permission-level: write + - name: Handle Command + uses: actions/github-script@v4 + env: + POINTS: ${{ steps.command.outputs.command-arguments }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const points = process.env.POINTS - if (isNaN(parseInt(points))) { - console.log("Malformed command - expected '/points '") - github.reactions.createForIssueComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: context.payload.comment.id, - content: "confused" - }) - return - } - const label = "points/" + points + if (isNaN(parseInt(points))) { + console.log("Malformed command - expected '/points '") + github.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: "confused" + }) + return + } + const label = "points/" + points + + // Delete our needs-points-label label. + try { + await github.issues.deleteLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: ['needs-points-label'] + }) + console.log("Deleted 'needs-points-label' label.") + } + catch(e) { + console.log("Label 'needs-points-label' probably didn't exist.") + } - // Delete our needs-points-label label. - try { - await github.issues.deleteLabel({ + // Add our points label. + github.issues.addLabels({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - name: ['needs-points-label'] + labels: [label] }) - console.log("Deleted 'needs-points-label' label.") - } - catch(e) { - console.log("Label 'needs-points-label' probably didn't exist.") - } - - // Add our points label. - github.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: [label] - }) - console.log("Added '" + label + "' label.") + console.log("Added '" + label + "' label.") # NOTE(negz): See also backport.yml, which is the variant that triggers on PR # merge rather than on comment. @@ -68,25 +72,25 @@ jobs: runs-on: ubuntu-20.04 if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/backport') steps: - - name: Extract Command - id: command - uses: xt0rted/slash-command-action@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - command: backport - reaction: "true" - reaction-type: "eyes" - allow-edits: "false" - permission-level: write + - name: Extract Command + id: command + uses: xt0rted/slash-command-action@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + command: backport + reaction: "true" + reaction-type: "eyes" + allow-edits: "false" + permission-level: write - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 - - name: Open Backport PR - uses: zeebe-io/backport-action@v0.0.4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - github_workspace: ${{ github.workspace }} - version: v0.0.4 + - name: Open Backport PR + uses: zeebe-io/backport-action@v0.0.4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + github_workspace: ${{ github.workspace }} + version: v0.0.4 diff --git a/.github/workflows/reuse-license-linter.yml b/.github/workflows/reuse-license-linter.yml new file mode 100644 index 00000000..b34fd684 --- /dev/null +++ b/.github/workflows/reuse-license-linter.yml @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. +# +# SPDX-License-Identifier: CC0-1.0 + +name: REUSE Compliance Check + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: REUSE Compliance Check + uses: fsfe/reuse-action@v2 + - name: REUSE SPDX SBOM + uses: fsfe/reuse-action@v2 + with: + args: spdx diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index c3ce1055..63f8deff 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -1,13 +1,17 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + name: Tag on: workflow_dispatch: inputs: version: - description: 'Release version (e.g. v0.1.0)' + description: "Release version (e.g. v0.1.0)" required: true message: - description: 'Tag message' + description: "Tag message" required: true jobs: diff --git a/.gitignore b/.gitignore index b28f57cf..c5c010b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + /.cache /.work /_output @@ -10,4 +14,4 @@ cover.out # ignore IDE folders .vscode/ -.idea/ \ No newline at end of file +.idea/ diff --git a/.gitmodules b/.gitmodules index c2fad470..bbd089e3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + [submodule "build"] - path = build - url = https://github.com/upbound/build +path = build +url = https://github.com/upbound/build diff --git a/.golangci.yml b/.golangci.yml index ae6e3c53..a6cc3f56 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,8 +1,12 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + run: timeout: 10m skip-files: - - "zz_generated\\..+\\.go$" + - "zz_generated\\..+\\.go$" output: # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" @@ -35,10 +39,15 @@ linters-settings: # simplify code: gofmt with `-s` option, true by default simplify: true - goimports: - # put imports beginning with prefix after 3rd-party packages; - # it's a comma-separated list of prefixes - local-prefixes: github.com/upbound/upjet + gci: + custom-order: true + sections: + - standard + - default + - prefix(github.com/crossplane/crossplane-runtime) + - prefix(github.com/crossplane/crossplane) + - blank + - dot gocyclo: # minimal code complexity to report, 30 by default (but we recommend 10-20) @@ -102,28 +111,40 @@ linters-settings: rangeValCopy: sizeThreshold: 32 + nolintlint: + require-explanation: false + require-specific: true + linters: enable: - megacheck - govet - gocyclo - gocritic - - interfacer - goconst - - goimports - - gofmt # We enable this as well as goimports for its simplify mode. + - gci + - gofmt # We enable this as well as goimports for its simplify mode. - prealloc - revive - unconvert - misspell - nakedret + - nolintlint + + disable: + # These linters are all deprecated as of golangci-lint v1.49.0. We disable + # them explicitly to avoid the linter logging deprecation warnings. + - deadcode + - varcheck + - scopelint + - structcheck + - interfacer presets: - bugs - unused fast: false - issues: # Excluding configuration per-path and per-linter exclude-rules: @@ -148,38 +169,36 @@ issues: # rather than using a pointer. - text: "(hugeParam|rangeValCopy):" linters: - - gocritic + - gocritic # This "TestMain should call os.Exit to set exit code" warning is not clever # enough to notice that we call a helper method that calls os.Exit. - text: "SA3000:" linters: - - staticcheck + - staticcheck - text: "k8s.io/api/core/v1" linters: - - goimports + - goimports # This is a "potential hardcoded credentials" warning. It's triggered by # any variable with 'secret' in the same, and thus hits a lot of false # positives in Kubernetes land where a Secret is an object type. - text: "G101:" linters: - - gosec - - gas + - gosec + - gas # This is an 'errors unhandled' warning that duplicates errcheck. - text: "G104:" linters: - - gosec - - gas + - gosec + - gas - # The Azure AddToUserAgent method appends to the existing user agent string. - # It returns an error if you pass it an empty string lettinga you know the - # user agent did not change, making it more of a warning. - - text: \.AddToUserAgent + # Some k8s dependencies do not have JSON tags on all fields in structs. + - path: k8s.io/ linters: - - errcheck + - musttag # Independently from option `exclude` we use default exclude patterns, # it can be disabled by this option. To list all diff --git a/CODEOWNERS b/CODEOWNERS index 2645cd0d..258549de 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,8 @@ + +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + # This file controls automatic PR reviewer assignment. See the following docs: # # * https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners @@ -8,10 +13,13 @@ # and merge PRs. All PRs must be approved by at least one maintainer before being merged. # # Where possible, prefer explicitly specifying a maintainer who is a subject -# matter expert for a particular part of the codebase rather than using the -# @upbound/team-extensions group. +# matter expert for a particular part of the codebase rather than using fallback +# owners. Fallback owners are listed at the bottom of this file. # # See also OWNERS.md for governance details +# Subject matter experts +pkg/migrations/* @sergenyalcin + # Fallback owners -* @ulucinar @sergenyalcin +* @ulucinar @sergenyalcin \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 18edcaab..26df864f 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,9 @@ -## Code of Conduct + + +# Community Code of Conduct + +This project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e08aac5f..c35a03a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,9 @@ + + # Contributing to Upjet Welcome, and thank you for considering contributing to Upjet. We encourage @@ -85,7 +91,7 @@ change in Upjet, the best way to test it is to use a `replace` statement in the `go.mod` file of the provider to use your local version as shown below. ``` -replace github.com/upbound/upjet => ../upjet +replace github.com/crossplane/upjet => ../upjet ``` Once you complete your change, make sure to run `make reviewable` before opening @@ -98,7 +104,7 @@ in your provider to point to a certain commit in your branch of the provider tha you opened a PR for. ``` -replace github.com/upbound/upjet => github.com//upjet +replace github.com/crossplane/upjet => github.com//upjet ``` [Slack]: https://crossplane.slack.com/archives/C01TRKD4623 diff --git a/LICENSE b/LICENSE index 5695f4d9..ca635233 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,73 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [YEAR] Upbound Inc. All rights reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Apache License +Version 2.0, January 2004 + + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 00000000..137069b8 --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSES/CC-BY-4.0.txt b/LICENSES/CC-BY-4.0.txt new file mode 100644 index 00000000..c0041c89 --- /dev/null +++ b/LICENSES/CC-BY-4.0.txt @@ -0,0 +1,156 @@ +Creative Commons Attribution 4.0 International + +Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. + +Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. + +Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. + +Section 1 – Definitions. + + a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. + + d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. + + g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. + + i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. + +Section 2 – Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: + + A. reproduce and Share the Licensed Material, in whole or in part; and + + B. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. + + 3. Term. The term of this Public License is specified in Section 6(a). + + 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. + + 5. Downstream recipients. + + A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. + + B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. + + 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). + +b. Other rights. + + 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this Public License. + + 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. + +Section 3 – License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified form), You must: + + A. retain the following if it is supplied by the Licensor with the Licensed Material: + + i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of warranties; + + v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; + + B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and + + C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. + + 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. + +Section 4 – Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; + + b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. +For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. + +Section 5 – Disclaimer of Warranties and Limitation of Liability. + + a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. + + b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. + + c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. + +Section 6 – Term and Termination. + + a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or + + 2. upon express reinstatement by the Licensor. + + c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. + + d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. + + e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. + +Section 7 – Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. + +Section 8 – Interpretation. + + a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. + + c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. + + d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. + +Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/Makefile b/Makefile index 66d4d5ad..940c7a81 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,13 @@ + +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + # ==================================================================================== # Setup Project PROJECT_NAME := upjet -PROJECT_REPO := github.com/upbound/$(PROJECT_NAME) +PROJECT_REPO := github.com/crossplane/$(PROJECT_NAME) # GOLANGCILINT_VERSION is inherited from build submodule by default. # Uncomment below if you need to override the version. @@ -55,12 +60,6 @@ fallthrough: submodules @echo Initial setup complete. Running make again . . . @make -# Generate a coverage report for cobertura applying exclusions on -# - generated file -cobertura: - @cat $(GO_TEST_OUTPUT)/coverage.txt | \ - $(GOCOVER_COBERTURA) > $(GO_TEST_OUTPUT)/cobertura-coverage.xml - # Update the submodules, such as the common build scripts. submodules: @git submodule sync @@ -78,4 +77,4 @@ go.cachedir: go.mod.cachedir: @go env GOMODCACHE -.PHONY: cobertura reviewable submodules fallthrough go.mod.cachedir go.cachedir +.PHONY: reviewable submodules fallthrough go.mod.cachedir go.cachedir diff --git a/NOTICE b/NOTICE index 14d5a307..d8ce029d 100644 --- a/NOTICE +++ b/NOTICE @@ -1,3 +1,9 @@ + + This project is a larger work that combines with software written by third parties, licensed under their own terms. diff --git a/OWNERS.md b/OWNERS.md index 599b40e4..dd6df311 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -1,9 +1,14 @@ -# OWNERS + -This page lists all maintainers for **this** repository. Each repository in the [Upbound -organization](https://github.com/upbound/) will list their repository maintainers in their own -`OWNERS.md` file. +# OWNERS +This page lists all maintainers for **this** repository. Each repository in the +[Crossplane organization](https://github.com/crossplane/) will list their +repository maintainers in their own `OWNERS.md` file. ## Maintainers diff --git a/README.md b/README.md index 2ce0acec..0ee78d07 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,53 @@ + + # Upjet - Generate Crossplane Providers from any Terraform Provider +
-![CI](https://github.com/upbound/upjet/workflows/CI/badge.svg) [![GitHub release](https://img.shields.io/github/release/upbound/upjet/all.svg?style=flat-square)](https://github.com/upbound/upjet/releases) [![Go Report Card](https://goreportcard.com/badge/github.com/upbound/upjet)](https://goreportcard.com/report/github.com/upbound/upjet) [![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) +![CI](https://github.com/crossplane/upjet/workflows/CI/badge.svg) +[![GitHub release](https://img.shields.io/github/release/crossplane/upjet/all.svg)](https://github.com/crossplane/upjet/releases) +[![Go Report Card](https://goreportcard.com/badge/github.com/crossplane/upjet)](https://goreportcard.com/report/github.com/crossplane/upjet) +[![Contributors](https://img.shields.io/github/contributors/crossplane/upjet)](https://github.com/crossplane/upjet/graphs/contributors) +[![Slack](https://img.shields.io/badge/Slack-4A154B?logo=slack)](https://crossplane.slack.com/archives/C05T19TB729) +[![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/crossplane_io)](https://twitter.com/crossplane_io)
Upjet is a code generator framework that allows developers to build code generation pipelines that can generate Crossplane controllers. Developers can -start building their code generation pipeline targeting specific Terraform Providers -by importing Upjet and wiring all generators together, customizing the whole -pipeline in the process. +start building their code generation pipeline targeting specific Terraform +Providers by importing Upjet and wiring all generators together, customizing the +whole pipeline in the process. -Here is some Crossplane providers built using Upjet: +Here are some Crossplane providers built using Upjet: -* [Provider AWS](https://github.com/upbound/provider-aws) -* [Provider Azure](https://github.com/upbound/provider-azure) -* [Provider GCP](https://github.com/upbound/provider-gcp) +- [upbound/provider-aws](https://github.com/upbound/provider-aws) +- [upbound/provider-azure](https://github.com/upbound/provider-azure) +- [upbound/provider-gcp](https://github.com/upbound/provider-gcp) +- [aviatrix/crossplane-provider-aviatrix](https://github.com/Aviatrix/crossplane-provider-aviatrix) ## Getting Started -You can get started by following the guides in [docs](docs/README.md) directory! +You can get started by following the guides in the [docs](docs/README.md) +directory. ## Report a Bug For filing bugs, suggesting improvements, or requesting new features, please -open an [issue](https://github.com/upbound/upjet/issues). +open an [issue](https://github.com/crossplane/upjet/issues). ## Contact -Please open a Github issue for all requests. If you need to reach out to Upbound, -you can do so via the following channels: -* Slack: [#upbound](https://crossplane.slack.com/archives/C01TRKD4623) channel in [Crossplane Slack](https://slack.crossplane.io) -* Twitter: [@upbound_io](https://twitter.com/upbound_io) -* Email: [support@upbound.io](mailto:support@upbound.io) +[#upjet](https://crossplane.slack.com/archives/C05T19TB729) channel in +[Crossplane Slack](https://slack.crossplane.io) ## Prior Art -Upjet originates from the [Terrajet][terrajet] project. See the original +Upjet originates from the [Terrajet][terrajet] project. See the original [design document][terrajet-design-doc]. ## Licensing @@ -44,4 +55,4 @@ Upjet originates from the [Terrajet][terrajet] project. See the original Upjet is under [the Apache 2.0 license](LICENSE) with [notice](NOTICE). [terrajet-design-doc]: https://github.com/crossplane/crossplane/blob/master/design/design-doc-terrajet.md -[terrajet]: https://github.com/crossplane/terrajet \ No newline at end of file +[terrajet]: https://github.com/crossplane/terrajet diff --git a/catalog-info.yaml b/catalog-info.yaml deleted file mode 100644 index 309500fe..00000000 --- a/catalog-info.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: backstage.io/v1alpha1 -kind: Component -metadata: - name: upjet - description: "A code generation framework and runtime for Crossplane providers" - links: - - url: https://github.com/upbound/upjet/blob/main/docs/README.md - title: Upjet Readme - annotations: - github.com/project-slug: upbound/upjet -spec: - type: service - lifecycle: production - owner: team-extensions diff --git a/cmd/scraper/main.go b/cmd/scraper/main.go index 0ec2af2b..077c3170 100644 --- a/cmd/scraper/main.go +++ b/cmd/scraper/main.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package main @@ -8,9 +8,8 @@ import ( "os" "path/filepath" + "github.com/crossplane/upjet/pkg/registry" "gopkg.in/alecthomas/kingpin.v2" - - "github.com/upbound/upjet/pkg/registry" ) func main() { diff --git a/docs/README.md b/docs/README.md index d0c40c36..aed5c437 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,35 +1,40 @@ -# Using Upjet - -Upjet consists of three main pieces: -* Framework to build a code generator pipeline. -* Generic reconciler implementation used by all generated `CustomResourceDefinition`s. -* A scraper to extract documentation for all generated `CustomResourceDefinition`s. - -The usual flow of development of a new provider is as following: -1. Create a provider by following the guide [here][generate-a-provider]. -2. Follow the guide [here][new-v1beta1] to add a `CustomResourceDefinition` for - every resource in the given Terraform provider. - -In most cases, the two guides above would be enough for you to get up and running -with a provider. - -The guides below are longer forms for when you get stuck and want a deeper -understanding: -* Description of all configuration knobs can be found [here][full-guide]. -* Detailed explanation of how to use Uptest to test your resources can be found - [here][uptest-guide]. - * You can find a troubleshooting guide [here][testing-instructions] that can - be useful to debug a failed test. -* References are inferred from the generated examples with a best effort manner. - Details about the process can be found [here][reference-generation]. - -Feel free to ask your questions by opening an issue, starting a discussion or -shooting a message on [Slack]! - -[generate-a-provider]: generating-a-provider.md -[new-v1beta1]: add-new-resource-short.md -[full-guide]: add-new-resource-long.md -[uptest-guide]: testing-resources-by-using-uptest.md -[testing-instructions]: testing-instructions.md -[reference-generation]: reference-generation.md -[Slack]: https://crossplane.slack.com/archives/C01TRKD4623 \ No newline at end of file + + +# What is Upjet? + +Upjet consists of four main components: + +![Upjet components](images/upjet-components.png) + +1. Framework to build a code generator pipeline for Crossplane providers. +1. Generic reconciler implementation (also known as the Upjet runtime) used by + all generated `CustomResourceDefinitions`. +1. A scraper to extract documentation for all generated + `CustomResourceDefinitions`. +1. Migration framework to support migrating from community providers to Official + Providers. + +## Generating a Crossplane provider using Upjet + +Follow the guide to start [generating a Crossplane +provider](generating-a-provider.md). + +Further information on developing a provider: + +- Guide for how to [configure a resource](configuring-a-resource.md) in your +provider. +- Guide on how to use Uptest to [test your resources](testing-with-uptest.md) +end to end. + +## Additional documentation + +- [Monitoring](monitoring.md) the Upjet runtime using Prometheus. +- [Sizing guide](sizing-guide.md) for Upjet providers. + +Feel free to ask your questions by opening an issue or starting a discussion in +the [#upjet](https://crossplane.slack.com/archives/C05T19TB729) channel in +[Crossplane Slack](https://slack.crossplane.io). diff --git a/docs/add-new-resource-long.md b/docs/archive/add-new-resource-long.md similarity index 88% rename from docs/add-new-resource-long.md rename to docs/archive/add-new-resource-long.md index 6a8e1c04..5d049cfb 100644 --- a/docs/add-new-resource-long.md +++ b/docs/archive/add-new-resource-long.md @@ -1,3 +1,9 @@ + + ## Configuring a Resource [Upjet] generates as much as it could using the available information in the @@ -106,14 +112,14 @@ conditions: ```go import ( - "github.com/upbound/upjet/pkg/config" - ... + "github.com/crossplane/upjet/pkg/config" + ... ) ... p.AddResourceConfigurator("aws_iam_user", func(r *config.Resource) { r.ExternalName = config.NameAsIdentifier - ... + ... } ``` @@ -133,8 +139,8 @@ also omit `bucket` and `bucket_prefix` arguments from the spec with ```go import ( - "github.com/upbound/upjet/pkg/config" - ... + "github.com/crossplane/upjet/pkg/config" + ... ) ... @@ -147,7 +153,7 @@ import ( "bucket", "bucket_prefix", } - ... + ... } ``` @@ -165,14 +171,14 @@ Here, we can just use [IdentifierFromProvider] configuration: ```go import ( - "github.com/upbound/upjet/pkg/config" - ... + "github.com/crossplane/upjet/pkg/config" + ... ) ... p.AddResourceConfigurator("aws_vpc", func(r *config.Resource) { r.ExternalName = config.IdentifierFromProvider - ... + ... } ``` @@ -196,25 +202,25 @@ this id back (`GetIDFn`). ```go import ( - "github.com/upbound/upjet/pkg/config" - ... + "github.com/crossplane/upjet/pkg/config" + ... ) func getNameFromFullyQualifiedID(tfstate map[string]any) (string, error) { - id, ok := tfstate["id"] - if !ok { - return "", errors.Errorf(ErrFmtNoAttribute, "id") - } - idStr, ok := id.(string) - if !ok { - return "", errors.Errorf(ErrFmtUnexpectedType, "id") - } - words := strings.Split(idStr, "/") - return words[len(words)-1], nil + id, ok := tfstate["id"] + if !ok { + return "", errors.Errorf(ErrFmtNoAttribute, "id") + } + idStr, ok := id.(string) + if !ok { + return "", errors.Errorf(ErrFmtUnexpectedType, "id") + } + words := strings.Split(idStr, "/") + return words[len(words)-1], nil } func getFullyQualifiedIDfunc(ctx context.Context, externalName string, parameters map[string]any, providerConfig map[string]any) (string, error) { - subID, ok := providerConfig["subscription_id"] + subID, ok := providerConfig["subscription_id"] if !ok { return "", errors.Errorf(ErrFmtNoAttribute, "subscription_id") } @@ -231,7 +237,7 @@ func getFullyQualifiedIDfunc(ctx context.Context, externalName string, parameter return "", errors.Errorf(ErrFmtUnexpectedType, "resource_group_name") } - name, ok := parameters["name"] + name, ok := parameters["name"] if !ok { return "", errors.Errorf(ErrFmtNoAttribute, "name") } @@ -248,7 +254,7 @@ func getFullyQualifiedIDfunc(ctx context.Context, externalName string, parameter r.ExternalName = config.NameAsIdentifier r.ExternalName.GetExternalNameFn = getNameFromFullyQualifiedID r.ExternalName.GetIDFn = getFullyQualifiedIDfunc - ... + ... } ``` @@ -261,7 +267,6 @@ functions to configure external names and, it visualizes which is used how:_ ![Alt text](./images/upjet-externalname.png) _Note that, initially, GetIDFn will use the external-name annotation to set the terraform.tfstate id and, after that, it uses the terraform.tfstate id to update the external-name annotation. For cases where both values are different, both GetIDFn and GetExternalNameFn must be set in order to have the correct configuration._ - ### Cross Resource Referencing Crossplane uses cross resource referencing to [handle dependencies] between @@ -352,11 +357,11 @@ case, we would need to provide the full path. Referencing to a [kms key] from ```go func Configure(p *config.Provider) { - p.AddResourceConfigurator("aws_ebs_volume", func(r *config.Resource) { - r.References["kms_key_id"] = config.Reference{ - Type: "github.com/crossplane-contrib/provider-tf-aws/apis/kms/v1alpha1.Key", - } - }) + p.AddResourceConfigurator("aws_ebs_volume", func(r *config.Resource) { + r.References["kms_key_id"] = config.Reference{ + Type: "github.com/crossplane-contrib/provider-tf-aws/apis/kms/v1alpha1.Key", + } + }) } ``` @@ -382,18 +387,18 @@ respectively. To see them with more common keys, i.e. `aws_access_key_id` and ```go func Configure(p *config.Provider) { - p.AddResourceConfigurator("aws_iam_access_key", func(r *config.Resource) { - r.Sensitive.AdditionalConnectionDetailsFn = func(attr map[string]any) (map[string][]byte, error) { - conn := map[string][]byte{} - if a, ok := attr["id"].(string); ok { - conn["aws_access_key_id"] = []byte(a) - } - if a, ok := attr["secret"].(string); ok { - conn["aws_secret_access_key"] = []byte(a) - } - return conn, nil - } - }) + p.AddResourceConfigurator("aws_iam_access_key", func(r *config.Resource) { + r.Sensitive.AdditionalConnectionDetailsFn = func(attr map[string]any) (map[string][]byte, error) { + conn := map[string][]byte{} + if a, ok := attr["id"].(string); ok { + conn["aws_access_key_id"] = []byte(a) + } + if a, ok := attr["secret"].(string); ok { + conn["aws_secret_access_key"] = []byte(a) + } + return conn, nil + } + }) } ``` @@ -423,17 +428,17 @@ observe failed: cannot run refresh: refresh failed: Invalid combination of argum "address_prefix": only one of `address_prefix,address_prefixes` can be specified, but `address_prefix,address_prefixes` were specified.: File name: main.tf.json ``` -If you would like to have the late-initialization library *not* to process the +If you would like to have the late-initialization library _not_ to process the [`address_prefix`] parameter field, then the following configuration where we specify the parameter field path is sufficient: ```go func Configure(p *config.Provider) { - p.AddResourceConfigurator("azurerm_subnet", func(r *config.Resource) { - r.LateInitializer = config.LateInitializer{ - IgnoredFields: []string{"address_prefix"}, - } - }) + p.AddResourceConfigurator("azurerm_subnet", func(r *config.Resource) { + r.LateInitializer = config.LateInitializer{ + IgnoredFields: []string{"address_prefix"}, + } + }) } ``` @@ -454,7 +459,7 @@ you will want/need to customize late-initialization behaviour. Thus, Upjet provides an extensible [late-initialization customization API] that controls late-initialization behaviour. -The associated resource struct is defined [here](https://github.com/upbound/upjet/blob/c9e21387298d8ed59fcd71c7f753ec401a3383a5/pkg/config/resource.go#L91) as follows: +The associated resource struct is defined [here](https://github.com/crossplane/upjet/blob/c9e21387298d8ed59fcd71c7f753ec401a3383a5/pkg/config/resource.go#L91) as follows: ```go // LateInitializer represents configurations that control @@ -573,32 +578,32 @@ There is a common struct (`Tagger`) in upjet to use the tagging convention: ```go // Tagger implements the Initialize function to set external tags type Tagger struct { - kube client.Client - fieldName string + kube client.Client + fieldName string } // NewTagger returns a Tagger object. func NewTagger(kube client.Client, fieldName string) *Tagger { - return &Tagger{kube: kube, fieldName: fieldName} + return &Tagger{kube: kube, fieldName: fieldName} } // Initialize is a custom initializer for setting external tags func (t *Tagger) Initialize(ctx context.Context, mg xpresource.Managed) error { - paved, err := fieldpath.PaveObject(mg) - if err != nil { - return err - } - pavedByte, err := setExternalTagsWithPaved(xpresource.GetExternalTags(mg), paved, t.fieldName) - if err != nil { - return err - } - if err := json.Unmarshal(pavedByte, mg); err != nil { - return err - } - if err := t.kube.Update(ctx, mg); err != nil { - return err - } - return nil + paved, err := fieldpath.PaveObject(mg) + if err != nil { + return err + } + pavedByte, err := setExternalTagsWithPaved(xpresource.GetExternalTags(mg), paved, t.fieldName) + if err != nil { + return err + } + if err := json.Unmarshal(pavedByte, mg); err != nil { + return err + } + if err := t.kube.Update(ctx, mg); err != nil { + return err + } + return nil } ``` @@ -612,7 +617,7 @@ as default: ```go // TagInitializer returns a tagger to use default tag initializer. var TagInitializer NewInitializerFn = func(client client.Client) managed.Initializer { - return NewTagger(client, "tags") + return NewTagger(client, "tags") } ``` @@ -622,11 +627,11 @@ In jet-aws provider, as a default process, if a resource has `tags` field in its ```go // AddExternalTagsField adds ExternalTagsFieldName configuration for resources that have tags field. func AddExternalTagsField() tjconfig.ResourceOption { - return func(r *tjconfig.Resource) { - if s, ok := r.TerraformResource.Schema["tags"]; ok && s.Type == schema.TypeMap { - r.InitializerFns = append(r.InitializerFns, tjconfig.TagInitializer) - } - } + return func(r *tjconfig.Resource) { + if s, ok := r.TerraformResource.Schema["tags"]; ok && s.Type == schema.TypeMap { + r.InitializerFns = append(r.InitializerFns, tjconfig.TagInitializer) + } + } } ``` @@ -635,7 +640,7 @@ called and the specific `fieldName` can be passed to this: ```go r.InitializerFns = append(r.InitializerFns, func(client client.Client) managed.Initializer { - return tjconfig.NewTagger(client, "example_tags_name") + return tjconfig.NewTagger(client, "example_tags_name") }) ``` @@ -654,49 +659,45 @@ Initializer is an interface in [crossplane-runtime]: ```go type Initializer interface { - Initialize(ctx context.Context, mg resource.Managed) error + Initialize(ctx context.Context, mg resource.Managed) error } ``` So, an interface must be passed to the related configuration field for adding initializers for a resource. -[comment]: <> (References) - -[Upjet]: https://github.com/upbound/upjet +[Upjet]: https://github.com/crossplane/upjet [External name]: #external-name [Cross Resource Referencing]: #cross-resource-referencing [Additional Sensitive Fields and Custom Connection Details]: #additional-sensitive-fields-and-custom-connection-details [Late Initialization Behavior]: #late-initialization-configuration [Overriding Terraform Resource Schema]: #overriding-terraform-resource-schema [the external name documentation]: https://crossplane.io/docs/v1.7/concepts/managed-resources.html#external-name -[concept to identify a resource]: https://www.terraform.io/docs/glossary#id [import section]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key#import -[the types for the External Name configuration]: https://github.com/upbound/upjet/blob/2299925ea2541e6a8088ede463cd865bd64eba32/pkg/config/resource.go#L67 +[the types for the External Name configuration]: https://github.com/crossplane/upjet/blob/2299925ea2541e6a8088ede463cd865bd64eba32/pkg/config/resource.go#L67 [aws_iam_user]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user -[NameAsIdentifier]: https://github.com/upbound/upjet/blob/2299925ea2541e6a8088ede463cd865bd64eba32/pkg/config/defaults.go#L31 +[NameAsIdentifier]: https://github.com/crossplane/upjet/blob/2299925ea2541e6a8088ede463cd865bd64eba32/pkg/config/defaults.go#L31 [aws_s3_bucket]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket [import section of s3 bucket]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#import [bucket]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#bucket [cluster_identifier]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster#cluster_identifier [aws_rds_cluster]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster. -[aws_vpc]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc [import section of aws_vpc]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc#import [arguments list]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc#argument-reference [example usages]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc#example-usage -[IdentifierFromProvider]: https://github.com/upbound/upjet/blob/2299925ea2541e6a8088ede463cd865bd64eba32/pkg/config/defaults.go#L46 +[IdentifierFromProvider]: https://github.com/crossplane/upjet/blob/2299925ea2541e6a8088ede463cd865bd64eba32/pkg/config/defaults.go#L46 [a similar identifier]: https://www.terraform.io/docs/glossary#id [import section of azurerm_sql_server]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/sql_server#import [handle dependencies]: https://crossplane.io/docs/v1.7/concepts/managed-resources.html#dependencies [user]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key#user [generate reference resolution methods]: https://github.com/crossplane/crossplane-tools/pull/35 -[configuration]: https://github.com/upbound/upjet/blob/874bb6ad5cff9741241fb790a3a5d71166900860/pkg/config/resource.go#L77 +[configuration]: https://github.com/crossplane/upjet/blob/874bb6ad5cff9741241fb790a3a5d71166900860/pkg/config/resource.go#L77 [iam_access_key]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key#argument-reference [kms key]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ebs_volume#kms_key_id [connection details]: https://crossplane.io/docs/v1.7/concepts/managed-resources.html#connection-details [id]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key#id [secret]: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key#secret -[`external.Observe`]: https://github.com/upbound/upjet/blob/874bb6ad5cff9741241fb790a3a5d71166900860/pkg/controller/external.go#L149 -[late-initialization customization API]: https://github.com/upbound/upjet/blob/874bb6ad5cff9741241fb790a3a5d71166900860/pkg/resource/lateinit.go#L86 +[`external.Observe`]: https://github.com/crossplane/upjet/blob/874bb6ad5cff9741241fb790a3a5d71166900860/pkg/controller/external.go#L149 +[late-initialization customization API]: https://github.com/crossplane/upjet/blob/874bb6ad5cff9741241fb790a3a5d71166900860/pkg/resource/lateinit.go#L86 [`address_prefix`]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet#address_prefix [Terraform schema of the resource]: https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L34 [Type]: https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L52 @@ -706,12 +707,10 @@ So, an interface must be passed to the related configuration field for adding in [Optional]: https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L80 [Computed]: https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L139 [tags_all for jet AWS resources]: https://github.com/upbound/provider-aws/blob/main/config/overrides.go#L62 -[boot_disk.initialize_params.labels]: https://github.com/upbound/provider-gcp/blob/main/config/compute/config.go#L121 [AWS region]: https://github.com/upbound/provider-aws/blob/main/config/overrides.go#L32 [this figure]: images/upjet-externalname.png [Initializers]: #initializers -[InitializerFns]: https://github.com/upbound/upjet/blob/ae78a0a4c438f01717002e00fac761524aa6e951/pkg/config/resource.go#L289 -[NewInitializerFn]: https://github.com/upbound/upjet/blob/ae78a0a4c438f01717002e00fac761524aa6e951/pkg/config/resource.go#L207 +[InitializerFns]: https://github.com/crossplane/upjet/blob/ae78a0a4c438f01717002e00fac761524aa6e951/pkg/config/resource.go#L289 +[NewInitializerFn]: https://github.com/crossplane/upjet/blob/ae78a0a4c438f01717002e00fac761524aa6e951/pkg/config/resource.go#L207 [crossplane-runtime]: https://github.com/crossplane/crossplane-runtime/blob/428b7c3903756bb0dcf5330f40298e1fa0c34301/pkg/reconciler/managed/reconciler.go#L138 -[some external labels]: https://github.com/crossplane/crossplane-runtime/blob/428b7c3903756bb0dcf5330f40298e1fa0c34301/pkg/resource/resource.go#L397 [tagging convention]: https://github.com/crossplane/crossplane/blob/60c7df9/design/one-pager-managed-resource-api-design.md#external-resource-labeling diff --git a/docs/add-new-resource-short.md b/docs/archive/add-new-resource-short.md similarity index 94% rename from docs/add-new-resource-short.md rename to docs/archive/add-new-resource-short.md index 9d5807e7..a5b2aa42 100644 --- a/docs/add-new-resource-short.md +++ b/docs/archive/add-new-resource-short.md @@ -1,3 +1,9 @@ + + ## Adding a New Resource There are a long and detailed guides showing [how to bootstrap a @@ -21,44 +27,52 @@ groups, such as `glue`, `grafana`, `guardduty` and `iam`. configuration from Upjet as our external name config. See section [External Name Cases](#external-name-cases) to see how you can infer in many different cases of Terraform ID. -1. First of all, please see the [Moving Untested Resources to v1beta1] +1. First of all, please see the [Moving Untested Resources to v1beta1] documentation. Go to `config/externalname.go` and add the following line to `ExternalNameConfigs` table: + ```golang // glue // // Imported using "name". "aws_glue_workflow": config.NameAsIdentifier, ``` + 1. Run `make reviewable`. 1. Go through the "Warning" boxes (if any) in the Terraform Registry page to see whether any of the fields are represented as separate resources as well. It usually goes like + ``` Routes can be defined either directly on the azurerm_iothub resource, or using the azurerm_iothub_route resource - but the two cannot be used together. ``` + In such cases, the field should be moved to status since we prefer to represent it only as a separate CRD. Go ahead and add a configuration block for that resource similar to the following: + ```golang p.AddResourceConfigurator("azurerm_iothub", func(r *config.Resource) { // Mutually exclusive with azurerm_iothub_route config.MoveToStatus(r.TerraformResource, "route") }) ``` + 1. Go to the end of the TF registry page to see the timeouts. If they are longer than 10 minutes, then we need to set the `UseAsync` property of the resource to `true`. Go ahead and add a configuration block for that resource similar to the following if it doesn't exist already: + ```golang p.AddResourceConfigurator("azurerm_iothub", func(r *config.Resource) { r.UseAsync = true }) ``` + Note that some providers have certain defaults, like Azure has this on by default, in such cases you need to set this parameter to `false` if the timeouts are less than 10 minutes. @@ -70,6 +84,7 @@ groups, such as `glue`, `grafana`, `guardduty` and `iam`. 1. Repeat the same process for other resources under `glue`. 1. Once `glue` is completed, the following would be the additions we made to the external name table and we'd have new examples under `examples/glue` folder. + ```golang // glue // @@ -86,6 +101,7 @@ groups, such as `glue`, `grafana`, `guardduty` and `iam`. // Imported using the account ID: 12356789012 "aws_glue_resource_policy": config.IdentifierFromProvider, ``` + 1. Create a commit to cover all manual changes so that it's easier for reviewer with a message like the following `aws: add glue group`. 1. Run `make reviewable` so that new resources are generated. @@ -109,11 +125,11 @@ a bug you can fix right away and in others resource is just not suitable for automated testing, such as the ones that require you to take a special action that a Crossplane provider cannot, such as uploading a file. - Our goal is to make it work with automated testing as much as possible. So, the next step is to test the resources manually in your local and try to spot the problems that prevent it from working with the automated testing. The steps for manual testing are roughly like the following (no Crossplane is needed): + * `kubectl apply -f package/crds` to install all CRDs into cluster. * `make run` to start the controllers. * You need to create a `ProviderConfig` named as `default` with correct @@ -135,19 +151,19 @@ metadata: upjet.upbound.io/manual-intervention: "User needs to upload a authorization script and give its path in spec.forProvider.filePath" ``` -If, for some reason, we cannot successfully test a managed resource even manually, -then we do not ship it with the `v1beta1` version and thus the external-name -configuration should be commented out with an appropriate code comment +If, for some reason, we cannot successfully test a managed resource even manually, +then we do not ship it with the `v1beta1` version and thus the external-name +configuration should be commented out with an appropriate code comment explaining the situation. -An issue in the official-providers repo explaining the situation +An issue in the official-providers repo explaining the situation [should be opened](https://github.com/upbound/official-providers/issues/new/choose) preferably with the example manifests (and any resource configuration) already tried. -As explained above, if the resource can successfully be manually tested but -not as part of the automated tests, the example manifest successfully validated -should still be included under the examples directory but with the proper -`upjet.upbound.io/manual-intervention` annotation. +As explained above, if the resource can successfully be manually tested but +not as part of the automated tests, the example manifest successfully validated +should still be included under the examples directory but with the proper +`upjet.upbound.io/manual-intervention` annotation. And successful manual testing still meets the `v1beta1` criteria. ## External Name Cases @@ -205,20 +221,20 @@ tell Upjet how to construct the full ID back and forth. func resourceName() config.ExternalName{ e := config.IdentifierFromProvider e.GetIDFn = func(_ context.Context, externalName string, parameters map[string]interface{}, _ map[string]interface{}) (string, error) { - cl, ok := parameters["cluster_name"] - if !ok { - return "", errors.New("cluster_name cannot be empty") - } - return fmt.Sprintf("%s:%s", cl.(string), externalName), nil - } - e.GetExternalNameFn = func(tfstate map[string]interface{}) (string, error) { - id, ok := tfstate["id"] - if !ok { - return "", errors.New("id in tfstate cannot be empty") - } - w := strings.Split(s.(string), ":") - return w[len(w)-1], nil - } + cl, ok := parameters["cluster_name"] + if !ok { + return "", errors.New("cluster_name cannot be empty") + } + return fmt.Sprintf("%s:%s", cl.(string), externalName), nil + } + e.GetExternalNameFn = func(tfstate map[string]interface{}) (string, error) { + id, ok := tfstate["id"] + if !ok { + return "", errors.New("id in tfstate cannot be empty") + } + w := strings.Split(s.(string), ":") + return w[len(w)-1], nil + } } ``` @@ -232,6 +248,7 @@ name and take the rest from parameters. Use `config.TemplatedStringAsIdentifier("", "")` in such cases. The following is the list of available parameters for you to use in your go template: + ``` parameters: A tree of parameters that you'd normally see in a Terraform HCL file. You can use TF registry documentation of given resource to @@ -252,6 +269,7 @@ You can see example usages in the big three providers below. For `aws_glue_user_defined_function`, we see that `name` argument is used to name the resource and the import instructions read as following: + ``` Glue User Defined Functions can be imported using the `catalog_id:database_name:function_name`. If you have not set a Catalog ID @@ -261,6 +279,7 @@ $ terraform import aws_glue_user_defined_function.func 123456789012:my_database: ``` Our configuration would look like the following: + ```golang "aws_glue_user_defined_function": config.TemplatedStringAsIdentifier("name", "{{ .parameters.catalog_id }}:{{ .parameters.database_name }}:{{ .externalName }}") ``` @@ -275,9 +294,10 @@ those cases like the following: However, there are cases where the ARN includes random substring and that would fall under Case 4. The following is such an example: + ``` // arn:aws:acm-pca:eu-central-1:609897127049:certificate-authority/ba0c7989-9641-4f36-a033-dee60121d595 - "aws_acmpca_certificate_authority_certificate": config.IdentifierFromProvider, + "aws_acmpca_certificate_authority_certificate": config.IdentifierFromProvider, ``` #### Azure @@ -287,6 +307,7 @@ identifier as Terraform ID. For `azurerm_mariadb_firewall_rule`, we see that `name` argument is used to name the resource and the import instructions read as following: + ``` MariaDB Firewall rules can be imported using the resource id, e.g. @@ -294,6 +315,7 @@ terraform import azurerm_mariadb_firewall_rule.rule1 /subscriptions/00000000-000 ``` Our configuration would look like the following: + ```golang "azurerm_mariadb_firewall_rule": config.TemplatedStringAsIdentifier("name", "/subscriptions/{{ .terraformProviderConfig.subscription_id }}/resourceGroups/{{ .parameters.resource_group_name }}/providers/Microsoft.DBforMariaDB/servers/{{ .parameters.server_name }}/firewallRules/{{ .externalName }}") ``` @@ -302,6 +324,7 @@ In some resources, an argument requires ID, like `azurerm_cosmosdb_sql_function` where it has `container_id` and `name` but no separate `resource_group_name` which would be required to build the full ID. Our configuration would look like the following in this case: + ```golang config.TemplatedStringAsIdentifier("name", "{{ .parameters.container_id }}/userDefinedFunctions/{{ .externalName }}") ``` @@ -313,6 +336,7 @@ identifier as Terraform ID. For `google_container_cluster`, we see that `name` argument is used to name the resource and the import instructions read as following: + ```console GKE clusters can be imported using the project , location, and name. If the project is omitted, the default provider value will be used. @@ -328,15 +352,18 @@ one with the least parameters so that we rely only on required fields because optional fields may have some defaults that are assigned after the creation which may make it tricky to work with. In this case, the following would be our configuration: + ```golang "google_compute_instance": config.TemplatedStringAsIdentifier("name", "{{ .parameters.location }}/{{ .externalName }}") ``` There are cases where one of the example import commands uses just `name`, like `google_compute_instance`: + ```console terraform import google_compute_instance.default {{name}} ``` + In such cases, we should use `config.NameAsIdentifier` since we'd like to have the least complexity in our configuration as possible. @@ -346,6 +373,7 @@ There is no instructions under `Import` section of the resource page in Terraform Registry, like `aws_acm_certificate_validation` from AWS. Use the following in such cases with comment indicating the case: + ```golang // No import documented. "aws_acm_certificate_validation": config.IdentifierFromProvider, @@ -391,11 +419,10 @@ argument depending on which one is given. You can take a look at the implementation [here][route-impl]. [This section][external-name-in-guide] in the detailed guide could also help you. - [provider-guide]: - https://github.com/upbound/upjet/blob/main/docs/generating-a-provider.md + https://github.com/crossplane/upjet/blob/main/docs/generating-a-provider.md [config-guide]: - https://github.com/upbound/upjet/blob/main/docs/add-new-resource-long.md + https://github.com/crossplane/upjet/blob/main/docs/add-new-resource-long.md [issue-90]: https://github.com/upbound/provider-aws/issues/90 [`aws_glue_workflow`]: @@ -407,5 +434,5 @@ detailed guide could also help you. [route-impl]: https://github.com/upbound/provider-aws/blob/8b3887c91c4b44dc14e1123b3a5ae1a70e0e45ed/config/externalname.go#L172 [external-name-in-guide]: - https://github.com/upbound/upjet/blob/main/docs/add-new-resource-long.md#external-name -[Moving Untested Resources to v1beta1]: https://github.com/upbound/upjet/blob/main/docs/moving-resources-to-v1beta1.md \ No newline at end of file + https://github.com/crossplane/upjet/blob/main/docs/add-new-resource-long.md#external-name +[Moving Untested Resources to v1beta1]: https://github.com/crossplane/upjet/blob/main/docs/moving-resources-to-v1beta1.md diff --git a/docs/adding-support-for-management-policies.md b/docs/archive/adding-support-for-management-policies.md similarity index 97% rename from docs/adding-support-for-management-policies.md rename to docs/archive/adding-support-for-management-policies.md index d54301f9..e0e1b728 100644 --- a/docs/adding-support-for-management-policies.md +++ b/docs/archive/adding-support-for-management-policies.md @@ -1,3 +1,9 @@ + + # Adding Support for Management Policies and initProvider in an Upjet Based Provider ## (Re)generating a provider with Management Policies @@ -40,7 +46,7 @@ directory on your local machine. ) ``` - Add the actual flag in `cmd/provider/main.go` file and pass the flag to the + Add the actual flag in `cmd/provider/main.go` file and pass the flag to the workspace store: ```diff @@ -89,14 +95,14 @@ directory on your local machine. kingpin.FatalIfError(mgr.Start(ctrl.SetupSignalHandler()), "Cannot start controller manager") } ``` - + > Note: If the provider was already updated to support observe-only resources, just add the feature flag to the workspaceStore. 3. Generate with the latest upjet and management policies: ```bash # Bump to the latest upjet - go get github.com/upbound/upjet@main + go get github.com/crossplane/upjet@main go mod tidy ``` @@ -146,8 +152,8 @@ directory on your local machine. # NOTE(hasheddan): we ensure up is installed prior to running platform-specific # build steps in parallel to avoid encountering an installation race condition. ``` - - and run with: + + and run with: ```shell make run @@ -168,7 +174,7 @@ them by creating a managed resource with `managementPolicies: ["Observe"]`. forProvider: region: us-west-1 ``` - + You should see the managed resource is ready & synced: ```bash @@ -185,11 +191,12 @@ them by creating a managed resource with `managementPolicies: ["Observe"]`. > Please note: You would need the `terraform` executable installed on your local machine. 5. Create a managed resource without `LateInitialize` like -`managementPolicies: ["Observe", "Create", "Update", "Delete"]` with +`managementPolicies: ["Observe", "Create", "Update", "Delete"]` with `spec.initProvider` fields to see the provider create the resource with combining `spec.initProvider` and `spec.forProvider` fields: For example: + ```yaml apiVersion: dynamodb.aws.upbound.io/v1beta1 kind: Table @@ -238,9 +245,9 @@ combining `spec.initProvider` and `spec.forProvider` fields: ```bash kubectl get tables.dynamodb.aws.upbound.io example -o yaml ``` - + As the late initialization is skipped, the `spec.forProvider` should be the same when we created the resource. - + In the provider console, you should see that the resource was created with - the values in the `initProvider` field. \ No newline at end of file + the values in the `initProvider` field. diff --git a/docs/design-doc-provider-identity-based-auth.md b/docs/archive/design-doc-provider-identity-based-auth.md similarity index 93% rename from docs/design-doc-provider-identity-based-auth.md rename to docs/archive/design-doc-provider-identity-based-auth.md index 80c98f0f..8ee6ef80 100644 --- a/docs/design-doc-provider-identity-based-auth.md +++ b/docs/archive/design-doc-provider-identity-based-auth.md @@ -1,3 +1,9 @@ + + # Identity Based Authentication for Crossplane Providers * Owner: Alper Rifat Uluçınar (@ulucinar) @@ -5,6 +11,7 @@ * Status: Draft ## Background + Crossplane providers need to authenticate themselves to their respective Cloud providers. This establishes an identity for the Crossplane provider that's later used by the Cloud provider to authorize the requests made by the Crossplane @@ -19,7 +26,7 @@ any required credentials for that method, etc.) together with any other provider specific configuration. Different authentication methods and/or different sets of credentials can be configured using separate cluster-scoped `ProviderConfig` CRs and by having different managed resources refer to these `ProviderConfig` -instances. +instances. The Crossplane provider establishes an identity for the requests it will issue to the Cloud provider in the [managed.ExternalConnecter](https://pkg.go.dev/github.com/crossplane/crossplane-runtime@v0.19.2/pkg/reconciler/managed#ExternalConnecter)'s `Connect` @@ -43,6 +50,7 @@ ServiceAccount][k8s-sa] that the provider's pod uses) is allowed, via RBAC, to ` ServiceAccounts) are allowed to directly `create` managed resources, then we cannot constrain them from referring to any `ProviderConfig` (thus to any Cloud provider credential set) in the cluster solely using RBAC. This is because: + 1. RBAC rules allow designated verbs (`get`, `list`, `create`, `update`, etc.) on the specified API resources for the specified subjects. If a subject, e.g., a `ServiceAccount`, is allowed to `create` a managed resource, RBAC @@ -70,7 +78,8 @@ infrastructure operator's responsibility to manage the infrastructure _across_ the tenants via cluster-scoped Crossplane resources, and it's possible and desirable from an isolation perspective to disallow application operators, who are tenant subjects, to directly access these shared cluster-scoped resources. -This distinction is currently possible with Crossplane because: +This distinction is currently possible with Crossplane because: + 1. Crossplane `Claim` types are defined via cluster-scoped XRDs by infrastructure operators and _namespaced_ `Claim` instances are used by the tenant subjects. This allows infrastructure operators to define RBAC rules @@ -85,22 +94,22 @@ This distinction is currently possible with Crossplane because: Thus in our multi-tenancy [guide](https://docs.crossplane.io/knowledge-base/guides/multi-tenant/), we propose a security scheme where: 1. The infrastructure operator follows a specific naming convention for the `ProviderConfig`s she provisions: The `ProviderConfig`s for different - tenants are named after those tenants' namespaces. + tenants are named after those tenants' namespaces. 2. The infrastructure operator carefully designs `Composition`s that patch `spec.providerConfigRef` of composed resources using the `Claim`'s - namespace. + namespace. 3. Tenant subjects are **not** allowed to provision managed resources directly (and also XRDs or `Composition`s) but only `Claim`s in their namespaces. And any `Composition` they can select with their `Claim`s will compose resources that refer to a `ProviderConfig` provisioned for their tenant (the - `ProviderConfig` with the same name as the tenant's namespace). + `ProviderConfig` with the same name as the tenant's namespace). 4. We also suggest that the naming conventions imposed by this scheme on `ProviderConfig`s can be relaxed to some degree by using `Composition`'s [patching capabilities](https://docs.crossplane.io/v1.12/concepts/composition/#compositions). For instance, a string [transform][patch-transform] of type `Format` can be used to combine the `Claim`'s namespace with an XR field's value to allow multiple `ProviderConfig`s per tenant and to allow selection of the - `ProviderConfig` with the `Claim`. + `ProviderConfig` with the `Claim`. As explained above, RBAC rules can only impose restrictions on the actions (`get`, `update`, etc.) performed on the API resource endpoints but they cannot @@ -181,9 +190,8 @@ spec: toFieldPath: spec.providerConfigRef.name ``` - - ### Limitations of Naming Convention-based or Admission Controller-based Approaches + The naming convention-based or admission controller-based approaches described above are not straightforward to configure, especially if you also consider that in addition to the RBAC configurations needed to isolate the tenants @@ -192,13 +200,13 @@ policies are also needed to properly isolate and fairly distribute the worker node resources and the network resources, respectively. Also due to the associated complexity, it's easy to misconfigure the cluster and difficult to verify a given security configuration guarantees proper isolation between -the tenants. +the tenants. As an example, consider the Kyverno `ClusterPolicy` given above: While the intent is to restrict the users under `tenant1` to using only the `ProviderConfig`s installed for them (e.g., those with names `tenant1*`), the scheme is broken if there exists a tenant in the system with `tenant1` as a -prefix to its name, such as `tenant10`. +prefix to its name, such as `tenant10`. Organizations, especially with hard multi-tenancy requirements (i.e., with tenants assumed to be untrustworthy or actively malicious), may not prefer or @@ -208,11 +216,12 @@ components) is a shared resource itself and it requires cross-tenant privileges such as accessing cluster-wide resources and accessing each tenant's namespaced resources (especially tenant Cloud credentials). This increases the attack surface in the dimensions of: -- Logical vulnerabilities (see the above example for a misconfiguration) -- Isolation vulnerabilities: For instance, controller *workqueue*s become shared + +* Logical vulnerabilities (see the above example for a misconfiguration) +* Isolation vulnerabilities: For instance, controller *workqueue*s become shared resources between the tenants. How can we ensure, for instance, that the workqueue capacity is fairly shared between the tenants? -- Code vulnerabilities: As an example, consider a hypothetical Crossplane +* Code vulnerabilities: As an example, consider a hypothetical Crossplane provider bug in which the provider fetches another `ProviderConfig` than the one declared in the managed resource, or other credentials than the ones declared in the referred `ProviderConfig`. Although the logical barriers @@ -221,7 +230,7 @@ surface in the dimensions of: barrier. In the current Crossplane provider deployment model, when a Crossplane provider -package is installed, there can be a single *active* `ProviderRevision` +package is installed, there can be a single _active_ `ProviderRevision` associated with it, which owns (via an owner reference) the Kubernetes deployment for running the provider. This single deployment, in turn, specifies a single Kubernetes service account under which the provider runs. @@ -231,16 +240,17 @@ this architecture, which are related to identity-based authentication. **Note**: The [multi-tenancy guide](https://docs.crossplane.io/knowledge-base/guides/multi-tenant/) also mentions multi-cluster multi-tenancy, where tenants are run on their respective Kubernetes clusters. -This form of multi-tenancy is out of scope in this document. - +This form of multi-tenancy is out of scope in this document. + ### Identity-based Authentication Schemes + Various Cloud providers, such as AWS, Azure and GCP, have some means of identity-based authentication. With identity-based authentication an entity, such as a Cloud service (a database server, a Kubernetes cluster, etc.) or a workload (an executable running in a VM, a pod running in a Kubernetes cluster) is assigned a Cloud identity and further authorization checks are performed against this identity. The advantage with identity-based authentication is that -no manually provisioned credentials are required. +no manually provisioned credentials are required. The traditional way for authenticating a Crossplane provider to the Cloud provider is to first provision a Cloud identity such as an AWS IAM user or a GCP @@ -249,15 +259,16 @@ associated with that identity (such as an AWS access key or a GCP service account key or Azure client ID & secret) and then to provision a Kubernetes secret containing these credentials. Then a `ProviderConfig` refers to this Kubernetes secret. There are some undesirable consequences of this flow: -- The associated Cloud credentials are generally long-term credentials and + +* The associated Cloud credentials are generally long-term credentials and require manual rotation. -- For fine-grained access control, you need multiple identities with such - credentials to be manually managed & rotated. -- These generally result in reusing such credentials, which in turn prevents - fine-grained access control and promotes aggregation of privileges. +* For fine-grained access control, you need multiple identities with such + credentials to be manually managed & rotated. +* These generally result in reusing such credentials, which in turn prevents + fine-grained access control and promotes aggregation of privileges. Different Cloud providers have different identity-based authentication -implementations: +implementations: **AWS**: [EKS node IAM roles][aws-eks-node-iam], or IAM roles for service accounts ([IRSA]) both allow for identity-based authentication. IRSA has @@ -269,12 +280,12 @@ introduced with Kubernetes 1.12. When enabled, `kubelet` [projects][k8s-volume-projection] a signed OIDC JWT for a pod's service account at the requested volume mount path in a container and periodically rotates the token. An AWS client can then exchange this token (issued by the API server) -with *temporary* credentials for an IAM role via the AWS Security Token Service +with _temporary_ credentials for an IAM role via the AWS Security Token Service ([STS]) [AssumeRoleWithWebIdentity] API operation. The IAM role to be associated with the Kubernetes service account can be specified via an annotation on the service account (`eks.amazonaws.com/role-arn`). As we will discuss later, this can also be used in conjunction with IAM role chaining to implement fine-grained -access control. +access control. As of this writing, `provider-aws` [supports][provider-aws-auth] `IRSA`, role chaining (via the [STS] [AssumeRole] API operation), and the [STS @@ -286,27 +297,28 @@ key and a security token. Also the target IAM role ARN (Amazon Resource Name) is configurable via the `provider-aws`'s `ProviderConfig` API. This allows Crossplane users to implement a fine-grained access policy for different tenants possibly using different AWS accounts: -- The initial IAM role, which is the target IAM role for the `IRSA` + +* The initial IAM role, which is the target IAM role for the `IRSA` authentication (via the `AssumeRoleWithWebIdentity` STS API operation) does not need privileges on the managed external resources when role chaining is used. -- `provider-aws` then assumes another IAM role by exchanging the initial set of +* `provider-aws` then assumes another IAM role by exchanging the initial set of temporary credentials via STS role chaining. However, currently the `ProviderConfig` API does not allow chains of length greater than one, i.e., `provider-aws` can only call the STS `AssumeRole` API once in a given chain. This is currently an artificial limitation in `provider-aws` imposed by the - `ProviderConfig` API. -- The target role ARN for the initial IRSA `AssumeRoleWithWebIdentity` operation + `ProviderConfig` API. +* The target role ARN for the initial IRSA `AssumeRoleWithWebIdentity` operation is configurable via the `ProviderConfig` API. Thus, if a proper cross-AWS account trust policy exists between the EKS cluster's OIDC provider and a target IAM role in a different account (than the account owning the EKS cluster and the OIDC provider), then it's possible to switch to an IAM role in that target AWS account. -- Privileges on the managed external resources need to be defined on the target +* Privileges on the managed external resources need to be defined on the target IAM roles of the STS `Assume*` operations. And as mentioned, fine-grained access policies can be defined on these target roles which are configurable with the `ProviderConfig` API. -- When combined with the already available single-cluster multi-tenancy +* When combined with the already available single-cluster multi-tenancy techniques discussed above, this allows `provider-aws` users to isolate their tenant identities and the privileges required for those identities. @@ -348,22 +360,22 @@ identities][azure-wi], which work in a similar way to IRSA: In Azure AD workload identities, similar to IRSA, a Kubernetes service account is associated with an Azure AD application client ID via the -`azure.workload.identity/client-id` annotation on the service account object. +`azure.workload.identity/client-id` annotation on the service account object. As of this writing, none of `provider-azure` or `provider-jet-azure` supports Azure workload identities. Terraform native `azurerm` provider itself currently -does *not* support workload identities, thus there are technical challenges if +does _not_ support workload identities, thus there are technical challenges if we would like to introduce support for workload identities in `provider-jet-azure`. However, using lower level APIs (then the [Azure Identity SDK for Go][azidentity]), it should be possible to [implement][azure-329] -workload identities for `provider-azure`. +workload identities for `provider-azure`. Both `provider-azure` and `provider-jet-azure` support system-assigned and user-assigned managed identitites as an alternate form of identity-based authentication (with `provider-azure` support being introduced by this [PR][azure-330]). -Using system-assigned managed identities, it's *not* possible to implement an +Using system-assigned managed identities, it's _not_ possible to implement an isolation between tenants (see the discussion above for `provider-aws`) by using separate Azure AD (AAD) applications (service principals) for them, because the system-assigned managed identity is shared between those tenants and currently @@ -371,12 +383,11 @@ it's not possible to switch identities within the Crossplane Azure providers*. However, using user-assigned managed identities and per-tenant `ProviderConfig`s as discussed above in the context of single-cluster multi-tenancy, it's possible to implement fine-grained access control for tenants again with the same -limitations mentioned there. +limitations mentioned there. *: Whether there exists an Azure service (similar to the [STS] of AWS) that allows us to exchange credentials of an AAD application with (temporary) credentials of -another AAD application needs further investigation. - +another AAD application needs further investigation. **GCP**: GCP also [recommends][gcp-wi] workload identities for assigning identities to workloads running in GKE clusters. With GKE workload identities, a @@ -413,9 +424,10 @@ to have more strict tenant isolation, we need more flexibility in the Crossplane deployment model. ## Decoupling Crossplane Provider Deployment + Flexibility in Crossplane provider deployment has been discussed especially in [[2]] and [[3]]. [[2]] proposes a provider partitioning scheme on -`ProviderConfig`s and [[3]] calls for a *Provider Runtime Interface* for +`ProviderConfig`s and [[3]] calls for a _Provider Runtime Interface_ for decoupling the runtime aspects of a provider (where & how a provider is deployed & run) from the core Crossplane package manager. We can combine these two approaches to have an extensible, flexible and future-proof deployment model for @@ -512,7 +524,7 @@ As an alternative, in order to deprecate the `ControllerConfig` API, the ``` This scheme makes the runtime implementation pluggable, i.e., in different -environments we can have different *provider runtime configuration* contollers +environments we can have different _provider runtime configuration_ contollers running (as Kubernetes controllers) with different capabilities. For instance, the existing deployment implementation embedded into the `PackageRevision` controller can still be shipped with the core Crossplane with a corresponding @@ -520,22 +532,15 @@ runtime configuration object. But another runtime configuration controller, which is also based on Kubernetes deployments, can implement advanced isolation semantics. - [1]: https://azure.github.io/azure-workload-identity/docs/introduction.html [2]: https://github.com/crossplane/crossplane/issues/2411 [3]: https://github.com/crossplane/crossplane/issues/2671 - -[v1.Reference]: TODO -[managed.ExternalConnecter]: TODO [aws-sdk]: https://github.com/aws/aws-sdk-go-v2 [azure-sdk]: https://github.com/Azure/azure-sdk-for-go [RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ [k8s-sa]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ -[xp-mt]: https://docs.crossplane.io/knowledge-base/guides/multi-tenant/ -[xp-2093]: https://github.com/crossplane/crossplane/pull/2093 -[ref-compositions]: https://docs.crossplane.io/v1.12/concepts/composition/#compositions [patch-transform]: https://github.com/crossplane/crossplane/blob/6c1b06507db47801c7a1c7d91704783e8d13856f/apis/apiextensions/v1/composition_transforms.go#L64 [kyverno]: https://kyverno.io/ @@ -567,5 +572,3 @@ semantics. [azidentity]: https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity [azure-329]: https://github.com/crossplane/provider-azure/issues/329 [azure-330]: https://github.com/crossplane/provider-azure/pull/330 - -[hnc]: https://github.com/kubernetes-sigs/hierarchical-namespaces \ No newline at end of file diff --git a/docs/manual-migration-guide-to-op.md b/docs/archive/manual-migration-guide-to-op.md similarity index 91% rename from docs/manual-migration-guide-to-op.md rename to docs/archive/manual-migration-guide-to-op.md index 67afc39f..a8fa9645 100644 --- a/docs/manual-migration-guide-to-op.md +++ b/docs/archive/manual-migration-guide-to-op.md @@ -1,10 +1,16 @@ + + ## Manual Migration Guide to Official Providers This document describes the steps that need to be applied to migrate from community providers to official providers manually. We plan to implement a -client-based tool to automate this process. +client-based tool to automate this process. -For the sake of simplicity, we only focus on migrating managed resources +For the sake of simplicity, we only focus on migrating managed resources and compositions in this guide. These scenarios can be extended with other tools like ArgoCD, Flux, Helm, Kustomize, etc. @@ -17,59 +23,77 @@ providers and apply those manifests to import existing cloud resources. To prevent a conflict between two provider controllers reconciling for the same external resource, we're scaling down the old provider. This can also be eliminated with the new [pause annotation feature]. - 1) Backup managed resource manifests + ```bash kubectl get managed -o yaml > backup-mrs.yaml ``` + 2) Update deletion policy to `Orphan` with the command below: + ```bash kubectl patch $(kubectl get managed -o name) -p '{"spec": {"deletionPolicy":"Orphan"}}' --type=merge ``` + 3) Install the official provider 4) Install provider config 5) Update managed resource manifests to the new API version `upbound.io`, external-name annotations and new field names/types. You can use [Upbound Marketplace] for comparing CRD schema changes. It is also planned to extend current documentation with external-name syntax in this [issue]. + ```bash cp backup-mrs.yaml op-mrs.yaml vi op-mrs.yaml ``` + 6) Scale down Crossplane deployment + ```bash kubectl scale deploy crossplane --replicas=0 ``` + 7) Scale down native provider deployment + ```bash kubectl scale deploy ${deployment_name} --replicas=0 ``` + 8) Apply updated managed resources and wait until they become ready + ```bash kubectl apply -f op-mrs.yaml ``` + 9) Delete old MRs + ```bash kubectl delete -f backup-mrs.yaml kubectl patch -f backup-mrs.yaml -p '{"metadata":{"finalizers":[]}}' --type=merge ``` + 10) Delete old provider config + ```bash kubectl delete providerconfigs ${provider_config_name} ``` + 11) Delete old provider + ```bash kubectl delete providers ${provider_name} ``` + 12) Scale up Crossplane deployment + ```bash kubectl scale deploy crossplane --replicas=1 -``` +``` #### Migrating VPC Managed Resource -In below, we display the required changes to migrate a native provider-aws VPC resource to an official -provider-aws VPC. As you can see, we have updated the API version and some field names/types in spec +In below, we display the required changes to migrate a native provider-aws VPC resource to an official +provider-aws VPC. As you can see, we have updated the API version and some field names/types in spec and status subresources. To find out which fields to update, we need to compare the CRDs in the current -provider version and the target official provider version. +provider version and the target official provider version. ```diff - apiVersion: ec2.aws.crossplane.io/v1beta1 @@ -118,27 +142,31 @@ provider version and the target official provider version. name: default ``` - ### Migrating Crossplane Configurations -Configuration migration can be more challenging. Because, in addition to managed resource migration, we need to +Configuration migration can be more challenging. Because, in addition to managed resource migration, we need to update our composition and claim files to match the new CRDs. Just like managed resource migration, we first start to import -our existing resources to official provider and then update our configuration package version to point to the -official provider. - +our existing resources to official provider and then update our configuration package version to point to the +official provider. 1) Backup managed resource manifests + ```bash kubectl get managed -o yaml > backup-mrs.yaml ``` + 2) Scale down Crossplane deployment + ```bash kubectl scale deploy crossplane --replicas=0 ``` + 3) Update deletion policy to `Orphan` with the command below: + ```bash kubectl patch $(kubectl get managed -o name) -p '{"spec": {"deletionPolicy":"Orphan"}}' --type=merge ``` + 4) Update composition files to the new API version `upbound.io`, external-name annotations and new field names/types. You can use [Upbound Marketplace] for comparing CRD schema changes. It is also planned to extend current documentation with external-name syntax in this [issue]. 5) Update `crossplane.yaml` file with official provider dependency. @@ -146,24 +174,33 @@ kubectl patch $(kubectl get managed -o name) -p '{"spec": {"deletionPolicy":"Orp 7) Install Official Provider 8) Install provider config 9) Update managed resource manifests with the same changes done on composition files + ```bash cp backup-mrs.yaml op-mrs.yaml vi op-mrs.yaml ``` + 10) Scale down native provider deployment + ```bash kubectl scale deploy ${deployment_name} --replicas=0 ``` + 11) Apply updated managed resources and wait until they become ready + ```bash kubectl apply -f op-mrs.yaml ``` + 12) Delete old MRs + ```bash kubectl delete -f backup-mrs.yaml kubectl patch -f backup-mrs.yaml -p '{"metadata":{"finalizers":[]}}' --type=merge ``` + 13) Update the configuration to the new version + ```bash cat < +- platform-ref-azure: +- platform-ref-gcp: [pause annotation feature]: https://github.com/upbound/product/issues/227 [Upbound Marketplace]: https://marketplace.upbound.io/ -[issue]: https://github.com/upbound/official-providers/issues/792 \ No newline at end of file +[issue]: https://github.com/upbound/official-providers/issues/792 diff --git a/docs/migrating-from-terrajet-to-upjet.md b/docs/archive/migrating-from-terrajet-to-upjet.md similarity index 67% rename from docs/migrating-from-terrajet-to-upjet.md rename to docs/archive/migrating-from-terrajet-to-upjet.md index c3c64b81..cbdabab0 100644 --- a/docs/migrating-from-terrajet-to-upjet.md +++ b/docs/archive/migrating-from-terrajet-to-upjet.md @@ -1,15 +1,21 @@ + + ## Migrating from Terrajet to Upjet As [Terrajet] is being [deprecated][terrrajet-deprecation] in favor of [Upjet], new Crossplane providers are expected to be generated using Upjet's [provider -template repository](https://github.com/upbound/upjet-provider-template), and +template repository](https://github.com/crossplane/upjet-provider-template), and this guide outlines the steps needed to convert an existing Terrajet-based Crossplane provider to an Upjet-based one. While this guide describes the steps needed to update the `github.com/crossplane/terrajet` Go module -dependency with the `github.com/upbound/upjet` module, especially if you +dependency with the `github.com/crossplane/upjet` module, especially if you have not customized your provider's CI pipelines and architecture, and -you want to utilize all the new features offered by Upjet -(such as documentation/example manifest generation), +you want to utilize all the new features offered by Upjet +(such as documentation/example manifest generation), you may also prefer to [bootstrap a new provider] from the [Upjet template repository] and copy your existing resource configurations under the `config` folder of your existing provider to the new provider. @@ -20,95 +26,108 @@ migration process itself. ### Overview of Terrajet-Upjet Migration Steps 1. Replace the `github.com/crossplane/terrajet` Go module dependency with the -latest release of `github.com/upbound/upjet`. You may run the following in the +latest release of `github.com/crossplane/upjet`. You may run the following in the provider repo's root (where the `go.mod` file lives) + ```shell go mod edit -droprequire github.com/crossplane/terrajet -go get github.com/upbound/upjet +go get github.com/crossplane/upjet ``` 2. Some code that will be generated by Upjet requires go1.18 or later. If your provider's Go module declares go1.17 or smaller, you will need to update your module's Go version, and run a `go mod tidy` to make sure that `go.mod` matches the source code in the provider's module: + ```shell go mod edit -go=1.19 go mod tidy ``` -3. Replace all occurrences of `crossplane/terrajet` with `upbound/upjet` in the +3. Replace all occurrences of `crossplane/terrajet` with `crossplane/upjet` in the code base. A sample command (to be run from the repo root) could be as follows, or you may use any other tool at your convenience: + ```shell -find . -type f -not \( -path './.git/*' -or -path './.cache/*' -or -path './.work/*' \) -exec sed -i '' 's|crossplane/terrajet|upbound/upjet|g' {} \; +find . -type f -not \( -path './.git/*' -or -path './.cache/*' -or -path './.work/*' \) -exec sed -i '' 's|crossplane/terrajet|crossplane/upjet|g' {} \; ``` 4. Now, you may attempt to run Upjet's code generation pipelines: + ```shell make generate ``` + At this step, you will most probably get some compile errors. Here are some potential changes that you need to make, assuming the provider was bootstrapped using Terrajet's now deprecated [provider template repository](https://github.com/crossplane-contrib/provider-jet-template): + - In `config/provider.go`: [pkg/config.NewProviderWithSchema](https://github.com/crossplane/terrajet/blob/8d0ed485f9511b65a8f3a83801092bcae60678dd/pkg/config/provider.go#L152) has been removed from Upjet. You may instead use -[pkg/config.NewProvider](https://github.com/upbound/upjet/blob/c82119f5ef342f752406a0ed38264940b02e795f/pkg/config/provider.go#L172). +[pkg/config.NewProvider](https://github.com/crossplane/upjet/blob/c82119f5ef342f752406a0ed38264940b02e795f/pkg/config/provider.go#L172). For an example invocation, please see -[here](https://github.com/upbound/upjet-provider-template/blob/d34119409586f6205ec8ed4b9b2c2481c74bf07e/config/provider.go#L29). +[here](https://github.com/crossplane/upjet-provider-template/blob/d34119409586f6205ec8ed4b9b2c2481c74bf07e/config/provider.go#L29). - In `config/provider.go`: [pkg/config.WithDefaultResourceFn](https://github.com/crossplane/terrajet/blob/8d0ed485f9511b65a8f3a83801092bcae60678dd/pkg/config/provider.go#L143) has been removed from Upjet. You may instead use -[pkg/config.WithDefaultResourceOptions](https://github.com/upbound/upjet/blob/c82119f5ef342f752406a0ed38264940b02e795f/pkg/config/provider.go#L154). +[pkg/config.WithDefaultResourceOptions](https://github.com/crossplane/upjet/blob/c82119f5ef342f752406a0ed38264940b02e795f/pkg/config/provider.go#L154). For an example invocation, please see -[here](https://github.com/upbound/upjet-provider-template/blob/d34119409586f6205ec8ed4b9b2c2481c74bf07e/config/provider.go#L31). +[here](https://github.com/crossplane/upjet-provider-template/blob/d34119409586f6205ec8ed4b9b2c2481c74bf07e/config/provider.go#L31). - In `config/provider.go`: If your provider does not have "base" API packages (like the `ProviderConfig` packages) at both `v1alpha1` and `v1beta1` versions, then you may need to override the provider's [`BasePackages` -configuration](https://github.com/upbound/upjet/blob/7e84c638a8bc5c93c6da3cf9420f961f165dd05d/pkg/config/provider.go#L76) +configuration](https://github.com/crossplane/upjet/blob/7e84c638a8bc5c93c6da3cf9420f961f165dd05d/pkg/config/provider.go#L76) configuration using -[pkg/config.WithBasePackages](https://github.com/upbound/upjet/blob/7e84c638a8bc5c93c6da3cf9420f961f165dd05d/pkg/config/provider.go#L146). +[pkg/config.WithBasePackages](https://github.com/crossplane/upjet/blob/7e84c638a8bc5c93c6da3cf9420f961f165dd05d/pkg/config/provider.go#L146). This is because Upjet -[assumes](https://github.com/upbound/upjet/blob/7e84c638a8bc5c93c6da3cf9420f961f165dd05d/pkg/config/common.go#L18) +[assumes](https://github.com/crossplane/upjet/blob/7e84c638a8bc5c93c6da3cf9420f961f165dd05d/pkg/config/common.go#L18) both `apis/v1alpha1` `apis/v1beta1` base API packages and the `internal/controller/providerconfig` controller package exist for your provider. If, for example, only the `apis/v1alpha1` base API package is available for your provider, then you may configure your provider with: + ```go config.WithBasePackages(config.BasePackages{ - APIVersion: []string{ - // Default package for ProviderConfig APIs - "apis/v1alpha1", - }, - Controller: []string{ - // Default package for ProviderConfig controllers - "internal/controller/providerconfig", - }, - }) + APIVersion: []string{ + // Default package for ProviderConfig APIs + "apis/v1alpha1", + }, + Controller: []string{ + // Default package for ProviderConfig controllers + "internal/controller/providerconfig", + }, + }) ``` + as a -[config.ProviderOption](https://github.com/upbound/upjet/blob/7e84c638a8bc5c93c6da3cf9420f961f165dd05d/pkg/config/provider.go#L115) +[config.ProviderOption](https://github.com/crossplane/upjet/blob/7e84c638a8bc5c93c6da3cf9420f961f165dd05d/pkg/config/provider.go#L115) passed to -[pkg/config.NewProvider](https://github.com/upbound/upjet/blob/c82119f5ef342f752406a0ed38264940b02e795f/pkg/config/provider.go#L172). +[pkg/config.NewProvider](https://github.com/crossplane/upjet/blob/c82119f5ef342f752406a0ed38264940b02e795f/pkg/config/provider.go#L172). 5. When you try to build the provider package, you may also encounter some compilation errors regarding managed resource configurations: + - In various `apis///zz_generated.resolvers.go`: You may observe errors referring to undeclared `spec` struct fields with names containing acronyms such as: + ```shell apis/ec2/v1alpha2/zz_generated.resolvers.go:195:38: mg.Spec.ForProvider.SecurityGroupIdRefs undefined (type InstanceParameters has no field or method SecurityGroupIdRefs) ``` + You will also need to fix the [reference configurations] for such managed resources using the [resource configuration API]. 6. You will need to make changes in your `Makefile` to get your make targets working again: + - You need to update the declared Go version with the `GO_REQUIRED_VERSION` make variable. - You need to update the declared Go linter version with the `GOLANGCILINT_VERSION` make variable. + ```shell GO_REQUIRED_VERSION ?= 1.19 GOLANGCILINT_VERSION ?= 1.50.0 @@ -130,20 +149,22 @@ CI pipelines especially if you've updated your provider module's Go version. with your provider's managed resources. Having successfully migrated from the `github.com/crossplane/terrajet` -Go module to the `github.com/upbound/upjet` module, you may now consider +Go module to the `github.com/crossplane/upjet` module, you may now consider enabling some new and advanced features only available in Upjet: 9. You may consider enabling metadata extraction from the [Terraform registry]. In order to enable metadata extraction for the provider, you will need to run Upjet's metadata scraper. This can be achieved by including a Go generate comment in your provider's `apis/generate.go` with something similar to: + ```go -//go:generate go run github.com/upbound/upjet/cmd/scraper -n ${TERRAFORM_PROVIDER_SOURCE} -r ../.work/${TERRAFORM_PROVIDER_SOURCE}/${TERRAFORM_DOCS_PATH} -o ../config/provider-metadata.yaml +//go:generate go run github.com/crossplane/upjet/cmd/scraper -n ${TERRAFORM_PROVIDER_SOURCE} -r ../.work/${TERRAFORM_PROVIDER_SOURCE}/${TERRAFORM_DOCS_PATH} -o ../config/provider-metadata.yaml ``` + This generate directive depends on some Makefile changes like adding the `pull-docs` target and declaring certain Makefile variables. For details, -please refer to Upjet's provider template [Makefile](https://github.com/upbound/upjet-provider-template/blob/main/Makefile) -and [apis/generate.go](https://github.com/upbound/upjet-provider-template/blob/main/apis/generate.go). +please refer to Upjet's provider template [Makefile](https://github.com/crossplane/upjet-provider-template/blob/main/Makefile) +and [apis/generate.go](https://github.com/crossplane/upjet-provider-template/blob/main/apis/generate.go). Now, when you run a `make generate`, you should find the extracted metadata at `config/provider-metadata.yaml`. @@ -152,29 +173,33 @@ Now, when you run a `make generate`, you should find the extracted metadata at to generate CRD documentation and to generate example CR manifests. In order to do this, you will need to configure your provider with the scraped metadata as follows: + - In `config/provider.go`: You need to load the provider's metadata from the `config/provider-metadata.yaml` file. You can easily do so with the `go:embed` directive: + ```go //go:embed provider-metadata.yaml var providerMetadata []byte ``` + - In `config/provider.go`: You need to make the provider metadata available to the provider configuration. This is done by passing the metadata bytes (stored in the variable `providerMetadata` above) to the `config.NewProvider` call. For an example, please refer to the template -repo's [config/provider.go](https://github.com/upbound/upjet-provider-template/blob/d34119409586f6205ec8ed4b9b2c2481c74bf07e/config/provider.go#L29). +repo's [config/provider.go](https://github.com/crossplane/upjet-provider-template/blob/d34119409586f6205ec8ed4b9b2c2481c74bf07e/config/provider.go#L29). Now, when you run `make generate`, the generated CRDs for your provider should have documentation scraped from the Terraform registry. And you should have some example manifests generated for your managed resources under the path `examples-generated` in the repo root. -Please also take a look at the template repo's [apis/generate.go](https://github.com/upbound/upjet-provider-template/blob/main/apis/generate.go) +Please also take a look at the template repo's [apis/generate.go](https://github.com/crossplane/upjet-provider-template/blob/main/apis/generate.go) for some other `go:generate` directives for cleaning up these newly generated artifacts. 11. You may also want to update your provider's `build` submodule to the latest version by running a: + ```shell git submodule update --remote --merge ``` @@ -187,18 +212,17 @@ if you would like to integrate the single image building process, please adapt your provider package's Dockerfile (`cluster/images//Dockerfile`) and Makefile (`cluster/images//Makefile`) -to the template repo's [associated files](https://github.com/upbound/upjet-provider-template/tree/main/cluster/images/upjet-provider-template). +to the template repo's [associated files](https://github.com/crossplane/upjet-provider-template/tree/main/cluster/images/upjet-provider-template). You will also need to adapt your provider's Makefile to match -the template repo's [Makefile](https://github.com/upbound/upjet-provider-template/blob/main/Makefile). - +the template repo's [Makefile](https://github.com/crossplane/upjet-provider-template/blob/main/Makefile). -[Upjet]: https://github.com/upbound/upjet +[Upjet]: https://github.com/crossplane/upjet [Terrajet]: https://github.com/crossplane/terrajet [terrrajet-deprecation]: https://github.com/crossplane/terrajet/issues/308 [resource configuration API]: - https://github.com/upbound/upjet/blob/7e84c638a8bc5c93c6da3cf9420f961f165dd05d/pkg/config/resource.go#L258 -[reference configurations]: https://github.com/upbound/upjet/blob/c82119f5ef342f752406a0ed38264940b02e795f/pkg/config/resource.go#L293 -[Upjet template repository]: https://github.com/upbound/upjet-provider-template -[Makefile]: https://github.com/upbound/upjet-provider-template/blob/d34119409586f6205ec8ed4b9b2c2481c74bf07e/Makefile#L41 + https://github.com/crossplane/upjet/blob/7e84c638a8bc5c93c6da3cf9420f961f165dd05d/pkg/config/resource.go#L258 +[reference configurations]: https://github.com/crossplane/upjet/blob/c82119f5ef342f752406a0ed38264940b02e795f/pkg/config/resource.go#L293 +[Upjet template repository]: https://github.com/crossplane/upjet-provider-template +[Makefile]: https://github.com/crossplane/upjet-provider-template/blob/d34119409586f6205ec8ed4b9b2c2481c74bf07e/Makefile#L41 [Terraform registry]: https://registry.terraform.io/ -[bootstrap a new provider]: https://github.com/upbound/upjet/blob/main/docs/generating-a-provider.md +[bootstrap a new provider]: https://github.com/crossplane/upjet/blob/main/docs/generating-a-provider.md diff --git a/docs/moving-resources-to-v1beta1.md b/docs/archive/moving-resources-to-v1beta1.md similarity index 89% rename from docs/moving-resources-to-v1beta1.md rename to docs/archive/moving-resources-to-v1beta1.md index 85822815..00a892c3 100644 --- a/docs/moving-resources-to-v1beta1.md +++ b/docs/archive/moving-resources-to-v1beta1.md @@ -1,3 +1,9 @@ + + ## Moving Untested Resources to v1beta1 For outside contributors, we wanted to form a baseline for resource test @@ -8,7 +14,7 @@ running `make generate`/`make reviewable` commands. For the generation of this resource’s schema and controller, we need to add it to the `ExternalNameConfigs` map. After this addition, this resource’s schema and -the controller will be started to generate. By default, every resource that was +the controller will be started to generate. By default, every resource that was added to this map will be generated in the `v1beta1` version. Here there are two important points. For starting to test efforts, you need a diff --git a/docs/reference-generation.md b/docs/archive/reference-generation.md similarity index 91% rename from docs/reference-generation.md rename to docs/archive/reference-generation.md index 1e420891..be402fb2 100644 --- a/docs/reference-generation.md +++ b/docs/archive/reference-generation.md @@ -1,11 +1,17 @@ + + ## Auto Cross Resource Reference Generation -Cross Resource Referencing is one of the key concepts of the resource -configuration. As a very common case, cloud services depend on other cloud -services. For example, AWS Subnet resource needs an AWS VPC for creation. So, -for creating a Subnet successfully, before you have to create a VPC resource. -Please see the [Dependencies] documentation for more details. And also, for -resource configuration-related parts of cross-resource referencing, please see +Cross Resource Referencing is one of the key concepts of the resource +configuration. As a very common case, cloud services depend on other cloud +services. For example, AWS Subnet resource needs an AWS VPC for creation. So, +for creating a Subnet successfully, before you have to create a VPC resource. +Please see the [Dependencies] documentation for more details. And also, for +resource configuration-related parts of cross-resource referencing, please see [this part] of [Configuring a Resource] documentation. These documentations focus on the general concepts and manual configurations @@ -14,6 +20,7 @@ automatic example&reference generation. Upjet has a scraper tool for scraping provider metadata from the Terraform Registry. The scraped metadata are: + - Resource Descriptions - Examples of Resources (in HCL format) - Field Documentations @@ -21,7 +28,7 @@ Registry. The scraped metadata are: These are very critical information for our automation processes. We use this scraped metadata in many contexts. For example, field documentation of -resources and descriptions are used as Golang comments for schema fields and +resources and descriptions are used as Golang comments for schema fields and CRDs. Another important scraped information is examples of resources. As a part @@ -96,15 +103,15 @@ Here, there are three very important points that scraper makes easy our life: - We do not have to find the correct value combinations for fields. So, we can easily use the generated example manifest in our tests. -- The HCL example was scraped from registry documentation of the `aws_ebs_snapshot` - resource. In the example, you also see the `aws_ebs_volume` resource manifest - because, for the creation of an EBS Snapshot, you need an EBS Volume resource. - Thanks to the source Registry, (in many cases, there are the dependent resources +- The HCL example was scraped from registry documentation of the `aws_ebs_snapshot` + resource. In the example, you also see the `aws_ebs_volume` resource manifest + because, for the creation of an EBS Snapshot, you need an EBS Volume resource. + Thanks to the source Registry, (in many cases, there are the dependent resources of target resources) we can also scrape the dependencies of target resources. - The last item is actually what is intended to be explained in this document. For using the Cross Resource References, as I mentioned above, you need to add some references to the resource configuration. But, in many cases, if in the - scraped example, the mentioned dependencies are already described you do not + scraped example, the mentioned dependencies are already described you do not have to write explicit references to resource configuration. The Cross Resource Reference generator generates the mentioned references. @@ -114,10 +121,10 @@ As I mentioned, many references are generated from scraped metadata by an auto reference generator. However, there are two cases where we miss generating the references. -The first one is related to some bugs or improvement points in the generator. -This means that the generator can handle many references in the scraped -examples and generate correctly them. But we cannot say that the ratio is %100. -For some cases, the generator cannot generate references although, they are in +The first one is related to some bugs or improvement points in the generator. +This means that the generator can handle many references in the scraped +examples and generate correctly them. But we cannot say that the ratio is %100. +For some cases, the generator cannot generate references although, they are in the scraped example manifests. The second one is related to the scraped example itself. As I mentioned above, @@ -167,10 +174,10 @@ or another resource record set in this hosted zone. ### Conclusion -As a result, mentioned scraper and example&reference generators are very useful +As a result, mentioned scraper and example&reference generators are very useful for making easy the test efforts. But using these functionalities, we must be careful to avoid undesired states. [Dependencies]: https://crossplane.io/docs/v1.7/concepts/managed-resources.html#dependencies -[this part]: https://github.com/upbound/upjet/blob/main/docs/configuring-a-resource.md#cross-resource-referencing -[Configuring a Resource]: https://github.com/upbound/upjet/blob/main/docs/configuring-a-resource.md +[this part]: https://github.com/crossplane/upjet/blob/main/docs/configuring-a-resource.md#cross-resource-referencing +[Configuring a Resource]: https://github.com/crossplane/upjet/blob/main/docs/configuring-a-resource.md diff --git a/docs/testing-instructions.md b/docs/archive/testing-instructions.md similarity index 84% rename from docs/testing-instructions.md rename to docs/archive/testing-instructions.md index 34199542..d2c23945 100644 --- a/docs/testing-instructions.md +++ b/docs/archive/testing-instructions.md @@ -1,12 +1,18 @@ + + # Testing Instructions and Known Error Cases While configuring resources, the testing effort is the longest part. Because the characteristics of cloud providers and services can change. This test effort can -be executed in two main methods. The first one is testing the resources in a -manual way and the second one is using the `Uptest` that is an automated test -tool for Official Providers. `Uptest` provides a framework to test resources in -an end-to-end pipeline during the resource configuration process. Together with -the example manifest generation tool, it allows us to avoid manual interventions +be executed in two main methods. The first one is testing the resources in a +manual way and the second one is using the `Uptest` that is an automated test +tool for Official Providers. `Uptest` provides a framework to test resources in +an end-to-end pipeline during the resource configuration process. Together with +the example manifest generation tool, it allows us to avoid manual interventions and shortens testing processes. ## Testing Methods @@ -15,21 +21,21 @@ and shortens testing processes. Configured resources can be tested by using manual method. This method generally contains the environment preparation and creating the example manifest in the -Kubernetes cluster steps. The following steps can be followed for preparing the +Kubernetes cluster steps. The following steps can be followed for preparing the environment: -1. Obtaining a Kubernetes Cluster: For manual/local effort, generally a Kind -cluster is sufficient and can be used. For detailed information about Kind see +1. Obtaining a Kubernetes Cluster: For manual/local effort, generally a Kind +cluster is sufficient and can be used. For detailed information about Kind see [this repo]. An alternative way to obtain a cluster is: [k3d] -2. Registering the CRDs (Custom Resource Definitions) to Cluster: We need to -apply the CRD manifests to the cluster. The relevant manifests are located in +2. Registering the CRDs (Custom Resource Definitions) to Cluster: We need to +apply the CRD manifests to the cluster. The relevant manifests are located in the `package/crds` folder of provider subdirectories such as: -`provider-aws/package/crds`. For registering them please run the following +`provider-aws/package/crds`. For registering them please run the following command: `kubectl apply -f package/crds` -3. Create ProviderConfig: ProviderConfig Custom Resource contains some +3. Create ProviderConfig: ProviderConfig Custom Resource contains some configurations and credentials for the provider. For example, to connect to the cloud provider, we use the credentials field of ProviderConfig. For creating the ProviderConfig with correct credentials, please see [the documentation]: @@ -39,13 +45,13 @@ controllers are part of the provider. So, for starting the reconciliations for Custom Resources, we need to run the provider (collect of controllers). For running provider, two ways can be used: - `make run`: This make target starts the controllers. - - Running provider in IDE: Especially for debug effort, you may want to use - an IDE. For running the provider in an IDE, some program arguments are + - Running provider in IDE: Especially for debug effort, you may want to use + an IDE. For running the provider in an IDE, some program arguments are needed to be passed. The following example is for `provider-aws`. - Values of the `--terraform-version`, `--terraform-provider-source` and + Values of the `--terraform-version`, `--terraform-provider-source` and `--terraform-provider-version` options can be collected from the Makefile of the provider: `provider-aws/Makefile` - - `-d` -> To see debug level logs. `make run` also is run the provider in + - `-d` -> To see debug level logs. `make run` also is run the provider in debug mode. - `--terraform-version 1.2.1`: Terraform version. - `--terraform-provider-source hashicorp/aws`: Provider source name. @@ -53,39 +59,40 @@ running provider, two ways can be used: Now our preparation steps are completed. This is the time for testing: -- Create Examples and Start Testing: After completing the steps above, your -environment is ready to testing. For testing, we need to apply some example -manifests to the cluster. The manifests in the `examples-generated` folder can be -used as a first step. Before starting to change these manifests, you should move -them from `examples-generated` folder to the `examples` folder. There are two -main reasons for this. The first one is that these manifests are generated for -every `make generate` command to catch the latest changes in the resources. So -for preserving your changes moving them is necessary. The second reason is that +- Create Examples and Start Testing: After completing the steps above, your +environment is ready to testing. For testing, we need to apply some example +manifests to the cluster. The manifests in the `examples-generated` folder can be +used as a first step. Before starting to change these manifests, you should move +them from `examples-generated` folder to the `examples` folder. There are two +main reasons for this. The first one is that these manifests are generated for +every `make generate` command to catch the latest changes in the resources. So +for preserving your changes moving them is necessary. The second reason is that we use the `examples` folder as the source for keeping these manifests and using them in our automated test effort. In some cases, these manifests need manual interventions so, for successfully -applying them to a cluster (passing the Kubernetes schema validation) you may +applying them to a cluster (passing the Kubernetes schema validation) you may need to do some work. Possible problems you might face: - - The generated manifest cannot provide at least one required field. So + +- The generated manifest cannot provide at least one required field. So before creating the resource you must set the required field in the manifest. - - In some fields of generated manifest the types of values cannot be matched. - For example, X field expects a string but the manifest provides an integer. - In these cases you need to provide the correct type in your example YAML +- In some fields of generated manifest the types of values cannot be matched. + For example, X field expects a string but the manifest provides an integer. + In these cases you need to provide the correct type in your example YAML manifest. -Successfully applying these example manifests to cluster is only the -first step. After successfully creating these Managed Resources, we need to -check whether their statuses are ready or not. So we need to expect a `True` -value for `Synced` and `Ready` conditions. To check the statuses of all created -example manifests quickly you can run the `kubectl get managed` command. We will +Successfully applying these example manifests to cluster is only the +first step. After successfully creating these Managed Resources, we need to +check whether their statuses are ready or not. So we need to expect a `True` +value for `Synced` and `Ready` conditions. To check the statuses of all created +example manifests quickly you can run the `kubectl get managed` command. We will wait for all values to be `True` in this list: ![img.png](images/managed-all.png) -When all of the `Synced` and `Ready` fields are `True`, the test was -successfully completed! However, if there are some resource values that are -`False`, you need to debug this situation. The main debugging ways will be +When all of the `Synced` and `Ready` fields are `True`, the test was +successfully completed! However, if there are some resource values that are +`False`, you need to debug this situation. The main debugging ways will be mentioned in the next parts. ``` @@ -96,7 +103,7 @@ cannot see the mentioned condition. Uptest adds this annotation to the tested resources, but if you want to see the value of conditions in your tests in your local environment (during manual tests) you need to add this condition manually. For the goal and details of this status condition please see this PR: -https://github.com/upbound/upjet/pull/23 +https://github.com/crossplane/upjet/pull/23 ``` ``` @@ -106,7 +113,7 @@ X. Many of the generated examples include these dependencies. However, in some cases, there may be missing dependencies. In these cases, please add the relevant dependencies to your example manifest. This is important both for you to pass the tests and to provide the correct manifests. -``` +``` ### Automated Tests - Uptest @@ -116,19 +123,19 @@ this part into two main application methods: #### Using Uptest in GitHub Actions We have a GitHub workflow `Automated Tests`. This is an integration test for -Official Providers. This workflow prepares the environment (provisioning Kind -cluster, creating ProviderConfig, installing Provider, etc.) and runs the Uptest -with the input manifest list that will be given by the person who triggers the +Official Providers. This workflow prepares the environment (provisioning Kind +cluster, creating ProviderConfig, installing Provider, etc.) and runs the Uptest +with the input manifest list that will be given by the person who triggers the test. -This `Automated Tests` job can be triggered from the PR that contains the -configuration test works for the related resources/groups. For triggering the +This `Automated Tests` job can be triggered from the PR that contains the +configuration test works for the related resources/groups. For triggering the test, you need to leave a comment in the PR in the following format: `/test-examples="provider-aws/examples/s3/bucket.yaml, provider-aws/examples/eks/cluster.yaml"` -We test using the API group approach for `Automated-Tests`. So, we wait for the -entire API group's resources to pass the test in a single test run. This means +We test using the API group approach for `Automated-Tests`. So, we wait for the +entire API group's resources to pass the test in a single test run. This means that while triggering tests, leaving the following type of comment is expected: `/test-examples="provider-aws/examples/s3` @@ -137,9 +144,9 @@ This comment will test all the examples of the `s3` group. **Ignoring Some Resources in Automated Tests** -Some resources require manual intervention such as providing valid public keys -or using on-the-fly values. These cases can be handled in manual tests, but in -cases where we cannot provide generic values for automated tests, we can skip +Some resources require manual intervention such as providing valid public keys +or using on-the-fly values. These cases can be handled in manual tests, but in +cases where we cannot provide generic values for automated tests, we can skip some resources in the tests of the relevant group via an annotation: ```yaml @@ -162,103 +169,105 @@ details please see [here]. #### Using Uptest in Local Dev Environment -The main difference between running `Uptest` from your local environment and -running GitHub Actions is that the environment is also prepared during GitHub -Actions. During your tests on local, `Uptest` is only responsible for creating -instance manifests and assertions of them. Therefore, all the preparation steps -mentioned in the Manual Testing section are also necessary for tests performed +The main difference between running `Uptest` from your local environment and +running GitHub Actions is that the environment is also prepared during GitHub +Actions. During your tests on local, `Uptest` is only responsible for creating +instance manifests and assertions of them. Therefore, all the preparation steps +mentioned in the Manual Testing section are also necessary for tests performed using `Uptest` locally. -After preparing the testing environment, you should run the following command to +After preparing the testing environment, you should run the following command to trigger tests locally by using `Uptest`: Example for single file test: + ``` make uptest-local PROVIDER_NAME=provider-aws EXAMPLE_LIST=provider-aws/examples/secretsmanager/secret.yaml ``` Example of whole API Group test: + ``` make uptest-local PROVIDER_NAME=provider-aws EXAMPLE_LIST=$(find provider-aws/examples/secretsmanager/*.yaml | tr '\n' ',') ``` ### Debugging Tests -Whether the tests fail using `Uptest` or when testing manually, the steps to be -followed are the same. What finally failed was a Managed Resource tested against -Official Providers. In this case, the first thing to do is to check the manifest -of the failing resource (where the value of `Synced` or `Ready` condition is +Whether the tests fail using `Uptest` or when testing manually, the steps to be +followed are the same. What finally failed was a Managed Resource tested against +Official Providers. In this case, the first thing to do is to check the manifest +of the failing resource (where the value of `Synced` or `Ready` condition is `False`) in the cluster. -If the test was in your local environment, you can check the current state of -the resource by using the following command: +If the test was in your local environment, you can check the current state of +the resource by using the following command: `kubectl get network.compute.gcp.upbound.io/example-network-1 -o yaml` If the test ran in the GitHub Actions, you need to check the action artifact mentioned in the previous part of the documentation. -The second important point to understand the problem is the provider logs. If -the test was in your local environment, you need to check the `make run` or IDE -logs. If testing was in GitHub Actions, you need to check the action artifact. +The second important point to understand the problem is the provider logs. If +the test was in your local environment, you need to check the `make run` or IDE +logs. If testing was in GitHub Actions, you need to check the action artifact. It contains the cluster dump that has the provider logs. ## Known Error Cases -1. `prevent_destroy` Case: In some cases, when unexpected changes or situations -occur in the resources, Terraform tries to delete the related resource and +1. `prevent_destroy` Case: In some cases, when unexpected changes or situations +occur in the resources, Terraform tries to delete the related resource and create it again. However, in order to prevent this situation, the resources are -configurable. In this context, the name of the field where you can provide this +configurable. In this context, the name of the field where you can provide this control is `prevent_destroy`. Please see details of [Terraform Resource Lifecycle]. -For resources in Official Providers, this value defaults to `true`. So the +For resources in Official Providers, this value defaults to `true`. So the deletion of the resource is blocked. -Encountering this situation (i.e. Terraform trying to delete and recreate the -resource) is not normal and may indicate a specific error. Some possible +Encountering this situation (i.e. Terraform trying to delete and recreate the +resource) is not normal and may indicate a specific error. Some possible problems could be: - - As a result of overriding the constructed ID after Terraform calls, Terraform - could not match the IDs and tries to recreate the resource. Please see - [this issue] for details. In this type of cases, you need to review your +- As a result of overriding the constructed ID after Terraform calls, Terraform + could not match the IDs and tries to recreate the resource. Please see + [this issue] for details. In this type of cases, you need to review your external name configuration. - - Crossplane's concept of [Late Initialization] may cause some side effects. - One of them is while late initialization, filling a field that is not initially +- Crossplane's concept of [Late Initialization] may cause some side effects. + One of them is while late initialization, filling a field that is not initially filled on the manifest may cause the resource to be destroyed and recreated. - In such a case, it should be evaluated that which field's value is set will - cause such an error. During this evaluation, it will be necessary to make use - of the terraform registry document. In the end, the field that is thought to - solve the problem is put into the ignore list using the - [late initialization configuration] and the test is repeated from the + In such a case, it should be evaluated that which field's value is set will + cause such an error. During this evaluation, it will be necessary to make use + of the terraform registry document. In the end, the field that is thought to + solve the problem is put into the ignore list using the + [late initialization configuration] and the test is repeated from the beginning. - - Some resources fall into `tainted` state as a result of certain steps in the +- Some resources fall into `tainted` state as a result of certain steps in the creation process fail. Please see [tainted issue] for details. -2. External Name Configuration Related Errors: The most common known issue is -errors in the external name configuration. A clear error message regarding this -situation may not be visible. Many error messages can be related to an incorrect -external name configuration. Such as, a field cannot be read properly from the -parameter map, there are unexpected fields in the generated `main.tf.json` file, +2. External Name Configuration Related Errors: The most common known issue is +errors in the external name configuration. A clear error message regarding this +situation may not be visible. Many error messages can be related to an incorrect +external name configuration. Such as, a field cannot be read properly from the +parameter map, there are unexpected fields in the generated `main.tf.json` file, etc. -Therefore, when debugging a non-ready resource; if you do not see errors -returned by the Cloud API related to the constraints or characteristics of the -service (for example, you are stuck on the creation limit of this resource in -this region, or the use of the relevant field for this resource depends on the -following conditions etc.), the first point to check is external name -configuration. +Therefore, when debugging a non-ready resource; if you do not see errors +returned by the Cloud API related to the constraints or characteristics of the +service (for example, you are stuck on the creation limit of this resource in +this region, or the use of the relevant field for this resource depends on the +following conditions etc.), the first point to check is external name +configuration. -3. Late Initialization Errors: Late Initialization is one of the key concepts of -Crossplane. It allows for some values that are not initially located in the +3. Late Initialization Errors: Late Initialization is one of the key concepts of +Crossplane. It allows for some values that are not initially located in the resource's manifest to be filled with the values returned by the cloud providers. -As a side effect of this, some fields conflict each other. In this case, a -detailed error message is usually displayed about which fields conflict with +As a side effect of this, some fields conflict each other. In this case, a +detailed error message is usually displayed about which fields conflict with each other. In this case, the relevant field should be skipped by [these steps]. -4. Provider Service Specific Errors: Every cloud provider and every service has -its own features and behavior. Therefore, you may see special error messages in -the status of the resources from time to time. These may say that you are out of -the allowed values in some fields of the resource, or that you need to enable -the relevant service, etc. In such cases, please review your example manifest +4. Provider Service Specific Errors: Every cloud provider and every service has +its own features and behavior. Therefore, you may see special error messages in +the status of the resources from time to time. These may say that you are out of +the allowed values in some fields of the resource, or that you need to enable +the relevant service, etc. In such cases, please review your example manifest and try to find the appropriate example. ``` @@ -269,15 +278,15 @@ configured/tested resource. In addition, the provider needs to be restarted after the changes in the controllers, because the controller change actually corresponds to the changes made in the running code. -``` +``` [this repo]: https://github.com/kubernetes-sigs/kind [the documentation]: https://crossplane.io/docs/v1.9/getting-started/install-configure.html#install-configuration-package [here]: https://github.com/upbound/official-providers/blob/main/docs/testing-resources-by-using-uptest.md#debugging-failed-test -[these steps]: https://github.com/upbound/upjet/blob/main/docs/configuring-a-resource.md#late-initialization-configuration -[late initialization configuration]: https://github.com/upbound/upjet/blob/main/docs/configuring-a-resource.md#late-initialization-configuration +[these steps]: https://github.com/crossplane/upjet/blob/main/docs/configuring-a-resource.md#late-initialization-configuration +[late initialization configuration]: https://github.com/crossplane/upjet/blob/main/docs/configuring-a-resource.md#late-initialization-configuration [Terraform Resource Lifecycle]: https://learn.hashicorp.com/tutorials/terraform/resource-lifecycle -[this issue]: https://github.com/upbound/upjet/issues/32 +[this issue]: https://github.com/crossplane/upjet/issues/32 [Late Initialization]: https://crossplane.io/docs/v1.9/concepts/managed-resources.html#late-initialization -[tainted issue]: https://github.com/upbound/upjet/issues/80 +[tainted issue]: https://github.com/crossplane/upjet/issues/80 [k3d]: https://k3d.io/ diff --git a/docs/testing-resources-by-using-uptest.md b/docs/archive/testing-resources-by-using-uptest.md similarity index 95% rename from docs/testing-resources-by-using-uptest.md rename to docs/archive/testing-resources-by-using-uptest.md index fc4c8918..88820cc7 100644 --- a/docs/testing-resources-by-using-uptest.md +++ b/docs/archive/testing-resources-by-using-uptest.md @@ -1,3 +1,9 @@ + + ## Testing Resources by Using Uptest `Uptest` provides a framework to test resources in an end-to-end @@ -81,4 +87,4 @@ credentials and ProviderConfig setup assuming you already have it all already configured in your environment. For a more heavyweight setup see `run_automated_tests` target which is used in a -centralized GitHub Actions invocation. \ No newline at end of file +centralized GitHub Actions invocation. diff --git a/docs/configuring-a-resource.md b/docs/configuring-a-resource.md new file mode 100644 index 00000000..6df57e8a --- /dev/null +++ b/docs/configuring-a-resource.md @@ -0,0 +1,942 @@ +# Configuring a resource + +[Upjet] generates as much as it could using the available information in the +Terraform resource schema. This includes an XRM-conformant schema of the +resource, controller logic, late initialization, sensitive data handling, etc. +However, there are still information that requires some input configuration +which can be found by checking the Terraform documentation of the resource: + +- [External name] +- [Cross Resource Referencing] +- [Additional Sensitive Fields and Custom Connection Details] +- [Late Initialization Behavior] +- [Overriding Terraform Resource Schema] +- [Initializers] + +## External Name + +Crossplane uses an annotation in managed resource CR to identify the external +resource which is managed by Crossplane. See [the external name documentation] +for more details. The format and source of the external name depends on the +cloud provider; sometimes it could simply be the name of resource (e.g. S3 +Bucket), and sometimes it is an auto-generated id by cloud API (e.g. VPC id ). +This is something specific to resource, and we need some input configuration for +upjet to appropriately generate a resource. + +Since Terraform already needs [a similar identifier] to import a resource, most +helpful part of resource documentation is the [import section]. + +Upjet performs some back and forth conversions between Crossplane resource model +and Terraform configuration. We need a custom, per resource configuration to +adapt Crossplane `external name` from Terraform `id`. + +Here are [the types for the External Name configuration]: + +```go +// SetIdentifierArgumentsFn sets the name of the resource in Terraform attributes map, +// i.e. Main HCL file. +type SetIdentifierArgumentsFn func(base map[string]any, externalName string) +// GetExternalNameFn returns the external name extracted from the TF State. +type GetExternalNameFn func(tfstate map[string]any) (string, error) +// GetIDFn returns the ID to be used in TF State file, i.e. "id" field in +// terraform.tfstate. +type GetIDFn func(ctx context.Context, externalName string, parameters map[string]any, providerConfig map[string]any) (string, error) + +// ExternalName contains all information that is necessary for naming operations, +// such as removal of those fields from spec schema and calling Configure function +// to fill attributes with information given in external name. +type ExternalName struct { + // SetIdentifierArgumentFn sets the name of the resource in Terraform argument + // map. In many cases, there is a field called "name" in the HCL schema, however, + // there are cases like RDS DB Cluster where the name field in HCL is called + // "cluster_identifier". This function is the place that you can take external + // name and assign it to that specific key for that resource type. + SetIdentifierArgumentFn SetIdentifierArgumentsFn + + // GetExternalNameFn returns the external name extracted from TF State. In most cases, + // "id" field contains all the information you need. You'll need to extract + // the format that is decided for external name annotation to use. + // For example the following is an Azure resource ID: + // /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1 + // The function should return "mygroup1" so that it can be used to set external + // name if it was not set already. + GetExternalNameFn GetExternalNameFn + + // GetIDFn returns the string that will be used as "id" key in TF state. In + // many cases, external name format is the same as "id" but when it is not + // we may need information from other places to construct it. For example, + // the following is an Azure resource ID: + // /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1 + // The function here should use information from supplied arguments to + // construct this ID, i.e. "mygroup1" from external name, subscription ID + // from providerConfig, and others from parameters map if needed. + GetIDFn GetIDFn + + // OmittedFields are the ones you'd like to be removed from the schema since + // they are specified via external name. For example, if you set + // "cluster_identifier" in SetIdentifierArgumentFn, then you need to omit + // that field. + // You can omit only the top level fields. + // No field is omitted by default. + OmittedFields []string + + // DisableNameInitializer allows you to specify whether the name initializer + // that sets external name to metadata.name if none specified should be disabled. + // It needs to be disabled for resources whose external identifier is randomly + // assigned by the provider, like AWS VPC where it gets vpc-21kn123 identifier + // and not let you name it. + DisableNameInitializer bool +} +``` + +Comments explain the purpose of each field but let's clarify further with some +example cases. + +### Case 1: Name as External Name and Terraform ID + +This is the simplest and most straightforward case with the following +conditions: + +- Terraform resource uses the `name` argument to identify the resources +- Terraform resource can be imported with `name`, i.e. `id`=`name` + +[aws_iam_user] is a good example here. In this case, we can just use the +[NameAsIdentifier] config of Upjet as follows: + +```go +import ( + "github.com/upbound/crossplane/pkg/config" + ... +) + +... + p.AddResourceConfigurator("aws_iam_user", func(r *config.Resource) { + r.ExternalName = config.NameAsIdentifier + ... + } +``` + +There are some resources which fits into this case with an exception by +expecting an argument other than `name` to name/identify a resource, for +example, [bucket] for [aws_s3_bucket] and [cluster_identifier] for +[aws_rds_cluster]. + +Let's check [aws_s3_bucket] further. Reading the [import section of s3 bucket] +we see that bucket is imported with its **name**, however, checking _arguments_ +section we see that this name is provided with the [bucket] argument. We also +notice, there is also another argument as `bucket_prefix` which conflicts with +`bucket` argument. We can just use the [NameAsIdentifier] config, however, we +also need to configure the `bucket` argument with `SetIdentifierArgumentFn` and +also omit `bucket` and `bucket_prefix` arguments from the spec with +`OmittedFields`: + +```go +import ( + "github.com/upbound/crossplane/pkg/config" + ... +) + +... + p.AddResourceConfigurator("aws_s3_bucket", func(r *config.Resource) { + r.ExternalName = config.NameAsIdentifier + r.ExternalName.SetIdentifierArgumentFn = func(base map[string]any, externalName string) { + base["bucket"] = externalName + } + r.ExternalName.OmittedFields = []string{ + "bucket", + "bucket_prefix", + } + ... + } +``` + +### Case 2: Identifier from Provider + +In this case, the (cloud) provider generates an identifier for the resource +independent of what we provided as arguments. + +Checking the [import section of aws_vpc], we see that this resource is being +imported with `vpc id`. When we check the [arguments list] and provided [example +usages], it is clear that this **id** is **not** something that user provides, +rather generated by AWS API. + +Here, we can just use [IdentifierFromProvider] configuration: + +```go +import ( + "github.com/upbound/crossplane/pkg/config" + ... +) + +... + p.AddResourceConfigurator("aws_vpc", func(r *config.Resource) { + r.ExternalName = config.IdentifierFromProvider + ... + } +``` + +### Case 3: Terraform ID as a Formatted String + +For some resources, Terraform uses a formatted string as `id` which include +resource identifier that Crossplane uses as external name but may also contain +some other parameters. + +Most `azurerm` resources fall into this category. Checking the [import section +of azurerm_sql_server], we see that can be imported with an `id` in the +following format: + +``` +/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myresourcegroup/providers/Microsoft.Sql/servers/myserver +``` + +To properly set external name for such a resource, we need to configure how to +extract external name from this string (`GetExternalNameFn`) and how to build +this id back (`GetIDFn`). + +```go +import ( + "github.com/upbound/crossplane/pkg/config" + ... +) + +func getNameFromFullyQualifiedID(tfstate map[string]any) (string, error) { + id, ok := tfstate["id"] + if !ok { + return "", errors.Errorf(ErrFmtNoAttribute, "id") + } + idStr, ok := id.(string) + if !ok { + return "", errors.Errorf(ErrFmtUnexpectedType, "id") + } + words := strings.Split(idStr, "/") + return words[len(words)-1], nil +} + +func getFullyQualifiedIDfunc(ctx context.Context, externalName string, parameters map[string]any, providerConfig map[string]any) (string, error) { + subID, ok := providerConfig["subscription_id"] + if !ok { + return "", errors.Errorf(ErrFmtNoAttribute, "subscription_id") + } + subIDStr, ok := subID.(string) + if !ok { + return "", errors.Errorf(ErrFmtUnexpectedType, "subscription_id") + } + rg, ok := parameters["resource_group_name"] + if !ok { + return "", errors.Errorf(ErrFmtNoAttribute, "resource_group_name") + } + rgStr, ok := rg.(string) + if !ok { + return "", errors.Errorf(ErrFmtUnexpectedType, "resource_group_name") + } + + name, ok := parameters["name"] + if !ok { + return "", errors.Errorf(ErrFmtNoAttribute, "name") + } + nameStr, ok := rg.(string) + if !ok { + return "", errors.Errorf(ErrFmtUnexpectedType, "name") + } + + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Sql/servers/%s", subIDStr, rgStr, nameStr), nil +} + +... + p.AddResourceConfigurator("azurerm_sql_server", func(r *config.Resource) { + r.ExternalName = config.NameAsIdentifier + r.ExternalName.GetExternalNameFn = getNameFromFullyQualifiedID + r.ExternalName.GetIDFn = getFullyQualifiedIDfunc + ... + } +``` + +With this, we have covered most common scenarios for configuring external name. +You can always check resource configurations of existing jet Providers as +further examples under `config//config.go` in their repositories. + +_Please see [this figure] to understand why we really need 3 different functions +to configure external names and, it visualizes which is used how:_ +![Alt text](../images/upjet-externalname.png) _Note that, initially, GetIDFn +will use the external-name annotation to set the terraform.tfstate id and, after +that, it uses the terraform.tfstate id to update the external-name annotation. +For cases where both values are different, both GetIDFn and GetExternalNameFn +must be set in order to have the correct configuration._ + +### Cross Resource Referencing + +Crossplane uses cross resource referencing to [handle dependencies] between +managed resources. For example, if you have an IAM User defined as a Crossplane +managed resource, and you want to create an Access Key for that user, you would +need to refer to the User CR from the Access Key resource. This is handled by +cross resource referencing. + +See how the [user] referenced at `forProvider.userRef.name` field of the Access +Key in the following example: + +```yaml +apiVersion: iam.aws.tf.crossplane.io/v1alpha1 +kind: User +metadata: + name: sample-user +spec: + forProvider: {} +--- +apiVersion: iam.aws.tf.crossplane.io/v1alpha1 +kind: AccessKey +metadata: + name: sample-access-key +spec: + forProvider: + userRef: + name: sample-user + writeConnectionSecretToRef: + name: sample-access-key-secret + namespace: crossplane-system +``` + +Historically, reference resolution method were written by hand which requires +some effort, however, with the latest Crossplane code generation tooling, it is +now possible to [generate reference resolution methods] by just adding some +marker on the fields. Now, the only manual step for generating cross resource +references is to provide which field of a resource depends on which information +(e.g. `id`, `name`, `arn` etc.) from the other. + +In Upjet, we have a [configuration] to provide this information for a field: + +```go +// Reference represents the Crossplane options used to generate +// reference resolvers for fields +type Reference struct { + // Type is the type name of the CRD if it is in the same package or + // . if it is in a different package. + Type string + // Extractor is the function to be used to extract value from the + // referenced type. Defaults to getting external name. + // Optional + Extractor string + // RefFieldName is the field name for the Reference field. Defaults to + // Ref or Refs. + // Optional + RefFieldName string + // SelectorFieldName is the field name for the Selector field. Defaults to + // Selector. + // Optional + SelectorFieldName string +} +``` + +For a resource that we want to generate, we need to check its argument list in +Terraform documentation and figure out which field needs reference to which +resource. + +Let's check [iam_access_key] as an example. In the argument list, we see the +[user] field which requires a reference to a IAM user. So, we need to the +following referencing configuration: + +```go +func Configure(p *config.Provider) { + p.AddResourceConfigurator("aws_iam_access_key", func (r *config.Resource) { + r.References["user"] = config.Reference{ + Type: "User", + } + }) +} +``` + +Please note the value of `Type` field needs to be a string representing the Go +type of the resource. Since, `AccessKey` and `User` resources are under the same +go package, we don't need to provide the package path. However, this is not +always the case and referenced resources might be in different package. In that +case, we would need to provide the full path. Referencing to a [kms key] from +`aws_ebs_volume` resource is a good example here: + +```go +func Configure(p *config.Provider) { + p.AddResourceConfigurator("aws_ebs_volume", func(r *config.Resource) { + r.References["kms_key_id"] = config.Reference{ + Type: "github.com/crossplane-contrib/provider-tf-aws/apis/kms/v1alpha1.Key", + } + }) +} +``` + +### Auto Cross Resource Reference Generation + +Cross Resource Referencing is one of the key concepts of the resource +configuration. As a very common case, cloud services depend on other cloud +services. For example, AWS Subnet resource needs an AWS VPC for creation. So, +for creating a Subnet successfully, before you have to create a VPC resource. +Please see the [Dependencies] documentation for more details. And also, for +resource configuration-related parts of cross-resource referencing, please see +[this part] of [Configuring a Resource] documentation. + +These documentations focus on the general concepts and manual configurations +of Cross Resource References. However, the main topic of this documentation is +automatic example&reference generation. + +Upjet has a scraper tool for scraping provider metadata from the Terraform +Registry. The scraped metadata are: + +- Resource Descriptions +- Examples of Resources (in HCL format) +- Field Documentations +- Import Statements + +These are very critical information for our automation processes. We use this +scraped metadata in many contexts. For example, field documentation of +resources and descriptions are used as Golang comments for schema fields and +CRDs. + +Another important scraped information is examples of resources. As a part +of testing efforts, finding the correct combination of field values is not easy +for every scenario. So, having a working example (combination) is very important +for easy testing. + +At this point, this example that is in HCL format is converted to a Managed +Resource manifest, and we can use this manifest in our test efforts. + +This is an example from Terraform Registry AWS Ebs Volume resource: + +``` +resource "aws_ebs_volume" "example" { + availability_zone = "us-west-2a" + size = 40 + + tags = { + Name = "HelloWorld" + } +} + +resource "aws_ebs_snapshot" "example_snapshot" { + volume_id = aws_ebs_volume.example.id + + tags = { + Name = "HelloWorld_snap" + } +} +``` + +The generated example: + +```yaml +apiVersion: ec2.aws.upbound.io/v1beta1 +kind: EBSSnapshot +metadata: + annotations: + meta.upbound.io/example-id: ec2/v1beta1/ebssnapshot + labels: + testing.upbound.io/example-name: example_snapshot + name: example-snapshot +spec: + forProvider: + region: us-west-1 + tags: + Name: HelloWorld_snap + volumeIdSelector: + matchLabels: + testing.upbound.io/example-name: example + +--- + +apiVersion: ec2.aws.upbound.io/v1beta1 +kind: EBSVolume +metadata: + annotations: + meta.upbound.io/example-id: ec2/v1beta1/ebssnapshot + labels: + testing.upbound.io/example-name: example + name: example +spec: + forProvider: + availabilityZone: us-west-2a + region: us-west-1 + size: 40 + tags: + Name: HelloWorld +``` + +Here, there are three very important points that scraper makes easy our life: + +- We do not have to find the correct value combinations for fields. So, we can + easily use the generated example manifest in our tests. +- The HCL example was scraped from registry documentation of the `aws_ebs_snapshot` + resource. In the example, you also see the `aws_ebs_volume` resource manifest + because, for the creation of an EBS Snapshot, you need an EBS Volume resource. + Thanks to the source Registry, (in many cases, there are the dependent resources + of target resources) we can also scrape the dependencies of target resources. +- The last item is actually what is intended to be explained in this document. + For using the Cross Resource References, as I mentioned above, you need to add + some references to the resource configuration. But, in many cases, if in the + scraped example, the mentioned dependencies are already described you do not + have to write explicit references to resource configuration. The Cross Resource + Reference generator generates the mentioned references. + +### Validating the Cross Resource References + +As I mentioned, many references are generated from scraped metadata by an auto +reference generator. However, there are two cases where we miss generating the +references. + +The first one is related to some bugs or improvement points in the generator. +This means that the generator can handle many references in the scraped +examples and generate correctly them. But we cannot say that the ratio is %100. +For some cases, the generator cannot generate references although, they are in +the scraped example manifests. + +The second one is related to the scraped example itself. As I mentioned above, +the source of the generator is the scraped example manifest. So, it checks the +manifest and tries to generate the found cross-resource references. In some +cases, although there are other reference fields, these do not exist in the +example manifest. They can only be mentioned in schema/field documentation. + +For these types of situations, you must configure cross-resource references +explicitly. + +### Removing Auto-Generated Cross Resource References In Some Corner Cases + +In some cases, the generated references can narrow the reference pool covered by +the field. For example, X resource has an A field and Y and Z resources can be +referenced via this field. However, since the reference to Y is mentioned in the +example manifest, this reference field will only be defined over Y. In this case, +since the reference pool of the relevant field will be narrowed, it would be +more appropriate to delete this reference. For example, + +``` +resource "aws_route53_record" "www" { + zone_id = aws_route53_zone.primary.zone_id + name = "example.com" + type = "A" + + alias { + name = aws_elb.main.dns_name + zone_id = aws_elb.main.zone_id + evaluate_target_health = true + } +} +``` + +Route53 Record resource’s alias.name field has a reference. In the example, this +reference is shown by using the `aws_elb` resource. However, when we check the +field documentation, we see that this field can also be used for reference +for other resources: + +``` +Alias +Alias records support the following: + +name - (Required) DNS domain name for a CloudFront distribution, S3 bucket, ELB, +or another resource record set in this hosted zone. +``` + +### Conclusion + +As a result, mentioned scraper and example&reference generators are very useful +for making easy the test efforts. But using these functionalities, we must be +careful to avoid undesired states. + +[Dependencies]: https://crossplane.io/docs/v1.7/concepts/managed-resources.html#dependencies +[this part]: https://github.com/upbound/crossplane/blob/main/docs/configuring-a-resource.md#cross-resource-referencing +[Configuring a Resource]: https://github.com/upbound/crossplane/blob/main/docs/configuring-a-resource.md + +## Additional Sensitive Fields and Custom Connection Details + +Crossplane stores sensitive information of a managed resource in a Kubernetes +secret, together with some additional fields that would help consumption of the +resource, a.k.a. [connection details]. + +In Upjet, we already handle sensitive fields that are marked as sensitive in +Terraform schema and no further action required for them. Upjet will properly +hide these fields from CRD spec and status by converting to a secret reference +or storing in connection details secret respectively. However, we still have +some custom configuration API that would allow including additional fields into +connection details secret no matter they are sensitive or not. + +As an example, let's use `aws_iam_access_key`. Currently, Upjet stores all +sensitive fields in Terraform schema as prefixed with `attribute.`, so without +any `AdditionalConnectionDetailsFn`, connection resource will have +`attribute.id` and `attribute.secret` corresponding to [id] and [secret] fields +respectively. To see them with more common keys, i.e. `aws_access_key_id` and +`aws_secret_access_key`, we would need to make the following configuration: + +```go +func Configure(p *config.Provider) { + p.AddResourceConfigurator("aws_iam_access_key", func(r *config.Resource) { + r.Sensitive.AdditionalConnectionDetailsFn = func(attr map[string]any) (map[string][]byte, error) { + conn := map[string][]byte{} + if a, ok := attr["id"].(string); ok { + conn["aws_access_key_id"] = []byte(a) + } + if a, ok := attr["secret"].(string); ok { + conn["aws_secret_access_key"] = []byte(a) + } + return conn, nil + } + }) +} +``` + +This will produce a connection details secret as follows: + +```yaml +apiVersion: v1 +data: + attribute.id: QUtJQVk0QUZUVFNFNDI2TlhKS0I= + attribute.secret: ABCxyzRedacted== + attribute.ses_smtp_password_v4: QQ00REDACTED== + aws_access_key_id: QUtJQVk0QUZUVFNFNDI2TlhKS0I= + aws_secret_access_key: ABCxyzRedacted== +kind: Secret +``` + +### Late Initialization Configuration + +Late initialization configuration is only required if there are conflicting +arguments in terraform resource configuration. Unfortunately, there is _no easy +way_ to figure that out without testing the resource, _so feel free to skip this +configuration_ at the first place and revisit _only if_ you have errors like +below while testing the resource. + +``` +observe failed: cannot run refresh: refresh failed: Invalid combination of arguments: + "address_prefix": only one of `address_prefix,address_prefixes` can be specified, but `address_prefix,address_prefixes` were specified.: File name: main.tf.json +``` + +If you would like to have the late-initialization library _not_ to process the +[`address_prefix`] parameter field, then the following configuration where we +specify the parameter field path is sufficient: + +```go +func Configure(p *config.Provider) { + p.AddResourceConfigurator("azurerm_subnet", func(r *config.Resource) { + r.LateInitializer = config.LateInitializer{ + IgnoredFields: []string{"address_prefix"}, + } + }) +} +``` + +_Please note that, there could be errors looking slightly different from above, +so please consider configuring late initialization behaviour whenever you got +some unexpected error starting with `observe failed:`, once you are sure that +you provided all necessary parameters to your resource._ + +### Further details on Late Initialization + +Upjet runtime automatically performs late-initialization during an +[`external.Observe`] call with means of runtime reflection. State of the world +observed by Terraform CLI is used to initialize any `nil`-valued pointer +parameters in the managed resource's `spec`. In most of the cases no custom +configuration should be necessary for late-initialization to work. However, +there are certain cases where you will want/need to customize +late-initialization behaviour. Thus, Upjet provides an extensible +[late-initialization customization API] that controls late-initialization +behaviour. + +The associated resource struct is defined +[here](https://github.com/upbound/crossplane/blob/c9e21387298d8ed59fcd71c7f753ec401a3383a5/pkg/config/resource.go#L91) +as follows: + +```go +// LateInitializer represents configurations that control +// late-initialization behaviour +type LateInitializer struct { + // IgnoredFields are the canonical field names to be skipped during + // late-initialization + IgnoredFields []string +} +``` + +Currently, it only involves a configuration option to specify certain `spec` +parameters to be ignored during late-initialization. Each element of the +`LateInitializer.IgnoredFields` slice represents the canonical path relative to +the parameters struct for the managed resource's `Spec` using `Go` type names as +path elements. As an example, with the following type definitions: + +```go +type Subnet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec SubnetSpec `json:"spec"` + Status SubnetStatus `json:"status,omitempty"` +} + +type SubnetSpec struct { + ForProvider SubnetParameters `json:"forProvider"` + ... +} + +type DelegationParameters struct { + // +kubebuilder:validation:Required + Name *string `json:"name" tf:"name,omitempty"` + ... +} + +type SubnetParameters struct { + // +kubebuilder:validation:Optional + AddressPrefix *string `json:"addressPrefix,omitempty" tf:"address_prefix,omitempty"` + // +kubebuilder:validation:Optional + Delegation []DelegationParameters `json:"delegation,omitempty" tf:"delegation,omitempty"` + ... +} +``` + +In most cases, custom late-initialization configuration will not be necessary. +However, after generating a new managed resource and observing its behaviour (at +runtime), it may turn out that late-initialization behaviour needs +customization. For certain resources like the `provider-tf-azure`'s +`PostgresqlServer` resource, we have observed that Terraform state contains +values for mutually exclusive parameters, e.g., for `PostgresqlServer`, both +`StorageMb` and `StorageProfile[].StorageMb` get late-initialized. Upon next +reconciliation, we generate values for both parameters in the Terraform +configuration, and although they happen to have the same value, Terraform +configuration validation requires them to be mutually exclusive. Currently, we +observe this behaviour at runtime, and upon observing that the resource cannot +transition to the `Ready` state and acquires the Terraform validation error +message in its `status.conditions`, we do the `LateInitializer.IgnoreFields` +custom configuration detailed above to skip one of the mutually exclusive fields +during late-initialization. + +## Overriding Terraform Resource Schema + +Upjet generates Crossplane resource schemas (CR spec/status) using the +[Terraform schema of the resource]. As of today, Upjet leverages the following +attributes in the schema: + +- [Type] and [Elem] to identify the type of the field. +- [Sensitive] to see if we need to keep it in a Secret instead of CR. +- [Description] to add as a description to the field in CRD. +- [Optional] and [Computed] to identify whether the fields go under spec or + status: + - Not Optional & Not Computed => Spec (required) + - Optional & Not Computed => Spec (optional) + - Optional & Computed => Spec (optional, to be late-initialized) + - Not Optional & Computed => Status + +Usually, we don't need to make any modifications in the resource schema and +resource schema just works as is. However, there could be some rare edge cases +like: + +- Field contains sensitive information but not marked as `Sensitive` or vice + versa. +- An attribute does not make sense to have in CRD schema, like [tags_all for jet + AWS resources]. +- Moving parameters from Terraform provider config to resources schema to fit + Crossplane model, e.g. [AWS region] parameter is part of provider config in + Terraform but Crossplane expects it in CR spec. + +Schema of a resource could be overridden as follows: + +```go +p.AddResourceConfigurator("aws_autoscaling_group", func(r *config.Resource) { + // Managed by Attachment resource. + if s, ok := r.TerraformResource.Schema["load_balancers"]; ok { + s.Optional = false + s.Computed = true + } + if s, ok := r.TerraformResource.Schema["target_group_arns"]; ok { + s.Optional = false + s.Computed = true + } +}) +``` + +## Initializers + +Initializers involve the operations that run before beginning of reconciliation. +This configuration option will provide that setting initializers for per +resource. + +Many resources in aws have `tags` field in their schema. Also, in Crossplane +there is a [tagging convention]. To implement the tagging convention for jet-aws +provider, this initializer configuration support was provided. + +There is a common struct (`Tagger`) in upjet to use the tagging convention: + +```go +// Tagger implements the Initialize function to set external tags +type Tagger struct { + kube client.Client + fieldName string +} + +// NewTagger returns a Tagger object. +func NewTagger(kube client.Client, fieldName string) *Tagger { + return &Tagger{kube: kube, fieldName: fieldName} +} + +// Initialize is a custom initializer for setting external tags +func (t *Tagger) Initialize(ctx context.Context, mg xpresource.Managed) error { + paved, err := fieldpath.PaveObject(mg) + if err != nil { + return err + } + pavedByte, err := setExternalTagsWithPaved(xpresource.GetExternalTags(mg), paved, t.fieldName) + if err != nil { + return err + } + if err := json.Unmarshal(pavedByte, mg); err != nil { + return err + } + if err := t.kube.Update(ctx, mg); err != nil { + return err + } + return nil +} +``` + +As seen above, the `Tagger` struct accepts a `fieldName`. This `fieldName` +specifies which value of field to set in the resource's spec. You can use the +common `Initializer` by specifying the field name that points to the external +tags in the configured resource. + +There is also a default initializer for tagging convention, `TagInitializer`. It +sets the value of `fieldName` to `tags` as default: + +```go +// TagInitializer returns a tagger to use default tag initializer. +var TagInitializer NewInitializerFn = func(client client.Client) managed.Initializer { + return NewTagger(client, "tags") +} +``` + +In jet-aws provider, as a default process, if a resource has `tags` field in its +schema, then the default initializer (`TagInitializer`) is added to Initializer +list of resource: + +```go +// AddExternalTagsField adds ExternalTagsFieldName configuration for resources that have tags field. +func AddExternalTagsField() tjconfig.ResourceOption { + return func(r *tjconfig.Resource) { + if s, ok := r.TerraformResource.Schema["tags"]; ok && s.Type == schema.TypeMap { + r.InitializerFns = append(r.InitializerFns, tjconfig.TagInitializer) + } + } +} +``` + +However, if the field name that used for the external label is different from +the `tags`, the `NewTagger` function can be called and the specific `fieldName` +can be passed to this: + +```go +r.InitializerFns = append(r.InitializerFns, func(client client.Client) managed.Initializer { + return tjconfig.NewTagger(client, "example_tags_name") +}) +``` + +If the above tagging convention logic does not work for you, and you want to use +this configuration option for a reason other than tagging convention (for +another custom initializer operation), you need to write your own struct in +provider and have this struct implement the `Initializer` function with a custom +logic. + +This configuration option is set by using the [InitializerFns] field that is a +list of [NewInitializerFn]: + +```go +// NewInitializerFn returns the Initializer with a client. +type NewInitializerFn func(client client.Client) managed.Initializer +``` + +Initializer is an interface in [crossplane-runtime]: + +```go +type Initializer interface { + Initialize(ctx context.Context, mg resource.Managed) error +} +``` + +So, an interface must be passed to the related configuration field for adding +initializers for a resource. + +[Upjet]: https://github.com/upbound/crossplane +[External name]: #external-name +[Cross Resource Referencing]: #cross-resource-referencing +[Additional Sensitive Fields and Custom Connection Details]: + #additional-sensitive-fields-and-custom-connection-details +[Late Initialization Behavior]: #late-initialization-configuration +[Overriding Terraform Resource Schema]: #overriding-terraform-resource-schema +[the external name documentation]: + https://crossplane.io/docs/v1.7/concepts/managed-resources.html#external-name +[import section]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key#import +[the types for the External Name configuration]: + https://github.com/upbound/crossplane/blob/2299925ea2541e6a8088ede463cd865bd64eba32/pkg/config/resource.go#L67 +[aws_iam_user]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user +[NameAsIdentifier]: + https://github.com/upbound/crossplane/blob/2299925ea2541e6a8088ede463cd865bd64eba32/pkg/config/defaults.go#L31 +[aws_s3_bucket]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket +[import section of s3 bucket]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#import +[bucket]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#bucket +[cluster_identifier]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster#cluster_identifier +[aws_rds_cluster]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster. +[import section of aws_vpc]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc#import +[arguments list]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc#argument-reference +[example usages]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc#example-usage +[IdentifierFromProvider]: + https://github.com/upbound/crossplane/blob/2299925ea2541e6a8088ede463cd865bd64eba32/pkg/config/defaults.go#L46 +[a similar identifier]: https://www.terraform.io/docs/glossary#id +[import section of azurerm_sql_server]: + https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/sql_server#import +[handle dependencies]: + https://crossplane.io/docs/v1.7/concepts/managed-resources.html#dependencies +[user]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key#user +[generate reference resolution methods]: + https://github.com/crossplane/crossplane-tools/pull/35 +[configuration]: + https://github.com/upbound/crossplane/blob/874bb6ad5cff9741241fb790a3a5d71166900860/pkg/config/resource.go#L77 +[iam_access_key]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key#argument-reference +[kms key]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ebs_volume#kms_key_id +[connection details]: + https://crossplane.io/docs/v1.7/concepts/managed-resources.html#connection-details +[id]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key#id +[secret]: + https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key#secret +[`external.Observe`]: + https://github.com/upbound/crossplane/blob/874bb6ad5cff9741241fb790a3a5d71166900860/pkg/controller/external.go#L149 +[late-initialization customization API]: + https://github.com/upbound/crossplane/blob/874bb6ad5cff9741241fb790a3a5d71166900860/pkg/resource/lateinit.go#L86 +[`address_prefix`]: + https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet#address_prefix +[Terraform schema of the resource]: + https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L34 +[Type]: + https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L52 +[Elem]: + https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L151 +[Sensitive]: + https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L244 +[Description]: + https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L120 +[Optional]: + https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L80 +[Computed]: + https://github.com/hashicorp/terraform-plugin-sdk/blob/e3325b095ef501cf551f7935254ce942c44c1af0/helper/schema/schema.go#L139 +[tags_all for jet AWS resources]: + https://github.com/upbound/provider-aws/blob/main/config/overrides.go#L62 +[AWS region]: + https://github.com/upbound/provider-aws/blob/main/config/overrides.go#L32 +[this figure]: ../images/upjet-externalname.png +[Initializers]: #initializers +[InitializerFns]: + https://github.com/upbound/crossplane/blob/ae78a0a4c438f01717002e00fac761524aa6e951/pkg/config/resource.go#L289 +[NewInitializerFn]: + https://github.com/upbound/crossplane/blob/ae78a0a4c438f01717002e00fac761524aa6e951/pkg/config/resource.go#L207 +[crossplane-runtime]: + https://github.com/crossplane/crossplane-runtime/blob/428b7c3903756bb0dcf5330f40298e1fa0c34301/pkg/reconciler/managed/reconciler.go#L138 +[tagging convention]: + https://github.com/crossplane/crossplane/blob/60c7df9/design/one-pager-managed-resource-api-design.md#external-resource-labeling diff --git a/docs/generating-a-provider.md b/docs/generating-a-provider.md index 035ebe8e..0e89e379 100644 --- a/docs/generating-a-provider.md +++ b/docs/generating-a-provider.md @@ -1,210 +1,229 @@ -# Generating a Crossplane Provider + +# Generating a Crossplane provider -We have chosen [Terraform GitHub provider] as an example, but the process will -be quite similar for any other Terraform provider. We will use `myorg` as the -example organization name to be used. +This guide shows you how to generate a Crossplane provider based on an existing +Terraform provider using Upjet. The guide uses the [Terraform GitHub provider] +as the example, but the process is similar for any other Terraform provider. -## Generate +## Prepare your new provider repository -1. Generate a GitHub repository for the Crossplane provider by hitting the - "**Use this template**" button in [upjet-provider-template] repository. The preferred repository name is `provider-` (e.g. `provider-github`), which is assumed by the `./hack/prepare.sh` script in step 3. -2. Clone the repository to your local and `cd` into the repository directory. - Fetch the [upbound/build] submodule by running the following: +1. Create a new GitHub repository for the Crossplane provider by clicking the +"**Use this template**" button in the [upjet-provider-template] repository. The +expected repository name is in the format `provider-`. For example, +`provider-github`. The script in step 3 expects this format and fails if you +follow a different naming convention. +1. Clone the repository to your local environment and `cd` into the repository +directory. +1. Fetch the [upbound/build] submodule by running the following +command: ```bash make submodules ``` -3. Replace `template` with your provider name. +1. To setup your provider name and group run the `./hack/prepare.sh` +script from the repository root to prepare the code. - 1. Run the `./hack/prepare.sh` script from repo root to prepare the repo, e.g., to - replace all occurrences of `template` with your provider name and `upbound` - with your organization name: - - ```bash - ./hack/prepare.sh - ``` - -4. To configure the Terraform provider to generate from, update the following - variables in `Makefile`: - - ```makefile - export TERRAFORM_PROVIDER_SOURCE := integrations/github - export TERRAFORM_PROVIDER_REPO := https://github.com/integrations/terraform-provider-github - export TERRAFORM_PROVIDER_VERSION := 5.5.0 - export TERRAFORM_PROVIDER_DOWNLOAD_NAME := terraform-provider-github - export TERRAFORM_NATIVE_PROVIDER_BINARY := terraform-provider-github_v5.5.0_x5 - export TERRAFORM_DOCS_PATH := website/docs/r + ```bash + ./hack/prepare.sh ``` - - TERRAFORM_PROVIDER_SOURCE: You can find this variable in [Terraform GitHub provider] documentation by hitting the "**USE PROVIDER**" button. - - TERRAFORM_PROVIDER_REPO: You can find this variable in [Terraform GitHub provider] documentation by clicking the "**Report an issue**" link. - - TERRAFORM_PROVIDER_VERSION: You can find this variable in [Terraform GitHub provider] documentation by hitting the "**USE PROVIDER**" button. - - TERRAFORM_PROVIDER_DOWNLOAD_NAME: terraform-provider-. - - TERRAFORM_NATIVE_PROVIDER_BINARY: terraform-provider-github_v_x5 - - TERRAFORM_DOCS_PATH: You can find this by going to the terraform provider repository => click `website` => click `docs` => click `resources`(where resource documentation is stored). - - Check [this line in controller Dockerfile] to see how `TERRAFORM_PROVIDER_SOURCE` and `TERRAFORM_PROVIDER_VERSION` are used to build the provider plugin binary. - - Please make sure your organization name in `PROJECT_REPO` is correct. - -5. Implement `ProviderConfig` logic. In `upjet-provider-template`, there is already - a boilerplate code in file `internal/clients/github.go` which - takes care of properly fetching secret data referenced from `ProviderConfig` - resource. +1. Ensure your organization name is correct in the `Makefile` for the + `PROJECT_REPO` variable. +1. To configure which Terraform provider to generate from, update the following +variables in the `Makefile`: + + | Variable | Description | + | -------- | ----------- | + | `TERRAFORM_PROVIDER_SOURCE` | Find this variable on the Terraform registry for the provider. You can see the source value when clicking on the "`USE PROVIDER`" dropdown button in the navigation. | + |`TERRAFORM_PROVIDER_REPO` | The URL to the repository that hosts the provider's code. | + | `TERRAFORM_PROVIDER_VERSION` | Find this variable on the Terraform registry for the provider. You can see the source value when clicking on the "`USE PROVIDER`" dropdown button in the navigation. | + |`TERRAFORM_PROVIDER_DOWNLOAD_NAME` | The name of the provider in the [Terraform registry](https://releases.hashicorp.com/) | + |`TERRAFORM_NATIVE_PROVIDER_BINARY` | The name of the binary in the Terraform provider. This follows the pattern `terraform-provider-{provider name}_v{provider version}`. | + |`TERRAFORM_DOCS_PATH` | The relative path, from the root of the repository, where the provider resource documentation exist. | + + For example, for the [Terraform GitHub provider], the variables are: + + ```makefile + export TERRAFORM_PROVIDER_SOURCE := integrations/github + export TERRAFORM_PROVIDER_REPO := https://github.com/integrations/terraform-provider-github + export TERRAFORM_PROVIDER_VERSION := 5.32.0 + export TERRAFORM_PROVIDER_DOWNLOAD_NAME := terraform-provider-github + export TERRAFORM_NATIVE_PROVIDER_BINARY := terraform-provider-github_v5.32.0 + export TERRAFORM_DOCS_PATH := website/docs/r + ``` + + Refer to [the Dockerfile](https://github.com/upbound/upjet-provider-template/blob/main/cluster/images/upjet-provider-template/Dockerfile) to see the variables called when building the provider. + +## Configure the provider resources + +1. First you need to add the `ProviderConfig` logic. + - In `upjet-provider-template`, there is + already boilerplate code in the file `internal/clients/github.go` which takes + care of fetching secret data referenced from the `ProviderConfig` resource. + - Reference the [Terraform Github provider] documentation for information on + authentication and provide the necessary keys.: + + ```go + const ( + ... + keyBaseURL = "base_url" + keyOwner = "owner" + keyToken = "token" + ) + ``` - For our GitHub provider, we need to check [Terraform documentation for provider - configuration] and provide the keys there: + ```go + func TerraformSetupBuilder(version, providerSource, providerVersion string) terraform.SetupFn { + ... + // set provider configuration + ps.Configuration = map[string]any{} + if v, ok := creds[keyBaseURL]; ok { + ps.Configuration[keyBaseURL] = v + } + if v, ok := creds[keyOwner]; ok { + ps.Configuration[keyOwner] = v + } + if v, ok := creds[keyToken]; ok { + ps.Configuration[keyToken] = v + } + return ps, nil + } + ``` - ```go - const ( - ... - keyBaseURL = "base_url" - keyOwner = "owner" - keyToken = "token" - ) - ``` - ```go - func TerraformSetupBuilder(version, providerSource, providerVersion string) terraform.SetupFn { - ... - // set provider configuration - ps.Configuration = map[string]any{} - if v, ok := creds[keyBaseURL]; ok { - ps.Configuration[keyBaseURL] = v - } - if v, ok := creds[keyOwner]; ok { - ps.Configuration[keyOwner] = v - } - if v, ok := creds[keyToken]; ok { - ps.Configuration[keyToken] = v - } - return ps, nil - } - ``` +1. Next add external name configurations for the [github_repository] and + [github_branch] Terraform resources. + + > [!NOTE] + > Only generate resources with an external name configuration defined. + + - Add external name configurations for these two resources in + `config/external_name.go` as an entry to the map called + `ExternalNameConfigs` + + ```go + // ExternalNameConfigs contains all external name configurations for this + // provider. + var ExternalNameConfigs = map[string]config.ExternalName{ + ... + // Name is a parameter and it is also used to import the resource. + "github_repository": config.NameAsIdentifier, + // The import ID consists of several parameters. We'll use branch name as + // the external name. + "github_branch": config.TemplatedStringAsIdentifier("branch", "{{ .parameters.repository }}:{{ .external_name }}:{{ .parameters.source_branch }}"), + } + ``` -6. Before generating all resources that the provider has, let's go step by step - and only start with generating CRDs for [github_repository] and - [github_branch] Terraform resources. - - Only the resources with external name configuration should be generated. - Let's add external name configurations for these two resources in - `config/external_name.go` as an entry to the map called `ExternalNameConfigs`: - - ```go - // ExternalNameConfigs contains all external name configurations for this - // provider. - var ExternalNameConfigs = map[string]config.ExternalName{ - ... - // Name is a parameter and it is also used to import the resource. - "github_repository": config.NameAsIdentifier, - // The import ID consists of several parameters. We'll use branch name as - // the external name. - "github_branch": config.TemplatedStringAsIdentifier("branch", "{{ .parameters.repository }}:{{ .external_name }}:{{ .parameters.source_branch }}"), - } - ``` + - Take a look at the documentation for configuring a resource for more + information about [external name configuration](configuring-a-resource.md#external-name). - Please take a look at the Configuring a Resource documentation for more information about [external name configuration]. +1. Next add custom configurations for these two resources as follows: -7. Finally, we would need to add some custom configurations for these two - resources as follows: + - Create custom configuration directory for whole repository group - ```bash - # Create custom configuration directory for whole repository group - mkdir config/repository - # Create custom configuration directory for whole branch group - mkdir config/branch - ``` + ```bash + mkdir config/repository + ``` - ```bash - cat < config/repository/config.go - package repository - - import "github.com/upbound/upjet/pkg/config" - - // Configure configures individual resources by adding custom ResourceConfigurators. - func Configure(p *config.Provider) { - p.AddResourceConfigurator("github_repository", func(r *config.Resource) { - // We need to override the default group that upjet generated for - // this resource, which would be "github" - r.ShortGroup = "repository" - }) - } - EOF - ``` + - Create custom configuration directory for whole branch group - ```bash - # Note that you need to change `myorg/provider-github`. - cat < config/branch/config.go - package branch - - import "github.com/upbound/upjet/pkg/config" - - func Configure(p *config.Provider) { - p.AddResourceConfigurator("github_branch", func(r *config.Resource) { - // We need to override the default group that upjet generated for - // this resource, which would be "github" - r.ShortGroup = "branch" - - // This resource need the repository in which branch would be created - // as an input. And by defining it as a reference to Repository - // object, we can build cross resource referencing. See - // repositoryRef in the example in the Testing section below. - r.References["repository"] = config.Reference{ - Type: "github.com/myorg/provider-github/apis/repository/v1alpha1.Repository", - } - }) - } - EOF - ``` + ```bash + mkdir config/branch + ``` - And register custom configurations in `config/provider.go`: + - Create the repository group configuration file - ```diff - import ( - ... + ```bash + cat < config/repository/config.go + package repository + + import "github.com/crossplane/upjet/pkg/config" + + // Configure configures individual resources by adding custom ResourceConfigurators. + func Configure(p *config.Provider) { + p.AddResourceConfigurator("github_repository", func(r *config.Resource) { + // We need to override the default group that upjet generated for + // this resource, which would be "github" + r.ShortGroup = "repository" + }) + } + EOF + ``` - ujconfig "github.com/upbound/upjet/pkg/config" + - Create the branch group configuration file - - "github.com/myorg/provider-github/config/null" - + "github.com/myorg/provider-github/config/branch" - + "github.com/myorg/provider-github/config/repository" - ) + > [!NOTE] + > Note that you need to change `myorg/provider-github` to your organization. - func GetProvider() *tjconfig.Provider { - ... - for _, configure := range []func(provider *tjconfig.Provider){ - // add custom config functions - - null.Configure, - + repository.Configure, - + branch.Configure, - } { - configure(pc) - } - ``` - - **_To learn more about custom resource configurations (in step 7), please see - the [Configuring a Resource](/docs/add-new-resource-long.md) document._** + ```bash + cat < config/branch/config.go + package branch + + import "github.com/crossplane/upjet/pkg/config" + + func Configure(p *config.Provider) { + p.AddResourceConfigurator("github_branch", func(r *config.Resource) { + // We need to override the default group that upjet generated for + // this resource, which would be "github" + r.ShortGroup = "branch" + + // This resource need the repository in which branch would be created + // as an input. And by defining it as a reference to Repository + // object, we can build cross resource referencing. See + // repositoryRef in the example in the Testing section below. + r.References["repository"] = config.Reference{ + Type: "github.com/myorg/provider-github/apis/repository/v1alpha1.Repository", + } + }) + } + EOF + ``` + And register custom configurations in `config/provider.go`: + + ```diff + import ( + ... + + ujconfig "github.com/upbound/crossplane/pkg/config" + + - "github.com/myorg/provider-github/config/null" + + "github.com/myorg/provider-github/config/branch" + + "github.com/myorg/provider-github/config/repository" + ) + + func GetProvider() *tjconfig.Provider { + ... + for _, configure := range []func(provider *tjconfig.Provider){ + // add custom config functions + - null.Configure, + + repository.Configure, + + branch.Configure, + } { + configure(pc) + } + ``` -8. Now we can generate our Upjet Provider: + _To learn more about custom resource configurations (in step 7), please + see the [Configuring a Resource](configuring-a-resource.md) document._ - Before we run make generate ensure to install `goimports` - ``` - go install golang.org/x/tools/cmd/goimports@latest - ``` +1. Now we can generate our Upjet Provider: - ```bash - make generate - ``` + Before we run `make generate` ensure to install `goimports` -### Adding More Resources + ```bash + go install golang.org/x/tools/cmd/goimports@latest + ``` -See the guide [here][new-resource-short] to add more resources. + ```bash + make generate + ``` -## Test +## Testing the generated resources Now let's test our generated resources. @@ -239,8 +258,7 @@ Now let's test our generated resources. ``` Create example for `repository` resource, which will use - `upjet-provider-template` repo as template for the repository - to be created: + `upjet-provider-template` repo as template for the repository to be created: ```bash cat < examples/repository/repository.yaml @@ -260,8 +278,8 @@ Now let's test our generated resources. EOF ``` - Create `branch` resource which refers to the above repository - managed resource: + Create `branch` resource which refers to the above repository managed + resource: ```bash cat < examples/branch/branch.yaml @@ -278,8 +296,9 @@ Now let's test our generated resources. EOF ``` - In order to change the `apiVersion`, you can use `WithRootGroup` and `WithShortName` - options in `config/provider.go` as arguments to `ujconfig.NewProvider`. + In order to change the `apiVersion`, you can use `WithRootGroup` and + `WithShortName` options in `config/provider.go` as arguments to + `ujconfig.NewProvider`. 2. Generate a [Personal Access Token](https://github.com/settings/tokens) for your Github account with `repo/public_repo` and `delete_repo` scopes. @@ -300,14 +319,16 @@ Now let's test our generated resources. 5. Run the provider: - Please make sure Terraform is installed before running the "make run" command, you can check [this guide](https://developer.hashicorp.com/terraform/downloads). - + Please make sure Terraform is installed before running the "make run" + command, you can check + [this guide](https://developer.hashicorp.com/terraform/downloads). + ```bash make run ``` -6. Apply ProviderConfig and example manifests (_In another terminal since - the previous command is blocking_): +6. Apply ProviderConfig and example manifests (_In another terminal since the + previous command is blocking_): ```bash # Create "crossplane-system" namespace if not exists @@ -326,10 +347,10 @@ Now let's test our generated resources. ```bash NAME READY SYNCED EXTERNAL-NAME AGE - branch.branch.github.upbound.io/hello-upjet True True hello-crossplane:hello-upjet 89s + branch.branch.github.jet.crossplane.io/hello-upjet True True hello-crossplane:hello-upjet 89s NAME READY SYNCED EXTERNAL-NAME AGE - repository.repository.github.upbound.io/hello-crossplane True True hello-crossplane 89s + repository.repository.github.jet.crossplane.io/hello-crossplane True True hello-crossplane 89s ``` Verify that repo `hello-crossplane` and branch `hello-upjet` created under @@ -348,14 +369,15 @@ Now let's test our generated resources. Verify that the repo got deleted once deletion is completed on the control plane. +## Next steps + +Now that you've seen the basics of generating `CustomResourceDefinitions` for +your provider, you can learn more about +[configuring resources](configuring-a-resource.md) or +[testing your resources](testing-with-uptest.md) with Uptest. [Terraform GitHub provider]: https://registry.terraform.io/providers/integrations/github/latest/docs [upjet-provider-template]: https://github.com/upbound/upjet-provider-template [upbound/build]: https://github.com/upbound/build -[Terraform documentation for provider configuration]: https://registry.terraform.io/providers/integrations/github/latest/docs#argument-reference [github_repository]: https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository [github_branch]: https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch -[this line in controller Dockerfile]: https://github.com/upbound/upjet-provider-template/blob/main/cluster/images/upjet-provider-template/Dockerfile#L20-L28 -[terraform-plugin-sdk]: https://github.com/hashicorp/terraform-plugin-sdk -[new-resource-short]: add-new-resource-short.md -[external name configuration]: https://github.com/upbound/upjet/blob/main/docs/add-new-resource-long.md#external-name \ No newline at end of file diff --git a/docs/images/artifacts.png.license b/docs/images/artifacts.png.license new file mode 100644 index 00000000..46b2c283 --- /dev/null +++ b/docs/images/artifacts.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/images/azure-wi.png.license b/docs/images/azure-wi.png.license new file mode 100644 index 00000000..46b2c283 --- /dev/null +++ b/docs/images/azure-wi.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/images/managed-all.png.license b/docs/images/managed-all.png.license new file mode 100644 index 00000000..46b2c283 --- /dev/null +++ b/docs/images/managed-all.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/images/summary.png.license b/docs/images/summary.png.license new file mode 100644 index 00000000..46b2c283 --- /dev/null +++ b/docs/images/summary.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/images/upjet-components.png b/docs/images/upjet-components.png new file mode 100644 index 0000000000000000000000000000000000000000..e8a06e4049aae5bdedfb463c11a24bc4ba97a57f GIT binary patch literal 282066 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%0A{Z4!3B|Q!2F;C8por9X+50lZj0Wu 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 S-5tQ+mCFu4;4j|#<^KSk)p6?p literal 0 HcmV?d00001 diff --git a/docs/images/upjet-components.png.license b/docs/images/upjet-components.png.license new file mode 100644 index 00000000..46b2c283 --- /dev/null +++ b/docs/images/upjet-components.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/images/upjet-externalname.excalidraw.license b/docs/images/upjet-externalname.excalidraw.license new file mode 100644 index 00000000..46b2c283 --- /dev/null +++ b/docs/images/upjet-externalname.excalidraw.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/images/upjet-externalname.png.license b/docs/images/upjet-externalname.png.license new file mode 100644 index 00000000..46b2c283 --- /dev/null +++ b/docs/images/upjet-externalname.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/monitoring.md b/docs/monitoring.md index 314d915f..b713ef72 100644 --- a/docs/monitoring.md +++ b/docs/monitoring.md @@ -1,9 +1,15 @@ -## Monitoring the Upjet Runtime + +# Monitoring the Upjet runtime + The [Kubernetes controller-runtime] library provides a Prometheus metrics endpoint by default. The Upjet based providers including the [upbound/provider-aws], [upbound/provider-azure], [upbound/provider-azuread] and -[upbound/provider-gcp] expose [various -metrics](https://book.kubebuilder.io/reference/metrics-reference.html) +[upbound/provider-gcp] expose +[various metrics](https://book.kubebuilder.io/reference/metrics-reference.html) from the controller-runtime to help monitor the health of the various runtime components, such as the [`controller-runtime` client], the [leader election client], the [controller workqueues], etc. In addition to these metrics, each @@ -14,10 +20,11 @@ reconciliation worker goroutines. In addition to these metrics exposed by the `controller-runtime`, the Upjet based providers also expose metrics specific to the Upjet runtime. The Upjet -runtime registers some custom metrics using the [available extension -mechanism](https://book.kubebuilder.io/reference/metrics.html#publishing-additional-metrics), +runtime registers some custom metrics using the +[available extension mechanism](https://book.kubebuilder.io/reference/metrics.html#publishing-additional-metrics), and are available from the default `/metrics` endpoint of the provider pod. Here are these custom metrics exposed from the Upjet runtime: + - `upjet_terraform_cli_duration`: This is a histogram metric and reports statistics, in seconds, on how long it takes a Terraform CLI invocation to complete. @@ -33,35 +40,37 @@ characteristics of the measurements being made, such as differentiating between the CLI processes and the Terraform provider processes when counting the number of active Terraform processes running. Here is a list of labels associated with each of the above custom Upjet metrics: + - Labels associated with the `upjet_terraform_cli_duration` metric: - - `subcommand`: The `terraform` subcommand that's run, e.g., `init`, - `apply`, `plan`, `destroy`, etc. - - `mode`: The execution mode of the Terraform CLI, one of `sync` (so that - the CLI was invoked synchronously as part of a reconcile loop), `async` - (so that the CLI was invoked asynchronously, the reconciler goroutine will - poll and collect results in future). + - `subcommand`: The `terraform` subcommand that's run, e.g., `init`, `apply`, + `plan`, `destroy`, etc. + - `mode`: The execution mode of the Terraform CLI, one of `sync` (so that the + CLI was invoked synchronously as part of a reconcile loop), `async` (so that + the CLI was invoked asynchronously, the reconciler goroutine will poll and + collect results in future). - Labels associated with the `upjet_terraform_active_cli_invocations` metric: - - `subcommand`: The `terraform` subcommand that's run, e.g., `init`, - `apply`, `plan`, `destroy`, etc. - - `mode`: The execution mode of the Terraform CLI, one of `sync` (so that - the CLI was invoked synchronously as part of a reconcile loop), `async` - (so that the CLI was invoked asynchronously, the reconciler goroutine will - poll and collect results in future). + - `subcommand`: The `terraform` subcommand that's run, e.g., `init`, `apply`, + `plan`, `destroy`, etc. + - `mode`: The execution mode of the Terraform CLI, one of `sync` (so that the + CLI was invoked synchronously as part of a reconcile loop), `async` (so that + the CLI was invoked asynchronously, the reconciler goroutine will poll and + collect results in future). - Labels associated with the `upjet_terraform_running_processes` metric: - - `type`: Either `cli` for Terraform CLI (the `terraform` process) processes - or `provider` for the Terraform provider processes. Please note that this - is a best effort metric that may not be able to precisely catch & report - all relevant processes. We may, in the future, improve this if needed by - for example watching the `fork` system calls. But currently, it may prove - to be useful to watch rouge Terraform provider processes. + - `type`: Either `cli` for Terraform CLI (the `terraform` process) processes + or `provider` for the Terraform provider processes. Please note that this is + a best effort metric that may not be able to precisely catch & report all + relevant processes. We may, in the future, improve this if needed by for + example watching the `fork` system calls. But currently, it may prove to be + useful to watch rouge Terraform provider processes. - Labels associated with the `upjet_resource_ttr` metric: - - `group`, `version`, `kind` labels record the [API group, version and - kind](https://kubernetes.io/docs/reference/using-api/api-concepts/) for - the managed resource, whose - [time-to-readiness](https://github.com/crossplane/terrajet/issues/55#issuecomment-929494212) - measurement is captured. + - `group`, `version`, `kind` labels record the + [API group, version and kind](https://kubernetes.io/docs/reference/using-api/api-concepts/) + for the managed resource, whose + [time-to-readiness](https://github.com/crossplane/terrajet/issues/55#issuecomment-929494212) + measurement is captured. ## Examples + You can [export](https://book.kubebuilder.io/reference/metrics.html) all these custom metrics and the `controller-runtime` metrics from the provider pod for Prometheus. Here are some examples showing the custom metrics in action from the @@ -80,16 +89,16 @@ Prometheus console: src="https://user-images.githubusercontent.com/9376684/223299401-8f128b74-8d9c-4c82-86c5-26870385bee7.png"> - The medians (0.5-quantiles) for these observations aggregated by the mode and -Terraform subcommand being invoked: image + Terraform subcommand being invoked: image - `upjet_resource_ttr` histogram metric, showing average resource TTR for the last 10m: image - The median (0.5-quantile) for these TTR observations: + alt="image" + src="https://user-images.githubusercontent.com/9376684/223309727-d1a0f4e2-1ed2-414b-be67-478a0575ee49.png"> These samples have been collected by provisioning 10 [upbound/provider-aws] `cognitoidp.UserPool` resources by running the provider with a poll interval of @@ -98,19 +107,20 @@ These samples have been collected by provisioning 10 [upbound/provider-aws] that, they were destroyed. ## Reference + You can find a full reference of the exposed metrics from the Upjet-based providers [here](provider_metrics_help.txt). [Kubernetes controller-runtime]: - https://github.com/kubernetes-sigs/controller-runtime + https://github.com/kubernetes-sigs/controller-runtime [upbound/provider-aws]: https://github.com/upbound/provider-aws [upbound/provider-azure]: https://github.com/upbound/provider-azure [upbound/provider-azuread]: https://github.com/upbound/provider-azuread [upbound/provider-gcp]: https://github.com/upbound/provider-gcp [`controller-runtime` client]: - https://github.com/kubernetes-sigs/controller-runtime/blob/60af59f5b22335516850ca11c974c8f614d5d073/pkg/metrics/client_go_adapter.go#L40 + https://github.com/kubernetes-sigs/controller-runtime/blob/60af59f5b22335516850ca11c974c8f614d5d073/pkg/metrics/client_go_adapter.go#L40 [leader election client]: - https://github.com/kubernetes-sigs/controller-runtime/blob/60af59f5b22335516850ca11c974c8f614d5d073/pkg/metrics/leaderelection.go#L12 + https://github.com/kubernetes-sigs/controller-runtime/blob/60af59f5b22335516850ca11c974c8f614d5d073/pkg/metrics/leaderelection.go#L12 [controller workqueues]: - https://github.com/kubernetes-sigs/controller-runtime/blob/60af59f5b22335516850ca11c974c8f614d5d073/pkg/metrics/workqueue.go#L40 + https://github.com/kubernetes-sigs/controller-runtime/blob/60af59f5b22335516850ca11c974c8f614d5d073/pkg/metrics/workqueue.go#L40 [labels]: https://prometheus.io/docs/practices/naming/#labels diff --git a/docs/provider_metrics_help.txt b/docs/provider_metrics_help.txt index 638a829c..8cdc467b 100644 --- a/docs/provider_metrics_help.txt +++ b/docs/provider_metrics_help.txt @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors + +# SPDX-License-Identifier: CC-BY-4.0 + # HELP upjet_terraform_cli_duration Measures in seconds how long it takes a Terraform CLI invocation to complete # TYPE upjet_terraform_cli_duration histogram diff --git a/docs/sizing-guide.md b/docs/sizing-guide.md index 3ff164fb..cc4eb5a9 100644 --- a/docs/sizing-guide.md +++ b/docs/sizing-guide.md @@ -1,7 +1,13 @@ + + # Sizing Guide -As a result of various tests (see [provider-aws], [provider-azure], and -[provider-gcp] issues) on the new runtime features, the following average resource +As a result of various tests (see [provider-aws], [provider-azure], and +[provider-gcp] issues) on the new runtime features, the following average resource utilization has been observed. | Provider Name | Memory (Avg.) | Memory (Peak) | CPU (Avg.) | @@ -10,82 +16,83 @@ utilization has been observed. | provider-azure | 1 GB | 3.5 GB | 4 Cores | | provider-gcp | 750 MB | 2 GB | 3 Cores | -**Memory (Avg.)** represents the average memory consumption. This value was -obtained from end-to-end tests that contain the provision and deletion of many +**Memory (Avg.)** represents the average memory consumption. This value was +obtained from end-to-end tests that contain the provision and deletion of many MRs. -**Memory (Peak)** represents the peak consumption observed during end-to-end -tests. This metric is important because the terraform provider process reaches -this consumption value. So, if you use a machine with lower memory, some -`OOMKilled` errors may be observed for the provider pod. In short, these values +**Memory (Peak)** represents the peak consumption observed during end-to-end +tests. This metric is important because the terraform provider process reaches +this consumption value. So, if you use a machine with lower memory, some +`OOMKilled` errors may be observed for the provider pod. In short, these values are the minimum recommendation for memory. -**CPU (Avg.)** represents the average consumption of the CPU. The unit of this -metric is the number of cores. +**CPU (Avg.)** represents the average consumption of the CPU. The unit of this +metric is the number of cores. ## Some Relevant Command-Line Options -We have some command-line options relevant to the provider's performance. +We have some command-line options relevant to the provider's performance. -- `max-reconcile-rate`: The global maximum rate per second at which resources may -be checked for drift from the desired state. This option directly affects the +- `max-reconcile-rate`: The global maximum rate per second at which resources may +be checked for drift from the desired state. This option directly affects the number of reconciliations. There are a number of internal parameters set by this -option. `max-reconcile-rate` configures both the average rate and the burstiness -of the global token bucket rate limiter, which acts as a rate limiter across -all the managed resource controllers. It also sets each controller’s maximum -concurrent reconciles option. The default for this command-line option is 10. -Thus with this default the global rate limiter allows on average 10 -reconciliations per second allowing bursts of up to 100 (10 * `max-reconcile-rate`). -And each managed resource controller will have 10 reconciliation workers. -This parameter has an impact on the CPU utilization of the Crossplane provider. -Higher values will result in higher CPU utilization depending on a number of +option. `max-reconcile-rate` configures both the average rate and the burstiness +of the global token bucket rate limiter, which acts as a rate limiter across +all the managed resource controllers. It also sets each controller’s maximum +concurrent reconciles option. The default for this command-line option is 10. +Thus with this default the global rate limiter allows on average 10 +reconciliations per second allowing bursts of up to 100 (10 * `max-reconcile-rate`). +And each managed resource controller will have 10 reconciliation workers. +This parameter has an impact on the CPU utilization of the Crossplane provider. +Higher values will result in higher CPU utilization depending on a number of other factors. -- `poll`: Poll interval controls how often an individual resource should be -checked for drift. This option has a Go [time.ParseDuration syntax]. Examples are -`5m`, `10m`, `1h`. The default is `10m`, meaning that each managed resource -will be reconciled to check for drifts at least every 10 minutes. An update on +- `poll`: Poll interval controls how often an individual resource should be +checked for drift. This option has a Go [time.ParseDuration syntax]. Examples are +`5m`, `10m`, `1h`. The default is `10m`, meaning that each managed resource +will be reconciled to check for drifts at least every 10 minutes. An update on the managed resource will trigger an early reconciliation. -- `provider-ttl`: TTL for the terraform provider processes before they are -replaced, i.e., gracefully terminated and restarted. The Upjet runtime replaces -the shared Terraform provider processes to prevent any potential memory leaks -from them. If the value of this option is very high, the memory consumption of +- `provider-ttl`: TTL for the terraform provider processes before they are +replaced, i.e., gracefully terminated and restarted. The Upjet runtime replaces +the shared Terraform provider processes to prevent any potential memory leaks +from them. If the value of this option is very high, the memory consumption of the Crossplane provider pod may increase. The default is 100. -- `terraform-native-provider-path`: Terraform provider path for shared execution. -To disable the shared server runtime, you can set it to an empty string: -`--terraform-native-provider-path=""`. The default is determined at build time -via an environment variable specific to the provider and is the path at which +- `terraform-native-provider-path`: Terraform provider path for shared execution. +To disable the shared server runtime, you can set it to an empty string: +`--terraform-native-provider-path=""`. The default is determined at build time +via an environment variable specific to the provider and is the path at which the Terraform provider binary resides in the pod’s filesystem. ## Some Limitations + The new runtime has some limitations: -- The shared scheduler currently has no cap on the number of forked Terraform -provider processes. If you have many AWS accounts (and many corresponding -ProviderConfigs for them) in a single cluster, and if you have MRs referencing -these ProviderConfigs, the Upjet runtime will fork long-running Terraform -provider processes for each. In such a case, you may just want to disable the -shared server runtime by passing `--terraform-native-provider-path=""` as a +- The shared scheduler currently has no cap on the number of forked Terraform +provider processes. If you have many AWS accounts (and many corresponding +ProviderConfigs for them) in a single cluster, and if you have MRs referencing +these ProviderConfigs, the Upjet runtime will fork long-running Terraform +provider processes for each. In such a case, you may just want to disable the +shared server runtime by passing `--terraform-native-provider-path=""` as a command-line parameter to the provider. -- Until an expired shared Terraform provider is replaced gracefully, you may +- Until an expired shared Terraform provider is replaced gracefully, you may observe temporary errors like the following: -`cannot schedule a native provider during observe: e3415719-13ce-45fc-8c82-4753a170ea06: -cannot schedule native Terraform provider process: native provider reuse budget -has been exceeded: invocationCount: 115, ttl: 100` Such errors are temporary and -the MRs should eventually be resync’ed once the Terraform provider process is +`cannot schedule a native provider during observe: e3415719-13ce-45fc-8c82-4753a170ea06: +cannot schedule native Terraform provider process: native provider reuse budget +has been exceeded: invocationCount: 115, ttl: 100` Such errors are temporary and +the MRs should eventually be resync’ed once the Terraform provider process is gracefully replaced. -- The `--max-reconcile-rate` command-line option sets the maximum number of -concurrent reconcilers to be used with each managed resource controller. Upjet -executes certain Terrafom CLI commands asynchronously and this may result in +- The `--max-reconcile-rate` command-line option sets the maximum number of +concurrent reconcilers to be used with each managed resource controller. Upjet +executes certain Terrafom CLI commands asynchronously and this may result in more than the `max-reconcile-rate` CLI invocations to be in flight at a given time. ## Adding Configuration Parameters to ControllerConfig -To apply the configuration parameters mentioned above, +To apply the configuration parameters mentioned above, you need to add them as arguments to the `ControllerConfig`. In the example below you can see the options specified under the @@ -113,7 +120,8 @@ spec: controllerConfigRef: name: example-config ``` -Note: Due to ControllerConfig being marked as deprecated, you will get the following + +Note: Due to ControllerConfig being marked as deprecated, you will get the following warning, which you can ignore for now: ``` diff --git a/docs/testing-with-uptest.md b/docs/testing-with-uptest.md new file mode 100644 index 00000000..71919054 --- /dev/null +++ b/docs/testing-with-uptest.md @@ -0,0 +1,370 @@ +# Testing resources by using Uptest + +`Uptest` provides a framework to test resources in an end-to-end pipeline during +the resource configuration process. Together with the example manifest +generation tool, it allows us to avoid manual interventions and shortens testing +processes. + +These integration tests are costly as they create real resources in cloud +providers. So they are not executed by default. Instead, a comment should be +posted to the PR for triggering tests. + +Tests can be run by adding something like the following expressions to the +anywhere in comment: + +- `/test-examples="provider-azure/examples/kubernetes/cluster.yaml"` +- `/test-examples="provider-aws/examples/s3/bucket.yaml, provider-aws/examples/eks/cluster.yaml"` + +You can trigger a test job for an only provider. Provider that the tests will +run is determined by using the first element of the comma separated list. If the +comment contains resources that are from different providers, then these +different resources will be skipped. So, if you want to run tests more than one +provider, you must post separate comments for each provider. + +## Debugging Failed Test + +After a test failed, it is important to understand what is going wrong. For +debugging the tests, we push some collected logs to GitHub Action artifacts. +These artifacts contain the following data: + +- Dump of Kind Cluster +- Kuttl input files (Applied manifests, assertion files) +- Managed resource yaml outputs + +To download the artifacts, firstly you must go to the `Summary` page of the +relevant job: + +![images/summary.png](images/summary.png) + +Then click the `1` under the `Artifacts` button in the upper right. If the +automated tests run for more than one providers, this number will be higher. + +When you click this, you can see the `Artifacts` list of job. You can download +the artifact you are interested in by clicking it. + +![images/artifacts.png](images/artifacts.png) + +When a test fails, the first point to look is the provider container's logs. In +test environment, we run provider by using the `-d` flag to see the debug logs. +In the provider logs, it is possible to see all errors caused by the content of +the resource manifest, caused by the configuration or returned by the cloud +provider. + +Also, as you know, yaml output of the managed resources (it is located in the +`managed.yaml` of the artifact archive's root level) are very useful to catch +errors. + +If you have any doubts about the generated kuttl files, please check the +`kuttl-inputs.yaml` file in the archive's root. + +## Running Uptest locally + +For a faster feedback loop, you might want to run `uptest` locally in your +development setup. + +To do so run a special `uptest-local` target that accepts `PROVIDER_NAME` and +`EXAMPLE_LIST` arguments as in the example below. + +```console +make uptest-local PROVIDER_NAME=provider-azure EXAMPLE_LIST="provider-azure/examples/resource/resourcegroup.yaml" +``` + +You may also provide all the files in a folder like below: + +```console +make uptest-local PROVIDER_NAME=provider-aws EXAMPLE_LIST=$(find provider-aws/examples/secretsmanager/*.yaml | tr '\n' ',') +``` + +The local invocation is intentionally lightweight and skips the local cluster, +credentials and ProviderConfig setup assuming you already have it all already +configured in your environment. + +For a more heavyweight setup see `run_automated_tests` target which is used in a +centralized GitHub Actions invocation. + +## Testing Instructions and Known Error Cases + +While configuring resources, the testing effort is the longest part. Because the +characteristics of cloud providers and services can change. This test effort can +be executed in two main methods. The first one is testing the resources in a +manual way and the second one is using the `Uptest` that is an automated test +tool for Official Providers. `Uptest` provides a framework to test resources in +an end-to-end pipeline during the resource configuration process. Together with +the example manifest generation tool, it allows us to avoid manual interventions +and shortens testing processes. + +### Testing Methods + +#### Manual Test + +Configured resources can be tested by using manual method. This method generally +contains the environment preparation and creating the example manifest in the +Kubernetes cluster steps. The following steps can be followed for preparing the +environment: + +1. Obtaining a Kubernetes Cluster: For manual/local effort, generally a Kind +cluster is sufficient and can be used. For detailed information about Kind see +[this repo]. +An alternative way to obtain a cluster is: [k3d] + +2. Registering the CRDs (Custom Resource Definitions) to Cluster: We need to +apply the CRD manifests to the cluster. The relevant manifests are located in +the `package/crds` folder of provider subdirectories such as: +`provider-aws/package/crds`. For registering them please run the following +command: `kubectl apply -f package/crds` + +3. Create ProviderConfig: ProviderConfig Custom Resource contains some +configurations and credentials for the provider. For example, to connect to the +cloud provider, we use the credentials field of ProviderConfig. For creating the +ProviderConfig with correct credentials, please see [the documentation]: + +4. Start Provider: For every Custom Resource, there is a controller and these +controllers are part of the provider. So, for starting the reconciliations for +Custom Resources, we need to run the provider (collect of controllers). For +running provider, two ways can be used: + - `make run`: This make target starts the controllers. + - Running provider in IDE: Especially for debug effort, you may want to use + an IDE. For running the provider in an IDE, some program arguments are + needed to be passed. The following example is for `provider-aws`. + Values of the `--terraform-version`, `--terraform-provider-source` and + `--terraform-provider-version` options can be collected from the Makefile of + the provider: `provider-aws/Makefile` + - `-d` -> To see debug level logs. `make run` also is run the provider in + debug mode. + - `--terraform-version 1.2.1`: Terraform version. + - `--terraform-provider-source hashicorp/aws`: Provider source name. + - `--terraform-provider-version 4.15.1`: Provider version. + +Now our preparation steps are completed. This is the time for testing: + +- Create Examples and Start Testing: After completing the steps above, your +environment is ready to testing. For testing, we need to apply some example +manifests to the cluster. The manifests in the `examples-generated` folder can be +used as a first step. Before starting to change these manifests, you should move +them from `examples-generated` folder to the `examples` folder. There are two +main reasons for this. The first one is that these manifests are generated for +every `make generate` command to catch the latest changes in the resources. So +for preserving your changes moving them is necessary. The second reason is that +we use the `examples` folder as the source for keeping these manifests and using +them in our automated test effort. + +In some cases, these manifests need manual interventions so, for successfully +applying them to a cluster (passing the Kubernetes schema validation) you may +need to do some work. Possible problems you might face: + +- The generated manifest cannot provide at least one required field. So + before creating the resource you must set the required field in the manifest. +- In some fields of generated manifest the types of values cannot be matched. + For example, X field expects a string but the manifest provides an integer. + In these cases you need to provide the correct type in your example YAML + manifest. + +Successfully applying these example manifests to cluster is only the +first step. After successfully creating these Managed Resources, we need to +check whether their statuses are ready or not. So we need to expect a `True` +value for `Synced` and `Ready` conditions. To check the statuses of all created +example manifests quickly you can run the `kubectl get managed` command. We will +wait for all values to be `True` in this list: + +![img.png](images/managed-all.png) + +When all of the `Synced` and `Ready` fields are `True`, the test was +successfully completed! However, if there are some resource values that are +`False`, you need to debug this situation. The main debugging ways will be +mentioned in the next parts. + +``` +NOTE: For following the test processes in a more accurate way, we have `UpToDate` +status condition. This status condition will be visible when you set the +annotation: `upjet.upbound.io/test=true`. Without adding this annotation you +cannot see the mentioned condition. Uptest adds this annotation to the tested +resources, but if you want to see the value of conditions in your tests in your +local environment (during manual tests) you need to add this condition manually. +For the goal and details of this status condition please see this PR: +https://github.com/upbound/crossplane/pull/23 +``` + +``` +NOTE: The resources that are tried to be created may have dependencies. For +example, you might actually need resources Y and Z while trying to test resource +X. Many of the generated examples include these dependencies. However, in some +cases, there may be missing dependencies. In these cases, please add the +relevant dependencies to your example manifest. This is important both for you +to pass the tests and to provide the correct manifests. +``` + +#### Automated Tests - Uptest + +Configured resources can be tested also by using `Uptest`. We can also separate +this part into two main application methods: + +##### Using Uptest in GitHub Actions + +We have a GitHub workflow `Automated Tests`. This is an integration test for +Official Providers. This workflow prepares the environment (provisioning Kind +cluster, creating ProviderConfig, installing Provider, etc.) and runs the Uptest +with the input manifest list that will be given by the person who triggers the +test. + +This `Automated Tests` job can be triggered from the PR that contains the +configuration test works for the related resources/groups. For triggering the +test, you need to leave a comment in the PR in the following format: + +`/test-examples="provider-aws/examples/s3/bucket.yaml, provider-aws/examples/eks/cluster.yaml"` + +We test using the API group approach for `Automated-Tests`. So, we wait for the +entire API group's resources to pass the test in a single test run. This means +that while triggering tests, leaving the following type of comment is expected: + +`/test-examples="provider-aws/examples/s3` + +This comment will test all the examples of the `s3` group. + +**Ignoring Some Resources in Automated Tests** + +Some resources require manual intervention such as providing valid public keys +or using on-the-fly values. These cases can be handled in manual tests, but in +cases where we cannot provide generic values for automated tests, we can skip +some resources in the tests of the relevant group via an annotation: + +```yaml +upjet.upbound.io/manual-intervention: "The Certificate needs to be provisioned successfully which requires a real domain." +``` + +The key is important for skipping, we are checking this `upjet.upbound.io/manual-intervention` +annotation key and if is in there, we skip the related resource. The value is also +important to see why we skip this resource. + +``` +NOTE: For resources that are ignored during Automated Tests, manual testing is a +must. Because we need to make sure that all resources published in the `v1beta1` +version are working. +``` + +At the end of the tests, Uptest will provide a report for you. And also for all +GitHub Actions, we will have an artifact that contains logs for debugging. For +details please see [here]. + +##### Using Uptest in Local Dev Environment + +The main difference between running `Uptest` from your local environment and +running GitHub Actions is that the environment is also prepared during GitHub +Actions. During your tests on local, `Uptest` is only responsible for creating +instance manifests and assertions of them. Therefore, all the preparation steps +mentioned in the Manual Testing section are also necessary for tests performed +using `Uptest` locally. + +After preparing the testing environment, you should run the following command to +trigger tests locally by using `Uptest`: + +Example for single file test: + +``` +make uptest-local PROVIDER_NAME=provider-aws EXAMPLE_LIST=provider-aws/examples/secretsmanager/secret.yaml +``` + +Example of whole API Group test: + +``` +make uptest-local PROVIDER_NAME=provider-aws EXAMPLE_LIST=$(find provider-aws/examples/secretsmanager/*.yaml | tr '\n' ',') +``` + +### Debugging Tests + +Whether the tests fail using `Uptest` or when testing manually, the steps to be +followed are the same. What finally failed was a Managed Resource tested against +Official Providers. In this case, the first thing to do is to check the manifest +of the failing resource (where the value of `Synced` or `Ready` condition is +`False`) in the cluster. + +If the test was in your local environment, you can check the current state of +the resource by using the following command: +`kubectl get network.compute.gcp.upbound.io/example-network-1 -o yaml` + +If the test ran in the GitHub Actions, you need to check the action artifact +mentioned in the previous part of the documentation. + +The second important point to understand the problem is the provider logs. If +the test was in your local environment, you need to check the `make run` or IDE +logs. If testing was in GitHub Actions, you need to check the action artifact. +It contains the cluster dump that has the provider logs. + +## Known Error Cases + +1. `prevent_destroy` Case: In some cases, when unexpected changes or situations +occur in the resources, Terraform tries to delete the related resource and +create it again. However, in order to prevent this situation, the resources are +configurable. In this context, the name of the field where you can provide this +control is `prevent_destroy`. Please see details of [Terraform Resource Lifecycle]. +For resources in Official Providers, this value defaults to `true`. So the +deletion of the resource is blocked. + +Encountering this situation (i.e. Terraform trying to delete and recreate the +resource) is not normal and may indicate a specific error. Some possible +problems could be: + +- As a result of overriding the constructed ID after Terraform calls, Terraform + could not match the IDs and tries to recreate the resource. Please see + [this issue] for details. In this type of cases, you need to review your + external name configuration. +- Crossplane's concept of [Late Initialization] may cause some side effects. + One of them is while late initialization, filling a field that is not initially + filled on the manifest may cause the resource to be destroyed and recreated. + In such a case, it should be evaluated that which field's value is set will + cause such an error. During this evaluation, it will be necessary to make use + of the terraform registry document. In the end, the field that is thought to + solve the problem is put into the ignore list using the + [late initialization configuration] and the test is repeated from the + beginning. +- Some resources fall into `tainted` state as a result of certain steps in the + creation process fail. Please see [tainted issue] for details. + +2. External Name Configuration Related Errors: The most common known issue is +errors in the external name configuration. A clear error message regarding this +situation may not be visible. Many error messages can be related to an incorrect +external name configuration. Such as, a field cannot be read properly from the +parameter map, there are unexpected fields in the generated `main.tf.json` file, +etc. + +Therefore, when debugging a non-ready resource; if you do not see errors +returned by the Cloud API related to the constraints or characteristics of the +service (for example, you are stuck on the creation limit of this resource in +this region, or the use of the relevant field for this resource depends on the +following conditions etc.), the first point to check is external name +configuration. + +3. Late Initialization Errors: Late Initialization is one of the key concepts of +Crossplane. It allows for some values that are not initially located in the +resource's manifest to be filled with the values returned by the cloud providers. + +As a side effect of this, some fields conflict each other. In this case, a +detailed error message is usually displayed about which fields conflict with +each other. In this case, the relevant field should be skipped by [these steps]. + +4. Provider Service Specific Errors: Every cloud provider and every service has +its own features and behavior. Therefore, you may see special error messages in +the status of the resources from time to time. These may say that you are out of +the allowed values in some fields of the resource, or that you need to enable +the relevant service, etc. In such cases, please review your example manifest +and try to find the appropriate example. + +``` +IMPORTANT NOTE: `make reviewable` and `kubectl apply -f package/crds` commands +must be run after any change that will affect the schema or controller of the +configured/tested resource. + +In addition, the provider needs to be restarted after the changes in the +controllers, because the controller change actually corresponds to the changes +made in the running code. +``` + +[this repo]: https://github.com/kubernetes-sigs/kind +[the documentation]: https://crossplane.io/docs/v1.9/getting-started/install-configure.html#install-configuration-package +[here]: https://github.com/upbound/official-providers/blob/main/docs/testing-resources-by-using-uptest.md#debugging-failed-test +[these steps]: https://github.com/upbound/crossplane/blob/main/docs/configuring-a-resource.md#late-initialization-configuration +[late initialization configuration]: https://github.com/upbound/crossplane/blob/main/docs/configuring-a-resource.md#late-initialization-configuration +[Terraform Resource Lifecycle]: https://learn.hashicorp.com/tutorials/terraform/resource-lifecycle +[this issue]: https://github.com/upbound/crossplane/issues/32 +[Late Initialization]: https://crossplane.io/docs/v1.9/concepts/managed-resources.html#late-initialization +[tainted issue]: https://github.com/upbound/crossplane/issues/80 +[k3d]: https://k3d.io/ diff --git a/go.mod b/go.mod index 10ca0ba8..5f5791f5 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,15 @@ -module github.com/upbound/upjet +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: CC0-1.0 + +module github.com/crossplane/upjet go 1.20 require ( dario.cat/mergo v1.0.0 github.com/antchfx/htmlquery v1.2.4 - github.com/crossplane/crossplane v1.10.0 + github.com/crossplane/crossplane v1.13.2 github.com/crossplane/crossplane-runtime v0.20.0 github.com/fatih/camelcase v1.0.0 github.com/golang/mock v1.6.0 diff --git a/go.sum.license b/go.sum.license new file mode 100644 index 00000000..ec785f69 --- /dev/null +++ b/go.sum.license @@ -0,0 +1,4 @@ + +SPDX-FileCopyrightText: 2023 The Crossplane Authors + +SPDX-License-Identifier: CC0-1.0 diff --git a/hack/boilerplate.txt b/hack/boilerplate.txt index d274fe3d..80110499 100644 --- a/hack/boilerplate.txt +++ b/hack/boilerplate.txt @@ -1,13 +1,3 @@ -Copyright 2021 Upbound Inc. +SPDX-FileCopyrightText: 2023 The Crossplane Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/pkg/common.go b/pkg/common.go index d99da481..1e55f5a1 100644 --- a/pkg/common.go +++ b/pkg/common.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package pkg import "strings" @@ -18,7 +22,7 @@ func FilterDescription(description, keyword string) string { } } if len(result) == 0 { - return strings.ReplaceAll(strings.ToLower(description), keyword, "Upbound official provider") + return strings.ReplaceAll(strings.ToLower(description), keyword, "provider") } return strings.Join(result, descriptionSeparator) } diff --git a/pkg/config/common.go b/pkg/config/common.go index 18756585..ac21fd66 100644 --- a/pkg/config/common.go +++ b/pkg/config/common.go @@ -1,16 +1,15 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package config import ( "strings" + "github.com/crossplane/upjet/pkg/registry" + tjname "github.com/crossplane/upjet/pkg/types/name" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/upbound/upjet/pkg/registry" - tjname "github.com/upbound/upjet/pkg/types/name" ) const ( @@ -34,7 +33,7 @@ var ( "apis/v1alpha1", "apis/v1beta1", }, - //nolint:staticcheck + Controller: []string{ // Default package for ProviderConfig controllers "internal/controller/providerconfig", diff --git a/pkg/config/common_test.go b/pkg/config/common_test.go index ac5a9601..db3fe5e5 100644 --- a/pkg/config/common_test.go +++ b/pkg/config/common_test.go @@ -1,17 +1,16 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package config import ( "testing" + "github.com/crossplane/upjet/pkg/registry" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/upbound/upjet/pkg/registry" ) func TestDefaultResource(t *testing.T) { diff --git a/pkg/config/externalname.go b/pkg/config/externalname.go index 5532d181..7b87884a 100644 --- a/pkg/config/externalname.go +++ b/pkg/config/externalname.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package config diff --git a/pkg/config/externalname_test.go b/pkg/config/externalname_test.go index 52f7ae0d..c9f39b2a 100644 --- a/pkg/config/externalname_test.go +++ b/pkg/config/externalname_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package config @@ -8,10 +8,10 @@ import ( "context" "testing" - "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/google/go-cmp/cmp" "github.com/crossplane/crossplane-runtime/pkg/errors" - "github.com/google/go-cmp/cmp" + "github.com/crossplane/crossplane-runtime/pkg/test" ) func TestGetExternalNameFromTemplated(t *testing.T) { diff --git a/pkg/config/provider.go b/pkg/config/provider.go index 56664ef5..8c6e1328 100644 --- a/pkg/config/provider.go +++ b/pkg/config/provider.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package config @@ -8,11 +8,10 @@ import ( "fmt" "regexp" + "github.com/crossplane/upjet/pkg/registry" + conversiontfjson "github.com/crossplane/upjet/pkg/types/conversion/tfjson" tfjson "github.com/hashicorp/terraform-json" "github.com/pkg/errors" - - "github.com/upbound/upjet/pkg/registry" - conversiontfjson "github.com/upbound/upjet/pkg/types/conversion/tfjson" ) // ResourceConfiguratorFn is a function that implements the ResourceConfigurator @@ -203,7 +202,7 @@ func WithMainTemplate(template string) ProviderOption { // NewProvider builds and returns a new Provider from provider // tfjson schema, that is generated using Terraform CLI with: // `terraform providers schema --json` -func NewProvider(schema []byte, prefix string, modulePath string, metadata []byte, opts ...ProviderOption) *Provider { // nolint:gocyclo +func NewProvider(schema []byte, prefix string, modulePath string, metadata []byte, opts ...ProviderOption) *Provider { //nolint:gocyclo ps := tfjson.ProviderSchemas{} if err := ps.UnmarshalJSON(schema); err != nil { panic(err) diff --git a/pkg/config/resource.go b/pkg/config/resource.go index 1d021f4f..e97b1809 100644 --- a/pkg/config/resource.go +++ b/pkg/config/resource.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package config @@ -9,6 +9,7 @@ import ( "fmt" "time" + "github.com/crossplane/upjet/pkg/registry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/util/json" @@ -20,8 +21,6 @@ import ( "github.com/crossplane/crossplane-runtime/pkg/fieldpath" "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/upbound/upjet/pkg/registry" ) // SetIdentifierArgumentsFn sets the name of the resource in Terraform attributes map, diff --git a/pkg/config/resource_test.go b/pkg/config/resource_test.go index c8f5e94e..4663781b 100644 --- a/pkg/config/resource_test.go +++ b/pkg/config/resource_test.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package config import ( diff --git a/pkg/controller/api.go b/pkg/controller/api.go index da17a591..f5a5c5f5 100644 --- a/pkg/controller/api.go +++ b/pkg/controller/api.go @@ -1,29 +1,15 @@ -/* - Copyright 2021 Upbound Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package controller import ( "context" - "github.com/upbound/upjet/pkg/controller/handler" - - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" - + "github.com/crossplane/upjet/pkg/controller/handler" + "github.com/crossplane/upjet/pkg/resource" + "github.com/crossplane/upjet/pkg/terraform" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -31,8 +17,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ctrl "sigs.k8s.io/controller-runtime/pkg/manager" - "github.com/upbound/upjet/pkg/resource" - "github.com/upbound/upjet/pkg/terraform" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" ) const ( diff --git a/pkg/controller/api_test.go b/pkg/controller/api_test.go index cfcbbaeb..b42e662c 100644 --- a/pkg/controller/api_test.go +++ b/pkg/controller/api_test.go @@ -1,18 +1,6 @@ -/* - Copyright 2021 Upbound Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package controller @@ -20,6 +8,9 @@ import ( "context" "testing" + "github.com/crossplane/upjet/pkg/resource" + "github.com/crossplane/upjet/pkg/resource/fake" + tjerrors "github.com/crossplane/upjet/pkg/terraform/errors" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" "sigs.k8s.io/controller-runtime/pkg/client" @@ -28,10 +19,6 @@ import ( xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" xpfake "github.com/crossplane/crossplane-runtime/pkg/resource/fake" "github.com/crossplane/crossplane-runtime/pkg/test" - - "github.com/upbound/upjet/pkg/resource" - "github.com/upbound/upjet/pkg/resource/fake" - tjerrors "github.com/upbound/upjet/pkg/terraform/errors" ) func TestAPICallbacksCreate(t *testing.T) { diff --git a/pkg/controller/external.go b/pkg/controller/external.go index f8b6ce60..62580935 100644 --- a/pkg/controller/external.go +++ b/pkg/controller/external.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package controller @@ -8,22 +8,21 @@ import ( "context" "time" - tferrors "github.com/upbound/upjet/pkg/terraform/errors" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/controller/handler" + "github.com/crossplane/upjet/pkg/metrics" + "github.com/crossplane/upjet/pkg/resource" + "github.com/crossplane/upjet/pkg/resource/json" + "github.com/crossplane/upjet/pkg/terraform" + tferrors "github.com/crossplane/upjet/pkg/terraform/errors" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/logging" "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/util/sets" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/controller/handler" - "github.com/upbound/upjet/pkg/metrics" - "github.com/upbound/upjet/pkg/resource" - "github.com/upbound/upjet/pkg/resource/json" - "github.com/upbound/upjet/pkg/terraform" ) const ( diff --git a/pkg/controller/external_test.go b/pkg/controller/external_test.go index cb76b511..7710e705 100644 --- a/pkg/controller/external_test.go +++ b/pkg/controller/external_test.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package controller @@ -18,6 +8,17 @@ import ( "context" "testing" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/resource" + "github.com/crossplane/upjet/pkg/resource/fake" + "github.com/crossplane/upjet/pkg/resource/json" + "github.com/crossplane/upjet/pkg/terraform" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/logging" xpmeta "github.com/crossplane/crossplane-runtime/pkg/meta" @@ -25,17 +26,6 @@ import ( xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" xpfake "github.com/crossplane/crossplane-runtime/pkg/resource/fake" "github.com/crossplane/crossplane-runtime/pkg/test" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/resource" - "github.com/upbound/upjet/pkg/resource/fake" - "github.com/upbound/upjet/pkg/resource/json" - "github.com/upbound/upjet/pkg/terraform" ) const ( diff --git a/pkg/controller/handler/eventhandler.go b/pkg/controller/handler/eventhandler.go index d5f14a01..734bc1b6 100644 --- a/pkg/controller/handler/eventhandler.go +++ b/pkg/controller/handler/eventhandler.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package handler @@ -18,12 +8,13 @@ import ( "context" "sync" - "github.com/crossplane/crossplane-runtime/pkg/logging" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/crossplane/crossplane-runtime/pkg/logging" ) // EventHandler handles Kubernetes events by queueing reconcile requests for diff --git a/pkg/controller/interfaces.go b/pkg/controller/interfaces.go index 04f638fc..7d4fc4a0 100644 --- a/pkg/controller/interfaces.go +++ b/pkg/controller/interfaces.go @@ -1,15 +1,15 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package controller import ( "context" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/resource" - "github.com/upbound/upjet/pkg/terraform" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/resource" + "github.com/crossplane/upjet/pkg/terraform" ) // TODO(muvaf): It's a bit weird that the functions return the struct of a diff --git a/pkg/controller/options.go b/pkg/controller/options.go index ae265f4c..32269dd0 100644 --- a/pkg/controller/options.go +++ b/pkg/controller/options.go @@ -1,17 +1,17 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package controller import ( "crypto/tls" - "github.com/crossplane/crossplane-runtime/pkg/controller" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/terraform" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/terraform" + "github.com/crossplane/crossplane-runtime/pkg/controller" ) // Options contains incriminating options for a given Upjet controller instance. diff --git a/pkg/examples/example.go b/pkg/examples/example.go index 2fcfcfe8..8c6981e6 100644 --- a/pkg/examples/example.go +++ b/pkg/examples/example.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package examples @@ -20,11 +20,11 @@ import ( "github.com/pkg/errors" "sigs.k8s.io/yaml" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/registry/reference" - "github.com/upbound/upjet/pkg/resource/json" - tjtypes "github.com/upbound/upjet/pkg/types" - "github.com/upbound/upjet/pkg/types/name" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/registry/reference" + "github.com/crossplane/upjet/pkg/resource/json" + tjtypes "github.com/crossplane/upjet/pkg/types" + "github.com/crossplane/upjet/pkg/types/name" ) var ( diff --git a/pkg/generate.go b/pkg/generate.go index 47466234..c2ec639b 100644 --- a/pkg/generate.go +++ b/pkg/generate.go @@ -1,16 +1,6 @@ -// Copyright 2021 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 //go:build generate // +build generate diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 31d73f0b..4e71dc7d 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package metrics diff --git a/pkg/migration/api_steps.go b/pkg/migration/api_steps.go index 6ae56fec..f077fc89 100644 --- a/pkg/migration/api_steps.go +++ b/pkg/migration/api_steps.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration @@ -18,13 +8,14 @@ import ( "fmt" "strconv" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/meta" "github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim" "github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) const ( @@ -69,7 +60,7 @@ func (pg *PlanGenerator) addStepsForManagedResource(u *UnstructuredWithMetadata) if _, ok, err := toManagedResource(pg.registry.scheme, u.Object); err != nil || !ok { // not a managed resource or unable to determine // whether it's a managed resource - return nil // nolint:nilerr + return nil //nolint:nilerr } } qName := getQualifiedName(u.Object) @@ -310,14 +301,14 @@ func (pg *PlanGenerator) stepEditClaims(claims []UnstructuredWithMetadata, conve // NOTE: to cover different migration scenarios, we may use // "migration templates" instead of a static plan. But a static plan should be // fine as a start. -func (pg *PlanGenerator) stepAPI(s step) *Step { // nolint:gocyclo // all steps under a single clause for readability +func (pg *PlanGenerator) stepAPI(s step) *Step { //nolint:gocyclo // all steps under a single clause for readability stepKey := strconv.Itoa(int(s)) if pg.Plan.Spec.stepMap[stepKey] != nil { return pg.Plan.Spec.stepMap[stepKey] } pg.Plan.Spec.stepMap[stepKey] = &Step{} - switch s { // nolint:exhaustive + switch s { //nolint:exhaustive case stepPauseManaged: setPatchStep("pause-managed", pg.Plan.Spec.stepMap[stepKey]) diff --git a/pkg/migration/categorical_steps.go b/pkg/migration/categorical_steps.go index cbec4287..965c5cae 100644 --- a/pkg/migration/categorical_steps.go +++ b/pkg/migration/categorical_steps.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration diff --git a/pkg/migration/configurationmetadata_steps.go b/pkg/migration/configurationmetadata_steps.go index 3cb9b626..ea18ff33 100644 --- a/pkg/migration/configurationmetadata_steps.go +++ b/pkg/migration/configurationmetadata_steps.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration @@ -18,9 +8,10 @@ import ( "fmt" "strconv" + "github.com/pkg/errors" + xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1" xpmetav1alpha1 "github.com/crossplane/crossplane/apis/pkg/meta/v1alpha1" - "github.com/pkg/errors" ) const ( @@ -113,7 +104,7 @@ func (pg *PlanGenerator) configurationSubStep(s step) string { return pg.subSteps[s] } -func (pg *PlanGenerator) stepConfigurationWithSubStep(s step, newSubStep bool) *Step { // nolint:gocyclo // easy to follow all steps here +func (pg *PlanGenerator) stepConfigurationWithSubStep(s step, newSubStep bool) *Step { //nolint:gocyclo // easy to follow all steps here stepKey := strconv.Itoa(int(s)) if newSubStep { stepKey = fmt.Sprintf("%s.%s", stepKey, pg.configurationSubStep(s)) @@ -123,7 +114,7 @@ func (pg *PlanGenerator) stepConfigurationWithSubStep(s step, newSubStep bool) * } pg.Plan.Spec.stepMap[stepKey] = &Step{} - switch s { // nolint:gocritic,exhaustive + switch s { //nolint:exhaustive case stepOrphanMRs: setPatchStep("deletion-policy-orphan", pg.Plan.Spec.stepMap[stepKey]) case stepRevertOrphanMRs: diff --git a/pkg/migration/configurationpackage_steps.go b/pkg/migration/configurationpackage_steps.go index a8231a1c..d8103eaa 100644 --- a/pkg/migration/configurationpackage_steps.go +++ b/pkg/migration/configurationpackage_steps.go @@ -1,28 +1,17 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration import ( "fmt" - "github.com/crossplane/crossplane-runtime/pkg/fieldpath" - - v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/pkg/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" ) const ( diff --git a/pkg/migration/converter.go b/pkg/migration/converter.go index 51ef31f2..b6cbe41f 100644 --- a/pkg/migration/converter.go +++ b/pkg/migration/converter.go @@ -1,30 +1,12 @@ -// Copyright 2022 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration import ( "fmt" - "github.com/crossplane/crossplane-runtime/pkg/fieldpath" - xpmeta "github.com/crossplane/crossplane-runtime/pkg/meta" - "github.com/crossplane/crossplane-runtime/pkg/resource" - xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1" - xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1" - xpmetav1alpha1 "github.com/crossplane/crossplane/apis/pkg/meta/v1alpha1" - xppkgv1 "github.com/crossplane/crossplane/apis/pkg/v1" - xppkgv1beta1 "github.com/crossplane/crossplane/apis/pkg/v1beta1" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -33,6 +15,16 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/json" k8sjson "sigs.k8s.io/json" + + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" + xpmeta "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/crossplane-runtime/pkg/resource" + + xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1" + xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1" + xpmetav1alpha1 "github.com/crossplane/crossplane/apis/pkg/meta/v1alpha1" + xppkgv1 "github.com/crossplane/crossplane/apis/pkg/v1" + xppkgv1beta1 "github.com/crossplane/crossplane/apis/pkg/v1beta1" ) const ( @@ -277,7 +269,7 @@ func toPackageLock(u unstructured.Unstructured) (*xppkgv1beta1.Lock, error) { func ConvertComposedTemplatePatchesMap(sourceTemplate xpv1.ComposedTemplate, conversionMap map[string]string) []xpv1.Patch { var patchesToAdd []xpv1.Patch for _, p := range sourceTemplate.Patches { - switch p.Type { // nolint:exhaustive + switch p.Type { //nolint:exhaustive case xpv1.PatchTypeFromCompositeFieldPath, xpv1.PatchTypeCombineFromComposite, "": { if p.ToFieldPath != nil { diff --git a/pkg/migration/errors.go b/pkg/migration/errors.go index b28b7b9c..84d6d255 100644 --- a/pkg/migration/errors.go +++ b/pkg/migration/errors.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration diff --git a/pkg/migration/exec_steps.go b/pkg/migration/exec_steps.go index ee414a31..bfd31725 100644 --- a/pkg/migration/exec_steps.go +++ b/pkg/migration/exec_steps.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration diff --git a/pkg/migration/fake/mocks/mock.go b/pkg/migration/fake/mocks/mock.go index da753ea3..607703b1 100644 --- a/pkg/migration/fake/mocks/mock.go +++ b/pkg/migration/fake/mocks/mock.go @@ -1,16 +1,6 @@ -// Copyright 2021 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. // Source: github.com/crossplane/crossplane-runtime/pkg/resource (interfaces: Managed) diff --git a/pkg/migration/fake/objects.go b/pkg/migration/fake/objects.go index e3c64885..c9b1a59e 100644 --- a/pkg/migration/fake/objects.go +++ b/pkg/migration/fake/objects.go @@ -1,26 +1,16 @@ -// Copyright 2022 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 //go:generate go run github.com/golang/mock/mockgen -copyright_file ../../../hack/boilerplate.txt -destination=./mocks/mock.go -package mocks github.com/crossplane/crossplane-runtime/pkg/resource Managed package fake import ( - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/upjet/pkg/migration/fake/mocks" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/upbound/upjet/pkg/migration/fake/mocks" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" ) const ( diff --git a/pkg/migration/filesystem.go b/pkg/migration/filesystem.go index b79a612b..ac52ae5b 100644 --- a/pkg/migration/filesystem.go +++ b/pkg/migration/filesystem.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package migration import ( diff --git a/pkg/migration/filesystem_test.go b/pkg/migration/filesystem_test.go index 8cf452e8..92b9e096 100644 --- a/pkg/migration/filesystem_test.go +++ b/pkg/migration/filesystem_test.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package migration import ( diff --git a/pkg/migration/fork_executor.go b/pkg/migration/fork_executor.go index 93b75088..61e46281 100644 --- a/pkg/migration/fork_executor.go +++ b/pkg/migration/fork_executor.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration diff --git a/pkg/migration/fork_executor_test.go b/pkg/migration/fork_executor_test.go index a0c57c2a..cc146156 100644 --- a/pkg/migration/fork_executor_test.go +++ b/pkg/migration/fork_executor_test.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration diff --git a/pkg/migration/interfaces.go b/pkg/migration/interfaces.go index a9aff16d..a8771588 100644 --- a/pkg/migration/interfaces.go +++ b/pkg/migration/interfaces.go @@ -1,21 +1,12 @@ -// Copyright 2022 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration import ( "github.com/crossplane/crossplane-runtime/pkg/resource" + xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1" xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1" xpmetav1alpha1 "github.com/crossplane/crossplane/apis/pkg/meta/v1alpha1" diff --git a/pkg/migration/kubernetes.go b/pkg/migration/kubernetes.go index 39b2c3ff..67cf4a6a 100644 --- a/pkg/migration/kubernetes.go +++ b/pkg/migration/kubernetes.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package migration import ( @@ -8,17 +12,16 @@ import ( "strings" "time" - "k8s.io/cli-runtime/pkg/resource" - "k8s.io/client-go/rest" - "github.com/pkg/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/discovery" "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" "k8s.io/client-go/restmapper" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" diff --git a/pkg/migration/kubernetes_test.go b/pkg/migration/kubernetes_test.go index 41a028fa..bddedcc7 100644 --- a/pkg/migration/kubernetes_test.go +++ b/pkg/migration/kubernetes_test.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package migration import ( diff --git a/pkg/migration/package_lock_steps.go b/pkg/migration/package_lock_steps.go index f14d3422..e3336cc3 100644 --- a/pkg/migration/package_lock_steps.go +++ b/pkg/migration/package_lock_steps.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration diff --git a/pkg/migration/patches.go b/pkg/migration/patches.go index 9b96c444..d5d84188 100644 --- a/pkg/migration/patches.go +++ b/pkg/migration/patches.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration @@ -21,10 +11,11 @@ import ( "regexp" "strings" - xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + + xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1" ) var ( @@ -67,7 +58,7 @@ func (pg *PlanGenerator) removeInvalidPatches(gvkSource, gvkTarget schema.GroupV var patches []xpv1.Patch for _, p := range targetTemplate.Patches { s := source - switch p.Type { // nolint:exhaustive + switch p.Type { //nolint:exhaustive case xpv1.PatchTypePatchSet: ps := getNamedPatchSet(p.PatchSetName, patchSets) if ps == nil { @@ -122,7 +113,7 @@ func assertPatchSchemaConformance(p xpv1.Patch, source, target any) (bool, error // because this is defaulting logic and what we default can be overridden // later in the convert, the type switch is not exhaustive // TODO: consider processing other patch types - switch p.Type { // nolint:exhaustive + switch p.Type { //nolint:exhaustive case xpv1.PatchTypeFromCompositeFieldPath, "": // the default type targetPath = p.ToFieldPath case xpv1.PatchTypeToCompositeFieldPath: @@ -167,7 +158,7 @@ func isRawExtension(source, target reflect.Type) bool { // assertNameAndTypeAtPath asserts that the migration source and target // templates both have the same kind for the type at the specified path. // Also validates the specific path is valid for the source. -func assertNameAndTypeAtPath(source, target reflect.Type, pathComponents []string) (bool, error) { // nolint:gocyclo +func assertNameAndTypeAtPath(source, target reflect.Type, pathComponents []string) (bool, error) { //nolint:gocyclo if len(pathComponents) < 1 { return compareKinds(source, target), nil } @@ -231,7 +222,7 @@ func compareKinds(s, t reflect.Type) bool { // with the specified serialized (JSON) name. Returns a nil (and a nil error) // if a field with the specified serialized name is not found // in the specified type. -func getFieldWithSerializedName(t reflect.Type, name string) (*reflect.StructField, error) { // nolint:gocyclo +func getFieldWithSerializedName(t reflect.Type, name string) (*reflect.StructField, error) { //nolint:gocyclo if t.Kind() == reflect.Pointer { t = t.Elem() } diff --git a/pkg/migration/patches_test.go b/pkg/migration/patches_test.go index 9c62d86e..02142bf9 100644 --- a/pkg/migration/patches_test.go +++ b/pkg/migration/patches_test.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration diff --git a/pkg/migration/plan_executor.go b/pkg/migration/plan_executor.go index fc720b23..63f4ff38 100644 --- a/pkg/migration/plan_executor.go +++ b/pkg/migration/plan_executor.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration diff --git a/pkg/migration/plan_generator.go b/pkg/migration/plan_generator.go index b74fb3af..fcd8d4a6 100644 --- a/pkg/migration/plan_generator.go +++ b/pkg/migration/plan_generator.go @@ -1,37 +1,28 @@ -// Copyright 2022 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration import ( "fmt" - "reflect" "time" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/rand" + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" "github.com/crossplane/crossplane-runtime/pkg/resource" + xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1" xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1" xpmetav1alpha1 "github.com/crossplane/crossplane/apis/pkg/meta/v1alpha1" xppkgv1 "github.com/crossplane/crossplane/apis/pkg/v1" xppkgv1beta1 "github.com/crossplane/crossplane/apis/pkg/v1beta1" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/rand" ) const ( @@ -453,7 +444,7 @@ func assertMetadataName(parentName string, resources []resource.Managed) { } } -func (pg *PlanGenerator) convertComposition(o UnstructuredWithMetadata) (*UnstructuredWithMetadata, bool, error) { // nolint:gocyclo +func (pg *PlanGenerator) convertComposition(o UnstructuredWithMetadata) (*UnstructuredWithMetadata, bool, error) { //nolint:gocyclo convertedPS, err := pg.convertPatchSets(o) if err != nil { return nil, false, errors.Wrap(err, "failed to convert patch sets") diff --git a/pkg/migration/plan_generator_test.go b/pkg/migration/plan_generator_test.go index 4b718ac3..eb16bdff 100644 --- a/pkg/migration/plan_generator_test.go +++ b/pkg/migration/plan_generator_test.go @@ -1,16 +1,6 @@ -// Copyright 2022 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration @@ -21,13 +11,7 @@ import ( "regexp" "testing" - xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" - "github.com/crossplane/crossplane-runtime/pkg/test" - v1 "github.com/crossplane/crossplane/apis/apiextensions/v1" - xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1" - xpmetav1alpha1 "github.com/crossplane/crossplane/apis/pkg/meta/v1alpha1" - xppkgv1 "github.com/crossplane/crossplane/apis/pkg/v1" - xppkgv1beta1 "github.com/crossplane/crossplane/apis/pkg/v1beta1" + "github.com/crossplane/upjet/pkg/migration/fake" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -37,7 +21,14 @@ import ( "k8s.io/apimachinery/pkg/util/yaml" k8syaml "sigs.k8s.io/yaml" - "github.com/upbound/upjet/pkg/migration/fake" + xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/test" + + v1 "github.com/crossplane/crossplane/apis/apiextensions/v1" + xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1" + xpmetav1alpha1 "github.com/crossplane/crossplane/apis/pkg/meta/v1alpha1" + xppkgv1 "github.com/crossplane/crossplane/apis/pkg/v1" + xppkgv1beta1 "github.com/crossplane/crossplane/apis/pkg/v1beta1" ) func TestGeneratePlan(t *testing.T) { diff --git a/pkg/migration/plan_steps.go b/pkg/migration/plan_steps.go index 4e29ed56..bab12351 100644 --- a/pkg/migration/plan_steps.go +++ b/pkg/migration/plan_steps.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration diff --git a/pkg/migration/provider_package_steps.go b/pkg/migration/provider_package_steps.go index 9b75f673..5064b086 100644 --- a/pkg/migration/provider_package_steps.go +++ b/pkg/migration/provider_package_steps.go @@ -1,16 +1,6 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration @@ -18,9 +8,10 @@ import ( "fmt" "strings" - "github.com/crossplane/crossplane-runtime/pkg/fieldpath" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" ) const ( diff --git a/pkg/migration/registry.go b/pkg/migration/registry.go index 51c582df..617dfd7b 100644 --- a/pkg/migration/registry.go +++ b/pkg/migration/registry.go @@ -1,31 +1,23 @@ -// Copyright 2022 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration import ( "regexp" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "github.com/crossplane/crossplane-runtime/pkg/resource" + xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1" xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1" xpmetav1alpha1 "github.com/crossplane/crossplane/apis/pkg/meta/v1alpha1" xppkgv1 "github.com/crossplane/crossplane/apis/pkg/v1" xppkgv1beta1 "github.com/crossplane/crossplane/apis/pkg/v1beta1" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" ) var ( diff --git a/pkg/migration/testdata/plan/claim.yaml b/pkg/migration/testdata/plan/claim.yaml index f5231590..1fd8ac65 100644 --- a/pkg/migration/testdata/plan/claim.yaml +++ b/pkg/migration/testdata/plan/claim.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: test.com/v1alpha1 kind: MyResource metadata: diff --git a/pkg/migration/testdata/plan/composition.yaml b/pkg/migration/testdata/plan/composition.yaml index d66c2477..9c7ca023 100644 --- a/pkg/migration/testdata/plan/composition.yaml +++ b/pkg/migration/testdata/plan/composition.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: apiextensions.crossplane.io/v1 kind: Composition metadata: diff --git a/pkg/migration/testdata/plan/configurationpkgv1.yaml b/pkg/migration/testdata/plan/configurationpkgv1.yaml index 972b8c3d..ee2aa48f 100644 --- a/pkg/migration/testdata/plan/configurationpkgv1.yaml +++ b/pkg/migration/testdata/plan/configurationpkgv1.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Configuration metadata: name: platform-ref-aws spec: - package: xpkg.upbound.io/upbound/provider-ref-aws:v0.1.0 \ No newline at end of file + package: xpkg.upbound.io/upbound/provider-ref-aws:v0.1.0 diff --git a/pkg/migration/testdata/plan/configurationv1.yaml b/pkg/migration/testdata/plan/configurationv1.yaml index 809b0a26..5004580e 100644 --- a/pkg/migration/testdata/plan/configurationv1.yaml +++ b/pkg/migration/testdata/plan/configurationv1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: meta.pkg.crossplane.io/v1 kind: Configuration metadata: @@ -33,4 +37,4 @@ spec: - provider: xpkg.upbound.io/upbound/provider-aws version: ">=v0.15.0" - provider: xpkg.upbound.io/crossplane-contrib/provider-helm - version: ">=v0.12.0" \ No newline at end of file + version: ">=v0.12.0" diff --git a/pkg/migration/testdata/plan/configurationv1alpha1.yaml b/pkg/migration/testdata/plan/configurationv1alpha1.yaml index 117faf74..772c5108 100644 --- a/pkg/migration/testdata/plan/configurationv1alpha1.yaml +++ b/pkg/migration/testdata/plan/configurationv1alpha1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: meta.pkg.crossplane.io/v1alpha1 kind: Configuration metadata: @@ -33,4 +37,4 @@ spec: - provider: xpkg.upbound.io/upbound/provider-aws version: ">=v0.15.0" - provider: xpkg.upbound.io/crossplane-contrib/provider-helm - version: ">=v0.12.0" \ No newline at end of file + version: ">=v0.12.0" diff --git a/pkg/migration/testdata/plan/generated/activate-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml b/pkg/migration/testdata/plan/generated/activate-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml index 2d83bc3c..263c101a 100644 --- a/pkg/migration/testdata/plan/generated/activate-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml +++ b/pkg/migration/testdata/plan/generated/activate-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: diff --git a/pkg/migration/testdata/plan/generated/activate-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml b/pkg/migration/testdata/plan/generated/activate-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml index dc9d0827..69893cfc 100644 --- a/pkg/migration/testdata/plan/generated/activate-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml +++ b/pkg/migration/testdata/plan/generated/activate-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: diff --git a/pkg/migration/testdata/plan/generated/activate-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml b/pkg/migration/testdata/plan/generated/activate-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml index 8491afdc..1761efe3 100644 --- a/pkg/migration/testdata/plan/generated/activate-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml +++ b/pkg/migration/testdata/plan/generated/activate-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: diff --git a/pkg/migration/testdata/plan/generated/configurationv1_migration_plan.yaml b/pkg/migration/testdata/plan/generated/configurationv1_migration_plan.yaml index e1dc326e..98e978d5 100644 --- a/pkg/migration/testdata/plan/generated/configurationv1_migration_plan.yaml +++ b/pkg/migration/testdata/plan/generated/configurationv1_migration_plan.yaml @@ -1,63 +1,67 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + spec: steps: - - exec: - command: sh - args: - - "-c" - - "kubectl get managed -o yaml > backup/managed-resources.yaml" - name: backup-managed-resources - manualExecution: - - sh -c "kubectl get managed -o yaml > backup/managed-resources.yaml" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl get managed -o yaml > backup/managed-resources.yaml" + name: backup-managed-resources + manualExecution: + - sh -c "kubectl get managed -o yaml > backup/managed-resources.yaml" + type: Exec - - exec: - command: sh - args: - - "-c" - - "kubectl get composite -o yaml > backup/composite-resources.yaml" - name: backup-composite-resources - manualExecution: - - sh -c "kubectl get composite -o yaml > backup/composite-resources.yaml" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl get composite -o yaml > backup/composite-resources.yaml" + name: backup-composite-resources + manualExecution: + - sh -c "kubectl get composite -o yaml > backup/composite-resources.yaml" + type: Exec - - exec: - command: sh - args: - - "-c" - - "kubectl get claim --all-namespaces -o yaml > backup/claim-resources.yaml" - name: backup-claim-resources - manualExecution: - - sh -c "kubectl get claim --all-namespaces -o yaml > backup/claim-resources.yaml" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl get claim --all-namespaces -o yaml > backup/claim-resources.yaml" + name: backup-claim-resources + manualExecution: + - sh -c "kubectl get claim --all-namespaces -o yaml > backup/claim-resources.yaml" + type: Exec - - exec: - command: sh - args: - - "-c" - - "cp edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1.yaml testdata/plan/configurationv1.yaml" - name: edit-configuration-metadata - manualExecution: - - sh -c "cp edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1.yaml testdata/plan/configurationv1.yaml" - type: Exec + - exec: + command: sh + args: + - "-c" + - "cp edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1.yaml testdata/plan/configurationv1.yaml" + name: edit-configuration-metadata + manualExecution: + - sh -c "cp edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1.yaml testdata/plan/configurationv1.yaml" + type: Exec - - exec: - command: sh - args: - - "-c" - - "up xpkg build --package-root={{PKG_ROOT}} --examples-root={{EXAMPLES_ROOT}} -o {{PKG_PATH}}" - name: build-configuration - manualExecution: - - sh -c "up xpkg build --package-root={{PKG_ROOT}} --examples-root={{EXAMPLES_ROOT}} -o {{PKG_PATH}}" - type: Exec + - exec: + command: sh + args: + - "-c" + - "up xpkg build --package-root={{PKG_ROOT}} --examples-root={{EXAMPLES_ROOT}} -o {{PKG_PATH}}" + name: build-configuration + manualExecution: + - sh -c "up xpkg build --package-root={{PKG_ROOT}} --examples-root={{EXAMPLES_ROOT}} -o {{PKG_PATH}}" + type: Exec - - exec: - command: sh - args: - - "-c" - - "up xpkg push {{TARGET_CONFIGURATION_PACKAGE}} -f {{PKG_PATH}}" - name: push-configuration - manualExecution: - - sh -c "up xpkg push {{TARGET_CONFIGURATION_PACKAGE}} -f {{PKG_PATH}}" - type: Exec + - exec: + command: sh + args: + - "-c" + - "up xpkg push {{TARGET_CONFIGURATION_PACKAGE}} -f {{PKG_PATH}}" + name: push-configuration + manualExecution: + - sh -c "up xpkg push {{TARGET_CONFIGURATION_PACKAGE}} -f {{PKG_PATH}}" + type: Exec version: 0.1.0 diff --git a/pkg/migration/testdata/plan/generated/configurationv1_pkg_migration_plan.yaml b/pkg/migration/testdata/plan/generated/configurationv1_pkg_migration_plan.yaml index 22b05730..c7c2128c 100644 --- a/pkg/migration/testdata/plan/generated/configurationv1_pkg_migration_plan.yaml +++ b/pkg/migration/testdata/plan/generated/configurationv1_pkg_migration_plan.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + spec: steps: - exec: diff --git a/pkg/migration/testdata/plan/generated/configurationv1alpha1_migration_plan.yaml b/pkg/migration/testdata/plan/generated/configurationv1alpha1_migration_plan.yaml index 2903ad87..ec9d0258 100644 --- a/pkg/migration/testdata/plan/generated/configurationv1alpha1_migration_plan.yaml +++ b/pkg/migration/testdata/plan/generated/configurationv1alpha1_migration_plan.yaml @@ -1,63 +1,67 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + spec: steps: - - exec: - command: sh - args: - - "-c" - - "kubectl get managed -o yaml > backup/managed-resources.yaml" - name: backup-managed-resources - manualExecution: - - sh -c "kubectl get managed -o yaml > backup/managed-resources.yaml" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl get managed -o yaml > backup/managed-resources.yaml" + name: backup-managed-resources + manualExecution: + - sh -c "kubectl get managed -o yaml > backup/managed-resources.yaml" + type: Exec - - exec: - command: sh - args: - - "-c" - - "kubectl get composite -o yaml > backup/composite-resources.yaml" - name: backup-composite-resources - manualExecution: - - sh -c "kubectl get composite -o yaml > backup/composite-resources.yaml" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl get composite -o yaml > backup/composite-resources.yaml" + name: backup-composite-resources + manualExecution: + - sh -c "kubectl get composite -o yaml > backup/composite-resources.yaml" + type: Exec - - exec: - command: sh - args: - - "-c" - - "kubectl get claim --all-namespaces -o yaml > backup/claim-resources.yaml" - name: backup-claim-resources - manualExecution: - - sh -c "kubectl get claim --all-namespaces -o yaml > backup/claim-resources.yaml" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl get claim --all-namespaces -o yaml > backup/claim-resources.yaml" + name: backup-claim-resources + manualExecution: + - sh -c "kubectl get claim --all-namespaces -o yaml > backup/claim-resources.yaml" + type: Exec - - exec: - command: sh - args: - - "-c" - - "cp edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1alpha1.yaml testdata/plan/configurationv1alpha1.yaml" - name: edit-configuration-metadata - manualExecution: - - sh -c "cp edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1alpha1.yaml testdata/plan/configurationv1alpha1.yaml" - type: Exec + - exec: + command: sh + args: + - "-c" + - "cp edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1alpha1.yaml testdata/plan/configurationv1alpha1.yaml" + name: edit-configuration-metadata + manualExecution: + - sh -c "cp edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1alpha1.yaml testdata/plan/configurationv1alpha1.yaml" + type: Exec - - exec: - command: sh - args: - - "-c" - - "up xpkg build --package-root={{PKG_ROOT}} --examples-root={{EXAMPLES_ROOT}} -o {{PKG_PATH}}" - name: build-configuration - manualExecution: - - sh -c "up xpkg build --package-root={{PKG_ROOT}} --examples-root={{EXAMPLES_ROOT}} -o {{PKG_PATH}}" - type: Exec + - exec: + command: sh + args: + - "-c" + - "up xpkg build --package-root={{PKG_ROOT}} --examples-root={{EXAMPLES_ROOT}} -o {{PKG_PATH}}" + name: build-configuration + manualExecution: + - sh -c "up xpkg build --package-root={{PKG_ROOT}} --examples-root={{EXAMPLES_ROOT}} -o {{PKG_PATH}}" + type: Exec - - exec: - command: sh - args: - - "-c" - - "up xpkg push {{TARGET_CONFIGURATION_PACKAGE}} -f {{PKG_PATH}}" - name: push-configuration - manualExecution: - - sh -c "up xpkg push {{TARGET_CONFIGURATION_PACKAGE}} -f {{PKG_PATH}}" - type: Exec + - exec: + command: sh + args: + - "-c" + - "up xpkg push {{TARGET_CONFIGURATION_PACKAGE}} -f {{PKG_PATH}}" + name: push-configuration + manualExecution: + - sh -c "up xpkg push {{TARGET_CONFIGURATION_PACKAGE}} -f {{PKG_PATH}}" + type: Exec version: 0.1.0 diff --git a/pkg/migration/testdata/plan/generated/create-new-managed/sample-vpc.vpcs.faketargetapi.yaml b/pkg/migration/testdata/plan/generated/create-new-managed/sample-vpc.vpcs.faketargetapi.yaml index 8a71e291..14597c2e 100644 --- a/pkg/migration/testdata/plan/generated/create-new-managed/sample-vpc.vpcs.faketargetapi.yaml +++ b/pkg/migration/testdata/plan/generated/create-new-managed/sample-vpc.vpcs.faketargetapi.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: faketargetapi/v1alpha1 kind: VPC metadata: diff --git a/pkg/migration/testdata/plan/generated/deletion-policy-delete/sample-vpc.vpcs.fakesourceapi_v1alpha1.yaml b/pkg/migration/testdata/plan/generated/deletion-policy-delete/sample-vpc.vpcs.fakesourceapi_v1alpha1.yaml index c1c0157d..f0c2cfcb 100644 --- a/pkg/migration/testdata/plan/generated/deletion-policy-delete/sample-vpc.vpcs.fakesourceapi_v1alpha1.yaml +++ b/pkg/migration/testdata/plan/generated/deletion-policy-delete/sample-vpc.vpcs.fakesourceapi_v1alpha1.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: fakesourceapi/v1alpha1 kind: VPC metadata: name: sample-vpc spec: - deletionPolicy: Delete \ No newline at end of file + deletionPolicy: Delete diff --git a/pkg/migration/testdata/plan/generated/deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi.yaml b/pkg/migration/testdata/plan/generated/deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi.yaml index aecbd81e..de5bbaf0 100644 --- a/pkg/migration/testdata/plan/generated/deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi.yaml +++ b/pkg/migration/testdata/plan/generated/deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: fakesourceapi/v1alpha1 kind: VPC metadata: diff --git a/pkg/migration/testdata/plan/generated/deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi_v1alpha1.yaml b/pkg/migration/testdata/plan/generated/deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi_v1alpha1.yaml index b855631f..de5bbaf0 100644 --- a/pkg/migration/testdata/plan/generated/deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi_v1alpha1.yaml +++ b/pkg/migration/testdata/plan/generated/deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi_v1alpha1.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: fakesourceapi/v1alpha1 kind: VPC metadata: name: sample-vpc spec: - deletionPolicy: Orphan \ No newline at end of file + deletionPolicy: Orphan diff --git a/pkg/migration/testdata/plan/generated/disable-dependency-resolution/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml b/pkg/migration/testdata/plan/generated/disable-dependency-resolution/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml index 5c747b09..2295384f 100644 --- a/pkg/migration/testdata/plan/generated/disable-dependency-resolution/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml +++ b/pkg/migration/testdata/plan/generated/disable-dependency-resolution/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Configuration metadata: diff --git a/pkg/migration/testdata/plan/generated/edit-claims/my-resource.myresources.test.com.yaml b/pkg/migration/testdata/plan/generated/edit-claims/my-resource.myresources.test.com.yaml index c2cb9a3a..e1cfffbc 100644 --- a/pkg/migration/testdata/plan/generated/edit-claims/my-resource.myresources.test.com.yaml +++ b/pkg/migration/testdata/plan/generated/edit-claims/my-resource.myresources.test.com.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: test.com/v1alpha1 kind: MyResource metadata: diff --git a/pkg/migration/testdata/plan/generated/edit-composites/my-resource-dwjgh.xmyresources.test.com.yaml b/pkg/migration/testdata/plan/generated/edit-composites/my-resource-dwjgh.xmyresources.test.com.yaml index 207c070d..3d3271dc 100644 --- a/pkg/migration/testdata/plan/generated/edit-composites/my-resource-dwjgh.xmyresources.test.com.yaml +++ b/pkg/migration/testdata/plan/generated/edit-composites/my-resource-dwjgh.xmyresources.test.com.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: test.com/v1alpha1 kind: XMyResource metadata: @@ -6,6 +10,6 @@ spec: compositionRef: name: example-migrated resourceRefs: - - apiVersion: faketargetapi/v1alpha1 - kind: VPC - name: sample-vpc + - apiVersion: faketargetapi/v1alpha1 + kind: VPC + name: sample-vpc diff --git a/pkg/migration/testdata/plan/generated/edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1.yaml b/pkg/migration/testdata/plan/generated/edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1.yaml index 32560007..24b6f3f3 100644 --- a/pkg/migration/testdata/plan/generated/edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1.yaml +++ b/pkg/migration/testdata/plan/generated/edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: meta.pkg.crossplane.io/v1 kind: Configuration metadata: diff --git a/pkg/migration/testdata/plan/generated/edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1alpha1.yaml b/pkg/migration/testdata/plan/generated/edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1alpha1.yaml index c64da0bd..ef6ef948 100644 --- a/pkg/migration/testdata/plan/generated/edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1alpha1.yaml +++ b/pkg/migration/testdata/plan/generated/edit-configuration-metadata/platform-ref-aws.configurations.meta.pkg.crossplane.io_v1alpha1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: meta.pkg.crossplane.io/v1alpha1 kind: Configuration metadata: diff --git a/pkg/migration/testdata/plan/generated/edit-configuration-package/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml b/pkg/migration/testdata/plan/generated/edit-configuration-package/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml index e74b8374..8b8ff78c 100644 --- a/pkg/migration/testdata/plan/generated/edit-configuration-package/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml +++ b/pkg/migration/testdata/plan/generated/edit-configuration-package/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Configuration metadata: diff --git a/pkg/migration/testdata/plan/generated/edit-package-lock/lock.locks.pkg.crossplane.io_v1beta1.yaml b/pkg/migration/testdata/plan/generated/edit-package-lock/lock.locks.pkg.crossplane.io_v1beta1.yaml index 3a0cfdb5..14ee1e63 100644 --- a/pkg/migration/testdata/plan/generated/edit-package-lock/lock.locks.pkg.crossplane.io_v1beta1.yaml +++ b/pkg/migration/testdata/plan/generated/edit-package-lock/lock.locks.pkg.crossplane.io_v1beta1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1beta1 kind: Lock metadata: @@ -11,4 +15,3 @@ packages: type: Provider source: xpkg.upbound.io/upbound/test-provider version: vX.Y.Z - diff --git a/pkg/migration/testdata/plan/generated/enable-dependency-resolution/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml b/pkg/migration/testdata/plan/generated/enable-dependency-resolution/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml index d0ee5a05..1f89d359 100644 --- a/pkg/migration/testdata/plan/generated/enable-dependency-resolution/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml +++ b/pkg/migration/testdata/plan/generated/enable-dependency-resolution/platform-ref-aws.configurations.pkg.crossplane.io_v1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Configuration metadata: diff --git a/pkg/migration/testdata/plan/generated/migration_plan.yaml b/pkg/migration/testdata/plan/generated/migration_plan.yaml index 1a8b4e91..e93b18b3 100644 --- a/pkg/migration/testdata/plan/generated/migration_plan.yaml +++ b/pkg/migration/testdata/plan/generated/migration_plan.yaml @@ -1,104 +1,108 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + spec: steps: - - patch: - type: merge - files: - - pause-managed/sample-vpc.vpcs.fakesourceapi.yaml - name: pause-managed - manualExecution: - - "kubectl patch --type='merge' -f pause-managed/sample-vpc.vpcs.fakesourceapi.yaml --patch-file pause-managed/sample-vpc.vpcs.fakesourceapi.yaml" - type: Patch + - patch: + type: merge + files: + - pause-managed/sample-vpc.vpcs.fakesourceapi.yaml + name: pause-managed + manualExecution: + - "kubectl patch --type='merge' -f pause-managed/sample-vpc.vpcs.fakesourceapi.yaml --patch-file pause-managed/sample-vpc.vpcs.fakesourceapi.yaml" + type: Patch - - patch: - type: merge - files: - - pause-composites/my-resource-dwjgh.xmyresources.test.com.yaml - name: pause-composites - manualExecution: - - "kubectl patch --type='merge' -f pause-composites/my-resource-dwjgh.xmyresources.test.com.yaml --patch-file pause-composites/my-resource-dwjgh.xmyresources.test.com.yaml" - type: Patch + - patch: + type: merge + files: + - pause-composites/my-resource-dwjgh.xmyresources.test.com.yaml + name: pause-composites + manualExecution: + - "kubectl patch --type='merge' -f pause-composites/my-resource-dwjgh.xmyresources.test.com.yaml --patch-file pause-composites/my-resource-dwjgh.xmyresources.test.com.yaml" + type: Patch - - apply: - files: - - create-new-managed/sample-vpc.vpcs.faketargetapi.yaml - name: create-new-managed - manualExecution: - - "kubectl apply -f create-new-managed/sample-vpc.vpcs.faketargetapi.yaml" - type: Apply + - apply: + files: + - create-new-managed/sample-vpc.vpcs.faketargetapi.yaml + name: create-new-managed + manualExecution: + - "kubectl apply -f create-new-managed/sample-vpc.vpcs.faketargetapi.yaml" + type: Apply - - apply: - files: - - new-compositions/example-migrated.compositions.apiextensions.crossplane.io.yaml - name: new-compositions - manualExecution: - - "kubectl apply -f new-compositions/example-migrated.compositions.apiextensions.crossplane.io.yaml" - type: Apply + - apply: + files: + - new-compositions/example-migrated.compositions.apiextensions.crossplane.io.yaml + name: new-compositions + manualExecution: + - "kubectl apply -f new-compositions/example-migrated.compositions.apiextensions.crossplane.io.yaml" + type: Apply - - patch: - type: merge - files: - - edit-composites/my-resource-dwjgh.xmyresources.test.com.yaml - name: edit-composites - manualExecution: - - "kubectl patch --type='merge' -f edit-composites/my-resource-dwjgh.xmyresources.test.com.yaml --patch-file edit-composites/my-resource-dwjgh.xmyresources.test.com.yaml" - type: Patch + - patch: + type: merge + files: + - edit-composites/my-resource-dwjgh.xmyresources.test.com.yaml + name: edit-composites + manualExecution: + - "kubectl patch --type='merge' -f edit-composites/my-resource-dwjgh.xmyresources.test.com.yaml --patch-file edit-composites/my-resource-dwjgh.xmyresources.test.com.yaml" + type: Patch - - patch: - type: merge - files: - - edit-claims/my-resource.myresources.test.com.yaml - name: edit-claims - manualExecution: - - "kubectl patch --type='merge' -f edit-claims/my-resource.myresources.test.com.yaml --patch-file edit-claims/my-resource.myresources.test.com.yaml" - type: Patch + - patch: + type: merge + files: + - edit-claims/my-resource.myresources.test.com.yaml + name: edit-claims + manualExecution: + - "kubectl patch --type='merge' -f edit-claims/my-resource.myresources.test.com.yaml --patch-file edit-claims/my-resource.myresources.test.com.yaml" + type: Patch - - patch: - type: merge - files: - - deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi.yaml - name: deletion-policy-orphan - manualExecution: - - "kubectl patch --type='merge' -f deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi.yaml --patch-file deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi.yaml" - type: Patch + - patch: + type: merge + files: + - deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi.yaml + name: deletion-policy-orphan + manualExecution: + - "kubectl patch --type='merge' -f deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi.yaml --patch-file deletion-policy-orphan/sample-vpc.vpcs.fakesourceapi.yaml" + type: Patch - - patch: - type: merge - files: - - remove-finalizers/sample-vpc.vpcs.fakesourceapi.yaml - name: remove-finalizers - manualExecution: - - "kubectl patch --type='merge' -f remove-finalizers/sample-vpc.vpcs.fakesourceapi.yaml --patch-file remove-finalizers/sample-vpc.vpcs.fakesourceapi.yaml" - type: Patch + - patch: + type: merge + files: + - remove-finalizers/sample-vpc.vpcs.fakesourceapi.yaml + name: remove-finalizers + manualExecution: + - "kubectl patch --type='merge' -f remove-finalizers/sample-vpc.vpcs.fakesourceapi.yaml --patch-file remove-finalizers/sample-vpc.vpcs.fakesourceapi.yaml" + type: Patch - - delete: - options: - finalizerPolicy: Remove - resources: - - group: fakesourceapi - kind: VPC - name: sample-vpc - version: v1alpha1 - name: delete-old-managed - manualExecution: - - "kubectl delete VPC.fakesourceapi sample-vpc" - type: Delete + - delete: + options: + finalizerPolicy: Remove + resources: + - group: fakesourceapi + kind: VPC + name: sample-vpc + version: v1alpha1 + name: delete-old-managed + manualExecution: + - "kubectl delete VPC.fakesourceapi sample-vpc" + type: Delete - - patch: - type: merge - files: - - start-managed/sample-vpc.vpcs.faketargetapi.yaml - name: start-managed - manualExecution: - - "kubectl patch --type='merge' -f start-managed/sample-vpc.vpcs.faketargetapi.yaml --patch-file start-managed/sample-vpc.vpcs.faketargetapi.yaml" - type: Patch + - patch: + type: merge + files: + - start-managed/sample-vpc.vpcs.faketargetapi.yaml + name: start-managed + manualExecution: + - "kubectl patch --type='merge' -f start-managed/sample-vpc.vpcs.faketargetapi.yaml --patch-file start-managed/sample-vpc.vpcs.faketargetapi.yaml" + type: Patch - - patch: - type: merge - files: - - start-composites/my-resource-dwjgh.xmyresources.test.com.yaml - name: start-composites - manualExecution: - - "kubectl patch --type='merge' -f start-composites/my-resource-dwjgh.xmyresources.test.com.yaml --patch-file start-composites/my-resource-dwjgh.xmyresources.test.com.yaml" - type: Patch + - patch: + type: merge + files: + - start-composites/my-resource-dwjgh.xmyresources.test.com.yaml + name: start-composites + manualExecution: + - "kubectl patch --type='merge' -f start-composites/my-resource-dwjgh.xmyresources.test.com.yaml --patch-file start-composites/my-resource-dwjgh.xmyresources.test.com.yaml" + type: Patch -version: 0.1.0 \ No newline at end of file +version: 0.1.0 diff --git a/pkg/migration/testdata/plan/generated/migration_plan_filesystem.yaml b/pkg/migration/testdata/plan/generated/migration_plan_filesystem.yaml index efc090ab..e60f1478 100644 --- a/pkg/migration/testdata/plan/generated/migration_plan_filesystem.yaml +++ b/pkg/migration/testdata/plan/generated/migration_plan_filesystem.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + spec: steps: - apply: @@ -52,4 +56,4 @@ spec: - "kubectl patch --type='merge' -f start-composites/my-resource-dwjgh.xmyresources.test.com.yaml --patch-file start-composites/my-resource-dwjgh.xmyresources.test.com.yaml" type: Patch -version: 0.1.0 \ No newline at end of file +version: 0.1.0 diff --git a/pkg/migration/testdata/plan/generated/new-compositions/example-migrated.compositions.apiextensions.crossplane.io.yaml b/pkg/migration/testdata/plan/generated/new-compositions/example-migrated.compositions.apiextensions.crossplane.io.yaml index 77540f4d..0dc3fb24 100644 --- a/pkg/migration/testdata/plan/generated/new-compositions/example-migrated.compositions.apiextensions.crossplane.io.yaml +++ b/pkg/migration/testdata/plan/generated/new-compositions/example-migrated.compositions.apiextensions.crossplane.io.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: apiextensions.crossplane.io/v1 kind: Composition metadata: @@ -38,37 +42,37 @@ spec: - fromFieldPath: "spec.parameters.tagValue" toFieldPath: spec.forProvider.tags["key4"] resources: - - base: - apiVersion: faketargetapi/v1alpha1 - kind: VPC - mockManaged: - ctrl: null - recorder: null - spec: - forProvider: - cidrBlock: 192.168.0.0/16 - region: us-west-1 - tags: - key1: val1 - key2: val2 - key3: val3 - name: vpc - patches: - - fromFieldPath: spec.parameters.tagValue - toFieldPath: spec.forProvider.tags["key1"] - - fromFieldPath: spec.parameters.tagValue - toFieldPath: spec.forProvider.tags["key2"] - - type: PatchSet - patchSetName: ps1 - - type: PatchSet - patchSetName: ps2 - - type: PatchSet - patchSetName: ps3 - - type: PatchSet - patchSetName: ps4 - - type: PatchSet - patchSetName: ps5 - - type: PatchSet - patchSetName: ps6 - - fromFieldPath: "spec.parameters.tagValue" - toFieldPath: spec.forProvider.param + - base: + apiVersion: faketargetapi/v1alpha1 + kind: VPC + mockManaged: + ctrl: null + recorder: null + spec: + forProvider: + cidrBlock: 192.168.0.0/16 + region: us-west-1 + tags: + key1: val1 + key2: val2 + key3: val3 + name: vpc + patches: + - fromFieldPath: spec.parameters.tagValue + toFieldPath: spec.forProvider.tags["key1"] + - fromFieldPath: spec.parameters.tagValue + toFieldPath: spec.forProvider.tags["key2"] + - type: PatchSet + patchSetName: ps1 + - type: PatchSet + patchSetName: ps2 + - type: PatchSet + patchSetName: ps3 + - type: PatchSet + patchSetName: ps4 + - type: PatchSet + patchSetName: ps5 + - type: PatchSet + patchSetName: ps6 + - fromFieldPath: "spec.parameters.tagValue" + toFieldPath: spec.forProvider.param diff --git a/pkg/migration/testdata/plan/generated/new-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml b/pkg/migration/testdata/plan/generated/new-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml index 4f6a43dd..22d58807 100644 --- a/pkg/migration/testdata/plan/generated/new-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml +++ b/pkg/migration/testdata/plan/generated/new-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: diff --git a/pkg/migration/testdata/plan/generated/new-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml b/pkg/migration/testdata/plan/generated/new-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml index 05fe4545..e8670c7d 100644 --- a/pkg/migration/testdata/plan/generated/new-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml +++ b/pkg/migration/testdata/plan/generated/new-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: diff --git a/pkg/migration/testdata/plan/generated/new-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml b/pkg/migration/testdata/plan/generated/new-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml index eddda03d..bfcc9825 100644 --- a/pkg/migration/testdata/plan/generated/new-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml +++ b/pkg/migration/testdata/plan/generated/new-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: diff --git a/pkg/migration/testdata/plan/generated/pause-composites/my-resource-dwjgh.xmyresources.test.com.yaml b/pkg/migration/testdata/plan/generated/pause-composites/my-resource-dwjgh.xmyresources.test.com.yaml index 56760677..26f88624 100644 --- a/pkg/migration/testdata/plan/generated/pause-composites/my-resource-dwjgh.xmyresources.test.com.yaml +++ b/pkg/migration/testdata/plan/generated/pause-composites/my-resource-dwjgh.xmyresources.test.com.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: test.com/v1alpha1 kind: XMyResource metadata: diff --git a/pkg/migration/testdata/plan/generated/pause-managed/sample-vpc.vpcs.fakesourceapi.yaml b/pkg/migration/testdata/plan/generated/pause-managed/sample-vpc.vpcs.fakesourceapi.yaml index 20d1ec18..663c2d2b 100644 --- a/pkg/migration/testdata/plan/generated/pause-managed/sample-vpc.vpcs.fakesourceapi.yaml +++ b/pkg/migration/testdata/plan/generated/pause-managed/sample-vpc.vpcs.fakesourceapi.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: fakesourceapi/v1alpha1 kind: VPC metadata: diff --git a/pkg/migration/testdata/plan/generated/providerv1_migration_plan.yaml b/pkg/migration/testdata/plan/generated/providerv1_migration_plan.yaml index 7cfa9a85..2636c549 100644 --- a/pkg/migration/testdata/plan/generated/providerv1_migration_plan.yaml +++ b/pkg/migration/testdata/plan/generated/providerv1_migration_plan.yaml @@ -1,114 +1,118 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + spec: steps: - - apply: - files: - - new-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml - name: new-ssop - manualExecution: - - "kubectl apply -f new-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml" - type: Apply + - apply: + files: + - new-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml + name: new-ssop + manualExecution: + - "kubectl apply -f new-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml" + type: Apply - - exec: - command: sh - args: - - "-c" - - "kubectl wait provider.pkg provider-family-aws --for condition=Healthy" - name: wait-for-healthy - manualExecution: - - sh -c "kubectl wait provider.pkg provider-family-aws --for condition=Healthy" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl wait provider.pkg provider-family-aws --for condition=Healthy" + name: wait-for-healthy + manualExecution: + - sh -c "kubectl wait provider.pkg provider-family-aws --for condition=Healthy" + type: Exec - - apply: - files: - - new-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml - - new-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml - name: new-ssop - manualExecution: - - "kubectl apply -f new-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml" - - "kubectl apply -f new-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml" - type: Apply + - apply: + files: + - new-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml + - new-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml + name: new-ssop + manualExecution: + - "kubectl apply -f new-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml" + - "kubectl apply -f new-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml" + type: Apply - - exec: - command: sh - args: - - "-c" - - "kubectl wait provider.pkg provider-aws-ec2 --for condition=Healthy" - name: wait-for-healthy - manualExecution: - - sh -c "kubectl wait provider.pkg provider-aws-ec2 --for condition=Healthy" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl wait provider.pkg provider-aws-ec2 --for condition=Healthy" + name: wait-for-healthy + manualExecution: + - sh -c "kubectl wait provider.pkg provider-aws-ec2 --for condition=Healthy" + type: Exec - - exec: - command: sh - args: - - "-c" - - "kubectl wait provider.pkg provider-aws-eks --for condition=Healthy" - name: wait-for-healthy - manualExecution: - - sh -c "kubectl wait provider.pkg provider-aws-eks --for condition=Healthy" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl wait provider.pkg provider-aws-eks --for condition=Healthy" + name: wait-for-healthy + manualExecution: + - sh -c "kubectl wait provider.pkg provider-aws-eks --for condition=Healthy" + type: Exec - - delete: - options: - finalizerPolicy: Remove - resources: - - group: pkg.crossplane.io - kind: Provider - name: provider-aws - version: v1 - name: delete-monolithic-provider - manualExecution: - - "kubectl delete Provider.pkg.crossplane.io provider-aws" - type: Delete + - delete: + options: + finalizerPolicy: Remove + resources: + - group: pkg.crossplane.io + kind: Provider + name: provider-aws + version: v1 + name: delete-monolithic-provider + manualExecution: + - "kubectl delete Provider.pkg.crossplane.io provider-aws" + type: Delete - - patch: - type: merge - files: - - activate-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml - name: activate-ssop - manualExecution: - - "kubectl patch --type='merge' -f activate-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml --patch-file activate-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml" - type: Patch + - patch: + type: merge + files: + - activate-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml + name: activate-ssop + manualExecution: + - "kubectl patch --type='merge' -f activate-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml --patch-file activate-ssop/provider-family-aws.providers.pkg.crossplane.io_v1.yaml" + type: Patch - - exec: - command: sh - args: - - "-c" - - "kubectl wait provider.pkg provider-family-aws --for condition=Installed" - name: wait-for-installed - manualExecution: - - sh -c "kubectl wait provider.pkg provider-family-aws --for condition=Installed" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl wait provider.pkg provider-family-aws --for condition=Installed" + name: wait-for-installed + manualExecution: + - sh -c "kubectl wait provider.pkg provider-family-aws --for condition=Installed" + type: Exec - - patch: - type: merge - files: - - activate-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml - - activate-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml - name: activate-ssop - manualExecution: - - "kubectl patch --type='merge' -f activate-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml --patch-file activate-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml" - - "kubectl patch --type='merge' -f activate-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml --patch-file activate-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml" - type: Patch + - patch: + type: merge + files: + - activate-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml + - activate-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml + name: activate-ssop + manualExecution: + - "kubectl patch --type='merge' -f activate-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml --patch-file activate-ssop/provider-aws-ec2.providers.pkg.crossplane.io_v1.yaml" + - "kubectl patch --type='merge' -f activate-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml --patch-file activate-ssop/provider-aws-eks.providers.pkg.crossplane.io_v1.yaml" + type: Patch - - exec: - command: sh - args: - - "-c" - - "kubectl wait provider.pkg provider-aws-ec2 --for condition=Installed" - name: wait-for-installed - manualExecution: - - sh -c "kubectl wait provider.pkg provider-aws-ec2 --for condition=Installed" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl wait provider.pkg provider-aws-ec2 --for condition=Installed" + name: wait-for-installed + manualExecution: + - sh -c "kubectl wait provider.pkg provider-aws-ec2 --for condition=Installed" + type: Exec - - exec: - command: sh - args: - - "-c" - - "kubectl wait provider.pkg provider-aws-eks --for condition=Installed" - name: wait-for-installed - manualExecution: - - sh -c "kubectl wait provider.pkg provider-aws-eks --for condition=Installed" - type: Exec + - exec: + command: sh + args: + - "-c" + - "kubectl wait provider.pkg provider-aws-eks --for condition=Installed" + name: wait-for-installed + manualExecution: + - sh -c "kubectl wait provider.pkg provider-aws-eks --for condition=Installed" + type: Exec version: 0.1.0 diff --git a/pkg/migration/testdata/plan/generated/remove-finalizers/sample-vpc.vpcs.fakesourceapi.yaml b/pkg/migration/testdata/plan/generated/remove-finalizers/sample-vpc.vpcs.fakesourceapi.yaml index 3544a379..8288dc18 100644 --- a/pkg/migration/testdata/plan/generated/remove-finalizers/sample-vpc.vpcs.fakesourceapi.yaml +++ b/pkg/migration/testdata/plan/generated/remove-finalizers/sample-vpc.vpcs.fakesourceapi.yaml @@ -1,6 +1,9 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: fakesourceapi/v1alpha1 kind: VPC metadata: name: sample-vpc finalizers: [] - diff --git a/pkg/migration/testdata/plan/generated/sp_migration_plan.yaml b/pkg/migration/testdata/plan/generated/sp_migration_plan.yaml index 98797fe2..c4988d82 100644 --- a/pkg/migration/testdata/plan/generated/sp_migration_plan.yaml +++ b/pkg/migration/testdata/plan/generated/sp_migration_plan.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + # Expected Parameters: # Monolith provider name # Configuration name @@ -156,4 +160,3 @@ spec: type: Patch version: 0.1.0 - diff --git a/pkg/migration/testdata/plan/generated/start-composites/my-resource-dwjgh.xmyresources.test.com.yaml b/pkg/migration/testdata/plan/generated/start-composites/my-resource-dwjgh.xmyresources.test.com.yaml index b1e5983e..9f7ff4f4 100644 --- a/pkg/migration/testdata/plan/generated/start-composites/my-resource-dwjgh.xmyresources.test.com.yaml +++ b/pkg/migration/testdata/plan/generated/start-composites/my-resource-dwjgh.xmyresources.test.com.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: test.com/v1alpha1 kind: XMyResource metadata: diff --git a/pkg/migration/testdata/plan/generated/start-managed/sample-vpc.vpcs.faketargetapi.yaml b/pkg/migration/testdata/plan/generated/start-managed/sample-vpc.vpcs.faketargetapi.yaml index db644a2a..01f174cd 100644 --- a/pkg/migration/testdata/plan/generated/start-managed/sample-vpc.vpcs.faketargetapi.yaml +++ b/pkg/migration/testdata/plan/generated/start-managed/sample-vpc.vpcs.faketargetapi.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: faketargetapi/v1alpha1 kind: VPC metadata: diff --git a/pkg/migration/testdata/plan/lockv1beta1.yaml b/pkg/migration/testdata/plan/lockv1beta1.yaml index ac24f14e..14591e64 100644 --- a/pkg/migration/testdata/plan/lockv1beta1.yaml +++ b/pkg/migration/testdata/plan/lockv1beta1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1beta1 kind: Lock metadata: diff --git a/pkg/migration/testdata/plan/providerv1.yaml b/pkg/migration/testdata/plan/providerv1.yaml index b34a4ecd..0c451011 100644 --- a/pkg/migration/testdata/plan/providerv1.yaml +++ b/pkg/migration/testdata/plan/providerv1.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: diff --git a/pkg/migration/testdata/plan/sourcevpc.yaml b/pkg/migration/testdata/plan/sourcevpc.yaml index 01ae52ec..97c5354c 100644 --- a/pkg/migration/testdata/plan/sourcevpc.yaml +++ b/pkg/migration/testdata/plan/sourcevpc.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: fakesourceapi/v1alpha1 kind: VPC metadata: diff --git a/pkg/migration/testdata/plan/sourcevpc2.yaml b/pkg/migration/testdata/plan/sourcevpc2.yaml index 42461ebb..1ac874f3 100644 --- a/pkg/migration/testdata/plan/sourcevpc2.yaml +++ b/pkg/migration/testdata/plan/sourcevpc2.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: fakesourceapi/v1alpha1 kind: VPC metadata: diff --git a/pkg/migration/testdata/plan/xr.yaml b/pkg/migration/testdata/plan/xr.yaml index 80e2b668..953b6439 100644 --- a/pkg/migration/testdata/plan/xr.yaml +++ b/pkg/migration/testdata/plan/xr.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: test.com/v1alpha1 kind: XMyResource metadata: diff --git a/pkg/migration/testdata/plan/xrd.yaml b/pkg/migration/testdata/plan/xrd.yaml index 4fba46ef..4a46fd00 100644 --- a/pkg/migration/testdata/plan/xrd.yaml +++ b/pkg/migration/testdata/plan/xrd.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: apiextensions.crossplane.io/v1 kind: CompositeResourceDefinition metadata: @@ -11,25 +15,25 @@ spec: kind: XMyResource plural: xmyresources versions: - - name: v1alpha1 - referenceable: true - schema: - openAPIV3Schema: - properties: - spec: - properties: - parameters: - properties: - tagValue: - type: string - region: - type: string - required: - - tagValue - - region - type: object - required: - - parameters - type: object - type: object - served: true + - name: v1alpha1 + referenceable: true + schema: + openAPIV3Schema: + properties: + spec: + properties: + parameters: + properties: + tagValue: + type: string + region: + type: string + required: + - tagValue + - region + type: object + required: + - parameters + type: object + type: object + served: true diff --git a/pkg/migration/testdata/source/awsvpc.yaml b/pkg/migration/testdata/source/awsvpc.yaml index c62e492a..88fc6925 100644 --- a/pkg/migration/testdata/source/awsvpc.yaml +++ b/pkg/migration/testdata/source/awsvpc.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: ec2.aws.crossplane.io/v1beta1 kind: VPC metadata: diff --git a/pkg/migration/testdata/source/resourcegroup.yaml b/pkg/migration/testdata/source/resourcegroup.yaml index a84ff84a..7b48c2ef 100644 --- a/pkg/migration/testdata/source/resourcegroup.yaml +++ b/pkg/migration/testdata/source/resourcegroup.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + apiVersion: azure.crossplane.io/v1beta1 kind: ResourceGroup metadata: diff --git a/pkg/migration/types.go b/pkg/migration/types.go index 40c4ad88..615e9ef6 100644 --- a/pkg/migration/types.go +++ b/pkg/migration/types.go @@ -1,16 +1,6 @@ -// Copyright 2022 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package migration diff --git a/pkg/pipeline/controller.go b/pkg/pipeline/controller.go index 5f2c70a8..e34f05aa 100644 --- a/pkg/pipeline/controller.go +++ b/pkg/pipeline/controller.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package pipeline @@ -9,11 +9,10 @@ import ( "path/filepath" "strings" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/pipeline/templates" "github.com/muvaf/typewriter/pkg/wrapper" "github.com/pkg/errors" - - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/pipeline/templates" ) // NewControllerGenerator returns a new ControllerGenerator. diff --git a/pkg/pipeline/crd.go b/pkg/pipeline/crd.go index 8eb9c0f6..1654051a 100644 --- a/pkg/pipeline/crd.go +++ b/pkg/pipeline/crd.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package pipeline @@ -16,10 +16,10 @@ import ( "github.com/muvaf/typewriter/pkg/wrapper" "github.com/pkg/errors" - tjpkg "github.com/upbound/upjet/pkg" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/pipeline/templates" - tjtypes "github.com/upbound/upjet/pkg/types" + tjpkg "github.com/crossplane/upjet/pkg" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/pipeline/templates" + tjtypes "github.com/crossplane/upjet/pkg/types" ) const ( diff --git a/pkg/pipeline/crd_test.go b/pkg/pipeline/crd_test.go index 76889e16..29b7708d 100644 --- a/pkg/pipeline/crd_test.go +++ b/pkg/pipeline/crd_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package pipeline @@ -8,7 +8,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/pkg/pipeline/register.go b/pkg/pipeline/register.go index cbba70b8..5cd80fbb 100644 --- a/pkg/pipeline/register.go +++ b/pkg/pipeline/register.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package pipeline @@ -9,10 +9,9 @@ import ( "path/filepath" "sort" + "github.com/crossplane/upjet/pkg/pipeline/templates" "github.com/muvaf/typewriter/pkg/wrapper" "github.com/pkg/errors" - - "github.com/upbound/upjet/pkg/pipeline/templates" ) // NewRegisterGenerator returns a new RegisterGenerator. diff --git a/pkg/pipeline/run.go b/pkg/pipeline/run.go index a2cd4ca0..4b1e3353 100644 --- a/pkg/pipeline/run.go +++ b/pkg/pipeline/run.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package pipeline @@ -11,10 +11,10 @@ import ( "sort" "strings" - "github.com/crossplane/crossplane-runtime/pkg/errors" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/examples" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/examples" + "github.com/crossplane/crossplane-runtime/pkg/errors" ) type terraformedInput struct { @@ -23,7 +23,7 @@ type terraformedInput struct { } // Run runs the Upjet code generation pipelines. -func Run(pc *config.Provider, rootDir string) { // nolint:gocyclo +func Run(pc *config.Provider, rootDir string) { //nolint:gocyclo // Note(turkenh): nolint reasoning - this is the main function of the code // generation pipeline. We didn't want to split it into multiple functions // for better readability considering the straightforward logic here. diff --git a/pkg/pipeline/setup.go b/pkg/pipeline/setup.go index 183cdc46..121f6cba 100644 --- a/pkg/pipeline/setup.go +++ b/pkg/pipeline/setup.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package pipeline @@ -12,11 +12,10 @@ import ( "sort" "text/template" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/pipeline/templates" "github.com/muvaf/typewriter/pkg/wrapper" "github.com/pkg/errors" - - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/pipeline/templates" ) // NewProviderGenerator returns a new ProviderGenerator. diff --git a/pkg/pipeline/templates/controller.go.tmpl b/pkg/pipeline/templates/controller.go.tmpl index 18d39219..435ed58f 100644 --- a/pkg/pipeline/templates/controller.go.tmpl +++ b/pkg/pipeline/templates/controller.go.tmpl @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + {{ .Header }} {{ .GenStatement }} @@ -12,9 +16,9 @@ import ( "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" - "github.com/upbound/upjet/pkg/controller/handler" - tjcontroller "github.com/upbound/upjet/pkg/controller" - "github.com/upbound/upjet/pkg/terraform" + "github.com/crossplane/upjet/pkg/controller/handler" + tjcontroller "github.com/crossplane/upjet/pkg/controller" + "github.com/crossplane/upjet/pkg/terraform" ctrl "sigs.k8s.io/controller-runtime" {{ .Imports }} diff --git a/pkg/pipeline/templates/crd_types.go.tmpl b/pkg/pipeline/templates/crd_types.go.tmpl index 6482343d..2c61f8d4 100644 --- a/pkg/pipeline/templates/crd_types.go.tmpl +++ b/pkg/pipeline/templates/crd_types.go.tmpl @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + {{ .Header }} {{ .GenStatement }} diff --git a/pkg/pipeline/templates/embed.go b/pkg/pipeline/templates/embed.go index 7acedda1..c809057a 100644 --- a/pkg/pipeline/templates/embed.go +++ b/pkg/pipeline/templates/embed.go @@ -1,10 +1,10 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package templates -import _ "embed" // nolint:golint +import _ "embed" //nolint:golint // CRDTypesTemplate is populated with CRD and type information. // diff --git a/pkg/pipeline/templates/groupversion_info.go.tmpl b/pkg/pipeline/templates/groupversion_info.go.tmpl index 56eb3dfd..91c31a01 100644 --- a/pkg/pipeline/templates/groupversion_info.go.tmpl +++ b/pkg/pipeline/templates/groupversion_info.go.tmpl @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + {{ .Header }} {{ .GenStatement }} diff --git a/pkg/pipeline/templates/register.go.tmpl b/pkg/pipeline/templates/register.go.tmpl index 2c9e01bd..bb84131a 100644 --- a/pkg/pipeline/templates/register.go.tmpl +++ b/pkg/pipeline/templates/register.go.tmpl @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + {{ .Header }} {{ .GenStatement }} diff --git a/pkg/pipeline/templates/setup.go.tmpl b/pkg/pipeline/templates/setup.go.tmpl index 72b9f5a4..184e6718 100644 --- a/pkg/pipeline/templates/setup.go.tmpl +++ b/pkg/pipeline/templates/setup.go.tmpl @@ -1,13 +1,13 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package controller import ( ctrl "sigs.k8s.io/controller-runtime" - "github.com/upbound/upjet/pkg/controller" + "github.com/crossplane/upjet/pkg/controller" {{ .Imports }} ) diff --git a/pkg/pipeline/templates/terraformed.go.tmpl b/pkg/pipeline/templates/terraformed.go.tmpl index 798cedf5..ee503fc4 100644 --- a/pkg/pipeline/templates/terraformed.go.tmpl +++ b/pkg/pipeline/templates/terraformed.go.tmpl @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + {{ .Header }} {{ .GenStatement }} @@ -7,8 +11,8 @@ package {{ .APIVersion }} import ( "github.com/pkg/errors" - "github.com/upbound/upjet/pkg/resource" - "github.com/upbound/upjet/pkg/resource/json" + "github.com/crossplane/upjet/pkg/resource" + "github.com/crossplane/upjet/pkg/resource/json" {{ .Imports }} ) {{ range .Resources }} diff --git a/pkg/pipeline/terraformed.go b/pkg/pipeline/terraformed.go index f412a894..10796b22 100644 --- a/pkg/pipeline/terraformed.go +++ b/pkg/pipeline/terraformed.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package pipeline @@ -10,10 +10,9 @@ import ( "path/filepath" "strings" + "github.com/crossplane/upjet/pkg/pipeline/templates" "github.com/muvaf/typewriter/pkg/wrapper" "github.com/pkg/errors" - - "github.com/upbound/upjet/pkg/pipeline/templates" ) // NewTerraformedGenerator returns a new TerraformedGenerator. diff --git a/pkg/pipeline/version.go b/pkg/pipeline/version.go index ab4425a5..c155a061 100644 --- a/pkg/pipeline/version.go +++ b/pkg/pipeline/version.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package pipeline @@ -10,10 +10,9 @@ import ( "path/filepath" "strings" + "github.com/crossplane/upjet/pkg/pipeline/templates" "github.com/muvaf/typewriter/pkg/wrapper" "github.com/pkg/errors" - - "github.com/upbound/upjet/pkg/pipeline/templates" ) // NewVersionGenerator returns a new VersionGenerator. diff --git a/pkg/registry/meta.go b/pkg/registry/meta.go index 7367e4ea..94033526 100644 --- a/pkg/registry/meta.go +++ b/pkg/registry/meta.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package registry @@ -71,7 +71,7 @@ func getResourceNameFromPath(path, resourcePrefix string) string { return fmt.Sprintf("%s%s", prefix, tokens[0]) } -func (r *Resource) scrapeExamples(doc *html.Node, codeElXPath string, path string, resourcePrefix string, debug bool) error { // nolint: gocyclo +func (r *Resource) scrapeExamples(doc *html.Node, codeElXPath string, path string, resourcePrefix string, debug bool) error { //nolint: gocyclo resourceName := r.Title nodes := htmlquery.Find(doc, codeElXPath) for _, n := range nodes { @@ -110,7 +110,7 @@ func (r *Resource) scrapeExamples(doc *html.Node, codeElXPath string, path strin return nil } -func (r *Resource) findReferences(parentPath string, file *hcl.File, b *hclsyntax.Block) (map[string]string, error) { // nolint: gocyclo +func (r *Resource) findReferences(parentPath string, file *hcl.File, b *hclsyntax.Block) (map[string]string, error) { //nolint: gocyclo refs := make(map[string]string) if parentPath == "" && b.Labels[0] != r.Name { return refs, nil @@ -362,7 +362,7 @@ func getPrevLiWithCodeText(codeText string, pNode *html.Node) *html.Node { // extractText extracts text from the children of an element node, // removing any HTML tags and leaving only text data. func extractText(n *html.Node) string { - switch n.Type { // nolint:exhaustive + switch n.Type { //nolint:exhaustive case html.TextNode: return n.Data case html.ElementNode: @@ -410,7 +410,7 @@ func (r *Resource) scrapeDocString(n *html.Node, attrName *string, processed map } processed[s] = struct{}{} - switch s.Type { // nolint:exhaustive + switch s.Type { //nolint:exhaustive case html.TextNode: sb.WriteString(s.Data) case html.ElementNode: diff --git a/pkg/registry/meta_test.go b/pkg/registry/meta_test.go index 0035ec06..4fb46975 100644 --- a/pkg/registry/meta_test.go +++ b/pkg/registry/meta_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package registry @@ -8,11 +8,11 @@ import ( "os" "testing" - "github.com/crossplane/crossplane-runtime/pkg/fieldpath" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "gopkg.in/yaml.v3" + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" xptest "github.com/crossplane/crossplane-runtime/pkg/test" ) diff --git a/pkg/registry/reference/references.go b/pkg/registry/reference/references.go index f59a16ec..f15b6dec 100644 --- a/pkg/registry/reference/references.go +++ b/pkg/registry/reference/references.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package reference @@ -8,15 +8,14 @@ import ( "fmt" "strings" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/registry" + "github.com/crossplane/upjet/pkg/types" "github.com/pkg/errors" - - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/registry" - "github.com/upbound/upjet/pkg/types" ) const ( - extractorPackagePath = "github.com/upbound/upjet/pkg/resource" + extractorPackagePath = "github.com/crossplane/upjet/pkg/resource" extractResourceIDFuncPath = extractorPackagePath + ".ExtractResourceID()" fmtExtractParamFuncPath = extractorPackagePath + `.ExtractParamPath("%s",%t)` ) @@ -56,7 +55,7 @@ func getExtractorFuncPath(r *config.Resource, sourceAttr string) string { // InjectReferences injects cross-resource references using the // provider metadata scraped from the Terraform registry. -func (rr *Injector) InjectReferences(configResources map[string]*config.Resource) error { // nolint:gocyclo +func (rr *Injector) InjectReferences(configResources map[string]*config.Resource) error { //nolint:gocyclo for n, r := range configResources { m := configResources[n].MetaResource if m == nil { diff --git a/pkg/registry/reference/resolver.go b/pkg/registry/reference/resolver.go index 91906d51..63941112 100644 --- a/pkg/registry/reference/resolver.go +++ b/pkg/registry/reference/resolver.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package reference @@ -10,12 +10,12 @@ import ( "strconv" "strings" - "github.com/crossplane/crossplane-runtime/pkg/fieldpath" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/registry" + "github.com/crossplane/upjet/pkg/resource/json" "github.com/pkg/errors" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/registry" - "github.com/upbound/upjet/pkg/resource/json" + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" ) const ( @@ -128,7 +128,7 @@ func (rr *Injector) ResolveReferencesOfPaved(pm *PavedWithManifest, resolutionCo return errors.Wrap(rr.resolveReferences(pm.Paved.UnstructuredContent(), resolutionContext), "failed to resolve references of paved") } -func (rr *Injector) resolveReferences(params map[string]any, resolutionContext *ResolutionContext) error { // nolint:gocyclo +func (rr *Injector) resolveReferences(params map[string]any, resolutionContext *ResolutionContext) error { //nolint:gocyclo for paramName, paramValue := range params { switch t := paramValue.(type) { case map[string]any: diff --git a/pkg/registry/resource.go b/pkg/registry/resource.go index 5cab89cb..53e9f781 100644 --- a/pkg/registry/resource.go +++ b/pkg/registry/resource.go @@ -1,15 +1,15 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package registry import ( - "github.com/crossplane/crossplane-runtime/pkg/fieldpath" + "github.com/crossplane/upjet/pkg/resource/json" "github.com/pkg/errors" "gopkg.in/yaml.v2" - "github.com/upbound/upjet/pkg/resource/json" + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" ) const ( diff --git a/pkg/registry/testdata/aws/pm.yaml b/pkg/registry/testdata/aws/pm.yaml index 47b80fbd..4eeb7ac8 100644 --- a/pkg/registry/testdata/aws/pm.yaml +++ b/pkg/registry/testdata/aws/pm.yaml @@ -1,150 +1,154 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + name: test-provider resources: - aws_accessanalyzer_analyzer: - subCategory: IAM Access Analyzer - description: Manages an Access Analyzer Analyzer - name: aws_accessanalyzer_analyzer - title: aws_accessanalyzer_analyzer - examples: - - name: example - manifest: |- - { - "analyzer_name": "example" - } - - name: example - manifest: |- - { - "analyzer_name": "example", - "depends_on": [ - "${aws_organizations_organization.example}" - ], - "type": "ORGANIZATION" - } - dependencies: - aws_organizations_organization.example: |- - { - "aws_service_access_principals": [ - "access-analyzer.amazonaws.com" - ] - } - argumentDocs: - analyzer_name: '- (Required) Name of the Analyzer.' - arn: '- The Amazon Resource Name (ARN) of the Analyzer.' - id: '- Analyzer name.' - tags: '- (Optional) Key-value map of resource tags. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level.' - tags_all: '- A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block.' - type: '- (Optional) Type of Analyzer. Valid values are ACCOUNT or ORGANIZATION. Defaults to ACCOUNT.' - importStatements: [] - aws_ebs_volume: - subCategory: EBS (EC2) - description: Provides an elastic block storage resource. - name: aws_ebs_volume - title: aws_ebs_volume - examples: - - name: example - manifest: |- - { - "availability_zone": "us-west-2a", - "size": 40, - "tags": { - "Name": "HelloWorld" + aws_accessanalyzer_analyzer: + subCategory: IAM Access Analyzer + description: Manages an Access Analyzer Analyzer + name: aws_accessanalyzer_analyzer + title: aws_accessanalyzer_analyzer + examples: + - name: example + manifest: |- + { + "analyzer_name": "example" + } + - name: example + manifest: |- + { + "analyzer_name": "example", + "depends_on": [ + "${aws_organizations_organization.example}" + ], + "type": "ORGANIZATION" + } + dependencies: + aws_organizations_organization.example: |- + { + "aws_service_access_principals": [ + "access-analyzer.amazonaws.com" + ] + } + argumentDocs: + analyzer_name: "- (Required) Name of the Analyzer." + arn: "- The Amazon Resource Name (ARN) of the Analyzer." + id: "- Analyzer name." + tags: "- (Optional) Key-value map of resource tags. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level." + tags_all: "- A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block." + type: "- (Optional) Type of Analyzer. Valid values are ACCOUNT or ORGANIZATION. Defaults to ACCOUNT." + importStatements: [] + aws_ebs_volume: + subCategory: EBS (EC2) + description: Provides an elastic block storage resource. + name: aws_ebs_volume + title: aws_ebs_volume + examples: + - name: example + manifest: |- + { + "availability_zone": "us-west-2a", + "size": 40, + "tags": { + "Name": "HelloWorld" + } + } + argumentDocs: + arn: "- The volume ARN (e.g., arn:aws:ec2:us-east-1:0123456789012:volume/vol-59fcb34e)." + availability_zone: "- (Required) The AZ where the EBS volume will exist." + create: "- (Default 5 minutes) Used for creating volumes. This includes the time required for the volume to become available" + delete: "- (Default 5 minutes) Used for destroying volumes" + encrypted: "- (Optional) If true, the disk will be encrypted." + id: "- The volume ID (e.g., vol-59fcb34e)." + iops: "- (Optional) The amount of IOPS to provision for the disk. Only valid for type of io1, io2 or gp3." + kms_key_id: "- (Optional) The ARN for the KMS encryption key. When specifying kms_key_id, encrypted needs to be set to true. Note: Terraform must be running with credentials which have the GenerateDataKeyWithoutPlaintext permission on the specified KMS key as required by the EBS KMS CMK volume provisioning process to prevent a volume from being created and almost immediately deleted." + multi_attach_enabled: "- (Optional) Specifies whether to enable Amazon EBS Multi-Attach. Multi-Attach is supported on io1 and io2 volumes." + outpost_arn: "- (Optional) The Amazon Resource Name (ARN) of the Outpost." + size: "- (Optional) The size of the drive in GiBs." + snapshot_id: (Optional) A snapshot to base the EBS volume off of. + tags: "- (Optional) A map of tags to assign to the resource. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level." + tags_all: "- A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block." + throughput: "- (Optional) The throughput that the volume supports, in MiB/s. Only valid for type of gp3." + type: "- (Optional) The type of EBS volume. Can be standard, gp2, gp3, io1, io2, sc1 or st1 (Default: gp2)." + update: "- (Default 5 minutes) Used for size, type, or iops volume changes" + importStatements: [] + aws_s3_bucket_acl: + subCategory: S3 (Simple Storage) + description: Provides an S3 bucket ACL resource. + name: aws_s3_bucket_acl + title: aws_s3_bucket_acl + examples: + - name: example_bucket_acl + manifest: |- + { + "acl": "private", + "bucket": "${aws_s3_bucket.example.id}" + } + references: + bucket: aws_s3_bucket.example.id + dependencies: + aws_s3_bucket.example: |- + { + "bucket": "my-tf-example-bucket" + } + - name: example + manifest: |- + { + "access_control_policy": [ + { + "grant": [ + { + "grantee": [ + { + "id": "${data.aws_canonical_user_id.current.id}", + "type": "CanonicalUser" + } + ], + "permission": "READ" + }, + { + "grantee": [ + { + "type": "Group", + "uri": "http://acs.amazonaws.com/groups/s3/LogDelivery" + } + ], + "permission": "READ_ACP" } - } - argumentDocs: - arn: '- The volume ARN (e.g., arn:aws:ec2:us-east-1:0123456789012:volume/vol-59fcb34e).' - availability_zone: '- (Required) The AZ where the EBS volume will exist.' - create: '- (Default 5 minutes) Used for creating volumes. This includes the time required for the volume to become available' - delete: '- (Default 5 minutes) Used for destroying volumes' - encrypted: '- (Optional) If true, the disk will be encrypted.' - id: '- The volume ID (e.g., vol-59fcb34e).' - iops: '- (Optional) The amount of IOPS to provision for the disk. Only valid for type of io1, io2 or gp3.' - kms_key_id: '- (Optional) The ARN for the KMS encryption key. When specifying kms_key_id, encrypted needs to be set to true. Note: Terraform must be running with credentials which have the GenerateDataKeyWithoutPlaintext permission on the specified KMS key as required by the EBS KMS CMK volume provisioning process to prevent a volume from being created and almost immediately deleted.' - multi_attach_enabled: '- (Optional) Specifies whether to enable Amazon EBS Multi-Attach. Multi-Attach is supported on io1 and io2 volumes.' - outpost_arn: '- (Optional) The Amazon Resource Name (ARN) of the Outpost.' - size: '- (Optional) The size of the drive in GiBs.' - snapshot_id: (Optional) A snapshot to base the EBS volume off of. - tags: '- (Optional) A map of tags to assign to the resource. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level.' - tags_all: '- A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block.' - throughput: '- (Optional) The throughput that the volume supports, in MiB/s. Only valid for type of gp3.' - type: '- (Optional) The type of EBS volume. Can be standard, gp2, gp3, io1, io2, sc1 or st1 (Default: gp2).' - update: '- (Default 5 minutes) Used for size, type, or iops volume changes' - importStatements: [] - aws_s3_bucket_acl: - subCategory: S3 (Simple Storage) - description: Provides an S3 bucket ACL resource. - name: aws_s3_bucket_acl - title: aws_s3_bucket_acl - examples: - - name: example_bucket_acl - manifest: |- - { - "acl": "private", - "bucket": "${aws_s3_bucket.example.id}" - } - references: - bucket: aws_s3_bucket.example.id - dependencies: - aws_s3_bucket.example: |- - { - "bucket": "my-tf-example-bucket" - } - - name: example - manifest: |- - { - "access_control_policy": [ - { - "grant": [ - { - "grantee": [ - { - "id": "${data.aws_canonical_user_id.current.id}", - "type": "CanonicalUser" - } - ], - "permission": "READ" - }, - { - "grantee": [ - { - "type": "Group", - "uri": "http://acs.amazonaws.com/groups/s3/LogDelivery" - } - ], - "permission": "READ_ACP" - } - ], - "owner": [ - { - "id": "${data.aws_canonical_user_id.current.id}" - } - ] - } - ], - "bucket": "${aws_s3_bucket.example.id}" - } - references: - access_control_policy.grant.grantee.id: data.aws_canonical_user_id.current.id - access_control_policy.owner.id: data.aws_canonical_user_id.current.id - bucket: aws_s3_bucket.example.id - dependencies: - aws_s3_bucket.example: |- - { - "bucket": "my-tf-example-bucket" - } - argumentDocs: - access_control_policy: '- (Optional, Conflicts with acl) A configuration block that sets the ACL permissions for an object per grantee documented below.' - access_control_policy.grant: '- (Required) Set of grant configuration blocks documented below.' - access_control_policy.grant.grantee: '- (Required) Configuration block for the person being granted permissions documented below.' - access_control_policy.grant.permission: '- (Required) Logging permissions assigned to the grantee for the bucket.' - access_control_policy.owner: '- (Required) Configuration block of the bucket owner''s display name and ID documented below.' - acl: '- (Optional, Conflicts with access_control_policy) The canned ACL to apply to the bucket.' - bucket: '- (Required, Forces new resource) The name of the bucket.' - expected_bucket_owner: '- (Optional, Forces new resource) The account ID of the expected bucket owner.' - grantee.email_address: '- (Optional) Email address of the grantee. See Regions and Endpoints for supported AWS regions where this argument can be specified.' - grantee.id: '- (Optional) The canonical user ID of the grantee.' - grantee.type: '- (Required) Type of grantee. Valid values: CanonicalUser, AmazonCustomerByEmail, Group.' - grantee.uri: '- (Optional) URI of the grantee group.' - id: '- The bucket, expected_bucket_owner (if configured), and acl (if configured) separated by commas (,).' - owner.display_name: '- (Optional) The display name of the owner.' - owner.id: '- (Required) The ID of the owner.' - importStatements: [] + ], + "owner": [ + { + "id": "${data.aws_canonical_user_id.current.id}" + } + ] + } + ], + "bucket": "${aws_s3_bucket.example.id}" + } + references: + access_control_policy.grant.grantee.id: data.aws_canonical_user_id.current.id + access_control_policy.owner.id: data.aws_canonical_user_id.current.id + bucket: aws_s3_bucket.example.id + dependencies: + aws_s3_bucket.example: |- + { + "bucket": "my-tf-example-bucket" + } + argumentDocs: + access_control_policy: "- (Optional, Conflicts with acl) A configuration block that sets the ACL permissions for an object per grantee documented below." + access_control_policy.grant: "- (Required) Set of grant configuration blocks documented below." + access_control_policy.grant.grantee: "- (Required) Configuration block for the person being granted permissions documented below." + access_control_policy.grant.permission: "- (Required) Logging permissions assigned to the grantee for the bucket." + access_control_policy.owner: "- (Required) Configuration block of the bucket owner's display name and ID documented below." + acl: "- (Optional, Conflicts with access_control_policy) The canned ACL to apply to the bucket." + bucket: "- (Required, Forces new resource) The name of the bucket." + expected_bucket_owner: "- (Optional, Forces new resource) The account ID of the expected bucket owner." + grantee.email_address: "- (Optional) Email address of the grantee. See Regions and Endpoints for supported AWS regions where this argument can be specified." + grantee.id: "- (Optional) The canonical user ID of the grantee." + grantee.type: "- (Required) Type of grantee. Valid values: CanonicalUser, AmazonCustomerByEmail, Group." + grantee.uri: "- (Optional) URI of the grantee group." + id: "- The bucket, expected_bucket_owner (if configured), and acl (if configured) separated by commas (,)." + owner.display_name: "- (Optional) The display name of the owner." + owner.id: "- (Required) The ID of the owner." + importStatements: [] diff --git a/pkg/registry/testdata/aws/r/accessanalyzer_analyzer.html.markdown b/pkg/registry/testdata/aws/r/accessanalyzer_analyzer.html.markdown index ef1d7c34..d4c1d209 100644 --- a/pkg/registry/testdata/aws/r/accessanalyzer_analyzer.html.markdown +++ b/pkg/registry/testdata/aws/r/accessanalyzer_analyzer.html.markdown @@ -1,4 +1,11 @@ + + --- + subcategory: "IAM Access Analyzer" layout: "aws" page_title: "AWS: aws_accessanalyzer_analyzer" @@ -59,5 +66,5 @@ In addition to all arguments above, the following attributes are exported: Access Analyzer Analyzers can be imported using the `analyzer_name`, e.g., ``` -$ terraform import aws_accessanalyzer_analyzer.example example +terraform import aws_accessanalyzer_analyzer.example example ``` diff --git a/pkg/registry/testdata/aws/r/ebs_volume.html.markdown b/pkg/registry/testdata/aws/r/ebs_volume.html.markdown index f363c50b..4a934ca8 100644 --- a/pkg/registry/testdata/aws/r/ebs_volume.html.markdown +++ b/pkg/registry/testdata/aws/r/ebs_volume.html.markdown @@ -1,4 +1,11 @@ + + --- + subcategory: "EBS (EC2)" layout: "aws" page_title: "AWS: aws_ebs_volume" @@ -55,14 +62,14 @@ In addition to all arguments above, the following attributes are exported: `aws_ebs_volume` provides the following [Timeouts](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts) configuration options: -- `create` - (Default `5 minutes`) Used for creating volumes. This includes the time required for the volume to become available -- `update` - (Default `5 minutes`) Used for `size`, `type`, or `iops` volume changes -- `delete` - (Default `5 minutes`) Used for destroying volumes +* `create` - (Default `5 minutes`) Used for creating volumes. This includes the time required for the volume to become available +* `update` - (Default `5 minutes`) Used for `size`, `type`, or `iops` volume changes +* `delete` - (Default `5 minutes`) Used for destroying volumes ## Import EBS Volumes can be imported using the `id`, e.g., ``` -$ terraform import aws_ebs_volume.id vol-049df61146c4d7901 +terraform import aws_ebs_volume.id vol-049df61146c4d7901 ``` diff --git a/pkg/registry/testdata/aws/r/s3_bucket_acl.html.markdown b/pkg/registry/testdata/aws/r/s3_bucket_acl.html.markdown index 65b619cb..ab2d176f 100644 --- a/pkg/registry/testdata/aws/r/s3_bucket_acl.html.markdown +++ b/pkg/registry/testdata/aws/r/s3_bucket_acl.html.markdown @@ -1,4 +1,11 @@ + + --- + subcategory: "S3 (Simple Storage)" layout: "aws" page_title: "AWS: aws_s3_bucket_acl" @@ -111,33 +118,32 @@ In addition to all arguments above, the following attributes are exported: S3 bucket ACL can be imported in one of four ways. - If the owner (account ID) of the source bucket is the _same_ account used to configure the Terraform AWS Provider, and the source bucket is **not configured** with a [canned ACL][1] (i.e. predefined grant), the S3 bucket ACL resource should be imported using the `bucket` e.g., ``` -$ terraform import aws_s3_bucket_acl.example bucket-name +terraform import aws_s3_bucket_acl.example bucket-name ``` If the owner (account ID) of the source bucket is the _same_ account used to configure the Terraform AWS Provider, and the source bucket is **configured** with a [canned ACL][1] (i.e. predefined grant), the S3 bucket ACL resource should be imported using the `bucket` and `acl` separated by a comma (`,`), e.g. ``` -$ terraform import aws_s3_bucket_acl.example bucket-name,private +terraform import aws_s3_bucket_acl.example bucket-name,private ``` If the owner (account ID) of the source bucket _differs_ from the account used to configure the Terraform AWS Provider, and the source bucket is **not configured** with a [canned ACL][1] (i.e. predefined grant), the S3 bucket ACL resource should be imported using the `bucket` and `expected_bucket_owner` separated by a comma (`,`) e.g., ``` -$ terraform import aws_s3_bucket_acl.example bucket-name,123456789012 +terraform import aws_s3_bucket_acl.example bucket-name,123456789012 ``` If the owner (account ID) of the source bucket _differs_ from the account used to configure the Terraform AWS Provider, and the source bucket is **configured** with a [canned ACL][1] (i.e. predefined grant), the S3 bucket ACL resource should be imported using the `bucket`, `expected_bucket_owner`, and `acl` separated by commas (`,`), e.g., ``` -$ terraform import aws_s3_bucket_acl.example bucket-name,123456789012,private +terraform import aws_s3_bucket_acl.example bucket-name,123456789012,private ``` [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl diff --git a/pkg/registry/testdata/azure/pm.yaml b/pkg/registry/testdata/azure/pm.yaml index f61672fd..546d1493 100644 --- a/pkg/registry/testdata/azure/pm.yaml +++ b/pkg/registry/testdata/azure/pm.yaml @@ -1,337 +1,341 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + name: test-provider resources: - azurerm_aadb2c_directory: - subCategory: AAD B2C - description: Manages an AAD B2C Directory. - name: azurerm_aadb2c_directory - title: azurerm_aadb2c_directory - examples: - - name: example - manifest: |- - { - "country_code": "US", - "data_residency_location": "United States", - "display_name": "example-b2c-tenant", - "domain_name": "exampleb2ctenant.onmicrosoft.com", - "resource_group_name": "example-rg", - "sku_name": "PremiumP1" - } - argumentDocs: - billing_type: '- The type of billing for the AAD B2C tenant. Possible values include: MAU or Auths.' - country_code: '- (Optional) Country code of the B2C tenant. The country_code should be valid for the specified data_residency_location. See official docs for valid country codes. Required when creating a new resource. Changing this forces a new AAD B2C Directory to be created.' - data_residency_location: '- (Required) Location in which the B2C tenant is hosted and data resides. The data_residency_location should be valid for the specified country_code. See official docs for more information. Changing this forces a new AAD B2C Directory to be created.' - display_name: '- (Optional) The initial display name of the B2C tenant. Required when creating a new resource. Changing this forces a new AAD B2C Directory to be created.' - domain_name: '- (Required) Domain name of the B2C tenant, including the .onmicrosoft.com suffix. Changing this forces a new AAD B2C Directory to be created.' - effective_start_date: '- The date from which the billing type took effect. May not be populated until after the first billing cycle.' - id: '- The ID of the AAD B2C Directory.' - resource_group_name: '- (Required) The name of the Resource Group where the AAD B2C Directory should exist. Changing this forces a new AAD B2C Directory to be created.' - sku_name: '- (Required) Billing SKU for the B2C tenant. Must be one of: PremiumP1 or PremiumP2 (Standard is not supported). See official docs for more information.' - tags: '- (Optional) A mapping of tags which should be assigned to the AAD B2C Directory.' - tenant_id: '- The Tenant ID for the AAD B2C tenant.' - timeouts.create: '- (Defaults to 30 minutes) Used when creating the AAD B2C Directory.' - timeouts.delete: '- (Defaults to 30 minutes) Used when deleting the AAD B2C Directory.' - timeouts.read: '- (Defaults to 5 minutes) Used when retrieving the AAD B2C Directory.' - timeouts.update: '- (Defaults to 30 minutes) Used when updating the AAD B2C Directory.' - importStatements: - - terraform import azurerm_aadb2c_directory.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory/b2cDirectories/directory-name - azurerm_attestation_provider: - subCategory: Attestation - description: Manages a Attestation Provider. - name: azurerm_attestation_provider - title: azurerm_attestation - examples: - - name: example - manifest: |- - { - "location": "${azurerm_resource_group.example.location}", - "name": "example-attestationprovider", - "policy_signing_certificate_data": "${file(\"./example/cert.pem\")}", - "resource_group_name": "${azurerm_resource_group.example.name}" - } - references: - location: azurerm_resource_group.example.location - resource_group_name: azurerm_resource_group.example.name - dependencies: - azurerm_resource_group.example: |- - { - "location": "West Europe", - "name": "example-resources" - } - argumentDocs: - attestation_uri: '- The URI of the Attestation Service.' - id: '- The ID of the Attestation Provider.' - location: '- (Required) The Azure Region where the Attestation Provider should exist. Changing this forces a new resource to be created.' - name: '- (Required) The name which should be used for this Attestation Provider. Changing this forces a new resource to be created.' - policy_signing_certificate_data: '- (Optional) A valid X.509 certificate (Section 4 of RFC4648). Changing this forces a new resource to be created.' - resource_group_name: '- (Required) The name of the Resource Group where the attestation provider should exist. Changing this forces a new resource to be created.' - tags: '- (Optional) A mapping of tags which should be assigned to the Attestation Provider.' - timeouts.create: '- (Defaults to 30 minutes) Used when creating the Attestation Provider.' - timeouts.delete: '- (Defaults to 30 minutes) Used when deleting the Attestation Provider.' - timeouts.read: '- (Defaults to 5 minutes) Used when retrieving the Attestation Provider.' - timeouts.update: '- (Defaults to 30 minutes) Used when updating the Attestation Provider.' - trust_model: '- Trust model used for the Attestation Service.' - importStatements: - - terraform import azurerm_attestation_provider.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Attestation/attestationProviders/provider1 - azurerm_kubernetes_cluster: - subCategory: Container - description: Manages a managed Kubernetes Cluster (also known as AKS / Azure Kubernetes Service) - name: azurerm_kubernetes_cluster - title: azurerm_kubernetes_cluster - examples: - - name: example - manifest: |- - { - "default_node_pool": [ - { - "name": "default", - "node_count": 1, - "vm_size": "Standard_D2_v2" - } - ], - "dns_prefix": "exampleaks1", - "identity": [ - { - "type": "SystemAssigned" - } - ], - "location": "${azurerm_resource_group.example.location}", - "name": "example-aks1", - "resource_group_name": "${azurerm_resource_group.example.name}", - "tags": { - "Environment": "Production" - } - } - references: - location: azurerm_resource_group.example.location - resource_group_name: azurerm_resource_group.example.name - dependencies: - azurerm_resource_group.example: |- - { - "location": "West Europe", - "name": "example-resources" - } - argumentDocs: - aci_connector_linux.subnet_name: '- (Required) The subnet name for the virtual nodes to run.' - allowed.day: '- (Required) A day in a week. Possible values are Sunday, Monday, Tuesday, Wednesday, Thursday, Friday and Saturday.' - allowed.hours: '- (Required) An array of hour slots in a day. For example, specifying 1 will allow maintenance from 1:00am to 2:00am. Specifying 1, 2 will allow maintenance from 1:00am to 3:00m. Possible values are between 0 and 23.' - auto_scaler_profile.balance_similar_node_groups: '- Detect similar node groups and balance the number of nodes between them. Defaults to false.' - auto_scaler_profile.empty_bulk_delete_max: '- Maximum number of empty nodes that can be deleted at the same time. Defaults to 10.' - auto_scaler_profile.expander: '- Expander to use. Possible values are least-waste, priority, most-pods and random. Defaults to random.' - auto_scaler_profile.max_graceful_termination_sec: '- Maximum number of seconds the cluster autoscaler waits for pod termination when trying to scale down a node. Defaults to 600.' - auto_scaler_profile.max_node_provisioning_time: '- Maximum time the autoscaler waits for a node to be provisioned. Defaults to 15m.' - auto_scaler_profile.max_unready_nodes: '- Maximum Number of allowed unready nodes. Defaults to 3.' - auto_scaler_profile.max_unready_percentage: '- Maximum percentage of unready nodes the cluster autoscaler will stop if the percentage is exceeded. Defaults to 45.' - auto_scaler_profile.new_pod_scale_up_delay: '- For scenarios like burst/batch scale where you don''t want CA to act before the kubernetes scheduler could schedule all the pods, you can tell CA to ignore unscheduled pods before they''re a certain age. Defaults to 10s.' - auto_scaler_profile.scale_down_delay_after_add: '- How long after the scale up of AKS nodes the scale down evaluation resumes. Defaults to 10m.' - auto_scaler_profile.scale_down_delay_after_delete: '- How long after node deletion that scale down evaluation resumes. Defaults to the value used for scan_interval.' - auto_scaler_profile.scale_down_delay_after_failure: '- How long after scale down failure that scale down evaluation resumes. Defaults to 3m.' - auto_scaler_profile.scale_down_unneeded: '- How long a node should be unneeded before it is eligible for scale down. Defaults to 10m.' - auto_scaler_profile.scale_down_unready: '- How long an unready node should be unneeded before it is eligible for scale down. Defaults to 20m.' - auto_scaler_profile.scale_down_utilization_threshold: '- Node utilization level, defined as sum of requested resources divided by capacity, below which a node can be considered for scale down. Defaults to 0.5.' - auto_scaler_profile.scan_interval: '- How often the AKS Cluster should be re-evaluated for scale up/down. Defaults to 10s.' - auto_scaler_profile.skip_nodes_with_local_storage: '- If true cluster autoscaler will never delete nodes with pods with local storage, for example, EmptyDir or HostPath. Defaults to true.' - auto_scaler_profile.skip_nodes_with_system_pods: '- If true cluster autoscaler will never delete nodes with pods from kube-system (except for DaemonSet or mirror pods). Defaults to true.' - azure_active_directory_role_based_access_control.admin_group_object_ids: '- (Optional) A list of Object IDs of Azure Active Directory Groups which should have Admin Role on the Cluster.' - azure_active_directory_role_based_access_control.azure_rbac_enabled: '- (Optional) Is Role Based Access Control based on Azure AD enabled?' - azure_active_directory_role_based_access_control.client_app_id: '- (Required) The Client ID of an Azure Active Directory Application.' - azure_active_directory_role_based_access_control.managed: '- (Optional) Is the Azure Active Directory integration Managed, meaning that Azure will create/manage the Service Principal used for integration.' - azure_active_directory_role_based_access_control.server_app_id: '- (Required) The Server ID of an Azure Active Directory Application.' - azure_active_directory_role_based_access_control.server_app_secret: '- (Required) The Server Secret of an Azure Active Directory Application.' - azure_active_directory_role_based_access_control.tenant_id: '- (Optional) The Tenant ID used for Azure Active Directory Application. If this isn''t specified the Tenant ID of the current Subscription is used.' - default_node_pool: '- (Required) A default_node_pool block as defined below.' - default_node_pool.enable_auto_scaling: '- (Optional) Should the Kubernetes Auto Scaler be enabled for this Node Pool? Defaults to false.' - default_node_pool.enable_host_encryption: '- (Optional) Should the nodes in the Default Node Pool have host encryption enabled? Defaults to false.' - default_node_pool.enable_node_public_ip: '- (Optional) Should nodes in this Node Pool have a Public IP Address? Defaults to false. Changing this forces a new resource to be created.' - default_node_pool.fips_enabled: '- (Optional) Should the nodes in this Node Pool have Federal Information Processing Standard enabled? Changing this forces a new resource to be created.' - default_node_pool.kubelet_config: '- (Optional) A kubelet_config block as defined below.' - default_node_pool.kubelet_disk_type: '- (Optional) The type of disk used by kubelet. Possible values are OS and Temporary.' - default_node_pool.linux_os_config: '- (Optional) A linux_os_config block as defined below.' - default_node_pool.max_count: '- (Required) The maximum number of nodes which should exist in this Node Pool. If specified this must be between 1 and 1000.' - default_node_pool.max_pods: '- (Optional) The maximum number of pods that can run on each agent. Changing this forces a new resource to be created.' - default_node_pool.min_count: '- (Required) The minimum number of nodes which should exist in this Node Pool. If specified this must be between 1 and 1000.' - default_node_pool.name: '- (Required) The name which should be used for the default Kubernetes Node Pool. Changing this forces a new resource to be created.' - default_node_pool.node_count: '- (Optional) The initial number of nodes which should exist in this Node Pool. If specified this must be between 1 and 1000 and between min_count and max_count.' - default_node_pool.node_labels: '- (Optional) A map of Kubernetes labels which should be applied to nodes in the Default Node Pool.' - default_node_pool.node_public_ip_prefix_id: '- (Optional) Resource ID for the Public IP Addresses Prefix for the nodes in this Node Pool. enable_node_public_ip should be true. Changing this forces a new resource to be created.' - default_node_pool.only_critical_addons_enabled: '- (Optional) Enabling this option will taint default node pool with CriticalAddonsOnly=true:NoSchedule taint. Changing this forces a new resource to be created.' - default_node_pool.orchestrator_version: '- (Optional) Version of Kubernetes used for the Agents. If not specified, the default node pool will be created with the version specified by kubernetes_version. If both are unspecified, the latest recommended version will be used at provisioning time (but won''t auto-upgrade)' - default_node_pool.os_disk_size_gb: '- (Optional) The size of the OS Disk which should be used for each agent in the Node Pool. Changing this forces a new resource to be created.' - default_node_pool.os_disk_type: '- (Optional) The type of disk which should be used for the Operating System. Possible values are Ephemeral and Managed. Defaults to Managed. Changing this forces a new resource to be created.' - default_node_pool.os_sku: '- (Optional) OsSKU to be used to specify Linux OSType. Not applicable to Windows OSType. Possible values include: Ubuntu, CBLMariner. Defaults to Ubuntu. Changing this forces a new resource to be created.' - default_node_pool.pod_subnet_id: '- (Optional) The ID of the Subnet where the pods in the default Node Pool should exist. Changing this forces a new resource to be created.' - default_node_pool.tags: '- (Optional) A mapping of tags to assign to the Node Pool.' - default_node_pool.type: '- (Optional) The type of Node Pool which should be created. Possible values are AvailabilitySet and VirtualMachineScaleSets. Defaults to VirtualMachineScaleSets.' - default_node_pool.ultra_ssd_enabled: '- (Optional) Used to specify whether the UltraSSD is enabled in the Default Node Pool. Defaults to false. See the documentation for more information.' - default_node_pool.upgrade_settings: '- (Optional) A upgrade_settings block as documented below.' - default_node_pool.vm_size: '- (Required) The size of the Virtual Machine, such as Standard_DS2_v2. Changing this forces a new resource to be created.' - default_node_pool.vnet_subnet_id: '- (Optional) The ID of a Subnet where the Kubernetes Node Pool should exist. Changing this forces a new resource to be created.' - default_node_pool.zones: '- (Optional) Specifies a list of Availability Zones in which this Kubernetes Cluster should be located. Changing this forces a new Kubernetes Cluster to be created.' - dns_prefix: '- (Optional) DNS prefix specified when creating the managed cluster. Changing this forces a new resource to be created.' - dns_prefix_private_cluster: '- (Optional) Specifies the DNS prefix to use with private clusters. Changing this forces a new resource to be created.' - fqdn: '- The FQDN of the Azure Kubernetes Managed Cluster.' - http_application_routing_zone_name: '- The Zone Name of the HTTP Application Routing.' - http_proxy_config.http_proxy: '- (Optional) The proxy address to be used when communicating over HTTP.' - http_proxy_config.https_proxy: '- (Optional) The proxy address to be used when communicating over HTTPS.' - http_proxy_config.no_proxy: '- (Optional) The list of domains that will not use the proxy for communication.' - http_proxy_config.trusted_ca: '- (Optional) The base64 encoded alternative CA certificate content in PEM format.' - id: '- The Kubernetes Managed Cluster ID.' - identity.aci_connector_linux: '- (Optional) A aci_connector_linux block as defined below. For more details, please visit Create and configure an AKS cluster to use virtual nodes.' - identity.api_server_authorized_ip_ranges: '- (Optional) The IP ranges to allow for incoming traffic to the server nodes.' - identity.auto_scaler_profile: '- (Optional) A auto_scaler_profile block as defined below.' - identity.automatic_channel_upgrade: '- (Optional) The upgrade channel for this Kubernetes Cluster. Possible values are patch, rapid, node-image and stable. Omitting this field sets this value to none.' - identity.azure_active_directory_role_based_access_control: '- (Optional) - A azure_active_directory_role_based_access_control block as defined below.' - identity.azure_policy_enabled: '- (Optional) Should the Azure Policy Add-On be enabled? For more details please visit Understand Azure Policy for Azure Kubernetes Service' - identity.disk_encryption_set_id: '- (Optional) The ID of the Disk Encryption Set which should be used for the Nodes and Volumes. More information can be found in the documentation.' - identity.http_application_routing_enabled: '- (Optional) Should HTTP Application Routing be enabled?' - identity.http_proxy_config: '- (Optional) A http_proxy_config block as defined below.' - identity.identity: '- (Optional) An identity block as defined below. One of either identity or service_principal must be specified.' - identity.identity_ids: '- (Optional) Specifies a list of User Assigned Managed Identity IDs to be assigned to this Kubernetes Cluster.' - identity.ingress_application_gateway: '- (Optional) A ingress_application_gateway block as defined below.' - identity.key_vault_secrets_provider: '- (Optional) A key_vault_secrets_provider block as defined below. For more details, please visit Azure Keyvault Secrets Provider for AKS.' - identity.kubelet_identity: '- A kubelet_identity block as defined below. Changing this forces a new resource to be created.' - identity.kubernetes_version: '- (Optional) Version of Kubernetes specified when creating the AKS managed cluster. If not specified, the latest recommended version will be used at provisioning time (but won''t auto-upgrade).' - identity.linux_profile: '- (Optional) A linux_profile block as defined below.' - identity.local_account_disabled: '- (Optional) - If true local accounts will be disabled. Defaults to false. See the documentation for more information.' - identity.maintenance_window: '- (Optional) A maintenance_window block as defined below.' - identity.microsoft_defender: '- (Optional) A microsoft_defender block as defined below.' - identity.network_profile: '- (Optional) A network_profile block as defined below.' - identity.node_resource_group: '- (Optional) The name of the Resource Group where the Kubernetes Nodes should exist. Changing this forces a new resource to be created.' - identity.oidc_issuer_enabled: '- (Required) Enable or Disable the OIDC issuer URL' - identity.oms_agent: '- (Optional) A oms_agent block as defined below.' - identity.open_service_mesh_enabled: '- (Optional) Is Open Service Mesh enabled? For more details, please visit Open Service Mesh for AKS.' - identity.principal_id: '- The Principal ID associated with this Managed Service Identity.' - identity.private_cluster_enabled: '- (Optional) Should this Kubernetes Cluster have its API server only exposed on internal IP addresses? This provides a Private IP Address for the Kubernetes API on the Virtual Network where the Kubernetes Cluster is located. Defaults to false. Changing this forces a new resource to be created.' - identity.private_cluster_public_fqdn_enabled: '- (Optional) Specifies whether a Public FQDN for this Private Cluster should be added. Defaults to false.' - identity.private_dns_zone_id: '- (Optional) Either the ID of Private DNS Zone which should be delegated to this Cluster, System to have AKS manage this or None. In case of None you will need to bring your own DNS server and set up resolving, otherwise cluster will have issues after provisioning. Changing this forces a new resource to be created.' - identity.role_based_access_control_enabled: (Optional) - Whether Role Based Access Control for the Kubernetes Cluster should be enabled. Defaults to true. Changing this forces a new resource to be created. - identity.run_command_enabled: '- (Optional) Whether to enable run command for the cluster or not. Defaults to true.' - identity.service_principal: '- (Optional) A service_principal block as documented below. One of either identity or service_principal must be specified.' - identity.sku_tier: '- (Optional) The SKU Tier that should be used for this Kubernetes Cluster. Possible values are Free and Paid (which includes the Uptime SLA). Defaults to Free.' - identity.tags: '- (Optional) A mapping of tags to assign to the resource.' - identity.tenant_id: '- The Tenant ID associated with this Managed Service Identity.' - identity.type: '- (Required) Specifies the type of Managed Service Identity that should be configured on this Kubernetes Cluster. Possible values are SystemAssigned, UserAssigned, SystemAssigned, UserAssigned (to enable both).' - identity.windows_profile: '- (Optional) A windows_profile block as defined below.' - ingress_application_gateway.effective_gateway_id: '- The ID of the Application Gateway associated with the ingress controller deployed to this Kubernetes Cluster.' - ingress_application_gateway.gateway_id: '- (Optional) The ID of the Application Gateway to integrate with the ingress controller of this Kubernetes Cluster. See this page for further details.' - ingress_application_gateway.gateway_name: '- (Optional) The name of the Application Gateway to be used or created in the Nodepool Resource Group, which in turn will be integrated with the ingress controller of this Kubernetes Cluster. See this page for further details.' - ingress_application_gateway.ingress_application_gateway_identity: '- An ingress_application_gateway_identity block is exported. The exported attributes are defined below.' - ingress_application_gateway.subnet_cidr: '- (Optional) The subnet CIDR to be used to create an Application Gateway, which in turn will be integrated with the ingress controller of this Kubernetes Cluster. See this page for further details.' - ingress_application_gateway.subnet_id: '- (Optional) The ID of the subnet on which to create an Application Gateway, which in turn will be integrated with the ingress controller of this Kubernetes Cluster. See this page for further details.' - ingress_application_gateway_identity.client_id: '- The Client ID of the user-defined Managed Identity used by the Application Gateway.' - ingress_application_gateway_identity.object_id: '- The Object ID of the user-defined Managed Identity used by the Application Gateway.' - ingress_application_gateway_identity.user_assigned_identity_id: '- The ID of the User Assigned Identity used by the Application Gateway.' - key_vault_secrets_provider.secret_identity: '- An secret_identity block is exported. The exported attributes are defined below.' - key_vault_secrets_provider.secret_identity.client_id: '- The Client ID of the user-defined Managed Identity used by the Secret Provider.' - key_vault_secrets_provider.secret_identity.object_id: '- The Object ID of the user-defined Managed Identity used by the Secret Provider.' - key_vault_secrets_provider.secret_identity.user_assigned_identity_id: '- The ID of the User Assigned Identity used by the Secret Provider.' - key_vault_secrets_provider.secret_rotation_enabled: '- (Required) Is secret rotation enabled?' - key_vault_secrets_provider.secret_rotation_interval: '- (Required) The interval to poll for secret rotation. This attribute is only set when secret_rotation is true and defaults to 2m.' - kube_admin_config: '- A kube_admin_config block as defined below. This is only available when Role Based Access Control with Azure Active Directory is enabled and local accounts enabled.' - kube_admin_config.client_certificate: '- Base64 encoded public certificate used by clients to authenticate to the Kubernetes cluster.' - kube_admin_config.client_key: '- Base64 encoded private key used by clients to authenticate to the Kubernetes cluster.' - kube_admin_config.cluster_ca_certificate: '- Base64 encoded public CA certificate used as the root of trust for the Kubernetes cluster.' - kube_admin_config.host: '- The Kubernetes cluster server host.' - kube_admin_config.password: '- A password or token used to authenticate to the Kubernetes cluster.' - kube_admin_config.username: '- A username used to authenticate to the Kubernetes cluster.' - kube_admin_config_raw: '- Raw Kubernetes config for the admin account to be used by kubectl and other compatible tools. This is only available when Role Based Access Control with Azure Active Directory is enabled and local accounts enabled.' - kube_config: '- A kube_config block as defined below.' - kube_config_raw: '- Raw Kubernetes config to be used by kubectl and other compatible tools.' - kubelet_config.allowed_unsafe_sysctls: '- (Optional) Specifies the allow list of unsafe sysctls command or patterns (ending in *). Changing this forces a new resource to be created.' - kubelet_config.container_log_max_line: '- (Optional) Specifies the maximum number of container log files that can be present for a container. must be at least 2. Changing this forces a new resource to be created.' - kubelet_config.container_log_max_size_mb: '- (Optional) Specifies the maximum size (e.g. 10MB) of container log file before it is rotated. Changing this forces a new resource to be created.' - kubelet_config.cpu_cfs_quota_enabled: '- (Optional) Is CPU CFS quota enforcement for containers enabled? Changing this forces a new resource to be created.' - kubelet_config.cpu_cfs_quota_period: '- (Optional) Specifies the CPU CFS quota period value. Changing this forces a new resource to be created.' - kubelet_config.cpu_manager_policy: '- (Optional) Specifies the CPU Manager policy to use. Possible values are none and static, Changing this forces a new resource to be created.' - kubelet_config.image_gc_high_threshold: '- (Optional) Specifies the percent of disk usage above which image garbage collection is always run. Must be between 0 and 100. Changing this forces a new resource to be created.' - kubelet_config.image_gc_low_threshold: '- (Optional) Specifies the percent of disk usage lower than which image garbage collection is never run. Must be between 0 and 100. Changing this forces a new resource to be created.' - kubelet_config.pod_max_pid: '- (Optional) Specifies the maximum number of processes per pod. Changing this forces a new resource to be created.' - kubelet_config.topology_manager_policy: '- (Optional) Specifies the Topology Manager policy to use. Possible values are none, best-effort, restricted or single-numa-node. Changing this forces a new resource to be created.' - kubelet_identity.client_id: '- (Required) The Client ID of the user-defined Managed Identity to be assigned to the Kubelets. If not specified a Managed Identity is created automatically.' - kubelet_identity.object_id: '- (Required) The Object ID of the user-defined Managed Identity assigned to the Kubelets.If not specified a Managed Identity is created automatically.' - kubelet_identity.user_assigned_identity_id: '- (Required) The ID of the User Assigned Identity assigned to the Kubelets. If not specified a Managed Identity is created automatically.' - linux_os_config.swap_file_size_mb: '- (Optional) Specifies the size of swap file on each node in MB. Changing this forces a new resource to be created.' - linux_os_config.sysctl_config: '- (Optional) A sysctl_config block as defined below. Changing this forces a new resource to be created.' - linux_os_config.transparent_huge_page_defrag: '- (Optional) specifies the defrag configuration for Transparent Huge Page. Possible values are always, defer, defer+madvise, madvise and never. Changing this forces a new resource to be created.' - linux_os_config.transparent_huge_page_enabled: '- (Optional) Specifies the Transparent Huge Page enabled configuration. Possible values are always, madvise and never. Changing this forces a new resource to be created.' - linux_profile.admin_username: '- (Required) The Admin Username for the Cluster. Changing this forces a new resource to be created.' - linux_profile.ssh_key: '- (Required) An ssh_key block. Only one is currently allowed. Changing this forces a new resource to be created.' - load_balancer_profile.effective_outbound_ips: '- The outcome (resource IDs) of the specified arguments.' - load_balancer_profile.idle_timeout_in_minutes: '- (Optional) Desired outbound flow idle timeout in minutes for the cluster load balancer. Must be between 4 and 120 inclusive. Defaults to 30.' - load_balancer_profile.managed_outbound_ip_count: '- (Optional) Count of desired managed outbound IPs for the cluster load balancer. Must be between 1 and 100 inclusive.' - load_balancer_profile.outbound_ip_address_ids: '- (Optional) The ID of the Public IP Addresses which should be used for outbound communication for the cluster load balancer.' - load_balancer_profile.outbound_ip_prefix_ids: '- (Optional) The ID of the outbound Public IP Address Prefixes which should be used for the cluster load balancer.' - load_balancer_profile.outbound_ports_allocated: '- (Optional) Number of desired SNAT port for each VM in the clusters load balancer. Must be between 0 and 64000 inclusive. Defaults to 0.' - location: '- (Required) The location where the Managed Kubernetes Cluster should be created. Changing this forces a new resource to be created.' - maintenance_window.allowed: '- (Optional) One or more allowed block as defined below.' - maintenance_window.not_allowed: '- (Optional) One or more not_allowed block as defined below.' - microsoft_defender.log_analytics_workspace_id: '- (Required) Specifies the ID of the Log Analytics Workspace where the audit logs collected by Microsoft Defender should be sent to.' - name: '- (Required) The name of the Managed Kubernetes Cluster to create. Changing this forces a new resource to be created.' - nat_gateway_profile.effective_outbound_ips: '- The outcome (resource IDs) of the specified arguments.' - nat_gateway_profile.idle_timeout_in_minutes: '- (Optional) Desired outbound flow idle timeout in minutes for the cluster load balancer. Must be between 4 and 120 inclusive. Defaults to 4.' - nat_gateway_profile.managed_outbound_ip_count: '- (Optional) Count of desired managed outbound IPs for the cluster load balancer. Must be between 1 and 100 inclusive.' - network_profile.network_plugin: '- (Required) Network plugin to use for networking. Currently supported values are azure, kubenet and none. Changing this forces a new resource to be created.' - network_profile.network_plugin.dns_service_ip: '- (Optional) IP address within the Kubernetes service address range that will be used by cluster service discovery (kube-dns). Changing this forces a new resource to be created.' - network_profile.network_plugin.docker_bridge_cidr: '- (Optional) IP address (in CIDR notation) used as the Docker bridge IP address on nodes. Changing this forces a new resource to be created.' - network_profile.network_plugin.ip_versions: '- (Optional) Specifies a list of IP versions the Kubernetes Cluster will use to assign IP addresses to its nodes and pods. Possible values are IPv4 and/or IPv6. IPv4 must always be specified. Changing this forces a new resource to be created.' - network_profile.network_plugin.load_balancer_profile: '- (Optional) A load_balancer_profile block. This can only be specified when load_balancer_sku is set to standard.' - network_profile.network_plugin.load_balancer_sku: '- (Optional) Specifies the SKU of the Load Balancer used for this Kubernetes Cluster. Possible values are basic and standard. Defaults to standard.' - network_profile.network_plugin.nat_gateway_profile: '- (Optional) A nat_gateway_profile block. This can only be specified when load_balancer_sku is set to standard and outbound_type is set to managedNATGateway or userAssignedNATGateway.' - network_profile.network_plugin.network_mode: '- (Optional) Network mode to be used with Azure CNI. Possible values are bridge and transparent. Changing this forces a new resource to be created.' - network_profile.network_plugin.network_policy: '- (Optional) Sets up network policy to be used with Azure CNI. Network policy allows us to control the traffic flow between pods. Currently supported values are calico and azure. Changing this forces a new resource to be created.' - network_profile.network_plugin.outbound_type: '- (Optional) The outbound (egress) routing method which should be used for this Kubernetes Cluster. Possible values are loadBalancer, userDefinedRouting, managedNATGateway and userAssignedNATGateway. Defaults to loadBalancer.' - network_profile.network_plugin.pod_cidr: '- (Optional) The CIDR to use for pod IP addresses. This field can only be set when network_plugin is set to kubenet. Changing this forces a new resource to be created.' - network_profile.network_plugin.service_cidr: '- (Optional) The Network Range used by the Kubernetes service. Changing this forces a new resource to be created.' - node_resource_group: '- The auto-generated Resource Group which contains the resources for this Managed Kubernetes Cluster.' - not_allowed.end: '- (Required) The end of a time span, formatted as an RFC3339 string.' - not_allowed.start: '- (Required) The start of a time span, formatted as an RFC3339 string.' - oidc_issuer_url: '- The OIDC issuer URL that is associated with the cluster.' - oms_agent.log_analytics_workspace_id: '- (Required) The ID of the Log Analytics Workspace which the OMS Agent should send data to.' - oms_agent.oms_agent_identity: '- An oms_agent_identity block is exported. The exported attributes are defined below.' - oms_agent.oms_agent_identity.client_id: '- The Client ID of the user-defined Managed Identity used by the OMS Agents.' - oms_agent.oms_agent_identity.object_id: '- The Object ID of the user-defined Managed Identity used by the OMS Agents.' - oms_agent.oms_agent_identity.user_assigned_identity_id: '- The ID of the User Assigned Identity used by the OMS Agents.' - portal_fqdn: '- The FQDN for the Azure Portal resources when private link has been enabled, which is only resolvable inside the Virtual Network used by the Kubernetes Cluster.' - private_fqdn: '- The FQDN for the Kubernetes Cluster when private link has been enabled, which is only resolvable inside the Virtual Network used by the Kubernetes Cluster.' - resource_group_name: '- (Required) Specifies the Resource Group where the Managed Kubernetes Cluster should exist. Changing this forces a new resource to be created.' - service_principal.client_id: '- (Required) The Client ID for the Service Principal.' - service_principal.client_secret: '- (Required) The Client Secret for the Service Principal.' - ssh_key.key_data: '- (Required) The Public SSH Key used to access the cluster. Changing this forces a new resource to be created.' - sysctl_config.fs_aio_max_nr: '- (Optional) The sysctl setting fs.aio-max-nr. Must be between 65536 and 6553500. Changing this forces a new resource to be created.' - sysctl_config.fs_file_max: '- (Optional) The sysctl setting fs.file-max. Must be between 8192 and 12000500. Changing this forces a new resource to be created.' - sysctl_config.fs_inotify_max_user_watches: '- (Optional) The sysctl setting fs.inotify.max_user_watches. Must be between 781250 and 2097152. Changing this forces a new resource to be created.' - sysctl_config.fs_nr_open: '- (Optional) The sysctl setting fs.nr_open. Must be between 8192 and 20000500. Changing this forces a new resource to be created.' - sysctl_config.kernel_threads_max: '- (Optional) The sysctl setting kernel.threads-max. Must be between 20 and 513785. Changing this forces a new resource to be created.' - sysctl_config.net_core_netdev_max_backlog: '- (Optional) The sysctl setting net.core.netdev_max_backlog. Must be between 1000 and 3240000. Changing this forces a new resource to be created.' - sysctl_config.net_core_optmem_max: '- (Optional) The sysctl setting net.core.optmem_max. Must be between 20480 and 4194304. Changing this forces a new resource to be created.' - sysctl_config.net_core_rmem_default: '- (Optional) The sysctl setting net.core.rmem_default. Must be between 212992 and 134217728. Changing this forces a new resource to be created.' - sysctl_config.net_core_rmem_max: '- (Optional) The sysctl setting net.core.rmem_max. Must be between 212992 and 134217728. Changing this forces a new resource to be created.' - sysctl_config.net_core_somaxconn: '- (Optional) The sysctl setting net.core.somaxconn. Must be between 4096 and 3240000. Changing this forces a new resource to be created.' - sysctl_config.net_core_wmem_default: '- (Optional) The sysctl setting net.core.wmem_default. Must be between 212992 and 134217728. Changing this forces a new resource to be created.' - sysctl_config.net_core_wmem_max: '- (Optional) The sysctl setting net.core.wmem_max. Must be between 212992 and 134217728. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_ip_local_port_range_max: '- (Optional) The sysctl setting net.ipv4.ip_local_port_range max value. Must be between 1024 and 60999. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_ip_local_port_range_min: '- (Optional) The sysctl setting net.ipv4.ip_local_port_range min value. Must be between 1024 and 60999. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_neigh_default_gc_thresh1: '- (Optional) The sysctl setting net.ipv4.neigh.default.gc_thresh1. Must be between 128 and 80000. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_neigh_default_gc_thresh2: '- (Optional) The sysctl setting net.ipv4.neigh.default.gc_thresh2. Must be between 512 and 90000. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_neigh_default_gc_thresh3: '- (Optional) The sysctl setting net.ipv4.neigh.default.gc_thresh3. Must be between 1024 and 100000. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_tcp_fin_timeout: '- (Optional) The sysctl setting net.ipv4.tcp_fin_timeout. Must be between 5 and 120. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_tcp_keepalive_intvl: '- (Optional) The sysctl setting net.ipv4.tcp_keepalive_intvl. Must be between 10 and 75. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_tcp_keepalive_probes: '- (Optional) The sysctl setting net.ipv4.tcp_keepalive_probes. Must be between 1 and 15. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_tcp_keepalive_time: '- (Optional) The sysctl setting net.ipv4.tcp_keepalive_time. Must be between 30 and 432000. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_tcp_max_syn_backlog: '- (Optional) The sysctl setting net.ipv4.tcp_max_syn_backlog. Must be between 128 and 3240000. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_tcp_max_tw_buckets: '- (Optional) The sysctl setting net.ipv4.tcp_max_tw_buckets. Must be between 8000 and 1440000. Changing this forces a new resource to be created.' - sysctl_config.net_ipv4_tcp_tw_reuse: '- (Optional) The sysctl setting net.ipv4.tcp_tw_reuse. Changing this forces a new resource to be created.' - sysctl_config.net_netfilter_nf_conntrack_buckets: '- (Optional) The sysctl setting net.netfilter.nf_conntrack_buckets. Must be between 65536 and 147456. Changing this forces a new resource to be created.' - sysctl_config.net_netfilter_nf_conntrack_max: '- (Optional) The sysctl setting net.netfilter.nf_conntrack_max. Must be between 131072 and 1048576. Changing this forces a new resource to be created.' - sysctl_config.vm_max_map_count: '- (Optional) The sysctl setting vm.max_map_count. Must be between 65530 and 262144. Changing this forces a new resource to be created.' - sysctl_config.vm_swappiness: '- (Optional) The sysctl setting vm.swappiness. Must be between 0 and 100. Changing this forces a new resource to be created.' - sysctl_config.vm_vfs_cache_pressure: '- (Optional) The sysctl setting vm.vfs_cache_pressure. Must be between 0 and 100. Changing this forces a new resource to be created.' - timeouts.create: '- (Defaults to 90 minutes) Used when creating the Kubernetes Cluster.' - timeouts.delete: '- (Defaults to 90 minutes) Used when deleting the Kubernetes Cluster.' - timeouts.read: '- (Defaults to 5 minutes) Used when retrieving the Kubernetes Cluster.' - timeouts.update: '- (Defaults to 90 minutes) Used when updating the Kubernetes Cluster.' - upgrade_settings.max_surge: '- (Required) The maximum number or percentage of nodes which will be added to the Node Pool size during an upgrade.' - windows_profile.admin_password: '- (Required) The Admin Password for Windows VMs. Length must be between 14 and 123 characters.' - windows_profile.admin_username: '- (Required) The Admin Username for Windows VMs.' - windows_profile.license: '- (Optional) Specifies the type of on-premise license which should be used for Node Pool Windows Virtual Machine. At this time the only possible value is Windows_Server.' - importStatements: - - terraform import azurerm_kubernetes_cluster.cluster1 /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/group1/providers/Microsoft.ContainerService/managedClusters/cluster1 + azurerm_aadb2c_directory: + subCategory: AAD B2C + description: Manages an AAD B2C Directory. + name: azurerm_aadb2c_directory + title: azurerm_aadb2c_directory + examples: + - name: example + manifest: |- + { + "country_code": "US", + "data_residency_location": "United States", + "display_name": "example-b2c-tenant", + "domain_name": "exampleb2ctenant.onmicrosoft.com", + "resource_group_name": "example-rg", + "sku_name": "PremiumP1" + } + argumentDocs: + billing_type: "- The type of billing for the AAD B2C tenant. Possible values include: MAU or Auths." + country_code: "- (Optional) Country code of the B2C tenant. The country_code should be valid for the specified data_residency_location. See official docs for valid country codes. Required when creating a new resource. Changing this forces a new AAD B2C Directory to be created." + data_residency_location: "- (Required) Location in which the B2C tenant is hosted and data resides. The data_residency_location should be valid for the specified country_code. See official docs for more information. Changing this forces a new AAD B2C Directory to be created." + display_name: "- (Optional) The initial display name of the B2C tenant. Required when creating a new resource. Changing this forces a new AAD B2C Directory to be created." + domain_name: "- (Required) Domain name of the B2C tenant, including the .onmicrosoft.com suffix. Changing this forces a new AAD B2C Directory to be created." + effective_start_date: "- The date from which the billing type took effect. May not be populated until after the first billing cycle." + id: "- The ID of the AAD B2C Directory." + resource_group_name: "- (Required) The name of the Resource Group where the AAD B2C Directory should exist. Changing this forces a new AAD B2C Directory to be created." + sku_name: "- (Required) Billing SKU for the B2C tenant. Must be one of: PremiumP1 or PremiumP2 (Standard is not supported). See official docs for more information." + tags: "- (Optional) A mapping of tags which should be assigned to the AAD B2C Directory." + tenant_id: "- The Tenant ID for the AAD B2C tenant." + timeouts.create: "- (Defaults to 30 minutes) Used when creating the AAD B2C Directory." + timeouts.delete: "- (Defaults to 30 minutes) Used when deleting the AAD B2C Directory." + timeouts.read: "- (Defaults to 5 minutes) Used when retrieving the AAD B2C Directory." + timeouts.update: "- (Defaults to 30 minutes) Used when updating the AAD B2C Directory." + importStatements: + - terraform import azurerm_aadb2c_directory.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory/b2cDirectories/directory-name + azurerm_attestation_provider: + subCategory: Attestation + description: Manages a Attestation Provider. + name: azurerm_attestation_provider + title: azurerm_attestation + examples: + - name: example + manifest: |- + { + "location": "${azurerm_resource_group.example.location}", + "name": "example-attestationprovider", + "policy_signing_certificate_data": "${file(\"./example/cert.pem\")}", + "resource_group_name": "${azurerm_resource_group.example.name}" + } + references: + location: azurerm_resource_group.example.location + resource_group_name: azurerm_resource_group.example.name + dependencies: + azurerm_resource_group.example: |- + { + "location": "West Europe", + "name": "example-resources" + } + argumentDocs: + attestation_uri: "- The URI of the Attestation Service." + id: "- The ID of the Attestation Provider." + location: "- (Required) The Azure Region where the Attestation Provider should exist. Changing this forces a new resource to be created." + name: "- (Required) The name which should be used for this Attestation Provider. Changing this forces a new resource to be created." + policy_signing_certificate_data: "- (Optional) A valid X.509 certificate (Section 4 of RFC4648). Changing this forces a new resource to be created." + resource_group_name: "- (Required) The name of the Resource Group where the attestation provider should exist. Changing this forces a new resource to be created." + tags: "- (Optional) A mapping of tags which should be assigned to the Attestation Provider." + timeouts.create: "- (Defaults to 30 minutes) Used when creating the Attestation Provider." + timeouts.delete: "- (Defaults to 30 minutes) Used when deleting the Attestation Provider." + timeouts.read: "- (Defaults to 5 minutes) Used when retrieving the Attestation Provider." + timeouts.update: "- (Defaults to 30 minutes) Used when updating the Attestation Provider." + trust_model: "- Trust model used for the Attestation Service." + importStatements: + - terraform import azurerm_attestation_provider.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Attestation/attestationProviders/provider1 + azurerm_kubernetes_cluster: + subCategory: Container + description: Manages a managed Kubernetes Cluster (also known as AKS / Azure Kubernetes Service) + name: azurerm_kubernetes_cluster + title: azurerm_kubernetes_cluster + examples: + - name: example + manifest: |- + { + "default_node_pool": [ + { + "name": "default", + "node_count": 1, + "vm_size": "Standard_D2_v2" + } + ], + "dns_prefix": "exampleaks1", + "identity": [ + { + "type": "SystemAssigned" + } + ], + "location": "${azurerm_resource_group.example.location}", + "name": "example-aks1", + "resource_group_name": "${azurerm_resource_group.example.name}", + "tags": { + "Environment": "Production" + } + } + references: + location: azurerm_resource_group.example.location + resource_group_name: azurerm_resource_group.example.name + dependencies: + azurerm_resource_group.example: |- + { + "location": "West Europe", + "name": "example-resources" + } + argumentDocs: + aci_connector_linux.subnet_name: "- (Required) The subnet name for the virtual nodes to run." + allowed.day: "- (Required) A day in a week. Possible values are Sunday, Monday, Tuesday, Wednesday, Thursday, Friday and Saturday." + allowed.hours: "- (Required) An array of hour slots in a day. For example, specifying 1 will allow maintenance from 1:00am to 2:00am. Specifying 1, 2 will allow maintenance from 1:00am to 3:00m. Possible values are between 0 and 23." + auto_scaler_profile.balance_similar_node_groups: "- Detect similar node groups and balance the number of nodes between them. Defaults to false." + auto_scaler_profile.empty_bulk_delete_max: "- Maximum number of empty nodes that can be deleted at the same time. Defaults to 10." + auto_scaler_profile.expander: "- Expander to use. Possible values are least-waste, priority, most-pods and random. Defaults to random." + auto_scaler_profile.max_graceful_termination_sec: "- Maximum number of seconds the cluster autoscaler waits for pod termination when trying to scale down a node. Defaults to 600." + auto_scaler_profile.max_node_provisioning_time: "- Maximum time the autoscaler waits for a node to be provisioned. Defaults to 15m." + auto_scaler_profile.max_unready_nodes: "- Maximum Number of allowed unready nodes. Defaults to 3." + auto_scaler_profile.max_unready_percentage: "- Maximum percentage of unready nodes the cluster autoscaler will stop if the percentage is exceeded. Defaults to 45." + auto_scaler_profile.new_pod_scale_up_delay: "- For scenarios like burst/batch scale where you don't want CA to act before the kubernetes scheduler could schedule all the pods, you can tell CA to ignore unscheduled pods before they're a certain age. Defaults to 10s." + auto_scaler_profile.scale_down_delay_after_add: "- How long after the scale up of AKS nodes the scale down evaluation resumes. Defaults to 10m." + auto_scaler_profile.scale_down_delay_after_delete: "- How long after node deletion that scale down evaluation resumes. Defaults to the value used for scan_interval." + auto_scaler_profile.scale_down_delay_after_failure: "- How long after scale down failure that scale down evaluation resumes. Defaults to 3m." + auto_scaler_profile.scale_down_unneeded: "- How long a node should be unneeded before it is eligible for scale down. Defaults to 10m." + auto_scaler_profile.scale_down_unready: "- How long an unready node should be unneeded before it is eligible for scale down. Defaults to 20m." + auto_scaler_profile.scale_down_utilization_threshold: "- Node utilization level, defined as sum of requested resources divided by capacity, below which a node can be considered for scale down. Defaults to 0.5." + auto_scaler_profile.scan_interval: "- How often the AKS Cluster should be re-evaluated for scale up/down. Defaults to 10s." + auto_scaler_profile.skip_nodes_with_local_storage: "- If true cluster autoscaler will never delete nodes with pods with local storage, for example, EmptyDir or HostPath. Defaults to true." + auto_scaler_profile.skip_nodes_with_system_pods: "- If true cluster autoscaler will never delete nodes with pods from kube-system (except for DaemonSet or mirror pods). Defaults to true." + azure_active_directory_role_based_access_control.admin_group_object_ids: "- (Optional) A list of Object IDs of Azure Active Directory Groups which should have Admin Role on the Cluster." + azure_active_directory_role_based_access_control.azure_rbac_enabled: "- (Optional) Is Role Based Access Control based on Azure AD enabled?" + azure_active_directory_role_based_access_control.client_app_id: "- (Required) The Client ID of an Azure Active Directory Application." + azure_active_directory_role_based_access_control.managed: "- (Optional) Is the Azure Active Directory integration Managed, meaning that Azure will create/manage the Service Principal used for integration." + azure_active_directory_role_based_access_control.server_app_id: "- (Required) The Server ID of an Azure Active Directory Application." + azure_active_directory_role_based_access_control.server_app_secret: "- (Required) The Server Secret of an Azure Active Directory Application." + azure_active_directory_role_based_access_control.tenant_id: "- (Optional) The Tenant ID used for Azure Active Directory Application. If this isn't specified the Tenant ID of the current Subscription is used." + default_node_pool: "- (Required) A default_node_pool block as defined below." + default_node_pool.enable_auto_scaling: "- (Optional) Should the Kubernetes Auto Scaler be enabled for this Node Pool? Defaults to false." + default_node_pool.enable_host_encryption: "- (Optional) Should the nodes in the Default Node Pool have host encryption enabled? Defaults to false." + default_node_pool.enable_node_public_ip: "- (Optional) Should nodes in this Node Pool have a Public IP Address? Defaults to false. Changing this forces a new resource to be created." + default_node_pool.fips_enabled: "- (Optional) Should the nodes in this Node Pool have Federal Information Processing Standard enabled? Changing this forces a new resource to be created." + default_node_pool.kubelet_config: "- (Optional) A kubelet_config block as defined below." + default_node_pool.kubelet_disk_type: "- (Optional) The type of disk used by kubelet. Possible values are OS and Temporary." + default_node_pool.linux_os_config: "- (Optional) A linux_os_config block as defined below." + default_node_pool.max_count: "- (Required) The maximum number of nodes which should exist in this Node Pool. If specified this must be between 1 and 1000." + default_node_pool.max_pods: "- (Optional) The maximum number of pods that can run on each agent. Changing this forces a new resource to be created." + default_node_pool.min_count: "- (Required) The minimum number of nodes which should exist in this Node Pool. If specified this must be between 1 and 1000." + default_node_pool.name: "- (Required) The name which should be used for the default Kubernetes Node Pool. Changing this forces a new resource to be created." + default_node_pool.node_count: "- (Optional) The initial number of nodes which should exist in this Node Pool. If specified this must be between 1 and 1000 and between min_count and max_count." + default_node_pool.node_labels: "- (Optional) A map of Kubernetes labels which should be applied to nodes in the Default Node Pool." + default_node_pool.node_public_ip_prefix_id: "- (Optional) Resource ID for the Public IP Addresses Prefix for the nodes in this Node Pool. enable_node_public_ip should be true. Changing this forces a new resource to be created." + default_node_pool.only_critical_addons_enabled: "- (Optional) Enabling this option will taint default node pool with CriticalAddonsOnly=true:NoSchedule taint. Changing this forces a new resource to be created." + default_node_pool.orchestrator_version: "- (Optional) Version of Kubernetes used for the Agents. If not specified, the default node pool will be created with the version specified by kubernetes_version. If both are unspecified, the latest recommended version will be used at provisioning time (but won't auto-upgrade)" + default_node_pool.os_disk_size_gb: "- (Optional) The size of the OS Disk which should be used for each agent in the Node Pool. Changing this forces a new resource to be created." + default_node_pool.os_disk_type: "- (Optional) The type of disk which should be used for the Operating System. Possible values are Ephemeral and Managed. Defaults to Managed. Changing this forces a new resource to be created." + default_node_pool.os_sku: "- (Optional) OsSKU to be used to specify Linux OSType. Not applicable to Windows OSType. Possible values include: Ubuntu, CBLMariner. Defaults to Ubuntu. Changing this forces a new resource to be created." + default_node_pool.pod_subnet_id: "- (Optional) The ID of the Subnet where the pods in the default Node Pool should exist. Changing this forces a new resource to be created." + default_node_pool.tags: "- (Optional) A mapping of tags to assign to the Node Pool." + default_node_pool.type: "- (Optional) The type of Node Pool which should be created. Possible values are AvailabilitySet and VirtualMachineScaleSets. Defaults to VirtualMachineScaleSets." + default_node_pool.ultra_ssd_enabled: "- (Optional) Used to specify whether the UltraSSD is enabled in the Default Node Pool. Defaults to false. See the documentation for more information." + default_node_pool.upgrade_settings: "- (Optional) A upgrade_settings block as documented below." + default_node_pool.vm_size: "- (Required) The size of the Virtual Machine, such as Standard_DS2_v2. Changing this forces a new resource to be created." + default_node_pool.vnet_subnet_id: "- (Optional) The ID of a Subnet where the Kubernetes Node Pool should exist. Changing this forces a new resource to be created." + default_node_pool.zones: "- (Optional) Specifies a list of Availability Zones in which this Kubernetes Cluster should be located. Changing this forces a new Kubernetes Cluster to be created." + dns_prefix: "- (Optional) DNS prefix specified when creating the managed cluster. Changing this forces a new resource to be created." + dns_prefix_private_cluster: "- (Optional) Specifies the DNS prefix to use with private clusters. Changing this forces a new resource to be created." + fqdn: "- The FQDN of the Azure Kubernetes Managed Cluster." + http_application_routing_zone_name: "- The Zone Name of the HTTP Application Routing." + http_proxy_config.http_proxy: "- (Optional) The proxy address to be used when communicating over HTTP." + http_proxy_config.https_proxy: "- (Optional) The proxy address to be used when communicating over HTTPS." + http_proxy_config.no_proxy: "- (Optional) The list of domains that will not use the proxy for communication." + http_proxy_config.trusted_ca: "- (Optional) The base64 encoded alternative CA certificate content in PEM format." + id: "- The Kubernetes Managed Cluster ID." + identity.aci_connector_linux: "- (Optional) A aci_connector_linux block as defined below. For more details, please visit Create and configure an AKS cluster to use virtual nodes." + identity.api_server_authorized_ip_ranges: "- (Optional) The IP ranges to allow for incoming traffic to the server nodes." + identity.auto_scaler_profile: "- (Optional) A auto_scaler_profile block as defined below." + identity.automatic_channel_upgrade: "- (Optional) The upgrade channel for this Kubernetes Cluster. Possible values are patch, rapid, node-image and stable. Omitting this field sets this value to none." + identity.azure_active_directory_role_based_access_control: "- (Optional) - A azure_active_directory_role_based_access_control block as defined below." + identity.azure_policy_enabled: "- (Optional) Should the Azure Policy Add-On be enabled? For more details please visit Understand Azure Policy for Azure Kubernetes Service" + identity.disk_encryption_set_id: "- (Optional) The ID of the Disk Encryption Set which should be used for the Nodes and Volumes. More information can be found in the documentation." + identity.http_application_routing_enabled: "- (Optional) Should HTTP Application Routing be enabled?" + identity.http_proxy_config: "- (Optional) A http_proxy_config block as defined below." + identity.identity: "- (Optional) An identity block as defined below. One of either identity or service_principal must be specified." + identity.identity_ids: "- (Optional) Specifies a list of User Assigned Managed Identity IDs to be assigned to this Kubernetes Cluster." + identity.ingress_application_gateway: "- (Optional) A ingress_application_gateway block as defined below." + identity.key_vault_secrets_provider: "- (Optional) A key_vault_secrets_provider block as defined below. For more details, please visit Azure Keyvault Secrets Provider for AKS." + identity.kubelet_identity: "- A kubelet_identity block as defined below. Changing this forces a new resource to be created." + identity.kubernetes_version: "- (Optional) Version of Kubernetes specified when creating the AKS managed cluster. If not specified, the latest recommended version will be used at provisioning time (but won't auto-upgrade)." + identity.linux_profile: "- (Optional) A linux_profile block as defined below." + identity.local_account_disabled: "- (Optional) - If true local accounts will be disabled. Defaults to false. See the documentation for more information." + identity.maintenance_window: "- (Optional) A maintenance_window block as defined below." + identity.microsoft_defender: "- (Optional) A microsoft_defender block as defined below." + identity.network_profile: "- (Optional) A network_profile block as defined below." + identity.node_resource_group: "- (Optional) The name of the Resource Group where the Kubernetes Nodes should exist. Changing this forces a new resource to be created." + identity.oidc_issuer_enabled: "- (Required) Enable or Disable the OIDC issuer URL" + identity.oms_agent: "- (Optional) A oms_agent block as defined below." + identity.open_service_mesh_enabled: "- (Optional) Is Open Service Mesh enabled? For more details, please visit Open Service Mesh for AKS." + identity.principal_id: "- The Principal ID associated with this Managed Service Identity." + identity.private_cluster_enabled: "- (Optional) Should this Kubernetes Cluster have its API server only exposed on internal IP addresses? This provides a Private IP Address for the Kubernetes API on the Virtual Network where the Kubernetes Cluster is located. Defaults to false. Changing this forces a new resource to be created." + identity.private_cluster_public_fqdn_enabled: "- (Optional) Specifies whether a Public FQDN for this Private Cluster should be added. Defaults to false." + identity.private_dns_zone_id: "- (Optional) Either the ID of Private DNS Zone which should be delegated to this Cluster, System to have AKS manage this or None. In case of None you will need to bring your own DNS server and set up resolving, otherwise cluster will have issues after provisioning. Changing this forces a new resource to be created." + identity.role_based_access_control_enabled: (Optional) - Whether Role Based Access Control for the Kubernetes Cluster should be enabled. Defaults to true. Changing this forces a new resource to be created. + identity.run_command_enabled: "- (Optional) Whether to enable run command for the cluster or not. Defaults to true." + identity.service_principal: "- (Optional) A service_principal block as documented below. One of either identity or service_principal must be specified." + identity.sku_tier: "- (Optional) The SKU Tier that should be used for this Kubernetes Cluster. Possible values are Free and Paid (which includes the Uptime SLA). Defaults to Free." + identity.tags: "- (Optional) A mapping of tags to assign to the resource." + identity.tenant_id: "- The Tenant ID associated with this Managed Service Identity." + identity.type: "- (Required) Specifies the type of Managed Service Identity that should be configured on this Kubernetes Cluster. Possible values are SystemAssigned, UserAssigned, SystemAssigned, UserAssigned (to enable both)." + identity.windows_profile: "- (Optional) A windows_profile block as defined below." + ingress_application_gateway.effective_gateway_id: "- The ID of the Application Gateway associated with the ingress controller deployed to this Kubernetes Cluster." + ingress_application_gateway.gateway_id: "- (Optional) The ID of the Application Gateway to integrate with the ingress controller of this Kubernetes Cluster. See this page for further details." + ingress_application_gateway.gateway_name: "- (Optional) The name of the Application Gateway to be used or created in the Nodepool Resource Group, which in turn will be integrated with the ingress controller of this Kubernetes Cluster. See this page for further details." + ingress_application_gateway.ingress_application_gateway_identity: "- An ingress_application_gateway_identity block is exported. The exported attributes are defined below." + ingress_application_gateway.subnet_cidr: "- (Optional) The subnet CIDR to be used to create an Application Gateway, which in turn will be integrated with the ingress controller of this Kubernetes Cluster. See this page for further details." + ingress_application_gateway.subnet_id: "- (Optional) The ID of the subnet on which to create an Application Gateway, which in turn will be integrated with the ingress controller of this Kubernetes Cluster. See this page for further details." + ingress_application_gateway_identity.client_id: "- The Client ID of the user-defined Managed Identity used by the Application Gateway." + ingress_application_gateway_identity.object_id: "- The Object ID of the user-defined Managed Identity used by the Application Gateway." + ingress_application_gateway_identity.user_assigned_identity_id: "- The ID of the User Assigned Identity used by the Application Gateway." + key_vault_secrets_provider.secret_identity: "- An secret_identity block is exported. The exported attributes are defined below." + key_vault_secrets_provider.secret_identity.client_id: "- The Client ID of the user-defined Managed Identity used by the Secret Provider." + key_vault_secrets_provider.secret_identity.object_id: "- The Object ID of the user-defined Managed Identity used by the Secret Provider." + key_vault_secrets_provider.secret_identity.user_assigned_identity_id: "- The ID of the User Assigned Identity used by the Secret Provider." + key_vault_secrets_provider.secret_rotation_enabled: "- (Required) Is secret rotation enabled?" + key_vault_secrets_provider.secret_rotation_interval: "- (Required) The interval to poll for secret rotation. This attribute is only set when secret_rotation is true and defaults to 2m." + kube_admin_config: "- A kube_admin_config block as defined below. This is only available when Role Based Access Control with Azure Active Directory is enabled and local accounts enabled." + kube_admin_config.client_certificate: "- Base64 encoded public certificate used by clients to authenticate to the Kubernetes cluster." + kube_admin_config.client_key: "- Base64 encoded private key used by clients to authenticate to the Kubernetes cluster." + kube_admin_config.cluster_ca_certificate: "- Base64 encoded public CA certificate used as the root of trust for the Kubernetes cluster." + kube_admin_config.host: "- The Kubernetes cluster server host." + kube_admin_config.password: "- A password or token used to authenticate to the Kubernetes cluster." + kube_admin_config.username: "- A username used to authenticate to the Kubernetes cluster." + kube_admin_config_raw: "- Raw Kubernetes config for the admin account to be used by kubectl and other compatible tools. This is only available when Role Based Access Control with Azure Active Directory is enabled and local accounts enabled." + kube_config: "- A kube_config block as defined below." + kube_config_raw: "- Raw Kubernetes config to be used by kubectl and other compatible tools." + kubelet_config.allowed_unsafe_sysctls: "- (Optional) Specifies the allow list of unsafe sysctls command or patterns (ending in *). Changing this forces a new resource to be created." + kubelet_config.container_log_max_line: "- (Optional) Specifies the maximum number of container log files that can be present for a container. must be at least 2. Changing this forces a new resource to be created." + kubelet_config.container_log_max_size_mb: "- (Optional) Specifies the maximum size (e.g. 10MB) of container log file before it is rotated. Changing this forces a new resource to be created." + kubelet_config.cpu_cfs_quota_enabled: "- (Optional) Is CPU CFS quota enforcement for containers enabled? Changing this forces a new resource to be created." + kubelet_config.cpu_cfs_quota_period: "- (Optional) Specifies the CPU CFS quota period value. Changing this forces a new resource to be created." + kubelet_config.cpu_manager_policy: "- (Optional) Specifies the CPU Manager policy to use. Possible values are none and static, Changing this forces a new resource to be created." + kubelet_config.image_gc_high_threshold: "- (Optional) Specifies the percent of disk usage above which image garbage collection is always run. Must be between 0 and 100. Changing this forces a new resource to be created." + kubelet_config.image_gc_low_threshold: "- (Optional) Specifies the percent of disk usage lower than which image garbage collection is never run. Must be between 0 and 100. Changing this forces a new resource to be created." + kubelet_config.pod_max_pid: "- (Optional) Specifies the maximum number of processes per pod. Changing this forces a new resource to be created." + kubelet_config.topology_manager_policy: "- (Optional) Specifies the Topology Manager policy to use. Possible values are none, best-effort, restricted or single-numa-node. Changing this forces a new resource to be created." + kubelet_identity.client_id: "- (Required) The Client ID of the user-defined Managed Identity to be assigned to the Kubelets. If not specified a Managed Identity is created automatically." + kubelet_identity.object_id: "- (Required) The Object ID of the user-defined Managed Identity assigned to the Kubelets.If not specified a Managed Identity is created automatically." + kubelet_identity.user_assigned_identity_id: "- (Required) The ID of the User Assigned Identity assigned to the Kubelets. If not specified a Managed Identity is created automatically." + linux_os_config.swap_file_size_mb: "- (Optional) Specifies the size of swap file on each node in MB. Changing this forces a new resource to be created." + linux_os_config.sysctl_config: "- (Optional) A sysctl_config block as defined below. Changing this forces a new resource to be created." + linux_os_config.transparent_huge_page_defrag: "- (Optional) specifies the defrag configuration for Transparent Huge Page. Possible values are always, defer, defer+madvise, madvise and never. Changing this forces a new resource to be created." + linux_os_config.transparent_huge_page_enabled: "- (Optional) Specifies the Transparent Huge Page enabled configuration. Possible values are always, madvise and never. Changing this forces a new resource to be created." + linux_profile.admin_username: "- (Required) The Admin Username for the Cluster. Changing this forces a new resource to be created." + linux_profile.ssh_key: "- (Required) An ssh_key block. Only one is currently allowed. Changing this forces a new resource to be created." + load_balancer_profile.effective_outbound_ips: "- The outcome (resource IDs) of the specified arguments." + load_balancer_profile.idle_timeout_in_minutes: "- (Optional) Desired outbound flow idle timeout in minutes for the cluster load balancer. Must be between 4 and 120 inclusive. Defaults to 30." + load_balancer_profile.managed_outbound_ip_count: "- (Optional) Count of desired managed outbound IPs for the cluster load balancer. Must be between 1 and 100 inclusive." + load_balancer_profile.outbound_ip_address_ids: "- (Optional) The ID of the Public IP Addresses which should be used for outbound communication for the cluster load balancer." + load_balancer_profile.outbound_ip_prefix_ids: "- (Optional) The ID of the outbound Public IP Address Prefixes which should be used for the cluster load balancer." + load_balancer_profile.outbound_ports_allocated: "- (Optional) Number of desired SNAT port for each VM in the clusters load balancer. Must be between 0 and 64000 inclusive. Defaults to 0." + location: "- (Required) The location where the Managed Kubernetes Cluster should be created. Changing this forces a new resource to be created." + maintenance_window.allowed: "- (Optional) One or more allowed block as defined below." + maintenance_window.not_allowed: "- (Optional) One or more not_allowed block as defined below." + microsoft_defender.log_analytics_workspace_id: "- (Required) Specifies the ID of the Log Analytics Workspace where the audit logs collected by Microsoft Defender should be sent to." + name: "- (Required) The name of the Managed Kubernetes Cluster to create. Changing this forces a new resource to be created." + nat_gateway_profile.effective_outbound_ips: "- The outcome (resource IDs) of the specified arguments." + nat_gateway_profile.idle_timeout_in_minutes: "- (Optional) Desired outbound flow idle timeout in minutes for the cluster load balancer. Must be between 4 and 120 inclusive. Defaults to 4." + nat_gateway_profile.managed_outbound_ip_count: "- (Optional) Count of desired managed outbound IPs for the cluster load balancer. Must be between 1 and 100 inclusive." + network_profile.network_plugin: "- (Required) Network plugin to use for networking. Currently supported values are azure, kubenet and none. Changing this forces a new resource to be created." + network_profile.network_plugin.dns_service_ip: "- (Optional) IP address within the Kubernetes service address range that will be used by cluster service discovery (kube-dns). Changing this forces a new resource to be created." + network_profile.network_plugin.docker_bridge_cidr: "- (Optional) IP address (in CIDR notation) used as the Docker bridge IP address on nodes. Changing this forces a new resource to be created." + network_profile.network_plugin.ip_versions: "- (Optional) Specifies a list of IP versions the Kubernetes Cluster will use to assign IP addresses to its nodes and pods. Possible values are IPv4 and/or IPv6. IPv4 must always be specified. Changing this forces a new resource to be created." + network_profile.network_plugin.load_balancer_profile: "- (Optional) A load_balancer_profile block. This can only be specified when load_balancer_sku is set to standard." + network_profile.network_plugin.load_balancer_sku: "- (Optional) Specifies the SKU of the Load Balancer used for this Kubernetes Cluster. Possible values are basic and standard. Defaults to standard." + network_profile.network_plugin.nat_gateway_profile: "- (Optional) A nat_gateway_profile block. This can only be specified when load_balancer_sku is set to standard and outbound_type is set to managedNATGateway or userAssignedNATGateway." + network_profile.network_plugin.network_mode: "- (Optional) Network mode to be used with Azure CNI. Possible values are bridge and transparent. Changing this forces a new resource to be created." + network_profile.network_plugin.network_policy: "- (Optional) Sets up network policy to be used with Azure CNI. Network policy allows us to control the traffic flow between pods. Currently supported values are calico and azure. Changing this forces a new resource to be created." + network_profile.network_plugin.outbound_type: "- (Optional) The outbound (egress) routing method which should be used for this Kubernetes Cluster. Possible values are loadBalancer, userDefinedRouting, managedNATGateway and userAssignedNATGateway. Defaults to loadBalancer." + network_profile.network_plugin.pod_cidr: "- (Optional) The CIDR to use for pod IP addresses. This field can only be set when network_plugin is set to kubenet. Changing this forces a new resource to be created." + network_profile.network_plugin.service_cidr: "- (Optional) The Network Range used by the Kubernetes service. Changing this forces a new resource to be created." + node_resource_group: "- The auto-generated Resource Group which contains the resources for this Managed Kubernetes Cluster." + not_allowed.end: "- (Required) The end of a time span, formatted as an RFC3339 string." + not_allowed.start: "- (Required) The start of a time span, formatted as an RFC3339 string." + oidc_issuer_url: "- The OIDC issuer URL that is associated with the cluster." + oms_agent.log_analytics_workspace_id: "- (Required) The ID of the Log Analytics Workspace which the OMS Agent should send data to." + oms_agent.oms_agent_identity: "- An oms_agent_identity block is exported. The exported attributes are defined below." + oms_agent.oms_agent_identity.client_id: "- The Client ID of the user-defined Managed Identity used by the OMS Agents." + oms_agent.oms_agent_identity.object_id: "- The Object ID of the user-defined Managed Identity used by the OMS Agents." + oms_agent.oms_agent_identity.user_assigned_identity_id: "- The ID of the User Assigned Identity used by the OMS Agents." + portal_fqdn: "- The FQDN for the Azure Portal resources when private link has been enabled, which is only resolvable inside the Virtual Network used by the Kubernetes Cluster." + private_fqdn: "- The FQDN for the Kubernetes Cluster when private link has been enabled, which is only resolvable inside the Virtual Network used by the Kubernetes Cluster." + resource_group_name: "- (Required) Specifies the Resource Group where the Managed Kubernetes Cluster should exist. Changing this forces a new resource to be created." + service_principal.client_id: "- (Required) The Client ID for the Service Principal." + service_principal.client_secret: "- (Required) The Client Secret for the Service Principal." + ssh_key.key_data: "- (Required) The Public SSH Key used to access the cluster. Changing this forces a new resource to be created." + sysctl_config.fs_aio_max_nr: "- (Optional) The sysctl setting fs.aio-max-nr. Must be between 65536 and 6553500. Changing this forces a new resource to be created." + sysctl_config.fs_file_max: "- (Optional) The sysctl setting fs.file-max. Must be between 8192 and 12000500. Changing this forces a new resource to be created." + sysctl_config.fs_inotify_max_user_watches: "- (Optional) The sysctl setting fs.inotify.max_user_watches. Must be between 781250 and 2097152. Changing this forces a new resource to be created." + sysctl_config.fs_nr_open: "- (Optional) The sysctl setting fs.nr_open. Must be between 8192 and 20000500. Changing this forces a new resource to be created." + sysctl_config.kernel_threads_max: "- (Optional) The sysctl setting kernel.threads-max. Must be between 20 and 513785. Changing this forces a new resource to be created." + sysctl_config.net_core_netdev_max_backlog: "- (Optional) The sysctl setting net.core.netdev_max_backlog. Must be between 1000 and 3240000. Changing this forces a new resource to be created." + sysctl_config.net_core_optmem_max: "- (Optional) The sysctl setting net.core.optmem_max. Must be between 20480 and 4194304. Changing this forces a new resource to be created." + sysctl_config.net_core_rmem_default: "- (Optional) The sysctl setting net.core.rmem_default. Must be between 212992 and 134217728. Changing this forces a new resource to be created." + sysctl_config.net_core_rmem_max: "- (Optional) The sysctl setting net.core.rmem_max. Must be between 212992 and 134217728. Changing this forces a new resource to be created." + sysctl_config.net_core_somaxconn: "- (Optional) The sysctl setting net.core.somaxconn. Must be between 4096 and 3240000. Changing this forces a new resource to be created." + sysctl_config.net_core_wmem_default: "- (Optional) The sysctl setting net.core.wmem_default. Must be between 212992 and 134217728. Changing this forces a new resource to be created." + sysctl_config.net_core_wmem_max: "- (Optional) The sysctl setting net.core.wmem_max. Must be between 212992 and 134217728. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_ip_local_port_range_max: "- (Optional) The sysctl setting net.ipv4.ip_local_port_range max value. Must be between 1024 and 60999. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_ip_local_port_range_min: "- (Optional) The sysctl setting net.ipv4.ip_local_port_range min value. Must be between 1024 and 60999. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_neigh_default_gc_thresh1: "- (Optional) The sysctl setting net.ipv4.neigh.default.gc_thresh1. Must be between 128 and 80000. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_neigh_default_gc_thresh2: "- (Optional) The sysctl setting net.ipv4.neigh.default.gc_thresh2. Must be between 512 and 90000. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_neigh_default_gc_thresh3: "- (Optional) The sysctl setting net.ipv4.neigh.default.gc_thresh3. Must be between 1024 and 100000. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_tcp_fin_timeout: "- (Optional) The sysctl setting net.ipv4.tcp_fin_timeout. Must be between 5 and 120. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_tcp_keepalive_intvl: "- (Optional) The sysctl setting net.ipv4.tcp_keepalive_intvl. Must be between 10 and 75. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_tcp_keepalive_probes: "- (Optional) The sysctl setting net.ipv4.tcp_keepalive_probes. Must be between 1 and 15. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_tcp_keepalive_time: "- (Optional) The sysctl setting net.ipv4.tcp_keepalive_time. Must be between 30 and 432000. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_tcp_max_syn_backlog: "- (Optional) The sysctl setting net.ipv4.tcp_max_syn_backlog. Must be between 128 and 3240000. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_tcp_max_tw_buckets: "- (Optional) The sysctl setting net.ipv4.tcp_max_tw_buckets. Must be between 8000 and 1440000. Changing this forces a new resource to be created." + sysctl_config.net_ipv4_tcp_tw_reuse: "- (Optional) The sysctl setting net.ipv4.tcp_tw_reuse. Changing this forces a new resource to be created." + sysctl_config.net_netfilter_nf_conntrack_buckets: "- (Optional) The sysctl setting net.netfilter.nf_conntrack_buckets. Must be between 65536 and 147456. Changing this forces a new resource to be created." + sysctl_config.net_netfilter_nf_conntrack_max: "- (Optional) The sysctl setting net.netfilter.nf_conntrack_max. Must be between 131072 and 1048576. Changing this forces a new resource to be created." + sysctl_config.vm_max_map_count: "- (Optional) The sysctl setting vm.max_map_count. Must be between 65530 and 262144. Changing this forces a new resource to be created." + sysctl_config.vm_swappiness: "- (Optional) The sysctl setting vm.swappiness. Must be between 0 and 100. Changing this forces a new resource to be created." + sysctl_config.vm_vfs_cache_pressure: "- (Optional) The sysctl setting vm.vfs_cache_pressure. Must be between 0 and 100. Changing this forces a new resource to be created." + timeouts.create: "- (Defaults to 90 minutes) Used when creating the Kubernetes Cluster." + timeouts.delete: "- (Defaults to 90 minutes) Used when deleting the Kubernetes Cluster." + timeouts.read: "- (Defaults to 5 minutes) Used when retrieving the Kubernetes Cluster." + timeouts.update: "- (Defaults to 90 minutes) Used when updating the Kubernetes Cluster." + upgrade_settings.max_surge: "- (Required) The maximum number or percentage of nodes which will be added to the Node Pool size during an upgrade." + windows_profile.admin_password: "- (Required) The Admin Password for Windows VMs. Length must be between 14 and 123 characters." + windows_profile.admin_username: "- (Required) The Admin Username for Windows VMs." + windows_profile.license: "- (Optional) Specifies the type of on-premise license which should be used for Node Pool Windows Virtual Machine. At this time the only possible value is Windows_Server." + importStatements: + - terraform import azurerm_kubernetes_cluster.cluster1 /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/group1/providers/Microsoft.ContainerService/managedClusters/cluster1 diff --git a/pkg/registry/testdata/azure/r/aadb2c_directory.html.markdown b/pkg/registry/testdata/azure/r/aadb2c_directory.html.markdown index 8a3c4b84..c97726d9 100644 --- a/pkg/registry/testdata/azure/r/aadb2c_directory.html.markdown +++ b/pkg/registry/testdata/azure/r/aadb2c_directory.html.markdown @@ -1,4 +1,11 @@ + + --- + subcategory: "AAD B2C" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_aadb2c_directory" @@ -43,7 +50,7 @@ The following arguments are supported: ## Attributes Reference -In addition to the Arguments listed above - the following Attributes are exported: +In addition to the Arguments listed above - the following Attributes are exported: * `id` - The ID of the AAD B2C Directory. diff --git a/pkg/registry/testdata/azure/r/attestation.html.markdown b/pkg/registry/testdata/azure/r/attestation.html.markdown index 10240a8f..257af10f 100644 --- a/pkg/registry/testdata/azure/r/attestation.html.markdown +++ b/pkg/registry/testdata/azure/r/attestation.html.markdown @@ -1,4 +1,11 @@ + + --- + subcategory: "Attestation" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_attestation" @@ -47,7 +54,7 @@ The following arguments are supported: ## Attributes Reference -The following Attributes are exported: +The following Attributes are exported: * `id` - The ID of the Attestation Provider. diff --git a/pkg/registry/testdata/azure/r/kubernetes_cluster.html.markdown b/pkg/registry/testdata/azure/r/kubernetes_cluster.html.markdown index 495a5da2..f1feaa5d 100644 --- a/pkg/registry/testdata/azure/r/kubernetes_cluster.html.markdown +++ b/pkg/registry/testdata/azure/r/kubernetes_cluster.html.markdown @@ -1,4 +1,11 @@ + + --- + subcategory: "Container" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_kubernetes_cluster" @@ -438,7 +445,7 @@ The `kubelet_identity` block supports the following: * `object_id` - (Required) The Object ID of the user-defined Managed Identity assigned to the Kubelets.If not specified a Managed Identity is created automatically. -* `user_assigned_identity_id` - (Required) The ID of the User Assigned Identity assigned to the Kubelets. If not specified a Managed Identity is created automatically. +* `user_assigned_identity_id` - (Required) The ID of the User Assigned Identity assigned to the Kubelets. If not specified a Managed Identity is created automatically. --- @@ -791,7 +798,7 @@ The `ingress_application_gateway_identity` block exports the following: --- -The `oms_agent` block exports the following: +The `oms_agent` block exports the following: * `oms_agent_identity` - An `oms_agent_identity` block is exported. The exported attributes are defined below. @@ -823,7 +830,6 @@ The `secret_identity` block exports the following: --- - ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: diff --git a/pkg/registry/testdata/gcp/pm.yaml b/pkg/registry/testdata/gcp/pm.yaml index 5af96687..c0cdfa9c 100644 --- a/pkg/registry/testdata/gcp/pm.yaml +++ b/pkg/registry/testdata/gcp/pm.yaml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: Apache-2.0 + name: test-provider resources: google_access_context_manager_access_level: diff --git a/pkg/registry/testdata/gcp/r/access_context_manager_access_level.html.markdown b/pkg/registry/testdata/gcp/r/access_context_manager_access_level.html.markdown index 4cd95e2e..82f5834a 100644 --- a/pkg/registry/testdata/gcp/r/access_context_manager_access_level.html.markdown +++ b/pkg/registry/testdata/gcp/r/access_context_manager_access_level.html.markdown @@ -1,3 +1,9 @@ + + --- # ---------------------------------------------------------------------------- # diff --git a/pkg/registry/testdata/gcp/r/container_cluster.html.markdown b/pkg/registry/testdata/gcp/r/container_cluster.html.markdown index ee43e8f7..1451d6d9 100644 --- a/pkg/registry/testdata/gcp/r/container_cluster.html.markdown +++ b/pkg/registry/testdata/gcp/r/container_cluster.html.markdown @@ -1,3 +1,9 @@ + + --- subcategory: "Kubernetes (Container) Engine" layout: "google" diff --git a/pkg/registry/testdata/gcp/r/storage_bucket.html.markdown b/pkg/registry/testdata/gcp/r/storage_bucket.html.markdown index 26c6297b..925cf83d 100644 --- a/pkg/registry/testdata/gcp/r/storage_bucket.html.markdown +++ b/pkg/registry/testdata/gcp/r/storage_bucket.html.markdown @@ -1,3 +1,9 @@ + + --- subcategory: "Cloud Storage" layout: "google" diff --git a/pkg/resource/conditions.go b/pkg/resource/conditions.go index cb5b488c..62e8462b 100644 --- a/pkg/resource/conditions.go +++ b/pkg/resource/conditions.go @@ -1,17 +1,16 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package resource import ( + tferrors "github.com/crossplane/upjet/pkg/terraform/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" - - tferrors "github.com/upbound/upjet/pkg/terraform/errors" ) // Condition constants. diff --git a/pkg/resource/extractor.go b/pkg/resource/extractor.go index 7ecbb78f..20d3f5e8 100644 --- a/pkg/resource/extractor.go +++ b/pkg/resource/extractor.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package resource diff --git a/pkg/resource/fake/mocks/mock.go b/pkg/resource/fake/mocks/mock.go index f46cfafc..d8ffdcec 100644 --- a/pkg/resource/fake/mocks/mock.go +++ b/pkg/resource/fake/mocks/mock.go @@ -1,19 +1,9 @@ -// Copyright 2021 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/upbound/upjet/pkg/resource (interfaces: SecretClient) +// Source: github.com/crossplane/upjet/pkg/resource (interfaces: SecretClient) // Package mocks is a generated GoMock package. package mocks diff --git a/pkg/resource/fake/terraformed.go b/pkg/resource/fake/terraformed.go index 0f1cb732..857788b8 100644 --- a/pkg/resource/fake/terraformed.go +++ b/pkg/resource/fake/terraformed.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package fake diff --git a/pkg/resource/ignored.go b/pkg/resource/ignored.go index c99edbd0..ce37a9bf 100644 --- a/pkg/resource/ignored.go +++ b/pkg/resource/ignored.go @@ -1,6 +1,6 @@ -/* -Copyright 2023 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package resource diff --git a/pkg/resource/ignored_test.go b/pkg/resource/ignored_test.go index e6843370..b0766633 100644 --- a/pkg/resource/ignored_test.go +++ b/pkg/resource/ignored_test.go @@ -1,14 +1,15 @@ -/* -Copyright 2023 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package resource import ( - _ "embed" "testing" "github.com/google/go-cmp/cmp" + + _ "embed" ) func TestGetIgnoredFields(t *testing.T) { diff --git a/pkg/resource/interfaces.go b/pkg/resource/interfaces.go index 9e5f93b4..2c68ea1d 100644 --- a/pkg/resource/interfaces.go +++ b/pkg/resource/interfaces.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package resource diff --git a/pkg/resource/json/json.go b/pkg/resource/json/json.go index 07cd0c7a..87ff2b5e 100644 --- a/pkg/resource/json/json.go +++ b/pkg/resource/json/json.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package json diff --git a/pkg/resource/json/statev4.go b/pkg/resource/json/statev4.go index 4b8117b6..842bf09c 100644 --- a/pkg/resource/json/statev4.go +++ b/pkg/resource/json/statev4.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package json diff --git a/pkg/resource/lateinit.go b/pkg/resource/lateinit.go index 4695f466..9d9ffa9a 100644 --- a/pkg/resource/lateinit.go +++ b/pkg/resource/lateinit.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package resource @@ -10,12 +10,12 @@ import ( "runtime/debug" "strings" - xpmeta "github.com/crossplane/crossplane-runtime/pkg/meta" - xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/upjet/pkg/config" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/upbound/upjet/pkg/config" + xpmeta "github.com/crossplane/crossplane-runtime/pkg/meta" + xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" ) const ( @@ -111,7 +111,8 @@ func WithZeroValueJSONOmitEmptyFilter(cName string) GenericLateInitializerOption // zeroValueJSONOmitEmptyFilter is a late-initialization ValueFilter that // skips initialization of a zero-valued field that has omitempty JSON tag -// nolint:gocyclo +// +//nolint:gocyclo func zeroValueJSONOmitEmptyFilter(cName string) ValueFilter { return func(cn string, f reflect.StructField, v reflect.Value) bool { if cName != CNameWildcard && cName != cn { @@ -178,7 +179,8 @@ func isZeroValueOmitted(tag string) bool { // Both crObject and responseObject must be pointers to structs. // Otherwise, an error will be returned. Returns `true` if at least one field has been stored // from source `responseObject` into a corresponding field of target `crObject`. -// nolint:gocyclo +// +//nolint:gocyclo func (li *GenericLateInitializer) LateInitialize(desiredObject, observedObject any) (changed bool, err error) { if desiredObject == nil || reflect.ValueOf(desiredObject).IsNil() || observedObject == nil || reflect.ValueOf(observedObject).IsNil() { @@ -204,7 +206,7 @@ func (li *GenericLateInitializer) LateInitialize(desiredObject, observedObject a return } -// nolint:gocyclo +//nolint:gocyclo func (li *GenericLateInitializer) handleStruct(parentName string, desiredObject any, observedObject any) (bool, error) { typeOfDesiredObject, typeOfObservedObject := reflect.TypeOf(desiredObject), reflect.TypeOf(observedObject) valueOfDesiredObject, valueOfObservedObject := reflect.ValueOf(desiredObject), reflect.ValueOf(observedObject).Elem() @@ -248,7 +250,7 @@ func (li *GenericLateInitializer) handleStruct(parentName string, desiredObject continue } - switch desiredStructField.Type.Kind() { // nolint:exhaustive + switch desiredStructField.Type.Kind() { //nolint:exhaustive // handle pointer struct field case reflect.Ptr: desiredKeepField, err = li.handlePtr(cName, desiredFieldValue, observedFieldValue) @@ -318,7 +320,7 @@ func (li *GenericLateInitializer) handleSlice(cName string, desiredFieldValue, o // error from processing the next element of the slice var err error // check slice item's kind (not slice type) - switch item.Elem().Kind() { // nolint:exhaustive + switch item.Elem().Kind() { //nolint:exhaustive // if dealing with a slice of pointers case reflect.Ptr: _, err = li.handlePtr(cName, item.Elem(), observedFieldValue.Index(i)) @@ -361,7 +363,7 @@ func (li *GenericLateInitializer) handleMap(cName string, desiredFieldValue, obs // error from processing the next element of the map var err error // check map item's kind (not map type) - switch item.Elem().Kind() { // nolint:exhaustive + switch item.Elem().Kind() { //nolint:exhaustive // if dealing with a slice of pointers case reflect.Ptr: _, err = li.handlePtr(cName, item.Elem(), observedFieldValue.MapIndex(k)) diff --git a/pkg/resource/lateinit_test.go b/pkg/resource/lateinit_test.go index d67a11dd..f3c90004 100644 --- a/pkg/resource/lateinit_test.go +++ b/pkg/resource/lateinit_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package resource diff --git a/pkg/resource/sensitive.go b/pkg/resource/sensitive.go index 1ca3bee5..70b21454 100644 --- a/pkg/resource/sensitive.go +++ b/pkg/resource/sensitive.go @@ -1,18 +1,6 @@ -/* -Copyright 2021 Upbound Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License mapping - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package resource @@ -22,15 +10,15 @@ import ( "regexp" "strings" - v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/fieldpath" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/upjet/pkg/config" "github.com/pkg/errors" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" - "github.com/upbound/upjet/pkg/config" + v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" ) const ( @@ -68,7 +56,7 @@ func init() { // SecretClient is the client to get sensitive data from kubernetes secrets // -//go:generate go run github.com/golang/mock/mockgen -copyright_file ../../hack/boilerplate.txt -destination ./fake/mocks/mock.go -package mocks github.com/upbound/upjet/pkg/resource SecretClient +//go:generate go run github.com/golang/mock/mockgen -copyright_file ../../hack/boilerplate.txt -destination ./fake/mocks/mock.go -package mocks github.com/crossplane/upjet/pkg/resource SecretClient type SecretClient interface { GetSecretData(ctx context.Context, ref *v1.SecretReference) (map[string][]byte, error) GetSecretValue(ctx context.Context, sel v1.SecretKeySelector) ([]byte, error) @@ -136,7 +124,7 @@ func GetSensitiveAttributes(from map[string]any, mapping map[string]string) (map // Note(turkenh): k8s secrets uses a strict regex to validate secret // keys which does not allow having brackets inside. So, we need to // do a conversion to be able to store as connection secret keys. - // See https://github.com/upbound/upjet/pull/94 for + // See https://github.com/crossplane/upjet/pull/94 for // more details. k, err := fieldPathToSecretKey(fp) if err != nil { diff --git a/pkg/resource/sensitive_test.go b/pkg/resource/sensitive_test.go index a694c805..4aa71919 100644 --- a/pkg/resource/sensitive_test.go +++ b/pkg/resource/sensitive_test.go @@ -1,18 +1,6 @@ -/* - Copyright 2021 Upbound Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package resource @@ -20,9 +8,10 @@ import ( "context" "testing" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/resource/fake" + "github.com/crossplane/upjet/pkg/resource/fake/mocks" + "github.com/crossplane/upjet/pkg/resource/json" "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" @@ -31,10 +20,9 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/resource/fake" - "github.com/upbound/upjet/pkg/resource/fake/mocks" - "github.com/upbound/upjet/pkg/resource/json" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/test" ) var ( diff --git a/pkg/terraform/errors/errors.go b/pkg/terraform/errors/errors.go index dbe91342..58bb4d6c 100644 --- a/pkg/terraform/errors/errors.go +++ b/pkg/terraform/errors/errors.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package errors diff --git a/pkg/terraform/errors/errors_test.go b/pkg/terraform/errors/errors_test.go index e8091063..e1602254 100644 --- a/pkg/terraform/errors/errors_test.go +++ b/pkg/terraform/errors/errors_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package errors diff --git a/pkg/terraform/files.go b/pkg/terraform/files.go index 8a556755..c724ec1c 100644 --- a/pkg/terraform/files.go +++ b/pkg/terraform/files.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package terraform @@ -12,15 +12,14 @@ import ( "strings" "dario.cat/mergo" - - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/resource" + "github.com/crossplane/upjet/pkg/resource/json" "github.com/pkg/errors" "github.com/spf13/afero" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/resource" - "github.com/upbound/upjet/pkg/resource/json" + "github.com/crossplane/crossplane-runtime/pkg/feature" + "github.com/crossplane/crossplane-runtime/pkg/meta" ) const ( @@ -185,7 +184,7 @@ func (fp *FileProducer) WriteMainTF() (ProviderHandle, error) { // EnsureTFState writes the Terraform state that should exist in the filesystem // to start any Terraform operation. -func (fp *FileProducer) EnsureTFState(ctx context.Context, tfID string) error { //nolint:gocyclo +func (fp *FileProducer) EnsureTFState(ctx context.Context, tfID string) error { // TODO(muvaf): Reduce the cyclomatic complexity by separating the attributes // generation into its own function/interface. empty, err := fp.isStateEmpty() diff --git a/pkg/terraform/files_test.go b/pkg/terraform/files_test.go index ec38af8e..11bfed06 100644 --- a/pkg/terraform/files_test.go +++ b/pkg/terraform/files_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package terraform @@ -11,19 +11,19 @@ import ( "testing" "time" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/meta" - xpfake "github.com/crossplane/crossplane-runtime/pkg/resource/fake" - "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/resource" + "github.com/crossplane/upjet/pkg/resource/fake" + "github.com/crossplane/upjet/pkg/resource/json" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" "github.com/spf13/afero" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/resource" - "github.com/upbound/upjet/pkg/resource/fake" - "github.com/upbound/upjet/pkg/resource/json" + "github.com/crossplane/crossplane-runtime/pkg/feature" + "github.com/crossplane/crossplane-runtime/pkg/meta" + xpfake "github.com/crossplane/crossplane-runtime/pkg/resource/fake" + "github.com/crossplane/crossplane-runtime/pkg/test" ) const ( diff --git a/pkg/terraform/finalizer.go b/pkg/terraform/finalizer.go index e38011eb..bff2607e 100644 --- a/pkg/terraform/finalizer.go +++ b/pkg/terraform/finalizer.go @@ -1,14 +1,15 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package terraform import ( "context" - xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" "github.com/pkg/errors" + + xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" ) const ( diff --git a/pkg/terraform/finalizer_test.go b/pkg/terraform/finalizer_test.go index c86e32f7..f2be6d11 100644 --- a/pkg/terraform/finalizer_test.go +++ b/pkg/terraform/finalizer_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package terraform @@ -8,13 +8,13 @@ import ( "context" "testing" - "github.com/crossplane/crossplane-runtime/pkg/logging" - xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" - "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/upjet/pkg/resource" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" - "github.com/upbound/upjet/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/logging" + xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/test" ) var ( diff --git a/pkg/terraform/operation.go b/pkg/terraform/operation.go index 4f199c18..8f71ddb0 100644 --- a/pkg/terraform/operation.go +++ b/pkg/terraform/operation.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package terraform diff --git a/pkg/terraform/operation_test.go b/pkg/terraform/operation_test.go index a630e3df..8700df2b 100644 --- a/pkg/terraform/operation_test.go +++ b/pkg/terraform/operation_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package terraform diff --git a/pkg/terraform/provider_runner.go b/pkg/terraform/provider_runner.go index 101fe0f6..6653995e 100644 --- a/pkg/terraform/provider_runner.go +++ b/pkg/terraform/provider_runner.go @@ -1,16 +1,6 @@ -// Copyright 2022 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package terraform @@ -22,10 +12,11 @@ import ( "sync" "time" - "github.com/crossplane/crossplane-runtime/pkg/logging" "github.com/pkg/errors" "k8s.io/utils/clock" "k8s.io/utils/exec" + + "github.com/crossplane/crossplane-runtime/pkg/logging" ) const ( diff --git a/pkg/terraform/provider_runner_test.go b/pkg/terraform/provider_runner_test.go index 7087d482..bd558110 100644 --- a/pkg/terraform/provider_runner_test.go +++ b/pkg/terraform/provider_runner_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package terraform @@ -14,13 +14,14 @@ import ( "testing" "time" - "github.com/crossplane/crossplane-runtime/pkg/logging" - "github.com/crossplane/crossplane-runtime/pkg/test" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" clock "k8s.io/utils/clock/testing" "k8s.io/utils/exec" testingexec "k8s.io/utils/exec/testing" + + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/pkg/test" ) func TestStartSharedServer(t *testing.T) { diff --git a/pkg/terraform/provider_scheduler.go b/pkg/terraform/provider_scheduler.go index da49c1ef..60abc288 100644 --- a/pkg/terraform/provider_scheduler.go +++ b/pkg/terraform/provider_scheduler.go @@ -1,26 +1,16 @@ -// Copyright 2023 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package terraform import ( "sync" - "github.com/crossplane/crossplane-runtime/pkg/logging" + tferrors "github.com/crossplane/upjet/pkg/terraform/errors" "github.com/pkg/errors" - tferrors "github.com/upbound/upjet/pkg/terraform/errors" + "github.com/crossplane/crossplane-runtime/pkg/logging" ) // ProviderHandle represents native plugin (Terraform provider) process diff --git a/pkg/terraform/store.go b/pkg/terraform/store.go index 0b86ea85..2f9b7f57 100644 --- a/pkg/terraform/store.go +++ b/pkg/terraform/store.go @@ -1,16 +1,6 @@ -// Copyright 2021 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package terraform @@ -25,10 +15,9 @@ import ( "sync" "time" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/logging" - "github.com/crossplane/crossplane-runtime/pkg/meta" - xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/metrics" + "github.com/crossplane/upjet/pkg/resource" "github.com/mitchellh/go-ps" "github.com/pkg/errors" "github.com/spf13/afero" @@ -36,9 +25,10 @@ import ( "k8s.io/utils/exec" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/metrics" - "github.com/upbound/upjet/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/feature" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/pkg/meta" + xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" ) const ( diff --git a/pkg/terraform/timeouts.go b/pkg/terraform/timeouts.go index 273d1ede..93c3d240 100644 --- a/pkg/terraform/timeouts.go +++ b/pkg/terraform/timeouts.go @@ -1,26 +1,14 @@ -/* - Copyright 2022 Upbound Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package terraform import ( - "github.com/crossplane/crossplane-runtime/pkg/errors" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/resource/json" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/resource/json" + "github.com/crossplane/crossplane-runtime/pkg/errors" ) // "e2bfb730-ecaa-11e6-8f88-34363bc7c4c0" is a hardcoded string for Terraform diff --git a/pkg/terraform/timeouts_test.go b/pkg/terraform/timeouts_test.go index 044fd67a..cc70cd72 100644 --- a/pkg/terraform/timeouts_test.go +++ b/pkg/terraform/timeouts_test.go @@ -1,18 +1,6 @@ -/* - Copyright 2022 Upbound Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package terraform @@ -181,7 +169,7 @@ func TestInsertTimeoutsMeta(t *testing.T) { }, }, want: want{ - err: errors.Wrap(errors.New(`ReadString: expects " or n, but found m, error found in #2 byte of ...|{malformed}|..., bigger context ...|{malformed}|...`), `cannot parse existing metadata`), // nolint: golint + err: errors.Wrap(errors.New(`ReadString: expects " or n, but found m, error found in #2 byte of ...|{malformed}|..., bigger context ...|{malformed}|...`), `cannot parse existing metadata`), //nolint: golint }, }, "ExistingMetaAndTimeout": { diff --git a/pkg/terraform/workspace.go b/pkg/terraform/workspace.go index 4096cfbe..beb6d2da 100644 --- a/pkg/terraform/workspace.go +++ b/pkg/terraform/workspace.go @@ -1,16 +1,6 @@ -// Copyright 2021 Upbound Inc. +// SPDX-FileCopyrightText: 2023 The Crossplane Authors // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package terraform @@ -23,16 +13,15 @@ import ( "sync" "time" + "github.com/crossplane/upjet/pkg/metrics" + "github.com/crossplane/upjet/pkg/resource" + "github.com/crossplane/upjet/pkg/resource/json" + tferrors "github.com/crossplane/upjet/pkg/terraform/errors" "github.com/pkg/errors" "github.com/spf13/afero" k8sExec "k8s.io/utils/exec" "github.com/crossplane/crossplane-runtime/pkg/logging" - - "github.com/upbound/upjet/pkg/metrics" - "github.com/upbound/upjet/pkg/resource" - "github.com/upbound/upjet/pkg/resource/json" - tferrors "github.com/upbound/upjet/pkg/terraform/errors" ) const ( @@ -355,7 +344,7 @@ type ImportResult RefreshResult // Import makes a blocking terraform import call where only the state file // is changed with the current state of the resource. -func (w *Workspace) Import(ctx context.Context, tr resource.Terraformed) (ImportResult, error) { // nolint:gocyclo +func (w *Workspace) Import(ctx context.Context, tr resource.Terraformed) (ImportResult, error) { //nolint:gocyclo switch { case w.LastOperation.IsRunning(): return ImportResult{ diff --git a/pkg/terraform/workspace_test.go b/pkg/terraform/workspace_test.go index abbcc1e0..b33c88ee 100644 --- a/pkg/terraform/workspace_test.go +++ b/pkg/terraform/workspace_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package terraform @@ -9,6 +9,8 @@ import ( "testing" "time" + "github.com/crossplane/upjet/pkg/resource/json" + tferrors "github.com/crossplane/upjet/pkg/terraform/errors" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" "github.com/spf13/afero" @@ -16,9 +18,6 @@ import ( testingexec "k8s.io/utils/exec/testing" "github.com/crossplane/crossplane-runtime/pkg/test" - - "github.com/upbound/upjet/pkg/resource/json" - tferrors "github.com/upbound/upjet/pkg/terraform/errors" ) var ( diff --git a/pkg/types/builder.go b/pkg/types/builder.go index a4f78574..3f4d2de9 100644 --- a/pkg/types/builder.go +++ b/pkg/types/builder.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package types @@ -11,14 +11,13 @@ import ( "sort" "strings" + "github.com/crossplane/upjet/pkg/config" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" twtypes "github.com/muvaf/typewriter/pkg/types" "github.com/pkg/errors" "k8s.io/utils/pointer" "github.com/crossplane/crossplane-runtime/pkg/fieldpath" - - "github.com/upbound/upjet/pkg/config" ) const ( @@ -160,7 +159,7 @@ func (g *Builder) AddToBuilder(typeNames *TypeNames, r *resource) (*types.Named, return paramType, obsType, initType } -func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, r *resource) (types.Type, types.Type, error) { // nolint:gocyclo +func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, r *resource) (types.Type, types.Type, error) { //nolint:gocyclo switch f.Schema.Type { case schema.TypeBool: return types.NewPointer(types.Universe.Lookup("bool").Type()), nil, nil @@ -244,7 +243,7 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, r } } // if unset - // see: https://github.com/upbound/upjet/issues/177 + // see: https://github.com/crossplane/upjet/issues/177 case nil: elemType = types.Universe.Lookup("string").Type() initElemType = elemType @@ -330,7 +329,7 @@ func (r *resource) addParameterField(f *Field, field *types.Var) { // not just the top level ones, due to having all forProvider // fields now optional. CEL rules should check if a field is // present either in forProvider or initProvider. - // https://github.com/upbound/upjet/issues/239 + // https://github.com/crossplane/upjet/issues/239 if requiredBySchema && !f.Identifier && len(f.CanonicalPaths) == 1 { requiredBySchema = false // If the field is not a terraform field, we should not require it in init, @@ -375,7 +374,7 @@ func (r *resource) addObservationField(f *Field, field *types.Var) { if obsF.Name() == field.Name() { // If the field is already added, we don't add it again. // Some nested types could have been previously added as an - // observation type while building their schema: https://github.com/upbound/upjet/blob/b89baca4ae24c8fbd8eb403c353ca18916093e5e/pkg/types/builder.go#L206 + // observation type while building their schema: https://github.com/crossplane/upjet/blob/b89baca4ae24c8fbd8eb403c353ca18916093e5e/pkg/types/builder.go#L206 return } } diff --git a/pkg/types/builder_test.go b/pkg/types/builder_test.go index 30680256..86fba91e 100644 --- a/pkg/types/builder_test.go +++ b/pkg/types/builder_test.go @@ -1,18 +1,6 @@ -/* - Copyright 2021 Upbound Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package types @@ -22,12 +10,12 @@ import ( "go/types" "testing" - "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/upjet/pkg/config" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/pkg/errors" - "github.com/upbound/upjet/pkg/config" + "github.com/crossplane/crossplane-runtime/pkg/test" ) func TestBuilder_generateTypeName(t *testing.T) { diff --git a/pkg/types/comments/comment.go b/pkg/types/comments/comment.go index 341b7321..baaec46e 100644 --- a/pkg/types/comments/comment.go +++ b/pkg/types/comments/comment.go @@ -1,10 +1,14 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package comments import ( "strings" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/types/markers" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/types/markers" ) // Option is a comment option diff --git a/pkg/types/comments/comment_test.go b/pkg/types/comments/comment_test.go index b6589d2d..8050d203 100644 --- a/pkg/types/comments/comment_test.go +++ b/pkg/types/comments/comment_test.go @@ -1,15 +1,19 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package comments import ( "reflect" "testing" - "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/types/markers" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/types/markers" + "github.com/crossplane/crossplane-runtime/pkg/test" ) func TestComment_Build(t *testing.T) { diff --git a/pkg/types/conversion/tfjson/tfjson.go b/pkg/types/conversion/tfjson/tfjson.go index 91a8bb63..a8b60102 100644 --- a/pkg/types/conversion/tfjson/tfjson.go +++ b/pkg/types/conversion/tfjson/tfjson.go @@ -1,18 +1,6 @@ -/* - Copyright 2022 Upbound Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package tfjson diff --git a/pkg/types/field.go b/pkg/types/field.go index 16dcf4dd..38529772 100644 --- a/pkg/types/field.go +++ b/pkg/types/field.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package types import ( @@ -8,14 +12,13 @@ import ( "sort" "strings" + "github.com/crossplane/upjet/pkg" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/types/comments" + "github.com/crossplane/upjet/pkg/types/name" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/pkg/errors" "k8s.io/utils/pointer" - - "github.com/upbound/upjet/pkg" - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/types/comments" - "github.com/upbound/upjet/pkg/types/name" ) var parentheses = regexp.MustCompile(`\(([^)]+)\)`) @@ -124,11 +127,11 @@ func NewField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, f.TransformedName = f.Name.LowerCamelComputed // Terraform paths, e.g. { "lifecycle_rule", "*", "transition", "*", "days" } for https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#lifecycle_rule - f.TerraformPaths = append(tfPath, f.Name.Snake) // nolint:gocritic + f.TerraformPaths = append(tfPath, f.Name.Snake) //nolint:gocritic // Crossplane paths, e.g. {"lifecycleRule", "*", "transition", "*", "days"} - f.CRDPaths = append(xpPath, f.Name.LowerCamelComputed) // nolint:gocritic + f.CRDPaths = append(xpPath, f.Name.LowerCamelComputed) //nolint:gocritic // Canonical paths, e.g. {"LifecycleRule", "Transition", "Days"} - f.CanonicalPaths = append(names[1:], f.Name.Camel) // nolint:gocritic + f.CanonicalPaths = append(names[1:], f.Name.Camel) //nolint:gocritic for _, ignoreField := range cfg.LateInitializer.IgnoredFields { // Convert configuration input from Terraform path to canonical path diff --git a/pkg/types/markers/crossplane.go b/pkg/types/markers/crossplane.go index fb2e529c..9c1b1143 100644 --- a/pkg/types/markers/crossplane.go +++ b/pkg/types/markers/crossplane.go @@ -1,9 +1,13 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package markers import ( "fmt" - "github.com/upbound/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/config" ) const ( diff --git a/pkg/types/markers/crossplane_test.go b/pkg/types/markers/crossplane_test.go index 4e7bd5b7..58269280 100644 --- a/pkg/types/markers/crossplane_test.go +++ b/pkg/types/markers/crossplane_test.go @@ -1,11 +1,14 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package markers import ( "testing" + "github.com/crossplane/upjet/pkg/config" "github.com/google/go-cmp/cmp" - - "github.com/upbound/upjet/pkg/config" ) func TestCrossplaneOptions_String(t *testing.T) { diff --git a/pkg/types/markers/kubebuilder.go b/pkg/types/markers/kubebuilder.go index e03f0a3d..6b1c6e15 100644 --- a/pkg/types/markers/kubebuilder.go +++ b/pkg/types/markers/kubebuilder.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package markers import "fmt" diff --git a/pkg/types/markers/kubebuilder_test.go b/pkg/types/markers/kubebuilder_test.go index 305db61a..3229e25a 100644 --- a/pkg/types/markers/kubebuilder_test.go +++ b/pkg/types/markers/kubebuilder_test.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package markers import ( diff --git a/pkg/types/markers/options.go b/pkg/types/markers/options.go index b850498a..fb5e06ae 100644 --- a/pkg/types/markers/options.go +++ b/pkg/types/markers/options.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package markers // Options represents marker options that Upjet need to parse or set. diff --git a/pkg/types/markers/terrajet.go b/pkg/types/markers/terrajet.go index 6738a12a..47af24d0 100644 --- a/pkg/types/markers/terrajet.go +++ b/pkg/types/markers/terrajet.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package markers import ( diff --git a/pkg/types/markers/terrajet_test.go b/pkg/types/markers/terrajet_test.go index a8f0decf..88d526e5 100644 --- a/pkg/types/markers/terrajet_test.go +++ b/pkg/types/markers/terrajet_test.go @@ -1,12 +1,17 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package markers import ( "fmt" "testing" - "github.com/crossplane/crossplane-runtime/pkg/test" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" + + "github.com/crossplane/crossplane-runtime/pkg/test" ) func Test_parseAsUpjetOption(t *testing.T) { diff --git a/pkg/types/name/name.go b/pkg/types/name/name.go index 1e40111c..520f234d 100644 --- a/pkg/types/name/name.go +++ b/pkg/types/name/name.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package name diff --git a/pkg/types/name/name_test.go b/pkg/types/name/name_test.go index 19b0cbcf..82af3c4b 100644 --- a/pkg/types/name/name_test.go +++ b/pkg/types/name/name_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2021 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package name diff --git a/pkg/types/name/reference.go b/pkg/types/name/reference.go index 4e63f3d9..6de6ebf0 100644 --- a/pkg/types/name/reference.go +++ b/pkg/types/name/reference.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package name diff --git a/pkg/types/name/reference_test.go b/pkg/types/name/reference_test.go index f4d45448..16fd19bb 100644 --- a/pkg/types/name/reference_test.go +++ b/pkg/types/name/reference_test.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package name diff --git a/pkg/types/reference.go b/pkg/types/reference.go index 7de9d7f1..8f29229c 100644 --- a/pkg/types/reference.go +++ b/pkg/types/reference.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package types import ( @@ -7,11 +11,10 @@ import ( "reflect" "strings" + "github.com/crossplane/upjet/pkg/types/comments" + "github.com/crossplane/upjet/pkg/types/markers" + "github.com/crossplane/upjet/pkg/types/name" "k8s.io/utils/pointer" - - "github.com/upbound/upjet/pkg/types/comments" - "github.com/upbound/upjet/pkg/types/markers" - "github.com/upbound/upjet/pkg/types/name" ) const ( diff --git a/pkg/types/reference_test.go b/pkg/types/reference_test.go index dcb06028..cf012348 100644 --- a/pkg/types/reference_test.go +++ b/pkg/types/reference_test.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + package types import ( @@ -5,15 +9,14 @@ import ( "go/types" "testing" + "github.com/crossplane/upjet/pkg/config" + "github.com/crossplane/upjet/pkg/types/name" "github.com/google/go-cmp/cmp" twtypes "github.com/muvaf/typewriter/pkg/types" - - "github.com/upbound/upjet/pkg/config" - "github.com/upbound/upjet/pkg/types/name" ) func TestBuilder_generateReferenceFields(t *testing.T) { - tp := types.NewPackage("github.com/upbound/upjet/pkg/types", "tjtypes") + tp := types.NewPackage("github.com/crossplane/upjet/pkg/types", "tjtypes") type args struct { t *types.TypeName @@ -48,8 +51,8 @@ func TestBuilder_generateReferenceFields(t *testing.T) { `json:"testFieldSelector,omitempty" tf:"-"`, }, outComments: twtypes.Comments{ - "github.com/upbound/upjet/pkg/types.Params:TestFieldRef": "// Reference to a testObject to populate testField.\n// +kubebuilder:validation:Optional\n", - "github.com/upbound/upjet/pkg/types.Params:TestFieldSelector": "// Selector for a testObject to populate testField.\n// +kubebuilder:validation:Optional\n", + "github.com/crossplane/upjet/pkg/types.Params:TestFieldRef": "// Reference to a testObject to populate testField.\n// +kubebuilder:validation:Optional\n", + "github.com/crossplane/upjet/pkg/types.Params:TestFieldSelector": "// Selector for a testObject to populate testField.\n// +kubebuilder:validation:Optional\n", }, }, }, @@ -73,8 +76,8 @@ func TestBuilder_generateReferenceFields(t *testing.T) { `json:"testFieldSelector,omitempty" tf:"-"`, }, outComments: twtypes.Comments{ - "github.com/upbound/upjet/pkg/types.Params:TestFieldRefs": "// References to testObject to populate testField.\n// +kubebuilder:validation:Optional\n", - "github.com/upbound/upjet/pkg/types.Params:TestFieldSelector": "// Selector for a list of testObject to populate testField.\n// +kubebuilder:validation:Optional\n", + "github.com/crossplane/upjet/pkg/types.Params:TestFieldRefs": "// References to testObject to populate testField.\n// +kubebuilder:validation:Optional\n", + "github.com/crossplane/upjet/pkg/types.Params:TestFieldSelector": "// Selector for a list of testObject to populate testField.\n// +kubebuilder:validation:Optional\n", }, }, }, @@ -99,8 +102,8 @@ func TestBuilder_generateReferenceFields(t *testing.T) { `json:"testFieldSelector,omitempty" tf:"-"`, }, outComments: twtypes.Comments{ - "github.com/upbound/upjet/pkg/types.Params:CustomRef": "// Reference to a TestObject to populate testField.\n// +kubebuilder:validation:Optional\n", - "github.com/upbound/upjet/pkg/types.Params:TestFieldSelector": "// Selector for a TestObject to populate testField.\n// +kubebuilder:validation:Optional\n", + "github.com/crossplane/upjet/pkg/types.Params:CustomRef": "// Reference to a TestObject to populate testField.\n// +kubebuilder:validation:Optional\n", + "github.com/crossplane/upjet/pkg/types.Params:TestFieldSelector": "// Selector for a TestObject to populate testField.\n// +kubebuilder:validation:Optional\n", }, }, }, @@ -125,8 +128,8 @@ func TestBuilder_generateReferenceFields(t *testing.T) { `json:"customSelector,omitempty" tf:"-"`, }, outComments: twtypes.Comments{ - "github.com/upbound/upjet/pkg/types.Params:TestFieldRef": "// Reference to a TestObject to populate testField.\n// +kubebuilder:validation:Optional\n", - "github.com/upbound/upjet/pkg/types.Params:CustomSelector": "// Selector for a TestObject to populate testField.\n// +kubebuilder:validation:Optional\n", + "github.com/crossplane/upjet/pkg/types.Params:TestFieldRef": "// Reference to a TestObject to populate testField.\n// +kubebuilder:validation:Optional\n", + "github.com/crossplane/upjet/pkg/types.Params:CustomSelector": "// Selector for a TestObject to populate testField.\n// +kubebuilder:validation:Optional\n", }, }, }, @@ -150,8 +153,8 @@ func TestBuilder_generateReferenceFields(t *testing.T) { `json:"testFieldSelector,omitempty" tf:"-"`, }, outComments: twtypes.Comments{ - "github.com/upbound/upjet/pkg/types.Params:TestFieldRef": "// Reference to a TestObject in somepackage to populate testField.\n// +kubebuilder:validation:Optional\n", - "github.com/upbound/upjet/pkg/types.Params:TestFieldSelector": "// Selector for a TestObject in somepackage to populate testField.\n// +kubebuilder:validation:Optional\n", + "github.com/crossplane/upjet/pkg/types.Params:TestFieldRef": "// Reference to a TestObject in somepackage to populate testField.\n// +kubebuilder:validation:Optional\n", + "github.com/crossplane/upjet/pkg/types.Params:TestFieldSelector": "// Selector for a TestObject in somepackage to populate testField.\n// +kubebuilder:validation:Optional\n", }, }, }, diff --git a/pkg/version/version.go b/pkg/version/version.go index 2a9e99b9..a0cc8e09 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + // Package version contains the version of upjet repo package version