From ba139387b30adecb07637b0810977e4569d824ee 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 --- .editorconfig | 11 + .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 | 14 +- README.md | 45 +- catalog-info.yaml | 14 - cmd/scraper/main.go | 9 +- docs/README.md | 78 +- docs/add-new-resource-short.md | 411 ----------- .../adding-support-for-management-policies.md | 59 +- ...urce-long.md => configuring-a-resource.md} | 552 +++++++++----- ...design-doc-provider-identity-based-auth.md | 407 ++++++----- docs/generating-a-provider.md | 408 ++++++----- 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/manual-migration-guide-to-op.md | 227 ------ docs/migrating-from-terrajet-to-upjet.md | 204 ------ docs/monitoring.md | 86 +-- docs/moving-resources-to-v1beta1.md | 19 - docs/provider_metrics_help.txt | 4 + docs/reference-generation.md | 176 ----- docs/sizing-guide.md | 130 ---- docs/testing-resources-by-using-uptest.md | 84 --- ...instructions.md => testing-with-uptest.md} | 376 ++++++---- go.mod | 10 +- go.sum | 10 +- 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 + prettier.config.js | 19 + 227 files changed, 3620 insertions(+), 4231 deletions(-) create mode 100644 .editorconfig 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 delete mode 100644 docs/add-new-resource-short.md rename docs/{add-new-resource-long.md => configuring-a-resource.md} (60%) 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 delete mode 100644 docs/manual-migration-guide-to-op.md delete mode 100644 docs/migrating-from-terrajet-to-upjet.md delete mode 100644 docs/moving-resources-to-v1beta1.md delete mode 100644 docs/reference-generation.md delete mode 100644 docs/sizing-guide.md delete mode 100644 docs/testing-resources-by-using-uptest.md rename docs/{testing-instructions.md => testing-with-uptest.md} (59%) create mode 100644 go.sum.license create mode 100644 prettier.config.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..b8613886 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2023 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 + +[*] +charset = utf-8 +insert_final_newline = true +end_of_line = lf +indent_style = space +indent_size = 2 +max_line_length = 80 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 d40af507..da228728 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 89cf872e..dac05e1f 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 c78c6240..488020f7 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 9a2c0de6..7d2ffdff 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@f43a0e5ff2bd294095638e18286ca9a3d1956744 #v3 - with: - fetch-depth: 0 + - name: Checkout + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 #v3 + 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 c87a43f3..e520dc4c 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..f8b61b10 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -1,13 +1,19 @@ -# 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 * Alper Ulucinar ([ulucinar](https://github.com/ulucinar)) * Sergen Yalcin ([sergenyalcin](https://github.com/sergenyalcin)) +* Jean du Plessis ([jeanduplessis](https://github.com/jeanduplessis)) See [CODEOWNERS](./CODEOWNERS) for automatic PR assignment. 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..a5a10073 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,35 +1,43 @@ -# 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. +- Guide on how to add support for +[management policies](adding-support-for-management-policies.md) to an existing +provider. + +## Additional documentation + +- [Provider identity based authentication](design-doc-provider-identity-based-auth.md) +- [Monitoring](monitoring.md) the Upjet runtime using Prometheus. + +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-short.md b/docs/add-new-resource-short.md deleted file mode 100644 index 9d5807e7..00000000 --- a/docs/add-new-resource-short.md +++ /dev/null @@ -1,411 +0,0 @@ -## Adding a New Resource - -There are a long and detailed guides showing [how to bootstrap a -provider][provider-guide] and [how to configure resources][config-guide]. Here -we will go over the steps that will take us to `v1beta1` quality without going -into much detail so that it can be followed repeatedly quickly. - -The steps are generally identical, so we'll just take a resource issue from AWS -[#90][issue-90] and you can generalize steps pretty much to all other -resources in all official providers. It has several resources from different API -groups, such as `glue`, `grafana`, `guardduty` and `iam`. - -1. Assign issue to yourself. -1. Start from the top and click the link for the first resource, - [`aws_glue_workflow`] in this case. -1. Here we'll look for clues about how the Terraform ID is shaped so that we can - infer the external name configuration. In this case, there is a `name` - argument seen under `Argument Reference` section and when we look at `Import` - section, we see that this is what's used to import, i.e. Terraform ID is same - as `name` argument. This means that we can use `config.NameAsIdentifier` - 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] - 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. -1. Resource configuration is largely done, so we need to prepare the example - YAML for testing. Copy `examples-generated/glue/workflow.yaml` into - `examples/glue/workflow.yaml` and then remove `spec.forProvider.name` field. - If there is nothing left under `spec.forProvider`, then give it empty struct, - e.g. `forProvider: {}` -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 - // - // Imported using "name". - "aws_glue_workflow": config.NameAsIdentifier, - // Imported using arn: arn:aws:glue:us-west-2:123456789012:schema/example/example - "aws_glue_schema": config.IdentifierFromProvider, - // Imported using "name". - "aws_glue_trigger": config.NameAsIdentifier, - // Imported using the catalog_id:database_name:function_name - // 123456789012:my_database:my_func - "aws_glue_user_defined_function": config.TemplatedStringAsIdentifier("name", "{{ .parameters.catalog_id }}:{{ .parameters.database_name }}:{{ .externalName }}"), - "aws_glue_security_configuration": config.NameAsIdentifier, - // 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. -1. Create another commit with a message like `aws: regenerate for glue group`. - -That's pretty much all we need to do in the codebase. With these two commits, we -can open a new PR. - -## Testing - -Our first option is to run it by the automated testing tool we have. In order to -trigger it, you can drop a comment on the PR containing the following: - -```console -# Wildcards like provider-aws/examples/glue/*.yaml also work. -/test-examples="provider-aws/examples/glue/catalogdatabase.yaml,provider-aws/examples/glue/catalogtable.yaml" -``` - -Once the automated tests pass, we're good to go. However, in some cases there is -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 - credentials. -* Now, you can create the examples you've got generated and check events/logs to - spot problems and fix them. - -There are cases where the resource requires user to take an action that is not -possible with a Crossplane provider or automated testing tool. In such cases, we -should leave the actions to be taken as annotation on the resource like the -following: - -```yaml -apiVersion: apigatewayv2.aws.upbound.io/v1beta1 -kind: VPCLink -metadata: - name: example - annotations: - 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 -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. -And successful manual testing still meets the `v1beta1` criteria. - -## External Name Cases - -### Case 1: `name` As Identifier - -There is a `name` argument under `Argument Reference` section and `Import` -section suggests to use `name` to import the resource. - -Use `config.NameAsIdentifier`. - -An example would be -[`aws_eks_cluster`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_cluster) -and -[here](https://github.com/upbound/provider-aws/blob/8b3887c91c4b44dc14e1123b3a5ae1a70e0e45ed/config/externalname.go#L284) -is its configuration. - -### Case 2: Parameter As Identifier - -There is an argument under `Argument Reference` section that is used like name, -i.e. `cluster_name` or `group_name`, and `Import` section suggests to use the -value of that argument to import the resource. - -Use `config.ParameterAsIdentifier()`. - -An example would be -[`aws_elasticache_cluster`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_cluster) -and -[here](https://github.com/upbound/provider-aws/blob/8b3887c91c4b44dc14e1123b3a5ae1a70e0e45ed/config/externalname.go#L299) -is its configuration. - -### Case 3: Random Identifier From Provider - -The ID used in `Import` section is completely random and assigned by provider, -like a UUID, where you don't have any means of impact on it. - -Use `config.IdentifierFromProvider`. - -An example would be -[`aws_vpc`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc) -and -[here](https://github.com/upbound/provider-aws/blob/8b3887c91c4b44dc14e1123b3a5ae1a70e0e45ed/config/externalname.go#L155) -is its configuration. - -### Case 4: Random Identifier Substring From Provider - -The ID used in `Import` section is partially random and assigned by provider. -For example, a node in a cluster could have a random ID like `13213` but the -Terraform Identifier could include the name of the cluster that's represented as -an argument field under `Argument Reference`, i.e. `cluster-name:23123`. In that -case, we'll use only the randomly assigned part as external name and we need to -tell Upjet how to construct the full ID back and forth. - -```golang -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 - } -} -``` - -### Case 5: Non-random Substrings as Identifier - -There are more than a single argument under `Argument Reference` that are -concatenated to make up the whole identifier, e.g. `//`. We will need to tell Upjet to use `` as external -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 - see what's available. - -terraformProviderConfig: The Terraform configuration object of the provider. You can - take a look at the TF registry provider configuration object - to see what's available. Not to be confused with ProviderConfig - custom resource of the Crossplane provider. - -externalName: The value of external name annotation of the custom resource. - It is required to use this as part of the template. -``` - -You can see example usages in the big three providers below. - -#### AWS - -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 -specify the AWS Account ID that the database is in, e.g., - -$ terraform import aws_glue_user_defined_function.func 123456789012:my_database:my_func -``` - -Our configuration would look like the following: -```golang -"aws_glue_user_defined_function": config.TemplatedStringAsIdentifier("name", "{{ .parameters.catalog_id }}:{{ .parameters.database_name }}:{{ .externalName }}") -``` - -Another prevalent case in AWS is the usage of Amazon Resource Name (ARN) to -identify a resource. We can use `config.TemplatedStringAsIdentifier` in many of -those cases like the following: - -``` -"aws_glue_registry": config.TemplatedStringAsIdentifier("registry_name", "arn:aws:glue:{{ .parameters.region }}:{{ .setup.client_metadata.account_id }}:registry/{{ .external_name }}"), -``` - -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, -``` - -#### Azure - -Most Azure resources fall under this case since they use fully qualified -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. - -terraform import azurerm_mariadb_firewall_rule.rule1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.DBforMariaDB/servers/server1/firewallRules/rule1 -``` - -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 }}") -``` - -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 }}") -``` - -#### GCP - -Most GCP resources fall under this case since they use fully qualified -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. -Examples: - -$ terraform import google_container_cluster.mycluster projects/my-gcp-project/locations/us-east1-a/clusters/my-cluster -$ terraform import google_container_cluster.mycluster my-gcp-project/us-east1-a/my-cluster -$ terraform import google_container_cluster.mycluster us-east1-a/my-cluster -``` - -In cases where there are multiple ways to construct the ID, we should take the -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. - -### Case 6: No Import Statement - -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, -``` - -### Case 7: Using Identifier of Another Resource - -There are auxiliary resources that don't have an ID and since they map -one-to-one to another resource, they just opt to use the identifier of that -other resource. In many cases, the identifier is also a valid argument, maybe -even the only argument, to configure this resource. - -An example would be -[`aws_ecrpublic_repository_policy`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecrpublic_repository_policy) -from AWS where the identifier is `repository_name`. - -Use `config.IdentifierFromProvider` because in these cases `repository_name` is -more meaningful as an argument rather than the name of the policy for users, -hence we assume the ID is coming from provider. - -### Case 8: Using Identifiers of Other Resources - -There are resources that mostly represent a relation between two resources -without any particular name that identifies the relation. An example would be -[`azurerm_subnet_nat_gateway_association`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_nat_gateway_association) -where the ID is made up of two arguments `nat_gateway_id` and `subnet_id` -without any particular field used to give a name to the resource. - -Use `config.IdentifierFromProvider` because in these cases, there is no name -argument to be used as external name and both creation and import scenarios -would work the same way even if you configured the resources with conversion -functions between arguments and ID. - -## No Matching Case - -If it doesn't match any of the cases above, then we'll need to implement the -external name configuration from the ground up. Though in most cases, it's just -a little bit different that we only need to override a few things on top of -common functions. - -One example is [`aws_route`] resource where the ID could use a different -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 -[config-guide]: - https://github.com/upbound/upjet/blob/main/docs/add-new-resource-long.md -[issue-90]: - https://github.com/upbound/provider-aws/issues/90 -[`aws_glue_workflow`]: - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/glue_workflow -[`aws_ecrpublic_repository_policy`]: - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecrpublic_repository_policy#import -[`aws_route`]: - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route -[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 diff --git a/docs/adding-support-for-management-policies.md b/docs/adding-support-for-management-policies.md index d54301f9..94299c9b 100644 --- a/docs/adding-support-for-management-policies.md +++ b/docs/adding-support-for-management-policies.md @@ -1,6 +1,12 @@ -# Adding Support for Management Policies and initProvider in an Upjet Based Provider + + +# Adding Support for Management Policies and initProvider + +## Regenerating a provider with Management Policies Check out the provider repo, e.g., upbound/provider-aws, and go to the project directory on your local machine. @@ -19,9 +25,10 @@ directory on your local machine. go mod tidy ``` -2. Introduce a feature flag for `Management Policies`. +1. Introduce a feature flag for `Management Policies`. - Add the feature flag definition into the `internal/features/features.go` file. + Add the feature flag definition into the `internal/features/features.go` + file. ```diff diff --git a/internal/features/features.go b/internal/features/features.go @@ -40,7 +47,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,18 +96,21 @@ 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: +> [!NOTE] +> If the provider was already updated to support observe-only resources, just + add the feature flag to the `workspaceStore`. + +1. 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 ``` - Enable management policies in the generator by adding `config.WithFeaturesPackage` option: + Enable management policies in the generator by adding + `config.WithFeaturesPackage` option: ```diff diff --git a/config/provider.go b/config/provider.go @@ -126,8 +136,8 @@ directory on your local machine. ## Testing: Locally Running the Provider with Management Policies Enabled 1. Create a fresh Kubernetes cluster. -2. Apply all of the provider's CRDs with `kubectl apply -f package/crds`. -3. Run the provider with `--enable-management-policies`. +1. Apply all of the provider's CRDs with `kubectl apply -f package/crds`. +1. Run the provider with `--enable-management-policies`. You can update the `run` target in the Makefile as below @@ -142,18 +152,15 @@ directory on your local machine. @# To see other arguments that can be provided, run the command with --help instead - UPBOUND_CONTEXT="local" $(GO_OUT_DIR)/provider --debug + UPBOUND_CONTEXT="local" $(GO_OUT_DIR)/provider --debug --enable-management-policies - - # 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 ``` -4. Create some resources in the provider's management console and try observing +1. Create some resources in the provider's management console and try observing them by creating a managed resource with `managementPolicies: ["Observe"]`. For example: @@ -168,7 +175,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 @@ -182,14 +189,16 @@ them by creating a managed resource with `managementPolicies: ["Observe"]`. kubectl get instance.rds.aws.upbound.io an-existing-dbinstance -o yaml ``` -> Please note: You would need the `terraform` executable installed on your local machine. +> [!NOTE] +> You need the `terraform` executable installed on your local machine. -5. Create a managed resource without `LateInitialize` like -`managementPolicies: ["Observe", "Create", "Update", "Delete"]` with +1. Create a managed resource without `LateInitialize` like +`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 +247,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/add-new-resource-long.md b/docs/configuring-a-resource.md similarity index 60% rename from docs/add-new-resource-long.md rename to docs/configuring-a-resource.md index 6a8e1c04..26c88ad0 100644 --- a/docs/add-new-resource-long.md +++ b/docs/configuring-a-resource.md @@ -1,11 +1,15 @@ -## Configuring a Resource + +# 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 couple of information that requires some input -configuration which could easily be provided by checking the Terraform -documentation of the resource: +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] @@ -14,22 +18,22 @@ documentation of the resource: - [Overriding Terraform Resource Schema] - [Initializers] -### External Name +## 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. +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`. +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]: @@ -93,7 +97,7 @@ type ExternalName struct { 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 +### Case 1: Name as External Name and Terraform ID This is the simplest and most straightforward case with the following conditions: @@ -106,14 +110,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 +137,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,46 +151,46 @@ import ( "bucket", "bucket_prefix", } - ... + ... } ``` -#### Case 2: Identifier from Provider +### 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. +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/upjet/pkg/config" - ... + "github.com/crossplane/upjet/pkg/config" + ... ) ... p.AddResourceConfigurator("aws_vpc", func(r *config.Resource) { r.ExternalName = config.IdentifierFromProvider - ... + ... } ``` -#### Case 3: Terraform ID as a Formatted String +### 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: +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: -``` +```text /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myresourcegroup/providers/Microsoft.Sql/servers/myserver ``` @@ -196,25 +200,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 +235,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 +252,7 @@ func getFullyQualifiedIDfunc(ctx context.Context, externalName string, parameter r.ExternalName = config.NameAsIdentifier r.ExternalName.GetExternalNameFn = getNameFromFullyQualifiedID r.ExternalName.GetIDFn = getFullyQualifiedIDfunc - ... + ... } ``` @@ -256,11 +260,13 @@ 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._ - +_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 @@ -270,8 +276,8 @@ 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: +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 @@ -352,26 +358,205 @@ 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", + } + }) +} +``` + +### 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: + +```go +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, + +```go +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 + } } ``` -### Additional Sensitive Fields and Custom Connection Details +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: + +```text +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/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 + +## 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. +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 @@ -382,18 +567,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 + } + }) } ``` @@ -413,27 +598,27 @@ 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. +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. -``` +```text 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 +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"}, + } + }) } ``` @@ -442,19 +627,21 @@ 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 +### 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. +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/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 @@ -466,11 +653,11 @@ type LateInitializer struct { } ``` -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: +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 { @@ -501,8 +688,8 @@ type SubnetParameters struct { ``` 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 +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 @@ -516,17 +703,17 @@ 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 +## 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: +[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: + status: - Not Optional & Not Computed => Spec (required) - Optional & Not Computed => Spec (optional) - Optional & Computed => Spec (optional, to be late-initialized) @@ -537,12 +724,12 @@ 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. + 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: @@ -560,90 +747,98 @@ p.AddResourceConfigurator("aws_autoscaling_group", func(r *config.Resource) { }) ``` -### Initializers +## Initializers -Initializers involve the operations that run before beginning of reconciliation. This configuration option will -provide that setting initializers for per resource. +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. +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 + 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 } ``` -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. +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: +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") + 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: +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) - } - } + 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: +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") + 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. +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]: +This configuration option is set by using the [InitializerFns] field that is a +list of [NewInitializerFn]: ```go // NewInitializerFn returns the Initializer with a client. @@ -654,49 +849,46 @@ 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) +So, an interface must be passed to the related configuration field for adding +initializers for a resource. -[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 +[the external name documentation]: https://docs.crossplane.io/master/concepts/managed-resources/#naming-external-resources [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/main/pkg/config/resource.go#L68 [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/main/pkg/config/externalname.go#L28 [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/main/config/externalname.go#L42 [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 +[handle dependencies]: https://docs.crossplane.io/master/concepts/managed-resources/#referencing-other-resources [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/main/pkg/config/resource.go#L123 [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 +[connection details]: https://docs.crossplane.io/master/concepts/managed-resources/#writeconnectionsecrettoref [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/main/pkg/controller/external.go#L175 +[late-initialization customization API]: https://github.com/crossplane/upjet/blob/main/pkg/resource/lateinit.go#L45 [`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 +898,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 +[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/main/pkg/config/resource.go#L297 +[NewInitializerFn]: https://github.com/crossplane/upjet/blob/main/pkg/config/resource.go#L210 [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/design-doc-provider-identity-based-auth.md b/docs/design-doc-provider-identity-based-auth.md index 80c98f0f..9c3080c7 100644 --- a/docs/design-doc-provider-identity-based-auth.md +++ b/docs/design-doc-provider-identity-based-auth.md @@ -1,10 +1,17 @@ + + # Identity Based Authentication for Crossplane Providers -* Owner: Alper Rifat Uluçınar (@ulucinar) -* Reviewers: Crossplane Maintainers -* Status: Draft +- Owner: Alper Rifat Uluçınar (@ulucinar) +- Reviewers: Crossplane Maintainers +- 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 @@ -13,94 +20,104 @@ Crossplane provider supports a subset of the underlying Cloud provider's authentication mechanisms and this subset is currently implemented in-tree, i.e., in the Crossplane provider's repo, there exists a CRD that's conventionally named as `ProviderConfig` and each managed resource of the -provider has a [v1.Reference](https://docs.crossplane.io/v1.12/concepts/managed-resources/#providerconfigref) to a `ProviderConfig` CR. This -`ProviderConfig` holds the authentication configuration (chosen authentication method, -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. +provider has a +[v1.Reference](https://docs.crossplane.io/v1.12/concepts/managed-resources/#providerconfigref) +to a `ProviderConfig` CR. This `ProviderConfig` holds the authentication +configuration (chosen authentication method, 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. 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` -implementation. This involves calling the associated authentication functions from -the Cloud SDK libraries (such as the [AWS SDK for Go][aws-sdk] or the [Azure -SDK for Go][azure-sdk]) with the supplied configuration and credentials from the -referenced `ProviderConfig` instance. - -Managed resources and `ProviderConfig`s are cluster-scoped, i.e., they do not exist within a -Kubernetes namespace but rather exist at the global (cluster) scope. This does -not fit well into a namespace-based multi-tenancy model, where each tenant is -confined to its own namespace. The cluster scope is shared between all -namespaces. In the namespace-based multi-tenancy model, the common approach is -to have Role-Based Access Control ([RBAC]) rules that disallow a tenant from -accessing API resources that do not reside in its namespace. Another dimension -to consider here is that all namespaced tenants are serviced by a shared -Crossplane provider deployment typically running in the `crossplane-system` -namespace. This shared provider instance (or more precisely, the [Kubernetes -ServiceAccount][k8s-sa] that the provider's pod uses) is allowed, via RBAC, to `get` the (cluster-scoped) -`ProviderConfig` resources. If tenant `subjects` (groups, users, -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: +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` implementation. This involves calling the associated authentication +functions from the Cloud SDK libraries (such as the [AWS SDK for Go][aws-sdk] or +the [Azure SDK for Go][azure-sdk]) with the supplied configuration and +credentials from the referenced `ProviderConfig` instance. + +Managed resources and `ProviderConfig`s are cluster-scoped, i.e., they do not +exist within a Kubernetes namespace but rather exist at the global (cluster) +scope. This does not fit well into a namespace-based multi-tenancy model, where +each tenant is confined to its own namespace. The cluster scope is shared +between all namespaces. In the namespace-based multi-tenancy model, the common +approach is to have Role-Based Access Control ([RBAC]) rules that disallow a +tenant from accessing API resources that do not reside in its namespace. Another +dimension to consider here is that all namespaced tenants are serviced by a +shared Crossplane provider deployment typically running in the +`crossplane-system` namespace. This shared provider instance (or more precisely, +the [Kubernetes ServiceAccount][k8s-sa] that the provider's pod uses) is +allowed, via RBAC, to `get` the (cluster-scoped) `ProviderConfig` resources. If +tenant `subjects` (groups, users, 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 alone cannot be used to constrain the set of `ProviderConfig`s that can be referenced by the `create`d managed resource. -1. The tenant subject itself does not directly access the `ProviderConfig` and in turn - the Cloud provider credential set referred by the `ProviderConfig`. It's the - Crossplane provider's `ServiceAccount` that accesses these resources, and as - mentioned above, this ServiceAccount currently serves all tenants. This - implies that we cannot isolate Cloud provider credentials among namespaced - tenants by only using RBAC rules if we allow tenant subjects to have `edit` - access (`create`, `update`, `patch`) to managed resources. Although it's - possible to prevent them from reading Cloud provider credentials of other - tenants in the cluster via RBAC rules, it's not possible to prevent them from - _using_ those credentials solely with RBAC. - -As discussed in detail in the [Crossplane Multi-tenancy Guide](https://docs.crossplane.io/knowledge-base/guides/multi-tenant/), -Crossplane is opinionated about the different personas in an organization adopting -Crossplane. We make a distinction between the _infrastructure operators_ (or -_platform builders_) who are expected to manage cluster-scoped resources (like -`ProviderConfig`s, XRDs and `Composition`s) and _application operators_, who are -expected to consume the infrastructure for their applications. And tenant -subjects are classified as _application operators_, i.e., it's the -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: +1. The tenant subject itself does not directly access the `ProviderConfig` and + in turn the Cloud provider credential set referred by the `ProviderConfig`. + It's the Crossplane provider's `ServiceAccount` that accesses these + resources, and as mentioned above, this ServiceAccount currently serves all + tenants. This implies that we cannot isolate Cloud provider credentials among + namespaced tenants by only using RBAC rules if we allow tenant subjects to + have `edit` access (`create`, `update`, `patch`) to managed resources. + Although it's possible to prevent them from reading Cloud provider + credentials of other tenants in the cluster via RBAC rules, it's not possible + to prevent them from _using_ those credentials solely with RBAC. + +As discussed in detail in the +[Crossplane Multi-tenancy Guide](https://docs.crossplane.io/knowledge-base/guides/multi-tenant/), +Crossplane is opinionated about the different personas in an organization +adopting Crossplane. We make a distinction between the _infrastructure +operators_ (or _platform builders_) who are expected to manage cluster-scoped +resources (like `ProviderConfig`s, XRDs and `Composition`s) and _application +operators_, who are expected to consume the infrastructure for their +applications. And tenant subjects are classified as _application operators_, +i.e., it's the 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: + 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 that allow tenant subjects to only access resources in their respective namespaces, e.g., `Claim`s. 1. However, [1] is not sufficient on itself, as the scheme is still prone to - privilege escalation attacks if the API exposed by the XR is not well designed. The - (shared) provider `ServiceAccount` has access to all Cloud provider - credentials in the cluster and if the exposed XR API allows a `Claim` to - reference cross-tenant `ProviderConfig`s, then a misbehaving tenant subject - can `create` a `Claim` which references some other tenant's credential set. - 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. - 2. The infrastructure operator carefully designs `Composition`s that patch - `spec.providerConfigRef` of composed resources using the `Claim`'s - 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). - 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`. + privilege escalation attacks if the API exposed by the XR is not well + designed. The (shared) provider `ServiceAccount` has access to all Cloud + provider credentials in the cluster and if the exposed XR API allows a + `Claim` to reference cross-tenant `ProviderConfig`s, then a misbehaving + tenant subject can `create` a `Claim` which references some other tenant's + credential set. 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. + 2. The infrastructure operator carefully designs `Composition`s that patch + `spec.providerConfigRef` of composed resources using the `Claim`'s + 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). + 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`. As explained above, RBAC rules can only impose restrictions on the actions (`get`, `update`, etc.) performed on the API resource endpoints but they cannot @@ -128,25 +145,24 @@ spec: kind: ClaimResourceGroup plural: claimresourcegroups versions: - - name: v1alpha1 - served: true - referenceable: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - name: - type: string - providerConfigName: - type: string - required: - - name + - name: v1alpha1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + name: + type: string + providerConfigName: + type: string + required: + - name --- - # kyverno ClusterPolicy apiVersion: kyverno.io/v1 kind: ClusterPolicy @@ -156,34 +172,34 @@ spec: validationFailureAction: enforce background: false rules: - - name: check-for-providerconfig-ref - match: - any: - - resources: - kinds: - # G/V/K for the Claim type - - example.org/v1alpha1/ClaimResourceGroup - subjects: - - kind: User - name: tenant1/user1 - validate: - message: "Only ProviderConfig names that have the prefix tenant1 are allowed for users under tenant1" - pattern: - spec: - providerConfigName: tenant1* + - name: check-for-providerconfig-ref + match: + any: + - resources: + kinds: + # G/V/K for the Claim type + - example.org/v1alpha1/ClaimResourceGroup + subjects: + - kind: User + name: tenant1/user1 + validate: + message: + "Only ProviderConfig names that have the prefix tenant1 are allowed + for users under tenant1" + pattern: + spec: + providerConfigName: tenant1* --- - # related patch in a Composition -... - patches: - - fromFieldPath: spec.providerConfigName - toFieldPath: spec.providerConfigRef.name +--- +patches: + - fromFieldPath: spec.providerConfigName + 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 @@ -191,14 +207,14 @@ in addition to the RBAC configurations needed to isolate the tenants 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. +verify a given security configuration guarantees proper isolation between 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 +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,10 +224,11 @@ 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 - resources between the tenants. How can we ensure, for instance, that the workqueue capacity - is fairly shared between the tenants? + 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 provider bug in which the provider fetches another `ProviderConfig` than the one declared in the managed resource, or other credentials than the ones @@ -221,7 +238,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. @@ -229,18 +246,21 @@ a single Kubernetes service account under which the provider runs. Apart from a vulnerability perspective, there are also some other limitations to 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. - +> [!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. + ### 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 +269,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 require manual rotation. - For fine-grained access control, you need multiple identities with such - credentials to be manually managed & rotated. + 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. + 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 +290,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 @@ -284,18 +305,19 @@ exhanging it with a set of temporary credentials associated with an IAM role. This set of temporary credentials consists of an access key ID, a secret access 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: +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` - authentication (via the `AssumeRoleWithWebIdentity` STS API - operation) does not need privileges on the managed external resources when - role chaining is used. + 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 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. + `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 @@ -341,42 +363,41 @@ user-assigned managed identity can be provisioned and assigned to the service instance. Similar to AWS IRSA, Azure has also introduced [Azure AD workload identities][azure-wi], which work in a similar way to IRSA: -| | -| :-: | -| drawing | +| | +| :--------------------------------------------------: | +| drawing | | Azure AD Workload Identities (reproduced from [[1]]) | 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 -it's not possible to switch identities within the Crossplane Azure providers*. +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. - -*: 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. +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. **GCP**: GCP also [recommends][gcp-wi] workload identities for assigning identities to workloads running in GKE clusters. With GKE workload identities, a @@ -405,17 +426,18 @@ organizational requirements around least-privilege and fine-grained access control, and they have isolated their tenants sharing the same Crossplane control-plane using the single-cluster multi-tenancy techniques described above. However, currently lacking similar semantics for "role chaining", to the best of -our knowledge, users of AKS and GKE workload identities cannot implement -similar fine-grained access control scenarios because the Crossplane provider is -running as a single Kubernetes deployment, which in turn is associated with a -single Kubernetes service account. And for `provider-aws` users who would like -to have more strict tenant isolation, we need more flexibility in the Crossplane +our knowledge, users of AKS and GKE workload identities cannot implement similar +fine-grained access control scenarios because the Crossplane provider is running +as a single Kubernetes deployment, which in turn is associated with a single +Kubernetes service account. And for `provider-aws` users who would like 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 @@ -434,7 +456,7 @@ spec: ... runtimeConfigs: - name: deploy-1 - runtime: + runtime: apiVersion: runtime.crossplane.io/v1alpha1 kind: KubernetesDeployment spec: @@ -469,7 +491,7 @@ directly manage a Kubernetes deployment for the active revision. Instead it would provision, for the active revision, a number of Kubernetes resources corresponding to each runtime configuration specified in the `runtimeConfigs` array. For the above example, the `PackageRevision` controller would provision -two `KubernetesDeployment` and one `DockerContainer` *runtime configuration* +two `KubernetesDeployment` and one `DockerContainer` _runtime configuration_ resources for the active revision. An example `KubernetesDeployment` object provisioned by the `PackageRevision` controller could look like the following: @@ -479,11 +501,11 @@ kind: KubernetesDeployment metadata: name: deploy-1 ownerReferences: - - apiVersion: pkg.crossplane.io/v1 - controller: true - kind: ProviderRevision - name: crossplane-provider-azure-91818efefdbe - uid: 3a58c719-019f-43eb-b338-d6116e299974 + - apiVersion: pkg.crossplane.io/v1 + controller: true + kind: ProviderRevision + name: crossplane-provider-azure-91818efefdbe + uid: 3a58c719-019f-43eb-b338-d6116e299974 spec: crossplaneProvider: crossplane/provider-azure-controller:v0.19.0 # ControllerConfig reference that defines the corresponding Kubernetes deployment @@ -495,24 +517,22 @@ As an alternative, in order to deprecate the `ControllerConfig` API, the `KubernetesDeployment` could also be defined as follows: ```yaml -... - runtimeConfigs: +--- +runtimeConfigs: - name: deploy-1 - runtime: + runtime: apiVersion: runtime.crossplane.io/v1alpha1 kind: KubernetesDeployment spec: template: # metadata that defines the corresponding Kubernetes deployment's metadata - metadata: - ... + metadata: ... # spec that defines the corresponding Kubernetes deployment's spec - spec: - ... + spec: ... ``` 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,52 +540,43 @@ 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 + https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ [patch-transform]: - https://github.com/crossplane/crossplane/blob/6c1b06507db47801c7a1c7d91704783e8d13856f/apis/apiextensions/v1/composition_transforms.go#L64 + https://github.com/crossplane/crossplane/blob/6c1b06507db47801c7a1c7d91704783e8d13856f/apis/apiextensions/v1/composition_transforms.go#L64 [kyverno]: https://kyverno.io/ [kyverno-policy]: https://kyverno.io/docs/kyverno-policies/ [aws-eks-node-iam]: - https://docs.aws.amazon.com/eks/latest/userguide/create-node-role.html + https://docs.aws.amazon.com/eks/latest/userguide/create-node-role.html [IRSA]: - https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html + https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html [kiam]: https://github.com/uswitch/kiam [kube2iam]: https://github.com/jtblin/kube2iam -[provider-aws-auth]: https://github.com/crossplane/provider-aws/blob/36299026cd9435c260ad13b32223d2e5fef3c443/AUTHENTICATION.md +[provider-aws-auth]: + https://github.com/crossplane/provider-aws/blob/36299026cd9435c260ad13b32223d2e5fef3c443/AUTHENTICATION.md [provider-aws-irsa]: - https://github.com/crossplane/provider-aws/blob/36299026cd9435c260ad13b32223d2e5fef3c443/AUTHENTICATION.md#using-iam-roles-for-serviceaccounts + https://github.com/crossplane/provider-aws/blob/36299026cd9435c260ad13b32223d2e5fef3c443/AUTHENTICATION.md#using-iam-roles-for-serviceaccounts [k8s-sa-projection]: - https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection + https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection [azure-msi]: - https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview + https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview [azure-wi]: - https://azure.github.io/azure-workload-identity/docs/introduction.html + https://azure.github.io/azure-workload-identity/docs/introduction.html [k8s-volume-projection]: - https://kubernetes.io/docs/concepts/storage/projected-volumes/ + https://kubernetes.io/docs/concepts/storage/projected-volumes/ [STS]: https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html [AssumeRoleWithWebIdentity]: - https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html + https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html [AssumeRole]: - https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html + https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html [gcp-wi]: - https://cloud.google.com/kubernetes-engine/docs/concepts/workload-identity + https://cloud.google.com/kubernetes-engine/docs/concepts/workload-identity [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/generating-a-provider.md b/docs/generating-a-provider.md index 035ebe8e..f5a91da3 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/crossplane/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 +[upjet-provider-template]: https://github.com/crossplane/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/manual-migration-guide-to-op.md b/docs/manual-migration-guide-to-op.md deleted file mode 100644 index 67afc39f..00000000 --- a/docs/manual-migration-guide-to-op.md +++ /dev/null @@ -1,227 +0,0 @@ -## 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. - -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. - -### Migrating Managed Resources - -Migrating existing managed resources to official providers can be simplified -as import scenarios. The aim is to modify the community provider's scheme to official -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 -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. - -```diff -- apiVersion: ec2.aws.crossplane.io/v1beta1 -+ apiVersion: ec2.aws.upbound.io/v1beta1 - kind: VPC - metadata: - annotations: - crossplane.io/external-create-pending: "2022-09-23T12:20:31Z" - crossplane.io/external-create-succeeded: "2022-09-23T12:20:33Z" - crossplane.io/external-name: vpc-008f150c8f525bf24 - kubectl.kubernetes.io/last-applied-configuration: | - {"apiVersion":"ec2.aws.crossplane.io/v1beta1","kind":"VPC","metadata":{"annotations":{},"name":"ezgi-vpc"},"spec":{"deletionPolicy":"Delete","forProvider":{"cidrBlock":"192.168.0.0/16","enableDnsHostNames":true,"enableDnsSupport":true,"instanceTenancy":"default","region":"us-west-2","tags":[{"key":"Name","value":"platformref-vpc"},{"key":"Owner","value":"Platform Team"},{"key":"crossplane-kind","value":"vpc.ec2.aws.crossplane.io"},{"key":"crossplane-name","value":"ezgi-plat-ref-aws-tcg6t-n6zph"},{"key":"crossplane-providerconfig","value":"default"}]},"providerConfigRef":{"name":"default"}}} - creationTimestamp: "2022-09-23T12:18:21Z" - finalizers: - - finalizer.managedresource.crossplane.io - generation: 2 - name: ezgi-vpc - resourceVersion: "22685" - uid: 81211d98-57f2-4f2e-a6db-04bb75cc60ff - spec: - deletionPolicy: Delete - forProvider: - cidrBlock: 192.168.0.0/16 -- enableDnsHostNames: true -+ enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - region: us-west-2 - tags: -- - key: Name -- value: platformref-vpc -- - key: Owner -- value: Platform Team -- - key: crossplane-kind -- value: vpc.ec2.aws.crossplane.io -- - key: crossplane-name -- value: ezgi-vpc -- - key: crossplane-providerconfig -- value: default -+ Name: platformref-vpc -+ Owner: Platform Team -+ crossplane-kind: vpc.ec2.aws.upbound.io -+ crossplane-name: ezgi-vpc -+ crossplane-providerconfig: default - providerConfigRef: - name: default -``` - - -### Migrating Crossplane Configurations - -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. - - -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. -6) Build and push the new configuration version -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 <//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 -``` - -This make variables must be available when the make targets consuming them -are run, so please make sure to include them at the head of the file. For an -example, please check the Upjet's provider template's [Makefile]. - -7. Now, you may want to run `make lint` to reveal and resolve any linter issues. -You will probably need to run `gofmt -s -w` on certain Go sources to properly -format them. - -After these steps, you should now be able to build your provider package with -`make build`. Please also keep in mind that you may need to update your repo's -CI pipelines especially if you've updated your provider module's Go version. - -8. Now, you may want to build your provider package and test the fresh package - 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 -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 -``` -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). - -Now, when you run a `make generate`, you should find the extracted metadata at -`config/provider-metadata.yaml`. - -10. Now that your provider extracts Terraform registry metadata, you can use it -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). - -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) -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 -``` - -12. If you would like to enable a shared gRPC server for your provider -(which implies that the Terraform CLI will not fork multiple native provider -processes to make requests and instead, share a common instance across -all requests to improve the performance of the Crossplane provider), or -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). -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). - - -[Upjet]: https://github.com/upbound/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 -[Terraform registry]: https://registry.terraform.io/ -[bootstrap a new provider]: https://github.com/upbound/upjet/blob/main/docs/generating-a-provider.md diff --git a/docs/monitoring.md b/docs/monitoring.md index 314d915f..a38673e0 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,16 @@ 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 +[Kubernetes 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 -[leader election client]: - 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 +[`controller-runtime` client]: 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 +[controller workqueues]: 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/moving-resources-to-v1beta1.md b/docs/moving-resources-to-v1beta1.md deleted file mode 100644 index 85822815..00000000 --- a/docs/moving-resources-to-v1beta1.md +++ /dev/null @@ -1,19 +0,0 @@ -## Moving Untested Resources to v1beta1 - -For outside contributors, we wanted to form a baseline for resource test -efforts. Therefore, we created a map: `ExternalNameNotTestedConfigs`. This map -contains the external name configurations of resources, but they were not tested. -And also, the resources’ schemas and controllers will not be generated after -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 -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 -generated CRD and controller. And for this generation, you need to move your -resource to the `ExternalNameConfigs` map. Then you can start testing and if the -test effort is successful, the new entry can remain on the main map. However, if -there are some problems in tests, and you cannot validate the resource please -move the entry to `ExternalNameNotTestedConfigs` again. 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/reference-generation.md b/docs/reference-generation.md deleted file mode 100644 index 1e420891..00000000 --- a/docs/reference-generation.md +++ /dev/null @@ -1,176 +0,0 @@ -## 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/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 diff --git a/docs/sizing-guide.md b/docs/sizing-guide.md deleted file mode 100644 index 3ff164fb..00000000 --- a/docs/sizing-guide.md +++ /dev/null @@ -1,130 +0,0 @@ -# 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 -utilization has been observed. - -| Provider Name | Memory (Avg.) | Memory (Peak) | CPU (Avg.) | -|----------------|---------------|---------------|------------| -| provider-aws | 1.1 GB | 3 GB | 8 Cores | -| 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 -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 -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. - -## Some Relevant Command-Line Options - -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 -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 -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 -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 -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 -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 -command-line parameter to the provider. - -- 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 -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 -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, -you need to add them as arguments to the `ControllerConfig`. - -In the example below you can see the options specified under the -`spec.args` section. - -You can apply this configuration by running `kubectl apply -f` -and referencing the local file path with the config. - -``` -apiVersion: pkg.crossplane.io/v1alpha1 -kind: ControllerConfig -metadata: - name: example-config -spec: - args: - - --max-reconcile-rate=20 - - --provider-ttl=250 ---- -apiVersion: pkg.crossplane.io/v1 -kind: Provider -metadata: - name: provider-aws -spec: - package: xpkg.upbound.io/upbound/provider-aws:v0.32.1 - controllerConfigRef: - name: example-config -``` -Note: Due to ControllerConfig being marked as deprecated, you will get the following -warning, which you can ignore for now: - -``` -Warning: This API is deprecated and is scheduled to be removed in a future release. -``` - -Please take a look at the Crossplane documentation for more information about -[ControllerConfig]. - -[provider-aws]: https://github.com/upbound/provider-aws/issues/576 -[provider-azure]: https://github.com/upbound/provider-azure/issues/404 -[provider-gcp]: https://github.com/upbound/provider-gcp/issues/255 -[time.ParseDuration syntax]: https://pkg.go.dev/time#ParseDuration -[ControllerConfig]: https://docs.crossplane.io/v1.11/concepts/packages/#speccontrollerconfigref:~:text=that%20is%20installed.-,spec.controllerConfigRef,-Warning diff --git a/docs/testing-resources-by-using-uptest.md b/docs/testing-resources-by-using-uptest.md deleted file mode 100644 index fc4c8918..00000000 --- a/docs/testing-resources-by-using-uptest.md +++ /dev/null @@ -1,84 +0,0 @@ -## 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. \ No newline at end of file diff --git a/docs/testing-instructions.md b/docs/testing-with-uptest.md similarity index 59% rename from docs/testing-instructions.md rename to docs/testing-with-uptest.md index 34199542..a14aca6a 100644 --- a/docs/testing-instructions.md +++ b/docs/testing-with-uptest.md @@ -1,35 +1,124 @@ -# Testing Instructions and Known Error Cases + +# 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. + +```bash +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: + +```bash +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 +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 +### Testing Methods -### Manual Test +#### 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 +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 +128,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,82 +142,81 @@ 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. -``` -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/upjet/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 +> [!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 +##### 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 +225,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 @@ -150,134 +238,132 @@ The key is important for skipping, we are checking this `upjet.upbound.io/manual 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. -``` +> [!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 +##### 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: -``` + +```bash make uptest-local PROVIDER_NAME=provider-aws EXAMPLE_LIST=provider-aws/examples/secretsmanager/secret.yaml ``` Example of whole API Group test: -``` + +```bash 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. -``` -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. -``` +> [!IMPORTANT] +> `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/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/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/upjet/issues/32 +[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/upjet/issues/80 +[tainted issue]: https://github.com/upbound/crossplane/issues/80 [k3d]: https://k3d.io/ diff --git a/go.mod b/go.mod index 10ca0ba8..2a58ea2a 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,16 @@ -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-runtime v0.20.0 + github.com/crossplane/crossplane v1.13.2 + github.com/crossplane/crossplane-runtime v1.13.0 github.com/fatih/camelcase v1.0.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.5.9 diff --git a/go.sum b/go.sum index 76d315af..99b67e47 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,7 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -76,10 +77,11 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crossplane/crossplane v1.10.0 h1:JP6TdhoZuRS27rYd+HCZNEBdf/1/w+rqIShW2qz/Qhk= -github.com/crossplane/crossplane v1.10.0/go.mod h1:a90wo/wkTDHhh8artgsUiLtQu5DIYwA7biHPDoMssms= -github.com/crossplane/crossplane-runtime v0.20.0 h1:MlPNrK6ELKLQdeHaIdKxQpZW2LSivSYXxHKVfU32auU= -github.com/crossplane/crossplane-runtime v0.20.0/go.mod h1:FuKIC8Mg8hE2gIAMyf2wCPkxkFPz+VnMQiYWBq1/p5A= +github.com/crossplane/crossplane v1.13.2 h1:/qxoQvNV9+eJyWVP3pu3j7q0ltdZXPgrDIkbAyCd1uI= +github.com/crossplane/crossplane v1.13.2/go.mod h1:jjYHNF5j2JidsrFZ7sfTZoVnBDVEvZHC64GyH/cYMbU= +github.com/crossplane/crossplane-runtime v1.13.0 h1:EumInUbS8mXV7otwoI3xa0rPczexJOky4XLVlHxxjO0= +github.com/crossplane/crossplane-runtime v1.13.0/go.mod h1:FuKIC8Mg8hE2gIAMyf2wCPkxkFPz+VnMQiYWBq1/p5A= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 32e091fb..e9e54a95 100644 --- a/pkg/controller/options.go +++ b/pkg/controller/options.go @@ -1,6 +1,6 @@ -/* -Copyright 2022 Upbound Inc. -*/ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 package controller @@ -8,11 +8,11 @@ import ( "crypto/tls" "time" - "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 4dcb6d8f..c85e320f 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 diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 00000000..b2b1c029 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors + +// SPDX-License-Identifier: CC0-1.0 + +/** @type {import("prettier").Config} */ +const config = { + overrides: [ + { + files: ['*.md'], + options: { + parser: 'markdown', + editorconfig: true, + proseWrap: 'always', + }, + }, + ], +} + +module.exports = config