├── .github └── workflows │ ├── ci.yml │ ├── export-repo-secrets.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── CHANGELOG.md ├── CODE-OF-CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── cmd └── root.go ├── codecov.yml ├── go.mod ├── go.sum ├── internal ├── files │ └── files.go ├── json │ └── json.go ├── slices │ └── slices.go ├── unstruct │ └── unstruct.go └── versions │ └── versions.go ├── main.go ├── pkg └── codegen │ ├── codegen.go │ ├── customresourcegenerator.go │ ├── customresourcegenerator_test.go │ ├── dotnet.go │ ├── golang.go │ ├── java.go │ ├── language.go │ ├── nodejs.go │ ├── packagegenerator.go │ ├── python.go │ └── schema.go └── tests ├── crds ├── GoogleCloudPlatform │ └── gke-managed-certs │ │ ├── README.md │ │ └── managedcertificates-crd.yaml ├── k8sversion │ └── mock_crd.yaml ├── regression │ ├── hyphenated-symbols │ │ ├── README.md │ │ └── hyphen-test.yml │ └── hyphenated-symbols2 │ │ ├── README.md │ │ └── hyphen-test2.yml └── underscored-types │ └── networkpolicy.yaml ├── crds_test.go ├── schema_test.go ├── test-combineschemas.yaml ├── test-gettypespec.json ├── test-gettypespec.yaml └── unneeded_go_files_test.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | pull_request: 4 | push: 5 | 6 | jobs: 7 | ci: 8 | runs-on: macos-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | 13 | - name: Install Go 14 | uses: actions/setup-go@v4 15 | with: 16 | go-version-file: go.mod 17 | cache-dependency-path: | 18 | **/go.sum 19 | 20 | - name: Run Go Build 21 | run: make build 22 | 23 | - name: Run tests 24 | run: make test 25 | 26 | - name: Upload coverage 27 | uses: codecov/codecov-action@v4 28 | env: 29 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 30 | 31 | - name: Goreleaser publishing dry run 32 | uses: goreleaser/goreleaser-action@v3 33 | with: 34 | version: v1.26.2 35 | args: release --rm-dist --skip-publish 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.PULUMI_BOT_TOKEN }} 38 | -------------------------------------------------------------------------------- /.github/workflows/export-repo-secrets.yml: -------------------------------------------------------------------------------- 1 | permissions: write-all # Equivalent to default permissions plus id-token: write 2 | name: Export secrets to ESC 3 | on: [ workflow_dispatch ] 4 | jobs: 5 | export-to-esc: 6 | runs-on: ubuntu-latest 7 | name: export GitHub secrets to ESC 8 | steps: 9 | - name: Generate a GitHub token 10 | id: generate-token 11 | uses: actions/create-github-app-token@v1 12 | with: 13 | app-id: 1256780 # Export Secrets GitHub App 14 | private-key: ${{ secrets.EXPORT_SECRETS_PRIVATE_KEY }} 15 | - name: Export secrets to ESC 16 | uses: pulumi/esc-export-secrets-action@9d6485759b6adff2538ae91f1b77cc96265c9dad # v1 17 | with: 18 | organization: pulumi 19 | org-environment: imports/github-secrets 20 | exclude-secrets: EXPORT_SECRETS_PRIVATE_KEY 21 | github-token: ${{ steps.generate-token.outputs.token }} 22 | oidc-auth: true 23 | oidc-requested-token-type: urn:pulumi:token-type:access_token:organization 24 | env: 25 | GITHUB_SECRETS: ${{ toJSON(secrets) }} 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | "on": 3 | push: 4 | tags: 5 | - v*.*.* 6 | - "!v*.*.*-**" 7 | 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: macos-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Install pulumictl 18 | uses: jaxxstorm/action-install-gh-release@v1.1.0 19 | with: 20 | repo: pulumi/pulumictl 21 | - name: Install Go 22 | uses: actions/setup-go@v4 23 | with: 24 | go-version-file: go.mod 25 | cache-dependency-path: | 26 | **/go.sum 27 | - name: Goreleaser publish 28 | uses: goreleaser/goreleaser-action@v3 29 | with: 30 | distribution: goreleaser 31 | version: v1.26.2 32 | args: release --rm-dist 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.PULUMI_BOT_TOKEN }} 35 | - name: Chocolatey Package Deployment 36 | run: | 37 | CURRENT_TAG=$(pulumictl get version --language generic -o) 38 | pulumictl create choco-deploy -a crd2pulumi ${CURRENT_TAG} 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.PULUMI_BOT_TOKEN }} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | .idea/ 3 | releases/ 4 | main 5 | dist/ 6 | bin/ 7 | coverage.txt 8 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - env: 3 | - CGO_ENABLED=0 4 | goos: 5 | - darwin 6 | - linux 7 | - windows 8 | ldflags: 9 | - -X github.com/pulumi/crd2pulumi/cmd.Version={{.Tag}} 10 | goarch: 11 | - amd64 12 | - arm64 13 | binary: crd2pulumi 14 | main: ./main.go 15 | overrides: 16 | # Some indirect dependencies (rjeczalik/notify, go-ieproxy) require linking against CGO for Darwin. 17 | - goos: darwin 18 | goarch: arm64 19 | goamd64: "" 20 | goarm: "" 21 | gomips: "" 22 | env: 23 | - CGO_ENABLED=1 24 | - goos: darwin 25 | goarch: amd64 26 | goamd64: v1 27 | goarm: "" 28 | gomips: "" 29 | env: 30 | - CGO_ENABLED=1 31 | ignore: 32 | - goos: windows 33 | goarch: arm64 34 | 35 | archives: 36 | - name_template: "{{ .Binary }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}" 37 | format_overrides: 38 | - goos: windows 39 | format: zip 40 | 41 | checksum: 42 | name_template: "checksums.txt" 43 | 44 | snapshot: 45 | name_template: "{{ .Tag }}-next" 46 | 47 | changelog: 48 | skip: true 49 | 50 | brews: 51 | - name: crd2pulumi 52 | tap: 53 | owner: pulumi 54 | name: homebrew-tap 55 | commit_author: 56 | name: pulumi-bot 57 | email: bot@pulumi.com 58 | homepage: "https://pulumi.com" 59 | description: "Generate typed CustomResources in Pulumi from Kubernetes CRDs" 60 | folder: Formula 61 | license: Apache-2.0 62 | test: | 63 | system "#{bin}/crd2pulumi version" 64 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.5.4 (2024-11-13) 4 | 5 | - NodeJS now uses correct input/output types for object metadata. (https://github.com/pulumi/crd2pulumi/issues/158) 6 | 7 | ## 1.5.3 (2024-09-30) 8 | 9 | - Fix crd2pulumi not generating all CRD versions. [#152](https://github.com/pulumi/crd2pulumi/issues/152) 10 | - Fix crd2pulumi generating packages and types with incorrect group names. [#152](https://github.com/pulumi/crd2pulumi/issues/152) 11 | 12 | ## 1.5.2 (2024-09-16) 13 | 14 | - Set the pulumi-kubernetes dependency for Python packages to v4.18.0. [#148](https://github.com/pulumi/crd2pulumi/issues/148) 15 | - Fixed generating Go types for StringMapArrayMap types. [#147](https://github.com/pulumi/crd2pulumi/issues/147) 16 | 17 | ## 1.5.1 (2024-09-13) 18 | 19 | - Fixed Patch varaints not generated for types that end in List. [#146](https://github.com/pulumi/crd2pulumi/pull/146) 20 | 21 | ## 1.5.0 (2024-09-13) 22 | 23 | ### Added 24 | - Patch variant resources are now generated for all custom resources. Patch resources allow you to modify and an existing custom resource. For more details on using Patch resources, see our [documentation](https://www.pulumi.com/registry/packages/kubernetes/how-to-guides/managing-resources-with-server-side-apply/#patch-a-resource). 25 | 26 | ### Changed 27 | - The Pulumi schema generation now utilizes the library from the Pulumi Kubernetes provider, replacing the previous custom implementation. This resolves a number of correctness issues when generating code. [#143](https://github.com/pulumi/crd2pulumi/pull/143) 28 | - Golang package generation now correctly adheres to the `--goPath` CLI flag, aligning with the behavior of other languages. [#89](https://github.com/pulumi/crd2pulumi/issues/89) 29 | - CRDs with oneOf fields are now correctly typed and not generic. [#97](https://github.com/pulumi/crd2pulumi/issues/97) 30 | 31 | 32 | ### Fixed 33 | - Various code generation correctness issues have been addressed, including: 34 | - Python packages can now be successfully imported and consumed by Pulumi Python programs. [#113](https://github.com/pulumi/crd2pulumi/issues/113) 35 | - Golang packages no longer produce compilation errors due to duplicate declarations. [#104](https://github.com/pulumi/crd2pulumi/issues/104) 36 | - NodeJS package names are now properly generated. [#70](https://github.com/pulumi/crd2pulumi/issues/70) 37 | - Dotnet packages now include the correct imports. [#49](https://github.com/pulumi/crd2pulumi/issues/49) 38 | - NodeJS object metadata types no longer accept undefined values. [#34](https://github.com/pulumi/crd2pulumi/issues/34) 39 | 40 | ## 1.4.0 (2024-05-29) 41 | 42 | - Fix unpinned Kubernetes version in generated nodejs resources. [#121](https://github.com/pulumi/crd2pulumi/pull/121) 43 | - Fix .NET generated code to use provider v4. [#134](https://github.com/pulumi/crd2pulumi/pull/134) 44 | - Fix invalid generated code due to unnamed properties. [#135](https://github.com/pulumi/crd2pulumi/pull/135) 45 | - Fix a panic when generating code with non-primitive defaults. [#136](https://github.com/pulumi/crd2pulumi/pull/136) 46 | - Add Java generation support. [#129](https://github.com/pulumi/crd2pulumi/pull/129) 47 | 48 | ## 1.3.0 (2023-12-12) 49 | 50 | - Fix: excluding files from unneededGoFiles was not working () 51 | - Support kubernetes provider v4 () 52 | 53 | ## 1.2.5 (2023-05-31) 54 | 55 | - Remove underscores in generated nested types () 56 | 57 | ## 1.2.4 (2023-03-23) 58 | 59 | - Requires Go 1.19 or higher now to build 60 | - Fix issue [#108](https://github.com/pulumi/crd2pulumi/issues/108) - crd2pulumi generator splits types apart into duplicate entires in pulumiTypes.go and pulumiTypes1.go 61 | 62 | ## 1.2.3 (2022-10-18) 63 | 64 | - Fix issue [#43: crd properties with - in name](https://github.com/pulumi/crd2pulumi/issues/43) () 65 | 66 | ## 1.2.2 (2022-07-20) 67 | 68 | - Fix regression that caused code in all languages to be generated regardless of selection. 69 | 70 | ## 1.2.1 (2022-07-19) 71 | 72 | This release is a refactor with no user-affecting changes. 73 | 74 | - Create public interface for codegen in the `pkg/codegen` namespace 75 | while placing internal utilities under `internal/` 76 | - Simplify cobra usage, simplify program config substantially 77 | - A new test env var, `TEST_SKIP_CLEANUP`, can be set to instruct the 78 | `crds_test.go` tests to not perform temp dir cleanup after the test 79 | run, for the purposes of investigating bad output during test failure. 80 | Generated code is now placed in temp dirs with friendly, identifiable 81 | names for each test case. 82 | - General refactoring: removal of dead code, reorganizing functions into 83 | more appropriately named files or packages. 84 | - Update to latest Pulumi SDK as well as update all other dependencies. 85 | - Update to Go 1.18 86 | - Upgrade to go 1.17 () 87 | 88 | ## 1.2.0 (2022-02-07) 89 | 90 | - [python] Do not overwrite _utilities.py () 91 | 92 | ## 1.1.0 (2022-01-04) 93 | 94 | - Update to Pulumi v3.21.0 () 95 | - Fix x-kubernetes-int-or-string precedence () 96 | - Add generating CRD from URL () 97 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at code-of-conduct@pulumi.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT := github.com/pulumi/crd2pulumi 2 | VERSION ?= $(shell (command -v pulumictl > /dev/null && pulumictl get version || echo "0.0.0-dev")) 3 | VERSION_PATH := cmd.Version 4 | 5 | GO ?= go 6 | 7 | all:: ensure build test 8 | 9 | ensure:: 10 | $(GO) mod download 11 | 12 | build:: 13 | go build -o bin/crd2pulumi -ldflags "-X ${PROJECT}/${VERSION_PATH}=${VERSION}" $(PROJECT) 14 | 15 | install:: 16 | go install -ldflags "-X ${PROJECT}/${VERSION_PATH}=${VERSION}" 17 | 18 | test:: 19 | $(GO) test -v -coverprofile="coverage.txt" -covermode=atomic -coverpkg=./... ./... 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crd2pulumi 2 | Generate typed CustomResources based on Kubernetes CustomResourceDefinitions. 3 | 4 | ## Goals 5 | 6 | `crd2pulumi` is a CLI tool that generates typed CustomResources based on Kubernetes CustomResourceDefinition (CRDs). 7 | CRDs allow you to extend the Kubernetes API by defining your own schemas for custom objects. While Pulumi lets you create 8 | [CustomResources](https://www.pulumi.com/docs/reference/pkg/kubernetes/apiextensions/customresource/), there was previously 9 | no strong-typing for these objects since every schema was, well, custom. This can be a massive headache for popular CRDs 10 | such as [cert-manager](https://github.com/jetstack/cert-manager/tree/master/deploy/crds) or 11 | [istio](https://github.com/istio/istio/tree/0321da58ca86fc786fb03a68afd29d082477e4f2/manifests/charts/base/crds), which 12 | contain thousands of lines of complex YAML schemas. By generating typed versions of CustomResources, `crd2pulumi` makes 13 | filling out their arguments more convenient by allowing you to leverage existing IDE type checking and autocomplete features. 14 | 15 | ## Building and Installation 16 | If you wish to use `crd2pulumi` without developing the tool itself, you can use one of the [binary releases](https://github.com/pulumi/crd2pulumi/releases) hosted on this repository. 17 | 18 | ### Homebrew 19 | `crd2pulumi` can be installed on Mac from the Pulumi Homebrew tap. 20 | ```console 21 | brew install pulumi/tap/crd2pulumi 22 | ``` 23 | 24 | `crd2pulumi` uses Go modules to manage dependencies. If you want to develop `crd2pulumi` itself, you'll need to have 25 | Go installed in order to build. Once you install this prerequisite, run the following to build the `crd2pulumi` binary 26 | and install it into `$GOPATH/bin`: 27 | 28 | ```bash 29 | $ go build -ldflags="-X github.com/pulumi/crd2pulumi/gen.Version=dev" -o $GOPATH/bin/crd2pulumi main.go 30 | ``` 31 | The `ldflags` argument is necessary to dynamically set the `crd2pulumi` version at build time. However, the version 32 | itself can be anything, so you don't have to set it to `dev`. 33 | 34 | Go should then automatically handle pulling the dependencies for you. If `$GOPATH/bin` is not on your path, you may 35 | want to move the `crd2pulumi` binary from `$GOPATH/bin` into a directory that is on your path. 36 | 37 | ## Usage 38 | ```bash 39 | crd2pulumi is a CLI tool that generates typed Kubernetes 40 | CustomResources to use in Pulumi programs, based on a 41 | CustomResourceDefinition YAML schema. 42 | 43 | Usage: 44 | crd2pulumi [-dgnp] [--nodejsPath path] [--pythonPath path] [--dotnetPath path] [--goPath path] [--javaPath path] [crd2.yaml ...] [flags] 45 | crd2pulumi [command] 46 | 47 | Examples: 48 | crd2pulumi --nodejs crontabs.yaml 49 | crd2pulumi -dgnp crd-certificates.yaml crd-issuers.yaml crd-challenges.yaml 50 | crd2pulumi --pythonPath=crds/python/istio --nodejsPath=crds/nodejs/istio crd-all.gen.yaml crd-mixer.yaml crd-operator.yaml 51 | crd2pulumi --pythonPath=crds/python/gke https://raw.githubusercontent.com/GoogleCloudPlatform/gke-managed-certs/master/deploy/managedcertificates-crd.yaml 52 | 53 | Notice that by just setting a language-specific output path (--pythonPath, --nodejsPath, etc) the code will 54 | still get generated, so setting -p, -n, etc becomes unnecessary. 55 | 56 | 57 | Available Commands: 58 | help Help about any command 59 | version Print the version number of crd2pulumi 60 | 61 | Flags: 62 | -d, --dotnet generate .NET 63 | --dotnetName string name of .NET package (default "crds") 64 | --dotnetPath string optional .NET output dir 65 | -f, --force overwrite existing files 66 | -g, --go generate Go 67 | --goName string name of Go package (default "crds") 68 | --goPath string optional Go output dir 69 | -h, --help help for crd2pulumi 70 | -j, --java generate Java 71 | --javaName string name of Java package (default "crds") 72 | --javaPath string optional Java output dir 73 | -n, --nodejs generate NodeJS 74 | --nodejsName string name of NodeJS package (default "crds") 75 | --nodejsPath string optional NodeJS output dir 76 | -p, --python generate Python 77 | --pythonName string name of Python package (default "crds") 78 | --pythonPath string optional Python output dir 79 | 80 | Use "crd2pulumi [command] --help" for more information about a command. 81 | ``` 82 | Setting only a language-specific flag will output the generated code in the default directory; so `-d` will output to 83 | `crds/dotnet`, `-g` will output to `crds/go`, `-j` will output to `crds/java`, `-n` will output to `crds/nodejs`, and 84 | `-p` will output to `crds/python`. You can also specify a language-specific path (`--pythonPath`, `--nodejsPath`, etc) 85 | to control where the code will be outputted, in which case setting `-p`, `-n`, etc becomes unnecessary. 86 | 87 | ## Examples 88 | Let's use the example CronTab CRD specified in `resourcedefinition.yaml` from the 89 | [Kubernetes Documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/). 90 | 91 | ### TypeScript 92 | To generate a strongly-typed CronTab CustomResource in TypeScript, we can run this command: 93 | ```bash 94 | $ crd2pulumi --nodejsPath ./crontabs resourcedefinition.yaml 95 | ``` 96 | Now let's import the generated code into a Pulumi program that provisions the CRD and creates an instance of it. 97 | ```typescript 98 | import * as crontabs from "./crontabs" 99 | import * as pulumi from "@pulumi/pulumi" 100 | import * as k8s from "@pulumi/kubernetes"; 101 | 102 | // Register the CronTab CRD. 103 | const cronTabDefinition = new k8s.yaml.ConfigFile("my-crontab-definition", { file: "resourcedefinition.yaml" }); 104 | 105 | // Instantiate a CronTab resource. 106 | const myCronTab = new crontabs.stable.v1.CronTab("my-new-cron-object", 107 | { 108 | metadata: { 109 | name: "my-new-cron-object", 110 | }, 111 | spec: { 112 | cronSpec: "* * * * */5", 113 | image: "my-awesome-cron-image", 114 | } 115 | }) 116 | 117 | ``` 118 | As you can see, the `CronTab` object is typed! For example, if you try to set 119 | `cronSpec` to a non-string or add an extra field, your IDE should immediately warn you. 120 | 121 | ### Python 122 | ```bash 123 | $ crd2pulumi --pythonPath ./crontabs resourcedefinition.yaml 124 | ``` 125 | ```python 126 | import pulumi_kubernetes as k8s 127 | import crontabs.pulumi_crds as crontabs 128 | 129 | 130 | # Register the CronTab CRD. 131 | crontab_definition = k8s.yaml.ConfigFile("my-crontab-definition", file="resourcedefinition.yaml") 132 | 133 | # Instantiate a CronTab resource. 134 | crontab_instance = crontabs.stable.v1.CronTab( 135 | "my-new-cron-object", 136 | metadata=k8s.meta.v1.ObjectMetaArgs( 137 | name="my-new-cron-object" 138 | ), 139 | spec=crontabs.stable.v1.CronTabSpecArgs( 140 | cron_spec="* * * */5", 141 | image="my-awesome-cron-image", 142 | ) 143 | ) 144 | 145 | ``` 146 | 147 | ### Go 148 | ```bash 149 | $ crd2pulumi --goPath ./crontabs resourcedefinition.yaml 150 | ``` 151 | Now we can access the `NewCronTab()` constructor. Create a `main.go` file with the following code. In this example, 152 | the Pulumi project's module is named `crds-go-final`, so the import path is `crds-go-final/crontabs/stable/v1`. Make 153 | sure to swap this out with your own module's name. 154 | ```go 155 | package main 156 | 157 | import ( 158 | crontabs_v1 "crds-go-final/crontabs/stable/v1" 159 | 160 | meta_v1 "github.com/pulumi/pulumi-kubernetes/sdk/v2/go/kubernetes/meta/v1" 161 | "github.com/pulumi/pulumi/sdk/v3/go/pulumi" 162 | ) 163 | 164 | func main() { 165 | pulumi.Run(func(ctx *pulumi.Context) error { 166 | // Register the CronTab CRD. 167 | _, err := yaml.NewConfigFile(ctx, "my-crontab-definition", 168 | &yaml.ConfigFileArgs{ 169 | File: "resourcedefinition.yaml", 170 | }, 171 | ) 172 | if err != nil { 173 | return err 174 | } 175 | 176 | // Instantiate a CronTab resource. 177 | _, err := crontabs_v1.NewCronTab(ctx, "cronTabInstance", &crontabs_v1.CronTabArgs{ 178 | Metadata: &meta_v1.ObjectMetaArgs{ 179 | Name: pulumi.String("my-new-cron-object"), 180 | }, 181 | Spec: crontabs_v1.CronTabSpecArgs{ 182 | CronSpec: pulumi.String("* * * * */5"), 183 | Image: pulumi.String("my-awesome-cron-image"), 184 | Replicas: pulumi.IntPtr(3), 185 | }, 186 | }) 187 | if err != nil { 188 | return err 189 | } 190 | 191 | return nil 192 | }) 193 | } 194 | 195 | ``` 196 | 197 | ### C\# 198 | ```bash 199 | $ crd2pulumi --dotnetPath ./crontabs resourcedefinition.yaml 200 | ``` 201 | ```csharp 202 | using Pulumi; 203 | using Pulumi.Kubernetes.Yaml; 204 | using Pulumi.Kubernetes.Types.Inputs.Meta.V1; 205 | 206 | class MyStack : Stack 207 | { 208 | public MyStack() 209 | { 210 | // Register a CronTab CRD. 211 | var cronTabDefinition = new Pulumi.Kubernetes.Yaml.ConfigFile("my-crontab-definition", 212 | new ConfigFileArgs{ 213 | File = "resourcedefinition.yaml" 214 | } 215 | ); 216 | 217 | // Instantiate a CronTab resource. 218 | var cronTabInstance = new Pulumi.Crds.Stable.V1.CronTab("cronTabInstance", 219 | new Pulumi.Kubernetes.Types.Inputs.Stable.V1.CronTabArgs{ 220 | Metadata = new ObjectMetaArgs{ 221 | Name = "my-new-cron-object" 222 | }, 223 | Spec = new Pulumi.Kubernetes.Types.Inputs.Stable.V1.CronTabSpecArgs{ 224 | CronSpec = "* * * * */5", 225 | Image = "my-awesome-cron-image" 226 | } 227 | }); 228 | } 229 | } 230 | 231 | ``` 232 | 233 | > If you get an `Duplicate 'global::System.Runtime.Versioning.TargetFrameworkAttribute' attribute` error when trying to run `pulumi up`, then try deleting the `crontabs/bin` and `crontabs/obj` folders. 234 | 235 | ### Java 236 | ```bash 237 | $ crd2pulumi --javaPath ./crontabs resourcedefinition.yaml 238 | ``` 239 | ```java 240 | package com.example; 241 | 242 | import com.pulumi.Pulumi; 243 | 244 | public class MyStack { 245 | 246 | public static void main(String[] args) { 247 | Pulumi.run(ctx -> { 248 | // Register a CronTab CRD (Coming Soon - see https://www.pulumi.com/registry/packages/kubernetes/api-docs/yaml/configfile/) 249 | 250 | // Instantiate a CronTab resource. 251 | var cronTabInstance = new com.pulumi.crds.stable.v1.CronTab("cronTabInstance", 252 | com.pulumi.crds.stable.v1.CronTabArgs.builder() 253 | .metadata(com.pulumi.kubernetes.meta.v1.inputs.ObjectMetaArgs.builder() 254 | .name("my-new-cron-object") 255 | .build()) 256 | .spec(com.pulumi.kubernetes.stable.v1.inputs.CronTabSpecArgs.builder() 257 | .cronSpec("* * * * */5") 258 | .image("my-awesome-cron-image") 259 | .build()) 260 | .build()); 261 | }); 262 | } 263 | } 264 | 265 | ``` 266 | 267 | Now let's run the program and perform the update. 268 | ```bash 269 | $ pulumi up 270 | Previewing update (dev): 271 | Type Name Plan 272 | pulumi:pulumi:Stack examples-dev 273 | + ├─ kubernetes:stable.example.com:CronTab my-new-cron-object create 274 | + └─ kubernetes:apiextensions.k8s.io:CustomResourceDefinition my-crontab-definition create 275 | Resources: 276 | + 2 to create 277 | 1 unchanged 278 | Do you want to perform this update? yes 279 | Updating (dev): 280 | Type Name Status 281 | pulumi:pulumi:Stack examples-dev 282 | + ├─ kubernetes:stable.example.com:CronTab my-new-cron-object created 283 | + └─ kubernetes:apiextensions.k8s.io:CustomResourceDefinition my-crontab-definition created 284 | Outputs: 285 | urn: "urn:pulumi:dev::examples::kubernetes:stable.example.com/v1:CronTab::my-new-cron-object" 286 | Resources: 287 | + 2 created 288 | 1 unchanged 289 | Duration: 17s 290 | Permalink: https://app.pulumi.com/albert-zhong/examples/dev/updates/4 291 | ``` 292 | It looks like both the CronTab definition and instance were both created! Finally, let's verify that they were created 293 | by manually viewing the raw YAML data: 294 | ```bash 295 | $ kubectl get ct -o yaml 296 | ``` 297 | ```yaml 298 | - apiVersion: stable.example.com/v1 299 | kind: CronTab 300 | metadata: 301 | annotations: 302 | kubectl.kubernetes.io/last-applied-configuration: | 303 | {"apiVersion":"stable.example.com/v1","kind":"CronTab","metadata":{"labels":{"app.kubernetes.io/managed-by":"pulumi"},"name":"my-new-cron-object"},"spec":{"cronSpec":"* * * * */5","image":"my-awesome-cron-image"}} 304 | creationTimestamp: "2020-08-10T09:50:38Z" 305 | generation: 1 306 | labels: 307 | app.kubernetes.io/managed-by: pulumi 308 | name: my-new-cron-object 309 | namespace: default 310 | resourceVersion: "1658962" 311 | selfLink: /apis/stable.example.com/v1/namespaces/default/crontabs/my-new-cron-object 312 | uid: 5e2c56a2-7332-49cf-b0fc-211a0892c3d5 313 | spec: 314 | cronSpec: '* * * * */5' 315 | image: my-awesome-cron-image 316 | kind: List 317 | metadata: 318 | resourceVersion: "" 319 | selfLink: "" 320 | ``` 321 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2020, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "io" 22 | "os" 23 | 24 | "github.com/pulumi/crd2pulumi/pkg/codegen" 25 | "github.com/spf13/cobra" 26 | ) 27 | 28 | // Version specifies the crd2pulumi version. It should be set by the linker via LDFLAGS. This defaults to dev 29 | var Version = "dev" 30 | 31 | const long = `crd2pulumi is a CLI tool that generates typed Kubernetes 32 | CustomResources to use in Pulumi programs, based on a 33 | CustomResourceDefinition YAML schema.` 34 | 35 | const example = `crd2pulumi --nodejs crontabs.yaml 36 | crd2pulumi -dgnp crd-certificates.yaml crd-issuers.yaml crd-challenges.yaml 37 | crd2pulumi --pythonPath=crds/python/istio --nodejsPath=crds/nodejs/istio crd-all.gen.yaml crd-mixer.yaml crd-operator.yaml 38 | crd2pulumi --pythonPath=crds/python/gke https://raw.githubusercontent.com/GoogleCloudPlatform/gke-managed-certs/master/deploy/managedcertificates-crd.yaml 39 | 40 | Notice that by just setting a language-specific output path (--pythonPath, --nodejsPath, etc) the code will 41 | still get generated, so setting -p, -n, etc becomes unnecessary. 42 | ` 43 | 44 | func New() *cobra.Command { 45 | dotNetSettings := &codegen.CodegenSettings{Language: "dotnet"} 46 | goSettings := &codegen.CodegenSettings{Language: "go"} 47 | nodejsSettings := &codegen.CodegenSettings{Language: "nodejs"} 48 | pythonSettings := &codegen.CodegenSettings{Language: "python"} 49 | javaSettings := &codegen.CodegenSettings{Language: "java"} 50 | allSettings := []*codegen.CodegenSettings{dotNetSettings, goSettings, nodejsSettings, pythonSettings, javaSettings} 51 | 52 | var force bool 53 | var packageVersion string 54 | 55 | rootCmd := &cobra.Command{ 56 | Use: "crd2pulumi [-dgnp] [--nodejsPath path] [--pythonPath path] [--dotnetPath path] [--goPath path] [crd2.yaml ...]", 57 | Short: "A tool that generates typed Kubernetes CustomResources", 58 | Long: long, 59 | Example: example, 60 | SilenceUsage: true, // Don't show the usage message upon program error 61 | Args: func(cmd *cobra.Command, args []string) error { 62 | if err := cobra.MinimumNArgs(1)(cmd, args); err != nil { 63 | return errors.New("must specify at least one CRD YAML file") 64 | } 65 | return nil 66 | }, 67 | PreRunE: func(cmd *cobra.Command, args []string) error { 68 | for _, cs := range allSettings { 69 | if force { 70 | cs.Overwrite = true 71 | } 72 | if cs.OutputDir != "" { 73 | cs.ShouldGenerate = true 74 | } 75 | cs.PackageVersion = packageVersion 76 | } 77 | return nil 78 | }, 79 | RunE: func(cmd *cobra.Command, args []string) error { 80 | var stdinData []byte 81 | shouldUseStdin := len(args) == 1 && args[0] == "-" 82 | if shouldUseStdin { 83 | var err error 84 | stdinData, err = io.ReadAll(os.Stdin) 85 | if err != nil { 86 | return fmt.Errorf("failed reading CRDs from stdin: %w", err) 87 | } 88 | } 89 | for _, cs := range allSettings { 90 | if !cs.ShouldGenerate { 91 | continue 92 | } 93 | var err error 94 | if shouldUseStdin { 95 | err = codegen.Generate(cs, []io.ReadCloser{io.NopCloser(bytes.NewBuffer(stdinData))}) 96 | } else { 97 | err = codegen.GenerateFromFiles(cs, args) 98 | } 99 | if err != nil { 100 | return fmt.Errorf("error generating code: %w", err) 101 | } 102 | fmt.Printf("Successfully generated %s code.\n", cs.Language) 103 | } 104 | return nil 105 | }, 106 | } 107 | rootCmd.AddCommand(&cobra.Command{ 108 | Use: "version", 109 | Short: "Print the version number of crd2pulumi", 110 | Long: `All software has versions. This is crd2pulumi's.`, 111 | Run: func(cmd *cobra.Command, args []string) { 112 | fmt.Println(Version) 113 | }, 114 | }) 115 | 116 | f := rootCmd.PersistentFlags() 117 | f.BoolVarP(&force, "force", "f", false, "overwrite existing files") 118 | f.StringVarP(&packageVersion, "version", "v", "0.0.0-dev", "version of the generated package") 119 | 120 | f.StringVarP(&dotNetSettings.PackageName, "dotnetName", "", codegen.DefaultName, "name of generated .NET package") 121 | f.StringVarP(&goSettings.PackageName, "goName", "", codegen.DefaultName, "name of generated Go package") 122 | f.StringVarP(&nodejsSettings.PackageName, "nodejsName", "", codegen.DefaultName, "name of generated NodeJS package") 123 | f.StringVarP(&pythonSettings.PackageName, "pythonName", "", codegen.DefaultName, "name of generated Python package") 124 | f.StringVarP(&javaSettings.PackageName, "javaName", "", codegen.DefaultName, "name of generated Java package") 125 | 126 | f.StringVarP(&dotNetSettings.OutputDir, "dotnetPath", "", "", "optional .NET output dir") 127 | f.StringVarP(&goSettings.OutputDir, "goPath", "", "", "optional Go output dir") 128 | f.StringVarP(&nodejsSettings.OutputDir, "nodejsPath", "", "", "optional NodeJS output dir") 129 | f.StringVarP(&pythonSettings.OutputDir, "pythonPath", "", "", "optional Python output dir") 130 | f.StringVarP(&javaSettings.OutputDir, "javaPath", "", "", "optional Java output dir") 131 | 132 | f.BoolVarP(&dotNetSettings.ShouldGenerate, "dotnet", "d", false, "generate .NET") 133 | f.BoolVarP(&goSettings.ShouldGenerate, "go", "g", false, "generate Go") 134 | f.BoolVarP(&nodejsSettings.ShouldGenerate, "nodejs", "n", false, "generate NodeJS") 135 | f.BoolVarP(&pythonSettings.ShouldGenerate, "python", "p", false, "generate Python") 136 | f.BoolVarP(&javaSettings.ShouldGenerate, "java", "j", false, "generate Java") 137 | return rootCmd 138 | } 139 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | patch: 7 | default: 8 | informational: true 9 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pulumi/crd2pulumi 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.3 6 | 7 | require ( 8 | github.com/go-openapi/jsonreference v0.21.0 9 | github.com/iancoleman/strcase v0.3.0 10 | github.com/pulumi/pulumi-java/pkg v1.12.0 11 | github.com/pulumi/pulumi-kubernetes/provider/v4 v4.0.0-20250530040339-1e2a2355c128 12 | github.com/pulumi/pulumi/pkg/v3 v3.173.0 13 | github.com/pulumi/pulumi/sdk/v3 v3.173.0 14 | github.com/spf13/cobra v1.9.1 15 | github.com/stretchr/testify v1.10.0 16 | golang.org/x/text v0.24.0 17 | k8s.io/apiextensions-apiserver v0.33.0 18 | k8s.io/apimachinery v0.33.0 19 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff 20 | ) 21 | 22 | require ( 23 | cel.dev/expr v0.19.1 // indirect 24 | dario.cat/mergo v1.0.1 // indirect 25 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 26 | github.com/BurntSushi/toml v1.4.0 // indirect 27 | github.com/MakeNowJust/heredoc v1.0.0 // indirect 28 | github.com/Microsoft/go-winio v0.6.2 // indirect 29 | github.com/ProtonMail/go-crypto v1.1.5 // indirect 30 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect 31 | github.com/agext/levenshtein v1.2.3 // indirect 32 | github.com/antlr4-go/antlr/v4 v4.13.1 // indirect 33 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 34 | github.com/atotto/clipboard v0.1.4 // indirect 35 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 36 | github.com/beorn7/perks v1.0.1 // indirect 37 | github.com/blang/semver v3.5.1+incompatible // indirect 38 | github.com/blang/semver/v4 v4.0.0 // indirect 39 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 40 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 41 | github.com/chai2010/gettext-go v1.0.2 // indirect 42 | github.com/charmbracelet/bubbles v0.16.1 // indirect 43 | github.com/charmbracelet/bubbletea v0.25.0 // indirect 44 | github.com/charmbracelet/lipgloss v0.9.1 // indirect 45 | github.com/cheggaaa/pb v1.0.29 // indirect 46 | github.com/cloudflare/circl v1.3.7 // indirect 47 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect 48 | github.com/cyphar/filepath-securejoin v0.3.6 // indirect 49 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 50 | github.com/deckarep/golang-set/v2 v2.5.0 // indirect 51 | github.com/djherbis/times v1.6.0 // indirect 52 | github.com/edsrzf/mmap-go v1.1.0 // indirect 53 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 54 | github.com/emirpasic/gods v1.18.1 // indirect 55 | github.com/evanphx/json-patch v5.9.0+incompatible // indirect 56 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 57 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect 58 | github.com/felixge/httpsnoop v1.0.4 // indirect 59 | github.com/fluxcd/cli-utils v0.36.0-flux.13 // indirect 60 | github.com/fluxcd/pkg/ssa v0.46.0 // indirect 61 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 62 | github.com/go-errors/errors v1.5.1 // indirect 63 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 64 | github.com/go-git/go-billy/v5 v5.6.2 // indirect 65 | github.com/go-git/go-git/v5 v5.13.2 // indirect 66 | github.com/go-logr/logr v1.4.2 // indirect 67 | github.com/go-logr/stdr v1.2.2 // indirect 68 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 69 | github.com/go-openapi/swag v0.23.0 // indirect 70 | github.com/go-test/deep v1.1.0 // indirect 71 | github.com/gofrs/uuid v4.4.0+incompatible // indirect 72 | github.com/gogo/protobuf v1.3.2 // indirect 73 | github.com/golang/glog v1.2.4 // indirect 74 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 75 | github.com/google/btree v1.1.3 // indirect 76 | github.com/google/cel-go v0.23.2 // indirect 77 | github.com/google/gnostic-models v0.6.9 // indirect 78 | github.com/google/go-cmp v0.7.0 // indirect 79 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 80 | github.com/google/uuid v1.6.0 // indirect 81 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect 82 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 83 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect 84 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect 85 | github.com/hashicorp/errwrap v1.1.0 // indirect 86 | github.com/hashicorp/go-multierror v1.1.1 // indirect 87 | github.com/hashicorp/hcl v1.0.1-vault-5 // indirect 88 | github.com/hashicorp/hcl/v2 v2.23.0 // indirect 89 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 90 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 91 | github.com/jonboulle/clockwork v0.4.0 // indirect 92 | github.com/josharian/intern v1.0.0 // indirect 93 | github.com/json-iterator/go v1.1.12 // indirect 94 | github.com/kevinburke/ssh_config v1.2.0 // indirect 95 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 96 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 97 | github.com/mailru/easyjson v0.9.0 // indirect 98 | github.com/mattn/go-isatty v0.0.20 // indirect 99 | github.com/mattn/go-localereader v0.0.1 // indirect 100 | github.com/mattn/go-runewidth v0.0.15 // indirect 101 | github.com/mitchellh/go-ps v1.0.0 // indirect 102 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 103 | github.com/moby/spdystream v0.5.0 // indirect 104 | github.com/moby/term v0.5.0 // indirect 105 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 106 | github.com/modern-go/reflect2 v1.0.2 // indirect 107 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 108 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 109 | github.com/muesli/cancelreader v0.2.2 // indirect 110 | github.com/muesli/reflow v0.3.0 // indirect 111 | github.com/muesli/termenv v0.15.2 // indirect 112 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 113 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 114 | github.com/natefinch/atomic v1.0.1 // indirect 115 | github.com/opentracing/basictracer-go v1.1.0 // indirect 116 | github.com/opentracing/opentracing-go v1.2.0 // indirect 117 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 118 | github.com/pgavlin/fx v0.1.6 // indirect 119 | github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 // indirect 120 | github.com/pjbgf/sha1cd v0.3.2 // indirect 121 | github.com/pkg/errors v0.9.1 // indirect 122 | github.com/pkg/term v1.1.0 // indirect 123 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 124 | github.com/prometheus/client_golang v1.22.0 // indirect 125 | github.com/prometheus/client_model v0.6.1 // indirect 126 | github.com/prometheus/common v0.62.0 // indirect 127 | github.com/prometheus/procfs v0.15.1 // indirect 128 | github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 // indirect 129 | github.com/pulumi/cloud-ready-checks v1.3.0 // indirect 130 | github.com/pulumi/esc v0.14.2 // indirect 131 | github.com/pulumi/inflector v0.1.1 // indirect 132 | github.com/rivo/uniseg v0.4.4 // indirect 133 | github.com/rogpeppe/go-internal v1.13.1 // indirect 134 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 135 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect 136 | github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect 137 | github.com/segmentio/asm v1.2.0 // indirect 138 | github.com/segmentio/encoding v0.4.0 // indirect 139 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 140 | github.com/skeema/knownhosts v1.3.0 // indirect 141 | github.com/spf13/afero v1.11.0 // indirect 142 | github.com/spf13/pflag v1.0.6 // indirect 143 | github.com/stoewer/go-strcase v1.3.0 // indirect 144 | github.com/texttheater/golang-levenshtein v1.0.1 // indirect 145 | github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect 146 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 147 | github.com/x448/float16 v0.8.4 // indirect 148 | github.com/xanzy/ssh-agent v0.3.3 // indirect 149 | github.com/xlab/treeprint v1.2.0 // indirect 150 | github.com/zclconf/go-cty v1.16.2 // indirect 151 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 152 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect 153 | go.opentelemetry.io/otel v1.34.0 // indirect 154 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect 155 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect 156 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 157 | go.opentelemetry.io/otel/sdk v1.33.0 // indirect 158 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 159 | go.opentelemetry.io/proto/otlp v1.4.0 // indirect 160 | go.uber.org/atomic v1.11.0 // indirect 161 | golang.org/x/crypto v0.37.0 // indirect 162 | golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect 163 | golang.org/x/mod v0.24.0 // indirect 164 | golang.org/x/net v0.39.0 // indirect 165 | golang.org/x/oauth2 v0.29.0 // indirect 166 | golang.org/x/sync v0.13.0 // indirect 167 | golang.org/x/sys v0.32.0 // indirect 168 | golang.org/x/term v0.31.0 // indirect 169 | golang.org/x/time v0.11.0 // indirect 170 | golang.org/x/tools v0.32.0 // indirect 171 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect 172 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect 173 | google.golang.org/grpc v1.68.1 // indirect 174 | google.golang.org/protobuf v1.36.6 // indirect 175 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 176 | gopkg.in/inf.v0 v0.9.1 // indirect 177 | gopkg.in/warnings.v0 v0.1.2 // indirect 178 | gopkg.in/yaml.v3 v3.0.1 // indirect 179 | k8s.io/api v0.33.0 // indirect 180 | k8s.io/apiserver v0.33.0 // indirect 181 | k8s.io/cli-runtime v0.33.0 // indirect 182 | k8s.io/client-go v0.33.0 // indirect 183 | k8s.io/component-base v0.33.0 // indirect 184 | k8s.io/klog/v2 v2.130.1 // indirect 185 | k8s.io/kubectl v0.33.0 // indirect 186 | k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect 187 | lukechampine.com/frand v1.4.2 // indirect 188 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect 189 | sigs.k8s.io/cli-utils v0.37.2 // indirect 190 | sigs.k8s.io/controller-runtime v0.20.4 // indirect 191 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 192 | sigs.k8s.io/kustomize/api v0.19.0 // indirect 193 | sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect 194 | sigs.k8s.io/randfill v1.0.0 // indirect 195 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 196 | sigs.k8s.io/yaml v1.4.0 // indirect 197 | ) 198 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= 2 | cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= 3 | cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= 4 | cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= 5 | cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= 6 | cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= 7 | cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= 8 | cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= 9 | cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= 10 | cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM= 11 | cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI= 12 | cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZw= 13 | cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= 14 | cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= 15 | cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= 16 | cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= 17 | cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= 18 | dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= 19 | dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 20 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= 21 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= 22 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= 23 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= 24 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= 25 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= 26 | github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM= 27 | github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= 28 | github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= 29 | github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= 30 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= 31 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 32 | github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= 33 | github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= 34 | github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= 35 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 36 | github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= 37 | github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= 38 | github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= 39 | github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 40 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 41 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 42 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 43 | github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= 44 | github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= 45 | github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= 46 | github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= 47 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= 48 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= 49 | github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= 50 | github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 51 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 52 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 53 | github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= 54 | github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= 55 | github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= 56 | github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= 57 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 58 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 59 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= 60 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 61 | github.com/aws/aws-sdk-go v1.50.36 h1:PjWXHwZPuTLMR1NIb8nEjLucZBMzmf84TLoLbD8BZqk= 62 | github.com/aws/aws-sdk-go v1.50.36/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= 63 | github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= 64 | github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= 65 | github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA= 66 | github.com/aws/aws-sdk-go-v2/config v1.27.11/go.mod h1:SMsV78RIOYdve1vf36z8LmnszlRWkwMQtomCAI0/mIE= 67 | github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs= 68 | github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo= 69 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= 70 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= 71 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= 72 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= 73 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= 74 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= 75 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= 76 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= 77 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= 78 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= 79 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= 80 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= 81 | github.com/aws/aws-sdk-go-v2/service/kms v1.30.1 h1:SBn4I0fJXF9FYOVRSVMWuhvEKoAHDikjGpS3wlmw5DE= 82 | github.com/aws/aws-sdk-go-v2/service/kms v1.30.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ= 83 | github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 h1:vN8hEbpRnL7+Hopy9dzmRle1xmDc7o8tmY0klsr175w= 84 | github.com/aws/aws-sdk-go-v2/service/sso v1.20.5/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= 85 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE= 86 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= 87 | github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU= 88 | github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= 89 | github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= 90 | github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= 91 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 92 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 93 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 94 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 95 | github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= 96 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 97 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 98 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 99 | github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= 100 | github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= 101 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 102 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 103 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 104 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 105 | github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= 106 | github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= 107 | github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= 108 | github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= 109 | github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= 110 | github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= 111 | github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= 112 | github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= 113 | github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= 114 | github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= 115 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 116 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 117 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= 118 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 119 | github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= 120 | github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= 121 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 122 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 123 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 124 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 125 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 126 | github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= 127 | github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 128 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 129 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 130 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 131 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 132 | github.com/deckarep/golang-set/v2 v2.5.0 h1:hn6cEZtQ0h3J8kFrHR/NrzyOoTnjgW1+FmNJzQ7y/sA= 133 | github.com/deckarep/golang-set/v2 v2.5.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= 134 | github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= 135 | github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= 136 | github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= 137 | github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= 138 | github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= 139 | github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= 140 | github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= 141 | github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 142 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 143 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 144 | github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= 145 | github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 146 | github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= 147 | github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= 148 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= 149 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= 150 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 151 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 152 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 153 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 154 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 155 | github.com/fluxcd/cli-utils v0.36.0-flux.13 h1:2X5yjz/rk9mg7+bMFBDZKGKzeZpAmY2s6iwbNZz7OzM= 156 | github.com/fluxcd/cli-utils v0.36.0-flux.13/go.mod h1:b2iSoIeDTtjfCB0IKtGgqlhhvWa1oux3e90CjOf81oA= 157 | github.com/fluxcd/pkg/ssa v0.46.0 h1:TGomtbA6zTfZrHF0TDn3mIGKH+bbX45zdWSkdYrwS8g= 158 | github.com/fluxcd/pkg/ssa v0.46.0/go.mod h1:qCek0b8tKumh9iNZLmga1mjeXOlZPlZpc6xip/hLMJM= 159 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 160 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 161 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 162 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 163 | github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= 164 | github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= 165 | github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= 166 | github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 167 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 168 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 169 | github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= 170 | github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= 171 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= 172 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= 173 | github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= 174 | github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= 175 | github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= 176 | github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= 177 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 178 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 179 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 180 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 181 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 182 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 183 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 184 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 185 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 186 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 187 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 188 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 189 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 190 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 191 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 192 | github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= 193 | github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 194 | github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= 195 | github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 196 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 197 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 198 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 199 | github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= 200 | github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 201 | github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= 202 | github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= 203 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 204 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 205 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 206 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 207 | github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= 208 | github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 209 | github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4= 210 | github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= 211 | github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= 212 | github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= 213 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 214 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 215 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 216 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 217 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 218 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 219 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= 220 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 221 | github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= 222 | github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= 223 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 224 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 225 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 226 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 227 | github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= 228 | github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= 229 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= 230 | github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= 231 | github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= 232 | github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= 233 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 234 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 235 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= 236 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= 237 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= 238 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 239 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 240 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 241 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= 242 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= 243 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= 244 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= 245 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 246 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 247 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 248 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 249 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 250 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 251 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 252 | github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= 253 | github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 254 | github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= 255 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 256 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= 257 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= 258 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= 259 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= 260 | github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= 261 | github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= 262 | github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= 263 | github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= 264 | github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= 265 | github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= 266 | github.com/hashicorp/vault/api v1.12.0 h1:meCpJSesvzQyao8FCOgk2fGdoADAnbDu2WPJN1lDLJ4= 267 | github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= 268 | github.com/hexops/autogold/v2 v2.2.1 h1:JPUXuZQGkcQMv7eeDXuNMovjfoRYaa0yVcm+F3voaGY= 269 | github.com/hexops/autogold/v2 v2.2.1/go.mod h1:IJwxtUfj1BGLm0YsR/k+dIxYi6xbeLjqGke2bzcOTMI= 270 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 271 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 272 | github.com/hexops/valast v1.4.4 h1:rETyycw+/L2ZVJHHNxEBgh8KUn+87WugH9MxcEv9PGs= 273 | github.com/hexops/valast v1.4.4/go.mod h1:Jcy1pNH7LNraVaAZDLyv21hHg2WBv9Nf9FL6fGxU7o4= 274 | github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= 275 | github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 276 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 277 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 278 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 279 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 280 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 281 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 282 | github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= 283 | github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= 284 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 285 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 286 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 287 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 288 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 289 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 290 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 291 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 292 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 293 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 294 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 295 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 296 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 297 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 298 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 299 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 300 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 301 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 302 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 303 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 304 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= 305 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 306 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 307 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 308 | github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= 309 | github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 310 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 311 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 312 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 313 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 314 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 315 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 316 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 317 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 318 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 319 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 320 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 321 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 322 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 323 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 324 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 325 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 326 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 327 | github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= 328 | github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= 329 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 330 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 331 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 332 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 333 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 334 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 335 | github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= 336 | github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= 337 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 338 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 339 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 340 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 341 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 342 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 343 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 344 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= 345 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 346 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 347 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 348 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 349 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 350 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 351 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 352 | github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= 353 | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 354 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 355 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 356 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= 357 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 358 | github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= 359 | github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= 360 | github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= 361 | github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= 362 | github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= 363 | github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= 364 | github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= 365 | github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= 366 | github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0= 367 | github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc= 368 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 369 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 370 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 371 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 372 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 373 | github.com/pgavlin/fx v0.1.6 h1:r9jEg69DhNoCd3Xh0+5mIbdbS3PqWrVWujkY76MFRTU= 374 | github.com/pgavlin/fx v0.1.6/go.mod h1:KWZJ6fqBBSh8GxHYqwYCf3rYE7Gp2p0N8tJp8xv9u9M= 375 | github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 h1:LoCV5cscNVWyK5ChN/uCoIFJz8jZD63VQiGJIRgr6uo= 376 | github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386/go.mod h1:MRxHTJrf9FhdfNQ8Hdeh9gmHevC9RJE/fu8M3JIGjoE= 377 | github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= 378 | github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= 379 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= 380 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 381 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 382 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 383 | github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= 384 | github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= 385 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 386 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 387 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 388 | github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 389 | github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 390 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 391 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 392 | github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= 393 | github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= 394 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 395 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 396 | github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435cARxCW6q9gc0S/Yxz7Mkd38pOb0= 397 | github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE= 398 | github.com/pulumi/cloud-ready-checks v1.3.0 h1:sakLHDVOxw6xQQI61NV+sjZNlVbDOe3G6Ie+YQ7le38= 399 | github.com/pulumi/cloud-ready-checks v1.3.0/go.mod h1:Qyyn9c1LftCgYmAHfPzR/f4NLR1j+F85cwDst8bXi18= 400 | github.com/pulumi/esc v0.14.2 h1:xHpjJXzKs1hk/QPpgwe1Rmif3VWA0QcZ7jDvTFYX/jM= 401 | github.com/pulumi/esc v0.14.2/go.mod h1:0dNzCWIiRUmdfFrhHdeBzU4GiDPBhSfpeWDNApZwZ08= 402 | github.com/pulumi/inflector v0.1.1 h1:dvlxlWtXwOJTUUtcYDvwnl6Mpg33prhK+7mzeF+SobA= 403 | github.com/pulumi/inflector v0.1.1/go.mod h1:HUFCjcPTz96YtTuUlwG3i3EZG4WlniBvR9bd+iJxCUY= 404 | github.com/pulumi/pulumi-java/pkg v1.12.0 h1:T7yFnFr0bgqy6huVUANMyUeGO1/Y3r2CJJ6S5YQDQCU= 405 | github.com/pulumi/pulumi-java/pkg v1.12.0/go.mod h1:g8QQjEgB5wTsZptyf1vbIcI/pgYEGJObnihAEgymkAo= 406 | github.com/pulumi/pulumi-kubernetes/provider/v4 v4.0.0-20250530040339-1e2a2355c128 h1:uAb7qWi3oGTeV7DcclehxITF6FJ6p99W6BDm21IQxrk= 407 | github.com/pulumi/pulumi-kubernetes/provider/v4 v4.0.0-20250530040339-1e2a2355c128/go.mod h1:Bw5K5lkcyBlTnF6aOfZYwueflpF+sqR+FMzOdj3A4uE= 408 | github.com/pulumi/pulumi/pkg/v3 v3.173.0 h1:jsIvG330UAsWEQH40qNy3q5H7rmUCSWYYhPeAvsJE5g= 409 | github.com/pulumi/pulumi/pkg/v3 v3.173.0/go.mod h1:w/gl1k30IEALsvZnnti5erhRPPgP4dOkkObooGTDsiQ= 410 | github.com/pulumi/pulumi/sdk/v3 v3.173.0 h1:0ChPOOCOb/MnR0Yi3X2tU4aDQhFPyQ78CCv1aqPv70Q= 411 | github.com/pulumi/pulumi/sdk/v3 v3.173.0/go.mod h1:AD2BrIxFG4wdCLCFODrOasXhURwrD/8hHrwBcjzyU9Y= 412 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 413 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 414 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 415 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 416 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 417 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 418 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 419 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 420 | github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= 421 | github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 422 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= 423 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= 424 | github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= 425 | github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= 426 | github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 427 | github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 428 | github.com/segmentio/encoding v0.4.0 h1:MEBYvRqiUB2nfR2criEXWqwdY6HJOUrCn5hboVOVmy8= 429 | github.com/segmentio/encoding v0.4.0/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI= 430 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= 431 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 432 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 433 | github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= 434 | github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= 435 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 436 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 437 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 438 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 439 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 440 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 441 | github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= 442 | github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= 443 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 444 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 445 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 446 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 447 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 448 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 449 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 450 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 451 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 452 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 453 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 454 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 455 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 456 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 457 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 458 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 459 | github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= 460 | github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= 461 | github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= 462 | github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 463 | github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= 464 | github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= 465 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 466 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 467 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 468 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 469 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= 470 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 471 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 472 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 473 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 474 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 475 | github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= 476 | github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 477 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 478 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 479 | github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= 480 | github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= 481 | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= 482 | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= 483 | go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8= 484 | go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY= 485 | go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc= 486 | go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs= 487 | go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY= 488 | go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU= 489 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 490 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 491 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 492 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 493 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= 494 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= 495 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= 496 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= 497 | go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= 498 | go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= 499 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= 500 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= 501 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= 502 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= 503 | go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= 504 | go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= 505 | go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= 506 | go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= 507 | go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= 508 | go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 509 | go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= 510 | go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= 511 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 512 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 513 | go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= 514 | go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 515 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 516 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 517 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 518 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 519 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 520 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 521 | gocloud.dev v0.37.0 h1:XF1rN6R0qZI/9DYjN16Uy0durAmSlf58DHOcb28GPro= 522 | gocloud.dev v0.37.0/go.mod h1:7/O4kqdInCNsc6LqgmuFnS0GRew4XNNYWpA44yQnwco= 523 | gocloud.dev/secrets/hashivault v0.37.0 h1:5ehGtUBP29DFAgAs6bPw7fVSgqQ3TxaoK2xVcLp1x+c= 524 | gocloud.dev/secrets/hashivault v0.37.0/go.mod h1:4ClUWjBfP8wLdGts56acjHz3mWLuATMoH9vi74FjIv8= 525 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 526 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 527 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 528 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 529 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 530 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 531 | golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= 532 | golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= 533 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 534 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 535 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 536 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 537 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 538 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 539 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 540 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 541 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 542 | golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 543 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 544 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 545 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 546 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 547 | golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= 548 | golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 549 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 550 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 551 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 552 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 553 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 554 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 555 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 556 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 557 | golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 558 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 559 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 560 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 561 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 562 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 563 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 564 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 565 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 566 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 567 | golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 568 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 569 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 570 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 571 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 572 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 573 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 574 | golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 575 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 576 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 577 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 578 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 579 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 580 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 581 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 582 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 583 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 584 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 585 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 586 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 587 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 588 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 589 | golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= 590 | golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= 591 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 592 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 593 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 594 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 595 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 596 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 597 | google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY= 598 | google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= 599 | google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= 600 | google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= 601 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= 602 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= 603 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= 604 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= 605 | google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= 606 | google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= 607 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 608 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 609 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 610 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 611 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 612 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 613 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 614 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 615 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 616 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 617 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 618 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 619 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 620 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 621 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 622 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 623 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 624 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 625 | k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= 626 | k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= 627 | k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= 628 | k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= 629 | k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= 630 | k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= 631 | k8s.io/apiserver v0.33.0 h1:QqcM6c+qEEjkOODHppFXRiw/cE2zP85704YrQ9YaBbc= 632 | k8s.io/apiserver v0.33.0/go.mod h1:EixYOit0YTxt8zrO2kBU7ixAtxFce9gKGq367nFmqI8= 633 | k8s.io/cli-runtime v0.33.0 h1:Lbl/pq/1o8BaIuyn+aVLdEPHVN665tBAXUePs8wjX7c= 634 | k8s.io/cli-runtime v0.33.0/go.mod h1:QcA+r43HeUM9jXFJx7A+yiTPfCooau/iCcP1wQh4NFw= 635 | k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= 636 | k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= 637 | k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk= 638 | k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU= 639 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 640 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 641 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= 642 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= 643 | k8s.io/kubectl v0.33.0 h1:HiRb1yqibBSCqic4pRZP+viiOBAnIdwYDpzUFejs07g= 644 | k8s.io/kubectl v0.33.0/go.mod h1:gAlGBuS1Jq1fYZ9AjGWbI/5Vk3M/VW2DK4g10Fpyn/0= 645 | k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= 646 | k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 647 | lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= 648 | lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s= 649 | mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= 650 | mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= 651 | pgregory.net/rapid v0.6.1 h1:4eyrDxyht86tT4Ztm+kvlyNBLIk071gR+ZQdhphc9dQ= 652 | pgregory.net/rapid v0.6.1/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= 653 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= 654 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= 655 | sigs.k8s.io/cli-utils v0.37.2 h1:GOfKw5RV2HDQZDJlru5KkfLO1tbxqMoyn1IYUxqBpNg= 656 | sigs.k8s.io/cli-utils v0.37.2/go.mod h1:V+IZZr4UoGj7gMJXklWBg6t5xbdThFBcpj4MrZuCYco= 657 | sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= 658 | sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= 659 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= 660 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 661 | sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= 662 | sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o= 663 | sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= 664 | sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= 665 | sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 666 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 667 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 668 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= 669 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= 670 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 671 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 672 | -------------------------------------------------------------------------------- /internal/files/files.go: -------------------------------------------------------------------------------- 1 | // Package files provides abstractions for working with files. 2 | package files 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | "github.com/pulumi/pulumi/sdk/v3/go/common/util/httputil" 13 | ) 14 | 15 | // ReadFromLocalOrRemote reads the contents of a file from the local filesystem or from a remote URL. 16 | func ReadFromLocalOrRemote(pathOrURL string, headers map[string]string) (io.ReadCloser, error) { 17 | if strings.HasPrefix(pathOrURL, "https://") { 18 | client := &http.Client{Timeout: 30 * time.Second} 19 | req, err := http.NewRequest("GET", pathOrURL, nil) 20 | if err != nil { 21 | return nil, fmt.Errorf("could not create HTTP request for %q: %w", pathOrURL, err) 22 | } 23 | for k, v := range headers { 24 | req.Header.Add(k, v) 25 | } 26 | 27 | resp, err := httputil.DoWithRetry(req, client) 28 | if err != nil { 29 | return nil, fmt.Errorf("failed to make HTTP request to %q: %w", pathOrURL, err) 30 | } 31 | if resp.StatusCode != http.StatusOK { 32 | return nil, fmt.Errorf("HTTP request to %q failed with status %d", pathOrURL, resp.StatusCode) 33 | } 34 | return resp.Body, nil 35 | } 36 | file, err := os.Open(pathOrURL) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to open file %q: %w", pathOrURL, err) 39 | } 40 | return file, nil 41 | } 42 | -------------------------------------------------------------------------------- /internal/json/json.go: -------------------------------------------------------------------------------- 1 | // Package json has functions for working with JSON objects 2 | package json 3 | 4 | import "encoding/json" 5 | 6 | // RawMessage takes any JSON object and returns a json.RawMessage representation of it 7 | func RawMessage(v any) (json.RawMessage, error) { 8 | return json.Marshal(v) 9 | } 10 | -------------------------------------------------------------------------------- /internal/slices/slices.go: -------------------------------------------------------------------------------- 1 | // Package slices has functions for working with slices 2 | package slices 3 | 4 | // ToAny converts any type of slice to new []any slice. 5 | func ToAny[T any](slice []T) []any { 6 | anySlice := make([]any, len(slice)) 7 | for i, v := range slice { 8 | anySlice[i] = v 9 | } 10 | return anySlice 11 | } 12 | -------------------------------------------------------------------------------- /internal/unstruct/unstruct.go: -------------------------------------------------------------------------------- 1 | // unstruct has utilities for working with k8s unstructured data 2 | package unstruct 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "strings" 9 | 10 | extensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 11 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 12 | "k8s.io/apimachinery/pkg/util/yaml" 13 | ) 14 | 15 | // NestedMapSlice returns a copy of []map[string]any value of a nested field. 16 | // Returns false if value is not found and an error if not a []any or contains non-map items in the slice. 17 | // If the value is found but not of type []any, this still returns true. 18 | func NestedMapSlice(obj map[string]any, fields ...string) ([]map[string]any, bool, error) { 19 | val, found, err := unstructured.NestedFieldNoCopy(obj, fields...) 20 | if !found || err != nil { 21 | return nil, found, err 22 | } 23 | m, ok := val.([]any) 24 | if !ok { 25 | return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected []any", jsonPath(fields), val, val) 26 | } 27 | mapSlice := make([]map[string]any, 0, len(m)) 28 | for _, v := range m { 29 | if strMap, ok := v.(map[string]any); ok { 30 | mapSlice = append(mapSlice, strMap) 31 | } else { 32 | return nil, false, fmt.Errorf("%v accessor error: contains non-map key in the slice: %v is of the type %T, expected map[string]any", jsonPath(fields), v, v) 33 | } 34 | } 35 | return mapSlice, true, nil 36 | } 37 | 38 | func jsonPath(fields []string) string { 39 | return "." + strings.Join(fields, ".") 40 | } 41 | 42 | const CRD = "CustomResourceDefinition" 43 | 44 | // UnmarshalYamls un-marshals the YAML documents in the given file into a slice of unstruct.Unstructureds, one for each 45 | // CRD. Only returns the YAML files for Kubernetes manifests that are CRDs and ignores others. Returns an error if any 46 | // document failed to unmarshal. 47 | func UnmarshalYamls(yamlFiles [][]byte) ([]extensionv1.CustomResourceDefinition, error) { 48 | var crds []extensionv1.CustomResourceDefinition 49 | for _, yamlFile := range yamlFiles { 50 | var err error 51 | dec := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(yamlFile), 128) 52 | for err != io.EOF { 53 | var crd extensionv1.CustomResourceDefinition 54 | if err = dec.Decode(&crd); err != nil && err != io.EOF { 55 | return nil, fmt.Errorf("failed to unmarshal yaml: %w", err) 56 | } 57 | if crd.Kind == CRD { 58 | crds = append(crds, crd) 59 | } 60 | } 61 | } 62 | return crds, nil 63 | } 64 | -------------------------------------------------------------------------------- /internal/versions/versions.go: -------------------------------------------------------------------------------- 1 | // Package versions has useful functions for working with versions. 2 | package versions 3 | 4 | import ( 5 | "fmt" 6 | "regexp" 7 | "strings" 8 | "unicode" 9 | ) 10 | 11 | var alphanumericRegex = regexp.MustCompile(`[^a-zA-Z0-9]+`) 12 | 13 | // SplitGroupVersion returns the and field of a string in the 14 | // format / 15 | func SplitGroupVersion(groupVersion string) (string, string, error) { 16 | parts := strings.Split(groupVersion, "/") 17 | if len(parts) != 2 { 18 | return "", "", fmt.Errorf("expected a version string with the format /, but got %q", groupVersion) 19 | } 20 | return parts[0], parts[1], nil 21 | } 22 | 23 | // groupPrefix returns the first word in the dot-separated group string, with 24 | // all non-alphanumeric characters removed. 25 | func GroupPrefix(group string) (string, error) { 26 | if group == "" { 27 | return "", fmt.Errorf("group cannot be empty") 28 | } 29 | return removeNonAlphanumeric(strings.Split(group, ".")[0]), nil 30 | } 31 | 32 | // Capitalizes and returns the given version. For example, 33 | // VersionToUpper("v2beta1") returns "V2Beta1". 34 | func VersionToUpper(version string) string { 35 | var sb strings.Builder 36 | for i, r := range version { 37 | if unicode.IsLetter(r) && (i == 0 || !unicode.IsLetter(rune(version[i-1]))) { 38 | sb.WriteRune(unicode.ToUpper(r)) 39 | } else { 40 | sb.WriteRune(r) 41 | } 42 | } 43 | return sb.String() 44 | } 45 | 46 | // removeNonAlphanumeric removes all non-alphanumeric characters 47 | func removeNonAlphanumeric(input string) string { 48 | return alphanumericRegex.ReplaceAllString(input, "") 49 | } 50 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/pulumi/crd2pulumi/cmd" 22 | ) 23 | 24 | func main() { 25 | err := cmd.New().Execute() 26 | if err != nil { 27 | fmt.Fprintf(os.Stderr, "error: %v\n", err) 28 | os.Exit(1) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/codegen/codegen.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/pulumi/crd2pulumi/internal/files" 11 | ) 12 | 13 | // GenerateFunc is the function that is called by the generator to generate the code. 14 | // It returns a mapping of filename to the contents of said file and any error that may have occurred. 15 | type GenerateFunc func(pg *PackageGenerator, name string) (mapFileNameToData map[string]*bytes.Buffer, err error) 16 | 17 | var codeGenFuncs = map[string]GenerateFunc{ 18 | Go: GenerateGo, 19 | DotNet: GenerateDotNet, 20 | NodeJS: GenerateNodeJS, 21 | Python: GeneratePython, 22 | Java: GenerateJava, 23 | } 24 | 25 | // PulumiToolName is a symbol that identifies to Pulumi the name of this program. 26 | const PulumiToolName = "crd2pulumi" 27 | 28 | // GenerateFromFiles performs the entire CRD codegen process. 29 | // The yamlPaths argument can contain both file paths and URLs. 30 | func GenerateFromFiles(cs *CodegenSettings, yamlPaths []string) error { 31 | yamlReaders := make([]io.ReadCloser, 0, len(yamlPaths)) 32 | for _, yamlPath := range yamlPaths { 33 | reader, err := files.ReadFromLocalOrRemote(yamlPath, map[string]string{"Accept": "application/x-yaml, text/yaml"}) 34 | if err != nil { 35 | return fmt.Errorf("could not open YAML document at %s: %w", yamlPath, err) 36 | } 37 | yamlReaders = append(yamlReaders, reader) 38 | } 39 | return Generate(cs, yamlReaders) 40 | } 41 | 42 | // Generate performs the entire CRD codegen process, reading YAML content from the given readers. 43 | func Generate(cs *CodegenSettings, yamls []io.ReadCloser) error { 44 | generate, ok := codeGenFuncs[cs.Language] 45 | if !ok { 46 | return fmt.Errorf("unsupported language %q, must be one of %q", cs.Language, SupportedLanguages) 47 | } 48 | 49 | if !cs.Overwrite { 50 | if dirExists(cs.Path()) { 51 | return fmt.Errorf("output already exists at %q, use --force to overwrite", cs.Path()) 52 | } 53 | } 54 | 55 | // Do the actual reading of files from source, may take substantial time depending on the sources. 56 | pg, err := ReadPackagesFromSource(cs.PackageVersion, yamls) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | // Do actual codegen 62 | output, err := generate(pg, cs.PackageName) 63 | if err != nil { 64 | return fmt.Errorf("failed to generate %q package %q: %w", cs.Language, cs.PackageName, err) 65 | } 66 | // Write output to disk 67 | err = writeFiles(output, cs.Path()) 68 | if err != nil { 69 | return fmt.Errorf("failed to write %q package %q to disk: %w", cs.Language, cs.PackageName, err) 70 | } 71 | return nil 72 | } 73 | 74 | // dirExists returns whether a given directory exists. 75 | func dirExists(filename string) bool { 76 | info, err := os.Stat(filename) 77 | if os.IsNotExist(err) { 78 | return false 79 | } 80 | return info.IsDir() 81 | } 82 | 83 | // writeFiles writes the contents of each buffer to its file path, relative to `outputDir`. 84 | // `files` should be a mapping from file path strings to buffers. 85 | func writeFiles(files map[string]*bytes.Buffer, outputDir string) error { 86 | for path, code := range files { 87 | outputFilePath := filepath.Join(outputDir, path) 88 | err := os.MkdirAll(filepath.Dir(outputFilePath), 0755) 89 | if err != nil { 90 | return fmt.Errorf("could not create directory to %s: %w", outputFilePath, err) 91 | } 92 | file, err := os.Create(outputFilePath) 93 | if err != nil { 94 | return fmt.Errorf("could not create file %s: %w", outputFilePath, err) 95 | } 96 | defer file.Close() 97 | if _, err := code.WriteTo(file); err != nil { 98 | return fmt.Errorf("could not write to file %s: %w", outputFilePath, err) 99 | } 100 | } 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /pkg/codegen/customresourcegenerator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2020, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codegen 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "github.com/go-openapi/jsonreference" 22 | "github.com/pulumi/pulumi/pkg/v3/codegen/cgstrings" 23 | extensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 24 | "k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder" 25 | "k8s.io/kube-openapi/pkg/validation/spec" 26 | ) 27 | 28 | const ( 29 | definitionPrefix = "#/definitions/" 30 | ) 31 | 32 | // CustomResourceGenerator generates a Pulumi schema for a single CustomResource 33 | type CustomResourceGenerator struct { 34 | // CustomResourceDefinition contains the unmarshalled CRD YAML 35 | CustomResourceDefinition extensionv1.CustomResourceDefinition 36 | // Schemas represents a mapping from each version in the `spec.versions` 37 | // list to its corresponding `openAPIV3Schema` field in the CRD YAML 38 | Schemas map[string]spec.Swagger 39 | // ApiVersion represents the `apiVersion` field in the CRD YAML 40 | APIVersion string 41 | // Kind represents the `spec.names.kind` field in the CRD YAML 42 | Kind string 43 | // Plural represents the `spec.names.plural` field in the CRD YAML 44 | Plural string 45 | // Group represents the `spec.group` field in the CRD YAML 46 | Group string 47 | // Versions is a slice of names of each version supported by this CRD 48 | Versions []string 49 | // GroupVersions is a slice of names of each version, in the format 50 | // /. 51 | GroupVersions []string 52 | // ResourceTokens is a slice of the token types of every versioned 53 | // CustomResource 54 | ResourceTokens []string 55 | } 56 | 57 | // flattenOpenAPI recursively finds all nested objects in the OpenAPI spec and flattens them into a single object as definitions. 58 | func flattenOpenAPI(sw *spec.Swagger) error { 59 | initialDefinitions := make([]string, 0, len(sw.Definitions)) 60 | 61 | for defName := range sw.Definitions { 62 | initialDefinitions = append(initialDefinitions, defName) 63 | } 64 | 65 | for _, defName := range initialDefinitions { 66 | definition := sw.Definitions[defName] 67 | spec, err := flattenRecursively(sw, defName, definition) 68 | if err != nil { 69 | return fmt.Errorf("error flattening OpenAPI spec: %w", err) 70 | } 71 | 72 | sw.Definitions[defName] = spec 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func flattenRecursively(sw *spec.Swagger, parentName string, currSpec spec.Schema) (spec.Schema, error) { 79 | // If at bottom of the stack, return the spec. 80 | if currSpec.Properties == nil && currSpec.Items == nil && currSpec.AdditionalProperties == nil { 81 | return currSpec, nil 82 | } 83 | 84 | // If the spec already has a reference to a URL, we can skip it. 85 | if currSpec.Ref.GetURL() != nil { 86 | return currSpec, nil 87 | } 88 | 89 | // If the property is an object with additional properties, we can skip it if it is not an array of inline objects. We only care about 90 | // nested objects that are explicitly defined. 91 | if currSpec.AdditionalProperties != nil { 92 | // Not an array of inline objects, so we can skip processing. 93 | if currSpec.AdditionalProperties.Schema.Items == nil { 94 | return currSpec, nil 95 | } 96 | 97 | // Property is an array of inline objects. 98 | s, ref, err := flattedArrayObject(parentName, sw, currSpec.AdditionalProperties.Schema.Items.Schema) 99 | if err != nil { 100 | return currSpec, fmt.Errorf("error flattening OpenAPI object of array property: %w", err) 101 | } 102 | 103 | currSpec.AdditionalProperties.Schema.Items.Schema = &s 104 | 105 | if ref != nil { 106 | currSpec.AdditionalProperties.Schema.Items.Schema.Ref = spec.Ref{Ref: *ref} 107 | currSpec.AdditionalProperties.Schema.Items.Schema.Type = nil 108 | currSpec.AdditionalProperties.Schema.Items.Schema.Properties = nil 109 | } 110 | 111 | return currSpec, nil 112 | } 113 | 114 | // If the property is an array, we need to remove any inline objects and replace them with references. 115 | if currSpec.Items != nil { 116 | if currSpec.Items.Schema == nil { 117 | return currSpec, fmt.Errorf("error flattening OpenAPI spec: items schema is nil") 118 | } 119 | 120 | s, ref, err := flattedArrayObject(parentName, sw, currSpec.Items.Schema) 121 | if err != nil { 122 | return currSpec, fmt.Errorf("error flattening OpenAPI array property: %w", err) 123 | } 124 | 125 | currSpec.Items.Schema = &s 126 | 127 | if ref != nil { 128 | currSpec.Items.Schema.Ref = spec.Ref{Ref: *ref} 129 | currSpec.Items.Schema.Type = nil 130 | currSpec.Items.Schema.Properties = nil 131 | } 132 | 133 | return currSpec, nil 134 | } 135 | 136 | // Recurse through the properties of the object. 137 | for nestedPropertyName, nestedProperty := range currSpec.Properties { 138 | // VistoriaMetrics has some weird fields - likely a typegen issue on their end, so let's skip them. 139 | if nestedPropertyName == "-" { 140 | delete(currSpec.Properties, nestedPropertyName) 141 | continue 142 | } 143 | // Create a new definition for the nested object by joining the parent definition name and the property name. 144 | // This is to ensure that the nested object is unique and does not conflict with other definitions. 145 | nestedDefinitionName := parentName + sanitizeReferenceName(nestedPropertyName) 146 | 147 | s, err := flattenRecursively(sw, nestedDefinitionName, nestedProperty) 148 | if err != nil { 149 | return currSpec, fmt.Errorf("error flattening OpenAPI spec: %w", err) 150 | } 151 | 152 | // The nested property is not an object, so we can skip adding it to the definitions and creating a reference. 153 | // We check this here as we want our recursive function to inspect both arrays and objects. 154 | if len(nestedProperty.Properties) == 0 { 155 | continue 156 | } 157 | 158 | sw.Definitions[nestedDefinitionName] = s 159 | 160 | // Reset the property to be a reference to the nested object. 161 | refName := definitionPrefix + nestedDefinitionName 162 | ref, err := jsonreference.New(refName) 163 | if err != nil { 164 | return currSpec, fmt.Errorf("error creating OpenAPI json reference for nested object: %w", err) 165 | } 166 | 167 | currSpec.Properties[nestedPropertyName] = spec.Schema{ 168 | SchemaProps: spec.SchemaProps{ 169 | Ref: spec.Ref{ 170 | Ref: ref, 171 | }, 172 | }, 173 | } 174 | } 175 | 176 | return currSpec, nil 177 | } 178 | 179 | // flattedArrayObject flattens an OpenAPI array property. 180 | func flattedArrayObject(parentName string, sw *spec.Swagger, itemsSchema *spec.Schema) (spec.Schema, *jsonreference.Ref, error) { 181 | nestedDefinitionName := parentName 182 | 183 | s, err := flattenRecursively(sw, nestedDefinitionName, *itemsSchema) 184 | if err != nil { 185 | return s, nil, fmt.Errorf("error flattening OpenAPI spec: %w", err) 186 | } 187 | 188 | if len(s.Properties) == 0 { 189 | return s, nil, nil 190 | } 191 | 192 | sw.Definitions[nestedDefinitionName] = s 193 | 194 | refName := definitionPrefix + nestedDefinitionName 195 | ref, err := jsonreference.New(refName) 196 | if err != nil { 197 | return s, nil, fmt.Errorf("error creating OpenAPI json reference for nested object: %w", err) 198 | } 199 | 200 | return s, &ref, nil 201 | } 202 | 203 | func sanitizeReferenceName(fieldName string) string { 204 | // If the field name is "arg" or "args", we need to change it to "Arguments" to avoid conflicts with Go reserved words. 205 | if s := strings.ToLower(fieldName); s == "arg" || s == "args" { 206 | return "Arguments" 207 | } 208 | 209 | // We need to strip out any hyphens and underscores in the reference. 210 | fieldName = cgstrings.Unhyphenate(fieldName) 211 | fieldName = cgstrings.ModifyStringAroundDelimeter(fieldName, "_", cgstrings.UppercaseFirst) 212 | 213 | return cgstrings.UppercaseFirst(fieldName) 214 | } 215 | 216 | // crdToOpenAPI generates the OpenAPI specs for a given CRD manifest. 217 | func crdToOpenAPI(crd *extensionv1.CustomResourceDefinition) (map[string]*spec.Swagger, error) { 218 | openAPIManifests := make(map[string]*spec.Swagger) 219 | 220 | setCRDDefaults(crd) 221 | 222 | for _, v := range crd.Spec.Versions { 223 | // Defaults are not pruned here, but before being served. 224 | sw, err := builder.BuildOpenAPIV2(crd, v.Name, builder.Options{V2: true, StripValueValidation: true, StripNullable: true, AllowNonStructural: true, IncludeSelectableFields: true}) 225 | if err != nil { 226 | return nil, err 227 | } 228 | 229 | err = flattenOpenAPI(sw) 230 | if err != nil { 231 | return nil, fmt.Errorf("error flattening OpenAPI spec: %w", err) 232 | } 233 | 234 | openAPIManifests[v.Name] = sw 235 | } 236 | 237 | return openAPIManifests, nil 238 | } 239 | 240 | // setCRDDefaults sets the default names for the CRD if they are not specified. 241 | // This allows the OpenAPI builder to generate the swagger specs correctly with 242 | // the correct defaults. 243 | func setCRDDefaults(crd *extensionv1.CustomResourceDefinition) { 244 | if crd.Spec.Names.Singular == "" { 245 | crd.Spec.Names.Singular = strings.ToLower(crd.Spec.Names.Kind) 246 | } 247 | if crd.Spec.Names.ListKind == "" { 248 | crd.Spec.Names.ListKind = crd.Spec.Names.Kind + "List" 249 | } 250 | } 251 | 252 | func NewCustomResourceGenerator(crd extensionv1.CustomResourceDefinition) (CustomResourceGenerator, error) { 253 | apiVersion := crd.APIVersion 254 | schemas := map[string]spec.Swagger{} 255 | 256 | swagger, err := crdToOpenAPI(&crd) 257 | if err != nil { 258 | return CustomResourceGenerator{}, fmt.Errorf("could not generate OpenAPI spec for CRD: %w", err) 259 | } 260 | 261 | for version, sw := range swagger { 262 | schemas[version] = *sw 263 | } 264 | 265 | kind := crd.Spec.Names.Kind 266 | plural := crd.Spec.Names.Plural 267 | group := crd.Spec.Group 268 | 269 | versions := make([]string, 0, len(schemas)) 270 | groupVersions := make([]string, 0, len(schemas)) 271 | resourceTokens := make([]string, 0, len(schemas)) 272 | for version := range schemas { 273 | versions = append(versions, version) 274 | groupVersions = append(groupVersions, group+"/"+version) 275 | resourceTokens = append(resourceTokens, getToken(group, version, kind)) 276 | } 277 | 278 | crg := CustomResourceGenerator{ 279 | CustomResourceDefinition: crd, 280 | Schemas: schemas, 281 | APIVersion: apiVersion, 282 | Kind: kind, 283 | Plural: plural, 284 | Group: group, 285 | Versions: versions, 286 | GroupVersions: groupVersions, 287 | ResourceTokens: resourceTokens, 288 | } 289 | 290 | return crg, nil 291 | } 292 | 293 | // HasSchemas returns true if the CustomResource specifies at least some schema, and false otherwise. 294 | func (crg *CustomResourceGenerator) HasSchemas() bool { 295 | return len(crg.Schemas) > 0 296 | } 297 | -------------------------------------------------------------------------------- /pkg/codegen/customresourcegenerator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codegen 16 | 17 | import ( 18 | "testing" 19 | 20 | extensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | func TestSetCRDDefaults(t *testing.T) { 25 | tests := []struct { 26 | name string 27 | crd extensionv1.CustomResourceDefinition 28 | expected extensionv1.CustomResourceDefinition 29 | }{ 30 | { 31 | name: "Singular and ListKind are empty", 32 | crd: extensionv1.CustomResourceDefinition{ 33 | Spec: extensionv1.CustomResourceDefinitionSpec{ 34 | Names: extensionv1.CustomResourceDefinitionNames{ 35 | Kind: "TestKind", 36 | }, 37 | }, 38 | }, 39 | expected: extensionv1.CustomResourceDefinition{ 40 | Spec: extensionv1.CustomResourceDefinitionSpec{ 41 | Names: extensionv1.CustomResourceDefinitionNames{ 42 | Kind: "TestKind", 43 | Singular: "testkind", 44 | ListKind: "TestKindList", 45 | }, 46 | }, 47 | }, 48 | }, 49 | { 50 | name: "Singular is set, ListKind is empty", 51 | crd: extensionv1.CustomResourceDefinition{ 52 | Spec: extensionv1.CustomResourceDefinitionSpec{ 53 | Names: extensionv1.CustomResourceDefinitionNames{ 54 | Kind: "TestKind", 55 | Singular: "customsingular", 56 | }, 57 | }, 58 | }, 59 | expected: extensionv1.CustomResourceDefinition{ 60 | Spec: extensionv1.CustomResourceDefinitionSpec{ 61 | Names: extensionv1.CustomResourceDefinitionNames{ 62 | Kind: "TestKind", 63 | Singular: "customsingular", 64 | ListKind: "TestKindList", 65 | }, 66 | }, 67 | }, 68 | }, 69 | { 70 | name: "Singular is empty, ListKind is set", 71 | crd: extensionv1.CustomResourceDefinition{ 72 | Spec: extensionv1.CustomResourceDefinitionSpec{ 73 | Names: extensionv1.CustomResourceDefinitionNames{ 74 | Kind: "TestKind", 75 | ListKind: "CustomListKind", 76 | }, 77 | }, 78 | }, 79 | expected: extensionv1.CustomResourceDefinition{ 80 | Spec: extensionv1.CustomResourceDefinitionSpec{ 81 | Names: extensionv1.CustomResourceDefinitionNames{ 82 | Kind: "TestKind", 83 | Singular: "testkind", 84 | ListKind: "CustomListKind", 85 | }, 86 | }, 87 | }, 88 | }, 89 | { 90 | name: "Singular and ListKind are set", 91 | crd: extensionv1.CustomResourceDefinition{ 92 | Spec: extensionv1.CustomResourceDefinitionSpec{ 93 | Names: extensionv1.CustomResourceDefinitionNames{ 94 | Kind: "TestKind", 95 | Singular: "customsingular", 96 | ListKind: "CustomListKind", 97 | }, 98 | }, 99 | }, 100 | expected: extensionv1.CustomResourceDefinition{ 101 | Spec: extensionv1.CustomResourceDefinitionSpec{ 102 | Names: extensionv1.CustomResourceDefinitionNames{ 103 | Kind: "TestKind", 104 | Singular: "customsingular", 105 | ListKind: "CustomListKind", 106 | }, 107 | }, 108 | }, 109 | }, 110 | } 111 | 112 | for _, tt := range tests { 113 | t.Run(tt.name, func(t *testing.T) { 114 | setCRDDefaults(&tt.crd) 115 | if tt.crd.Spec.Names.Singular != tt.expected.Spec.Names.Singular { 116 | t.Errorf("expected Singular %s, got %s", tt.expected.Spec.Names.Singular, tt.crd.Spec.Names.Singular) 117 | } 118 | if tt.crd.Spec.Names.ListKind != tt.expected.Spec.Names.ListKind { 119 | t.Errorf("expected ListKind %s, got %s", tt.expected.Spec.Names.ListKind, tt.crd.Spec.Names.ListKind) 120 | } 121 | }) 122 | } 123 | } 124 | func TestNewCustomResourceGenerator(t *testing.T) { 125 | tests := []struct { 126 | name string 127 | crd extensionv1.CustomResourceDefinition 128 | expected CustomResourceGenerator 129 | wantErr bool 130 | }{ 131 | { 132 | name: "Valid CRD", 133 | crd: extensionv1.CustomResourceDefinition{ 134 | TypeMeta: metav1.TypeMeta{ 135 | APIVersion: "apiextensions.k8s.io/v1", 136 | }, 137 | Spec: extensionv1.CustomResourceDefinitionSpec{ 138 | Group: "example.com", 139 | Names: extensionv1.CustomResourceDefinitionNames{ 140 | Kind: "TestKind", 141 | Plural: "testkinds", 142 | }, 143 | Versions: []extensionv1.CustomResourceDefinitionVersion{ 144 | {Name: "v1"}, 145 | }, 146 | }, 147 | }, 148 | expected: CustomResourceGenerator{ 149 | APIVersion: "apiextensions.k8s.io/v1", 150 | Kind: "TestKind", 151 | Plural: "testkinds", 152 | Group: "example.com", 153 | Versions: []string{"v1"}, 154 | GroupVersions: []string{ 155 | "example.com/v1", 156 | }, 157 | ResourceTokens: []string{ 158 | "example.com:TestKind:v1", 159 | }, 160 | }, 161 | wantErr: false, 162 | }, 163 | { 164 | name: "Valid CRD with multiple versions", 165 | crd: extensionv1.CustomResourceDefinition{ 166 | TypeMeta: metav1.TypeMeta{ 167 | APIVersion: "apiextensions.k8s.io/v1", 168 | }, 169 | Spec: extensionv1.CustomResourceDefinitionSpec{ 170 | Group: "example.com", 171 | Names: extensionv1.CustomResourceDefinitionNames{ 172 | Kind: "TestKind", 173 | Plural: "testkinds", 174 | }, 175 | Versions: []extensionv1.CustomResourceDefinitionVersion{ 176 | {Name: "v1"}, 177 | {Name: "v1alpha1"}, 178 | }, 179 | }, 180 | }, 181 | expected: CustomResourceGenerator{ 182 | APIVersion: "apiextensions.k8s.io/v1", 183 | Kind: "TestKind", 184 | Plural: "testkinds", 185 | Group: "example.com", 186 | Versions: []string{"v1alpha1", "v1"}, 187 | GroupVersions: []string{ 188 | "example.com/v1", 189 | "example.com/v1alpha1", 190 | }, 191 | ResourceTokens: []string{ 192 | "example.com:TestKind:v1", 193 | "example.com:TestKind:v1alpha1", 194 | }, 195 | }, 196 | wantErr: false, 197 | }, 198 | } 199 | 200 | for _, tt := range tests { 201 | t.Run(tt.name, func(t *testing.T) { 202 | got, err := NewCustomResourceGenerator(tt.crd) 203 | if (err != nil) != tt.wantErr { 204 | t.Errorf("NewCustomResourceGenerator() error = %v, wantErr %v", err, tt.wantErr) 205 | return 206 | } 207 | if tt.wantErr != false { 208 | return 209 | } 210 | if got.APIVersion != tt.expected.APIVersion { 211 | t.Errorf("expected APIVersion %s, got %s", tt.expected.APIVersion, got.APIVersion) 212 | } 213 | if got.Kind != tt.expected.Kind { 214 | t.Errorf("expected Kind %s, got %s", tt.expected.Kind, got.Kind) 215 | } 216 | if got.Plural != tt.expected.Plural { 217 | t.Errorf("expected Plural %s, got %s", tt.expected.Plural, got.Plural) 218 | } 219 | if got.Group != tt.expected.Group { 220 | t.Errorf("expected Group %s, got %s", tt.expected.Group, got.Group) 221 | } 222 | if len(got.Versions) != len(tt.expected.Versions) { 223 | t.Errorf("expected Versions %v, got %v", tt.expected.Versions, got.Versions) 224 | } 225 | if len(got.GroupVersions) != len(tt.expected.GroupVersions) { 226 | t.Errorf("expected GroupVersions %v, got %v", tt.expected.GroupVersions, got.GroupVersions) 227 | } 228 | if len(got.ResourceTokens) != len(tt.expected.ResourceTokens) { 229 | t.Errorf("expected ResourceTokens %v, got %v", tt.expected.ResourceTokens, got.ResourceTokens) 230 | } 231 | }) 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /pkg/codegen/dotnet.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codegen 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | 21 | "github.com/pulumi/crd2pulumi/internal/versions" 22 | "github.com/pulumi/pulumi/pkg/v3/codegen/dotnet" 23 | "golang.org/x/text/cases" 24 | "golang.org/x/text/language" 25 | ) 26 | 27 | var unneededDotNetFiles = []string{ 28 | "Meta/V1/Inputs/ObjectMetaArgs.cs", 29 | "Meta/V1/Outputs/ObjectMeta.cs", 30 | "Meta/V1/README.md", 31 | "Meta/README.md", 32 | "Provider.cs", 33 | } 34 | 35 | func GenerateDotNet(pg *PackageGenerator, name string) (map[string]*bytes.Buffer, error) { 36 | pkg := pg.SchemaPackageWithObjectMetaType() 37 | 38 | // Set up C# namespaces 39 | namespaces := map[string]string{} 40 | for _, groupVersion := range pg.GroupVersions { 41 | group, version, err := versions.SplitGroupVersion(groupVersion) 42 | if err != nil { 43 | return nil, fmt.Errorf("invalid version: %w", err) 44 | } 45 | groupPrefix, err := versions.GroupPrefix(group) 46 | if err != nil { 47 | return nil, fmt.Errorf("invalid version: %w", err) 48 | } 49 | namespaces[groupVersion] = cases.Title(language.Und).String(groupPrefix) + "." + versions.VersionToUpper(version) 50 | } 51 | namespaces["meta/v1"] = "Meta.V1" 52 | 53 | // Configure C# language-specific settings. Notice that we set 54 | // `compatibility` to `kubernetes20`. This is because the actual ObjectMeta 55 | // class defined in the .NET SDK is located at 56 | // `Pulumi.Kubernetes.Types.Outputs.Meta.V1.ObjectMeta`. This path would 57 | // only get generated properly if `compatibility` was `kubernetes20`. 58 | oldName := pkg.Name 59 | pkg.Name = name 60 | var err error 61 | 62 | files, err := dotnet.GeneratePackage(PulumiToolName, pkg, nil, nil) 63 | if err != nil { 64 | return nil, fmt.Errorf("could not generate .NET package: %w", err) 65 | } 66 | 67 | pkg.Name = oldName 68 | delete(pkg.Language, "csharp") 69 | 70 | namespaceName := dotnet.Title(name) 71 | files["KubernetesResource.cs"] = []byte(kubernetesResource(namespaceName)) 72 | files["Utilities.cs"] = []byte(dotNetUtilities(namespaceName)) 73 | 74 | // Delete unneeded files 75 | for _, unneededFile := range unneededDotNetFiles { 76 | delete(files, unneededFile) 77 | } 78 | 79 | buffers := map[string]*bytes.Buffer{} 80 | for name, code := range files { 81 | buffers[name] = bytes.NewBuffer(code) 82 | } 83 | 84 | return buffers, nil 85 | } 86 | 87 | func kubernetesResource(name string) string { 88 | return `// Copyright 2016-2022, Pulumi Corporation 89 | namespace Pulumi.` + name + `{ 90 | /// 91 | /// A base class for all Kubernetes resources. 92 | /// 93 | public abstract class KubernetesResource : CustomResource 94 | { 95 | /// 96 | /// Standard constructor passing arguments to . 97 | /// 98 | internal KubernetesResource(string type, string name, ResourceArgs? args, CustomResourceOptions? options = null) 99 | : base(type, name, args, options) 100 | { 101 | } 102 | 103 | /// 104 | /// Additional constructor for dynamic arguments received from YAML-based sources. 105 | /// 106 | internal KubernetesResource(string type, string name, DictionaryResourceArgs? args, CustomResourceOptions? options = null) 107 | : base(type, name, args, options) 108 | { 109 | } 110 | } 111 | } 112 | ` 113 | } 114 | 115 | // For some reason, we get a `Missing embedded version.txt file` error if we 116 | // tried running `pulumi up` with the normal `Utilities.cs` file. 117 | // As a temporary fix, this modified `Utilities.cs` file just removes the 118 | // `static Utilities()` method. 119 | func dotNetUtilities(name string) string { 120 | return `// *** WARNING: this file was generated by crd2pulumi. *** 121 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 122 | 123 | using System; 124 | using System.Reflection; 125 | using Pulumi.Kubernetes; 126 | 127 | namespace Pulumi.` + name + ` 128 | { 129 | static class Utilities 130 | { 131 | public static string Version { get; } = GetVersion(); 132 | 133 | private static string GetVersion() 134 | { 135 | const string UtilitiesType = "Pulumi.Kubernetes.Utilities"; 136 | const string VersionProperty = "Version"; 137 | 138 | Type? type = Assembly.GetAssembly(typeof(Provider))?.GetType(UtilitiesType); 139 | if (type is null) 140 | { 141 | throw new InvalidOperationException($"{UtilitiesType} type could not be obtained."); 142 | } 143 | 144 | PropertyInfo? prop = type.GetProperty(VersionProperty, BindingFlags.Static | BindingFlags.Public); 145 | if (prop is null) 146 | { 147 | throw new InvalidOperationException($"{UtilitiesType}.{VersionProperty} property could not be obtained."); 148 | } 149 | 150 | var result = prop.GetValue(type, null) as string; 151 | if (result is null) 152 | { 153 | throw new InvalidOperationException($"Expected {UtilitiesType}.{VersionProperty} to return a non-null string."); 154 | } 155 | return result; 156 | } 157 | } 158 | 159 | internal sealed class ` + name + `ResourceTypeAttribute : Pulumi.ResourceTypeAttribute 160 | { 161 | public ` + name + `ResourceTypeAttribute(string type) : base(type, Utilities.Version) 162 | { 163 | } 164 | } 165 | } 166 | ` 167 | } 168 | -------------------------------------------------------------------------------- /pkg/codegen/golang.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codegen 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | 21 | "github.com/pulumi/pulumi/pkg/v3/codegen" 22 | goGen "github.com/pulumi/pulumi/pkg/v3/codegen/go" 23 | ) 24 | 25 | var UnneededGoFiles = codegen.NewStringSet( 26 | // The root directory doesn't define any resources: 27 | "doc.go", 28 | "init.go", 29 | "provider.go", 30 | 31 | // We use the standard Kubernetes meta/v1 types, so skip generating them: 32 | "meta/v1/pulumiTypes.go", 33 | 34 | // No need to generate these, they are imported from pulumi-kubernetes directly: 35 | "utilities/pulumiUtilities.go", 36 | "utilities/pulumiVersion.go", 37 | ) 38 | 39 | func GenerateGo(pg *PackageGenerator, name string) (buffers map[string]*bytes.Buffer, err error) { 40 | defer func() { 41 | if r := recover(); r != nil { 42 | err = fmt.Errorf("%v", r) 43 | } 44 | }() 45 | 46 | pkg := pg.SchemaPackageWithObjectMetaType() 47 | langName := "go" 48 | oldName := pkg.Name 49 | pkg.Name = name 50 | moduleToPackage, err := pg.ModuleToPackage() 51 | if err != nil { 52 | return nil, fmt.Errorf("%w", err) 53 | } 54 | moduleToPackage["meta/v1"] = "meta/v1" 55 | 56 | files, err := goGen.GeneratePackage("crd2pulumi", pkg, nil) 57 | if err != nil { 58 | return nil, fmt.Errorf("could not generate Go package: %w", err) 59 | } 60 | 61 | pkg.Name = oldName 62 | delete(pkg.Language, langName) 63 | 64 | buffers = map[string]*bytes.Buffer{} 65 | for path, code := range files { 66 | if !UnneededGoFiles.Has(path) { 67 | buffers[path] = bytes.NewBuffer(code) 68 | } 69 | } 70 | 71 | return buffers, err 72 | } 73 | -------------------------------------------------------------------------------- /pkg/codegen/java.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codegen 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "regexp" 21 | 22 | "github.com/pulumi/crd2pulumi/internal/versions" 23 | javaGen "github.com/pulumi/pulumi-java/pkg/codegen/java" 24 | ) 25 | 26 | func GenerateJava(pg *PackageGenerator, name string) (map[string]*bytes.Buffer, error) { 27 | pkg := pg.SchemaPackageWithObjectMetaType() 28 | 29 | // These fields are required for the Java code generation 30 | pkg.Description = "Generated Java SDK via crd2pulumi" 31 | pkg.Repository = "Placeholder" 32 | 33 | // Set up packages 34 | packages := map[string]string{} 35 | for _, groupVersion := range pg.GroupVersions { 36 | group, version, err := versions.SplitGroupVersion(groupVersion) 37 | if err != nil { 38 | return nil, fmt.Errorf("invalid version: %w", err) 39 | } 40 | groupPrefix, err := versions.GroupPrefix(group) 41 | if err != nil { 42 | return nil, fmt.Errorf("invalid version: %w", err) 43 | } 44 | packages[groupVersion] = groupPrefix + "." + version 45 | } 46 | packages["meta/v1"] = "meta.v1" 47 | 48 | langName := "java" 49 | oldName := pkg.Name 50 | pkg.Name = name 51 | 52 | files, err := javaGen.GeneratePackage("crd2pulumi", pkg, nil, nil, true, false) 53 | if err != nil { 54 | return nil, fmt.Errorf("could not generate Java package: %w", err) 55 | } 56 | 57 | pkg.Name = oldName 58 | delete(pkg.Language, langName) 59 | 60 | // Pin the kubernetes provider version used 61 | utilsPath := "src/main/java/com/pulumi/" + name + "/Utilities.java" 62 | utils, ok := files[utilsPath] 63 | if !ok { 64 | return nil, fmt.Errorf("cannot find generated utilities.ts") 65 | } 66 | re := regexp.MustCompile(`static \{(?:[^{}]|{[^{}]*})*}`) 67 | files[utilsPath] = []byte(re.ReplaceAllString(string(utils), `static { 68 | version = "4.9.0"; 69 | }`)) 70 | 71 | var unneededJavaFiles = []string{ 72 | "src/main/java/com/pulumi/" + name + "/Provider.java", 73 | "src/main/java/com/pulumi/" + name + "/ProviderArgs.java", 74 | "src/main/java/com/pulumi/kubernetes/meta/v1/inputs/ObjectMetaArgs.java", 75 | "src/main/java/com/pulumi/kubernetes/meta/v1/outputs/ObjectMeta.java", 76 | } 77 | 78 | // Remove unneeded files 79 | for _, unneededFile := range unneededJavaFiles { 80 | delete(files, unneededFile) 81 | } 82 | 83 | buffers := map[string]*bytes.Buffer{} 84 | for name, code := range files { 85 | buffers[name] = bytes.NewBuffer(code) 86 | } 87 | 88 | return buffers, err 89 | } 90 | -------------------------------------------------------------------------------- /pkg/codegen/language.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codegen 16 | 17 | import ( 18 | "path/filepath" 19 | ) 20 | 21 | var SupportedLanguages = []string{ 22 | DotNet, 23 | Go, 24 | NodeJS, 25 | Python, 26 | } 27 | 28 | const DotNet string = "dotnet" 29 | const Go string = "go" 30 | const NodeJS string = "nodejs" 31 | const Python string = "python" 32 | const Java string = "java" 33 | 34 | type CodegenSettings struct { 35 | Language string 36 | OutputDir string 37 | PackageName string 38 | PackageVersion string 39 | Overwrite bool 40 | ShouldGenerate bool 41 | } 42 | 43 | func (cs *CodegenSettings) Path() string { 44 | if cs.OutputDir == "" { 45 | cs.OutputDir = filepath.Join(cs.PackageName, cs.Language) 46 | } 47 | return cs.OutputDir 48 | } 49 | -------------------------------------------------------------------------------- /pkg/codegen/nodejs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codegen 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | 21 | "github.com/pulumi/pulumi/pkg/v3/codegen/nodejs" 22 | ) 23 | 24 | const nodejsName = "nodejs" 25 | const nodejsMetaPath = "meta/v1.ts" 26 | const nodejsMetaFile = `import * as k8s from "@pulumi/kubernetes"; 27 | 28 | export type ObjectMeta = k8s.types.input.meta.v1.ObjectMeta; 29 | export type ObjectMetaPatch = k8s.types.input.meta.v1.ObjectMetaPatch; 30 | ` 31 | 32 | func GenerateNodeJS(pg *PackageGenerator, name string) (map[string]*bytes.Buffer, error) { 33 | pkg := pg.SchemaPackageWithObjectMetaType() 34 | oldName := pkg.Name 35 | pkg.Name = name 36 | 37 | files, err := nodejs.GeneratePackage(PulumiToolName, pkg, nil, nil, true, nil) 38 | if err != nil { 39 | return nil, fmt.Errorf("could not generate nodejs package: %w", err) 40 | } 41 | 42 | pkg.Name = oldName 43 | delete(pkg.Language, nodejsName) 44 | 45 | // Pin the kubernetes provider version used 46 | utilities, ok := files["utilities.ts"] 47 | if !ok { 48 | return nil, fmt.Errorf("cannot find generated utilities.ts") 49 | } 50 | files["utilities.ts"] = bytes.ReplaceAll(utilities, 51 | []byte("export function getVersion(): string {"), 52 | []byte(fmt.Sprintf(`export const getVersion: () => string = () => "%s" 53 | 54 | function unusedGetVersion(): string {`, KubernetesProviderVersion))) 55 | 56 | // Create a helper `meta/v1.ts` script that exports the ObjectMeta class from the SDK. If there happens to already 57 | // be a `meta/v1.ts` file, then just append the script. 58 | if code, ok := files[nodejsMetaPath]; !ok { 59 | files[nodejsMetaPath] = []byte(nodejsMetaFile) 60 | } else { 61 | files[nodejsMetaPath] = append(code, []byte("\n"+nodejsMetaFile)...) 62 | } 63 | 64 | buffers := map[string]*bytes.Buffer{} 65 | for name, code := range files { 66 | buffers[name] = bytes.NewBuffer(code) 67 | } 68 | 69 | return buffers, nil 70 | } 71 | -------------------------------------------------------------------------------- /pkg/codegen/packagegenerator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codegen 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | "github.com/pulumi/crd2pulumi/internal/unstruct" 22 | "github.com/pulumi/crd2pulumi/internal/versions" 23 | pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 24 | "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 25 | ) 26 | 27 | const ( 28 | KubernetesProviderVersion string = "4.23.0" 29 | ) 30 | 31 | // PackageGenerator generates code for multiple CustomResources 32 | type PackageGenerator struct { 33 | // CustomResourceGenerators contains a slice of all CustomResourceGenerators 34 | CustomResourceGenerators []CustomResourceGenerator 35 | // ResourceTokens is a slice of the token types of every CustomResource 36 | ResourceTokens []string 37 | // GroupVersions is a slice of the names of every CustomResource's versions, 38 | // in the format / 39 | GroupVersions []string 40 | // Types is a mapping from every type's token name to its ComplexTypeSpec 41 | Types map[string]pschema.ComplexTypeSpec 42 | // Version is the semver that will be stamped into the generated package 43 | Version string 44 | // schemaPackage is the Pulumi schema package used to generate code for 45 | // languages that do not need an ObjectMeta type (NodeJS) 46 | schemaPackage *pschema.Package 47 | // schemaPackageWithObjectMetaType is the Pulumi schema package used to 48 | // generate code for languages that need an ObjectMeta type (Python, Go, and .NET) 49 | schemaPackageWithObjectMetaType *pschema.Package 50 | } 51 | 52 | // ReadPackagesFromSource reads one or more documents and returns a PackageGenerator that can be used to generate Pulumi code. 53 | // Calling this function will fully read and close each document. 54 | func ReadPackagesFromSource(version string, yamlSources []io.ReadCloser) (*PackageGenerator, error) { 55 | yamlData := make([][]byte, len(yamlSources)) 56 | 57 | for i, yamlSource := range yamlSources { 58 | defer yamlSource.Close() 59 | var err error 60 | yamlData[i], err = io.ReadAll(yamlSource) 61 | if err != nil { 62 | return nil, fmt.Errorf("failed to read YAML: %w", err) 63 | } 64 | } 65 | 66 | crds, err := unstruct.UnmarshalYamls(yamlData) 67 | if err != nil { 68 | return nil, fmt.Errorf("could not unmarshal yaml file(s): %w", err) 69 | } 70 | 71 | if len(crds) == 0 { 72 | return nil, fmt.Errorf("could not find any CRD YAML files") 73 | } 74 | 75 | resourceTokensSize := 0 76 | groupVersionsSize := 0 77 | 78 | crgs := make([]CustomResourceGenerator, 0, len(crds)) 79 | for i, crd := range crds { 80 | crg, err := NewCustomResourceGenerator(crd) 81 | if err != nil { 82 | return nil, fmt.Errorf("could not parse crd %d: %w", i, err) 83 | } 84 | resourceTokensSize += len(crg.ResourceTokens) 85 | groupVersionsSize += len(crg.GroupVersions) 86 | crgs = append(crgs, crg) 87 | } 88 | 89 | baseRefs := make([]string, 0, resourceTokensSize) 90 | groupVersions := make([]string, 0, groupVersionsSize) 91 | for _, crg := range crgs { 92 | baseRefs = append(baseRefs, crg.ResourceTokens...) 93 | groupVersions = append(groupVersions, crg.GroupVersions...) 94 | } 95 | 96 | pg := &PackageGenerator{ 97 | CustomResourceGenerators: crgs, 98 | ResourceTokens: baseRefs, 99 | GroupVersions: groupVersions, 100 | Version: version, 101 | } 102 | return pg, nil 103 | } 104 | 105 | // SchemaPackage returns the Pulumi schema package with no ObjectMeta type. 106 | // This is only necessary for NodeJS and Python. 107 | func (pg *PackageGenerator) SchemaPackage() *pschema.Package { 108 | if pg.schemaPackage == nil { 109 | pkg, err := genPackage(pg.Version, pg.CustomResourceGenerators, false) 110 | contract.AssertNoErrorf(err, "could not parse Pulumi package") 111 | pg.schemaPackage = pkg 112 | } 113 | return pg.schemaPackage 114 | } 115 | 116 | // SchemaPackageWithObjectMetaType returns the Pulumi schema package with 117 | // an ObjectMeta type. This is only necessary for Go and .NET. 118 | func (pg *PackageGenerator) SchemaPackageWithObjectMetaType() *pschema.Package { 119 | if pg.schemaPackageWithObjectMetaType == nil { 120 | pkg, err := genPackage(pg.Version, pg.CustomResourceGenerators, true) 121 | contract.AssertNoErrorf(err, "could not parse Pulumi package") 122 | pg.schemaPackageWithObjectMetaType = pkg 123 | } 124 | return pg.schemaPackageWithObjectMetaType 125 | } 126 | 127 | // Returns language-specific 'ModuleToPackage' map. Creates a mapping from 128 | // every groupVersion string / to /. 129 | func (pg *PackageGenerator) ModuleToPackage() (map[string]string, error) { 130 | moduleToPackage := map[string]string{} 131 | for _, groupVersion := range pg.GroupVersions { 132 | group, version, err := versions.SplitGroupVersion(groupVersion) 133 | if err != nil { 134 | return nil, fmt.Errorf("invalid version: %w", err) 135 | } 136 | prefix, err := versions.GroupPrefix(group) 137 | if err != nil { 138 | return nil, fmt.Errorf("invalid version: %w", err) 139 | } 140 | moduleToPackage[groupVersion] = prefix + "/" + version 141 | } 142 | return moduleToPackage, nil 143 | } 144 | 145 | // HasSchemas returns true if there exists at least one CustomResource with a schema in this package. 146 | func (pg *PackageGenerator) HasSchemas() bool { 147 | for _, crg := range pg.CustomResourceGenerators { 148 | if crg.HasSchemas() { 149 | return true 150 | } 151 | } 152 | return false 153 | } 154 | -------------------------------------------------------------------------------- /pkg/codegen/python.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codegen 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "path/filepath" 21 | 22 | "github.com/pulumi/pulumi/pkg/v3/codegen/python" 23 | "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 24 | ) 25 | 26 | const pythonMetaFile = `from pulumi_kubernetes.meta.v1._inputs import * 27 | import pulumi_kubernetes.meta.v1.outputs 28 | ` 29 | 30 | func GeneratePython(pg *PackageGenerator, name string) (map[string]*bytes.Buffer, error) { 31 | pkg := pg.SchemaPackageWithObjectMetaType() 32 | 33 | langName := "python" 34 | oldName := pkg.Name 35 | pkg.Name = name 36 | 37 | files, err := python.GeneratePackage(PulumiToolName, pkg, nil, nil) 38 | if err != nil { 39 | return nil, fmt.Errorf("could not generate Go package: %w", err) 40 | } 41 | 42 | pkg.Name = oldName 43 | delete(pkg.Language, langName) 44 | 45 | pythonPackageDir := "pulumi_" + name 46 | 47 | // Remove unneeded files 48 | var unneededPythonFiles = []string{ 49 | filepath.Join(pythonPackageDir, "README.md"), 50 | } 51 | for _, unneededFile := range unneededPythonFiles { 52 | delete(files, unneededFile) 53 | } 54 | 55 | // Import the actual SDK ObjectMeta types in place of our placeholder ones 56 | if pg.HasSchemas() { 57 | metaPath := filepath.Join(pythonPackageDir, "meta/v1", "__init__.py") 58 | code, ok := files[metaPath] 59 | contract.Assertf(ok, "missing meta/v1/__init__.py file") 60 | files[metaPath] = append(code, []byte(pythonMetaFile)...) 61 | } 62 | 63 | buffers := map[string]*bytes.Buffer{} 64 | for name, code := range files { 65 | if name == "pyproject.toml" { 66 | code = bytes.ReplaceAll(code, []byte(`0.0.0+dev`), []byte(KubernetesProviderVersion)) 67 | } 68 | buffers[name] = bytes.NewBuffer(code) 69 | } 70 | return buffers, nil 71 | } 72 | -------------------------------------------------------------------------------- /pkg/codegen/schema.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2020, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codegen 16 | 17 | import ( 18 | "encoding/json" 19 | "errors" 20 | "fmt" 21 | "strconv" 22 | "strings" 23 | 24 | "github.com/iancoleman/strcase" 25 | "github.com/pulumi/crd2pulumi/internal/slices" 26 | "github.com/pulumi/crd2pulumi/internal/unstruct" 27 | "github.com/pulumi/pulumi-kubernetes/provider/v4/pkg/gen" 28 | pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 29 | "k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder" 30 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 31 | "k8s.io/kube-openapi/pkg/validation/spec" 32 | ) 33 | 34 | // DefaultName specifies the default value for the package name 35 | const DefaultName = "crds" 36 | 37 | // pulumiKubernetesNameShim is a hack name since upstream schemagen creates types 38 | // in the `kubernetes` namespace. When binding, we need the package name to be the 39 | // same namespace as the types. 40 | const pulumiKubernetesNameShim = "kubernetes" 41 | 42 | const ( 43 | Boolean string = "boolean" 44 | Integer string = "integer" 45 | Number string = "number" 46 | String string = "string" 47 | Array string = "array" 48 | Object string = "object" 49 | ) 50 | 51 | const anyTypeRef = "pulumi.json#/Any" 52 | 53 | var anyTypeSpec = pschema.TypeSpec{ 54 | Ref: anyTypeRef, 55 | } 56 | 57 | var arbitraryJSONTypeSpec = pschema.TypeSpec{ 58 | Type: Object, 59 | AdditionalProperties: &anyTypeSpec, 60 | } 61 | 62 | var emptySpec = pschema.ComplexTypeSpec{ 63 | ObjectTypeSpec: pschema.ObjectTypeSpec{ 64 | Type: Object, 65 | Properties: map[string]pschema.PropertySpec{}, 66 | }, 67 | } 68 | 69 | const ( 70 | objectMetaRef = "#/types/kubernetes:meta/v1:ObjectMeta" 71 | objectMetaToken = "kubernetes:meta/v1:ObjectMeta" 72 | objectMetaPatchToken = "kubernetes:meta/v1:ObjectMetaPatch" 73 | ) 74 | 75 | // Union type of integer and string 76 | var intOrStringTypeSpec = pschema.TypeSpec{ 77 | OneOf: []pschema.TypeSpec{ 78 | { 79 | Type: Integer, 80 | }, 81 | { 82 | Type: String, 83 | }, 84 | }, 85 | } 86 | 87 | // mergeSpecs merges a slice of OpenAPI specs into a single OpenAPI spec. 88 | func mergeSpecs(specs []*spec.Swagger) (*spec.Swagger, error) { 89 | if len(specs) == 0 { 90 | return nil, errors.New("no OpenAPI specs to merge") 91 | } 92 | 93 | mergedSpecs, err := builder.MergeSpecs(specs[0], specs[1:]...) 94 | if err != nil { 95 | return nil, fmt.Errorf("error merging OpenAPI specs: %w", err) 96 | } 97 | 98 | return mergedSpecs, nil 99 | } 100 | 101 | // Returns the Pulumi package given a types map and a slice of the token types 102 | // of every CustomResource. If includeObjectMetaType is true, then a 103 | // ObjectMetaType type is also generated. 104 | func genPackage(version string, crgenerators []CustomResourceGenerator, includeObjectMetaType bool) (*pschema.Package, error) { 105 | var allCRDSpecs []*spec.Swagger 106 | // Merge all OpenAPI specs into a single OpenAPI spec. 107 | for _, crg := range crgenerators { 108 | for _, spec := range crg.Schemas { 109 | allCRDSpecs = append(allCRDSpecs, &spec) 110 | } 111 | } 112 | 113 | mergedSpec, err := mergeSpecs(allCRDSpecs) 114 | if err != nil { 115 | return &pschema.Package{}, fmt.Errorf("could not merge OpenAPI specs: %w", err) 116 | } 117 | 118 | marshaledOpenAPISchema, err := json.Marshal(mergedSpec) 119 | if err != nil { 120 | return nil, fmt.Errorf("error marshalling OpenAPI spec: %v", err) 121 | } 122 | 123 | unstructuredOpenAPISchema := make(map[string]any) 124 | err = json.Unmarshal(marshaledOpenAPISchema, &unstructuredOpenAPISchema) 125 | if err != nil { 126 | return nil, fmt.Errorf("error unmarshalling OpenAPI spec: %v", err) 127 | } 128 | // We need to allow hyphens in the property names since Kubernetes CRDs could contain fields that have them and 129 | // crd2pulumi should not panic for backwards compatibility. 130 | // This will only currently work for Go and Python as they have the correct annotations to serialize/deserialize 131 | // the hyphenated fields to their non-hyphenated equivalents. 132 | // See: https://github.com/pulumi/crd2pulumi/issues/43 133 | pkgSpec := gen.PulumiSchema(unstructuredOpenAPISchema, gen.WithAllowHyphens(true), gen.WithPulumiKubernetesDependency(KubernetesProviderVersion)) 134 | 135 | // Populate the package spec with information used in previous versions of crd2pulumi to maintain consistency 136 | // with older versions. 137 | pkgSpec.Name = pulumiKubernetesNameShim 138 | pkgSpec.Version = version 139 | pkgSpec.Config = pschema.ConfigSpec{} 140 | pkgSpec.Provider = pschema.ResourceSpec{} 141 | 142 | if !includeObjectMetaType { 143 | delete(pkgSpec.Types, objectMetaToken) 144 | delete(pkgSpec.Types, objectMetaPatchToken) 145 | } 146 | 147 | // Remove excess resources generated from the OpenAPI spec. 148 | for resourceName := range pkgSpec.Resources { 149 | if strings.HasPrefix(resourceName, "kubernetes:meta/v1:") { 150 | delete(pkgSpec.Resources, resourceName) 151 | } 152 | } 153 | 154 | pkg, err := pschema.ImportSpec(pkgSpec, nil, pschema.ValidationOptions{}) 155 | if err != nil { 156 | return &pschema.Package{}, fmt.Errorf("could not import spec: %w", err) 157 | } 158 | 159 | pkg.Name = DefaultName 160 | 161 | return pkg, nil 162 | } 163 | 164 | // Returns true if the given TypeSpec is of type any; returns false otherwise 165 | func isAnyType(typeSpec pschema.TypeSpec) bool { 166 | return typeSpec.Ref == anyTypeRef 167 | } 168 | 169 | // AddType converts the given OpenAPI `schema` to a ObjectTypeSpec and adds it 170 | // to the `types` map under the given `name`. Recursively converts and adds all 171 | // nested schemas as well. 172 | func AddType(schema map[string]any, name string, types map[string]pschema.ComplexTypeSpec) { 173 | properties, foundProperties, _ := unstructured.NestedMap(schema, "properties") 174 | description, _, _ := unstructured.NestedString(schema, "description") 175 | schemaType, _, _ := unstructured.NestedString(schema, "type") 176 | required, _, _ := unstructured.NestedStringSlice(schema, "required") 177 | 178 | propertySpecs := map[string]pschema.PropertySpec{} 179 | for propertyName := range properties { 180 | // Ignore unnamed properties like "-". 181 | camelCase := strcase.ToCamel(propertyName) 182 | if camelCase == "" { 183 | continue 184 | } 185 | propertySchema, _, _ := unstructured.NestedMap(properties, propertyName) 186 | propertyDescription, _, _ := unstructured.NestedString(propertySchema, "description") 187 | typeSpec := GetTypeSpec(propertySchema, name+strcase.ToCamel(propertyName), types) 188 | // Pulumi's schema doesn't support defaults for objects, so ignore them. 189 | var defaultValue any 190 | if !(typeSpec.Type == "object" || typeSpec.Type == "array") { 191 | defaultValue, _, _ = unstructured.NestedFieldNoCopy(propertySchema, "default") 192 | } 193 | propertySpecs[propertyName] = pschema.PropertySpec{ 194 | TypeSpec: typeSpec, 195 | Description: propertyDescription, 196 | Default: defaultValue, 197 | } 198 | } 199 | 200 | // If the type wasn't specified but we found properties, then we can infer that the type is an object 201 | if foundProperties && schemaType == "" { 202 | schemaType = Object 203 | } 204 | 205 | types[name] = pschema.ComplexTypeSpec{ 206 | ObjectTypeSpec: pschema.ObjectTypeSpec{ 207 | Type: schemaType, 208 | Properties: propertySpecs, 209 | Required: required, 210 | Description: description, 211 | }, 212 | } 213 | } 214 | 215 | // GetTypeSpec returns the corresponding pschema.TypeSpec for a OpenAPI v3 216 | // schema. Handles nested pschema.TypeSpecs in case the schema type is an array, 217 | // object, or "combined schema" (oneOf, allOf, anyOf). Also recursively converts 218 | // and adds all schemas of type object to the types map. 219 | func GetTypeSpec(schema map[string]any, name string, types map[string]pschema.ComplexTypeSpec) pschema.TypeSpec { 220 | if schema == nil { 221 | return anyTypeSpec 222 | } 223 | 224 | intOrString, foundIntOrString, _ := unstructured.NestedBool(schema, "x-kubernetes-int-or-string") 225 | if foundIntOrString && intOrString { 226 | return intOrStringTypeSpec 227 | } 228 | 229 | // If the schema is of the `oneOf` type: return a TypeSpec with the `OneOf` 230 | // field filled with the TypeSpec of all sub-schemas. 231 | oneOf, foundOneOf, _ := unstruct.NestedMapSlice(schema, "oneOf") 232 | if foundOneOf { 233 | oneOfTypeSpecs := make([]pschema.TypeSpec, 0, len(oneOf)) 234 | for i, oneOfSchema := range oneOf { 235 | oneOfTypeSpec := GetTypeSpec(oneOfSchema, name+"OneOf"+strconv.Itoa(i), types) 236 | if isAnyType(oneOfTypeSpec) { 237 | return anyTypeSpec 238 | } 239 | oneOfTypeSpecs = append(oneOfTypeSpecs, oneOfTypeSpec) 240 | } 241 | return pschema.TypeSpec{ 242 | OneOf: oneOfTypeSpecs, 243 | } 244 | } 245 | 246 | // If the schema is of `allOf` type: combine `properties` and `required` 247 | // fields of sub-schemas into a single schema. Then return the `TypeSpec` 248 | // of that combined schema. 249 | allOf, foundAllOf, _ := unstruct.NestedMapSlice(schema, "allOf") 250 | if foundAllOf { 251 | combinedSchema := CombineSchemas(true, allOf...) 252 | return GetTypeSpec(combinedSchema, name, types) 253 | } 254 | 255 | // If the schema is of `anyOf` type: combine only `properties` of 256 | // sub-schemas into a single schema, with all `properties` set to optional. 257 | // Then return the `TypeSpec` of that combined schema. 258 | anyOf, foundAnyOf, _ := unstruct.NestedMapSlice(schema, "anyOf") 259 | if foundAnyOf { 260 | combinedSchema := CombineSchemas(false, anyOf...) 261 | return GetTypeSpec(combinedSchema, name, types) 262 | } 263 | 264 | preserveUnknownFields, foundPreserveUnknownFields, _ := unstructured.NestedBool(schema, "x-kubernetes-preserve-unknown-fields") 265 | if foundPreserveUnknownFields && preserveUnknownFields { 266 | return arbitraryJSONTypeSpec 267 | } 268 | 269 | // If the the schema wasn't some combination of other types (`oneOf`, 270 | // `allOf`, `anyOf`), then it must have a "type" field, otherwise we 271 | // cannot represent it. If we cannot represent it, we simply set it to be 272 | // any type. 273 | schemaType, foundSchemaType, _ := unstructured.NestedString(schema, "type") 274 | if !foundSchemaType { 275 | return anyTypeSpec 276 | } 277 | 278 | switch schemaType { 279 | case Array: 280 | items, _, _ := unstructured.NestedMap(schema, "items") 281 | arrayTypeSpec := GetTypeSpec(items, name, types) 282 | return pschema.TypeSpec{ 283 | Type: Array, 284 | Items: &arrayTypeSpec, 285 | } 286 | case Object: 287 | AddType(schema, name, types) 288 | // If `additionalProperties` has a sub-schema, then we generate a type for a map from string --> sub-schema type 289 | additionalProperties, foundAdditionalProperties, _ := unstructured.NestedMap(schema, "additionalProperties") 290 | if foundAdditionalProperties { 291 | additionalPropertiesTypeSpec := GetTypeSpec(additionalProperties, name, types) 292 | return pschema.TypeSpec{ 293 | Type: Object, 294 | AdditionalProperties: &additionalPropertiesTypeSpec, 295 | } 296 | } 297 | // `additionalProperties: true` is equivalent to `additionalProperties: {}`, meaning a map from string -> any 298 | additionalPropertiesIsTrue, additionalPropertiesIsTrueFound, _ := unstructured.NestedBool(schema, "additionalProperties") 299 | if additionalPropertiesIsTrueFound && additionalPropertiesIsTrue { 300 | return pschema.TypeSpec{ 301 | Type: Object, 302 | AdditionalProperties: &anyTypeSpec, 303 | } 304 | } 305 | // If no properties are found, then it can be arbitrary JSON 306 | _, foundProperties, _ := unstructured.NestedMap(schema, "properties") 307 | if !foundProperties { 308 | return arbitraryJSONTypeSpec 309 | } 310 | // If properties are found, then we must specify those in a seperate interface 311 | return pschema.TypeSpec{ 312 | Type: Object, 313 | Ref: "#/types/" + name, 314 | } 315 | case Integer: 316 | fallthrough 317 | case Boolean: 318 | fallthrough 319 | case String: 320 | fallthrough 321 | case Number: 322 | return pschema.TypeSpec{ 323 | Type: schemaType, 324 | } 325 | default: 326 | return anyTypeSpec 327 | } 328 | } 329 | 330 | // CombineSchemas combines the `properties` fields of the given sub-schemas into 331 | // a single schema. Returns nil if no schemas are given. Returns the schema if 332 | // only 1 schema is given. If combineRequired == true, then each sub-schema's 333 | // `required` fields are also combined. In this case the combined schema's 334 | // `required` field is of type []any, not []string. 335 | func CombineSchemas(combineRequired bool, schemas ...map[string]any) map[string]any { 336 | if len(schemas) == 0 { 337 | return nil 338 | } 339 | if len(schemas) == 1 { 340 | return schemas[0] 341 | } 342 | 343 | combinedProperties := map[string]any{} 344 | combinedRequired := make([]string, 0) 345 | 346 | for _, schema := range schemas { 347 | properties, _, _ := unstructured.NestedMap(schema, "properties") 348 | for propertyName := range properties { 349 | propertySchema, _, _ := unstructured.NestedMap(properties, propertyName) 350 | combinedProperties[propertyName] = propertySchema 351 | } 352 | if combineRequired { 353 | required, foundRequired, _ := unstructured.NestedStringSlice(schema, "required") 354 | if foundRequired { 355 | combinedRequired = append(combinedRequired, required...) 356 | } 357 | } 358 | } 359 | 360 | combinedSchema := map[string]any{ 361 | "type": Object, 362 | "properties": combinedProperties, 363 | } 364 | if combineRequired { 365 | combinedSchema["required"] = slices.ToAny(combinedRequired) 366 | } 367 | return combinedSchema 368 | } 369 | 370 | func getToken(group, version, kind string) string { 371 | return fmt.Sprintf("kubernetes:%s/%s:%s", group, version, kind) 372 | } 373 | -------------------------------------------------------------------------------- /tests/crds/GoogleCloudPlatform/gke-managed-certs/README.md: -------------------------------------------------------------------------------- 1 | Taken from 2 | https://github.com/GoogleCloudPlatform/gke-managed-certs/blob/master/deploy/managedcertificates-crd.yaml 3 | 4 | -------------------------------------------------------------------------------- /tests/crds/GoogleCloudPlatform/gke-managed-certs/managedcertificates-crd.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | apiVersion: apiextensions.k8s.io/v1 16 | kind: CustomResourceDefinition 17 | metadata: 18 | name: managedcertificates.networking.gke.io 19 | spec: 20 | group: networking.gke.io 21 | versions: 22 | - name: v1beta1 # (Deprecated) 23 | served: true 24 | storage: false 25 | schema: 26 | openAPIV3Schema: 27 | type: object 28 | properties: 29 | status: 30 | type: object 31 | properties: 32 | certificateStatus: 33 | # The status of the managed certificate. 34 | type: string 35 | domainStatus: 36 | # The status of certificate provisioning for domains 37 | # selected by the user. 38 | type: array 39 | items: 40 | type: object 41 | required: 42 | - domain 43 | - status 44 | properties: 45 | domain: 46 | type: string 47 | status: 48 | type: string 49 | certificateName: 50 | # The name of the provisioned managed certificate. 51 | type: string 52 | expireTime: 53 | # The expire time of the provisioned managed certificate. 54 | # The certificate will be renewed automatically unless 55 | # the DNS configuration changes and prevents that. 56 | type: string 57 | format: date-time 58 | spec: 59 | type: object 60 | properties: 61 | domains: 62 | # One non-wildcard domain name up to 63 characters long. 63 | type: array 64 | maxItems: 1 65 | items: 66 | type: string 67 | maxLength: 63 68 | pattern: '^(([a-z0-9]+|[a-z0-9][-a-z0-9]*[a-z0-9])\.)+[a-z][-a-z0-9]*[a-z0-9]$' 69 | - name: v1beta2 # (Deprecated) 70 | served: true 71 | storage: false 72 | schema: 73 | openAPIV3Schema: 74 | type: object 75 | properties: 76 | status: 77 | type: object 78 | properties: 79 | certificateStatus: 80 | # The status of the managed certificate. 81 | type: string 82 | domainStatus: 83 | # The status of certificate provisioning for domains 84 | # selected by the user. 85 | type: array 86 | items: 87 | type: object 88 | required: 89 | - domain 90 | - status 91 | properties: 92 | domain: 93 | type: string 94 | status: 95 | type: string 96 | certificateName: 97 | # The name of the provisioned managed certificate. 98 | type: string 99 | expireTime: 100 | # The expire time of the provisioned managed certificate. 101 | # The certificate will be renewed automatically unless 102 | # the DNS configuration changes and prevents that. 103 | type: string 104 | format: date-time 105 | spec: 106 | type: object 107 | properties: 108 | domains: 109 | # Up to 100 non-wildcard domain names, each up to 63 characters long. 110 | type: array 111 | maxItems: 100 112 | items: 113 | type: string 114 | maxLength: 63 115 | pattern: '^(([a-z0-9]+|[a-z0-9][-a-z0-9]*[a-z0-9])\.)+[a-z][-a-z0-9]*[a-z0-9]$' 116 | - name: v1 117 | served: true 118 | storage: true 119 | schema: 120 | openAPIV3Schema: 121 | type: object 122 | properties: 123 | status: 124 | type: object 125 | properties: 126 | certificateStatus: 127 | # The status of the managed certificate. 128 | type: string 129 | domainStatus: 130 | # The status of certificate provisioning for domains 131 | # selected by the user. 132 | type: array 133 | items: 134 | type: object 135 | required: 136 | - domain 137 | - status 138 | properties: 139 | domain: 140 | type: string 141 | status: 142 | type: string 143 | certificateName: 144 | # The name of the provisioned managed certificate. 145 | type: string 146 | expireTime: 147 | # The expire time of the provisioned managed certificate. 148 | # The certificate will be renewed automatically unless 149 | # the DNS configuration changes and prevents that. 150 | type: string 151 | format: date-time 152 | spec: 153 | type: object 154 | properties: 155 | domains: 156 | # Up to 100 non-wildcard domain names, each up to 63 characters long. 157 | type: array 158 | maxItems: 100 159 | items: 160 | type: string 161 | maxLength: 63 162 | pattern: '^(([a-z0-9]+|[a-z0-9][-a-z0-9]*[a-z0-9])\.)+[a-z][-a-z0-9]*[a-z0-9]$' 163 | additionalPrinterColumns: 164 | - name: Age 165 | type: date 166 | jsonPath: .metadata.creationTimestamp 167 | - name: Status 168 | type: string 169 | description: Status of the managed certificate 170 | jsonPath: .status.certificateStatus 171 | scope: Namespaced 172 | names: 173 | plural: managedcertificates 174 | singular: managedcertificate 175 | kind: ManagedCertificate 176 | shortNames: 177 | - mcrt -------------------------------------------------------------------------------- /tests/crds/k8sversion/mock_crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | spec: 4 | group: k8sversion.pulumi.com 5 | names: 6 | plural: testresources 7 | singular: testresource 8 | kind: TestResource 9 | scope: Namespaced 10 | versions: 11 | - test: 12 | served: true 13 | storage: true 14 | name: test 15 | schema: 16 | openAPIV3Schema: 17 | properties: 18 | testProperty: 19 | type: string 20 | -------------------------------------------------------------------------------- /tests/crds/regression/hyphenated-symbols/README.md: -------------------------------------------------------------------------------- 1 | Regression test to verify the fix for https://github.com/pulumi/crd2pulumi/issues/43 2 | -------------------------------------------------------------------------------- /tests/crds/regression/hyphenated-symbols/hyphen-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | spec: 5 | group: hyphentest.pulumi.com 6 | names: 7 | kind: HyphenTest 8 | listKind: HyphenTestList 9 | plural: hyphenTests 10 | singular: hyphentest 11 | scope: Namespaced 12 | versions: 13 | - name: HyphenTest 14 | served: true 15 | storage: true 16 | schema: 17 | openAPIV3Schema: 18 | type: object 19 | properties: 20 | spec: 21 | type: object 22 | properties: 23 | has-a-hyphen: 24 | type: string 25 | -------------------------------------------------------------------------------- /tests/crds/regression/hyphenated-symbols2/README.md: -------------------------------------------------------------------------------- 1 | Regression test to verify the fix for https://github.com/pulumi/crd2pulumi/issues/43 2 | It is slightly different from the other `hyphenated-symbols` tests to more closely mimmic the user's schema. -------------------------------------------------------------------------------- /tests/crds/regression/hyphenated-symbols2/hyphen-test2.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | spec: 4 | group: hyphentest2.pulumi.com 5 | names: 6 | kind: HyphenTest2 7 | listKind: hyphentest2list 8 | plural: hyphentest2s 9 | singular: hyphentest2 10 | scope: Namespaced 11 | versions: 12 | - name: v1 13 | storage: true 14 | served: true 15 | schema: 16 | openAPIV3Schema: 17 | properties: 18 | has-hyphen: 19 | type: string 20 | -------------------------------------------------------------------------------- /tests/crds/underscored-types/networkpolicy.yaml: -------------------------------------------------------------------------------- 1 | # Ensure generated nested types are not underscored. 2 | # See https://github.com/pulumi/crd2pulumi/issues/107 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | myinfo: abcdefghijkl 8 | generation: 4 9 | labels: 10 | creator.ac.com: myinfo 11 | name: networkpolicies.juice.box.com 12 | spec: 13 | conversion: 14 | strategy: None 15 | group: juice.box.com 16 | names: 17 | kind: NetworkPolicy 18 | listKind: NetworkPolicyList 19 | plural: networkpolicies 20 | shortNames: 21 | - anp 22 | - anps 23 | singular: networkpolicy 24 | scope: Namespaced 25 | versions: 26 | - name: v1alpha1 27 | schema: 28 | openAPIV3Schema: 29 | type: object 30 | properties: 31 | spec: 32 | type: object 33 | description: NetworkPolicySpec is a specification of the network 34 | entitlements for a pod. 35 | properties: 36 | apps_incoming: 37 | description: apps_incoming specifies which applications are permitted 38 | to establish a TCP connection to a POD. 39 | items: 40 | properties: 41 | app: 42 | pattern: ^(__kubernetes__|plb\.juice-plb\.juice-prod|((([A-Za-z0-9]+[-A-Za-z0-9]?)*[A-Za-z0-9])\.){2}(kk|kube))$ 43 | type: string 44 | cluster: 45 | description: cluster that this policy applies to. Defaults to 46 | the local cluster. Setting cluster to 'ALL' will match all 47 | clusters 48 | type: string 49 | required: 50 | - app 51 | type: object 52 | type: array 53 | apps_outgoing: 54 | description: apps_outgoing specifies what applications a pod may attempt 55 | to make TCP connections to. 56 | items: 57 | properties: 58 | app: 59 | pattern: ^(__kubernetes__|plb\.juice-plb\.juice-prod|((([A-Za-z0-9]+[-A-Za-z0-9]?)*[A-Za-z0-9])\.){2}(kk|kube))$ 60 | type: string 61 | cluster: 62 | description: cluster that this policy applies to. Defaults to 63 | the local cluster. Setting cluster to 'ALL' will match all 64 | clusters 65 | type: string 66 | required: 67 | - app 68 | type: object 69 | type: array 70 | namespaces_incoming: 71 | description: namespaces_incoming specifies which kubernetes namespace 72 | are permitted to establish incoming TCP sessions. 73 | items: 74 | properties: 75 | cluster: 76 | description: cluster that this policy applies to. Defaults to 77 | the local cluster. Setting cluster to 'ALL' will match all 78 | clusters 79 | type: string 80 | namespace: 81 | pattern: ^(((([A-Za-z0-9]+[-A-Za-z0-9]?)*[A-Za-z0-9])\.)(kk|kube))$ 82 | type: string 83 | required: 84 | - namespace 85 | type: object 86 | type: array 87 | namespaces_outgoing: 88 | description: namespaces_outgoing specifies which kubernetes namespace 89 | are permitted to establish outgoing TCP sessions. 90 | items: 91 | properties: 92 | cluster: 93 | description: cluster that this policy applies to. Defaults to 94 | the local cluster. Setting cluster to 'ALL' will match all 95 | clusters 96 | type: string 97 | namespace: 98 | pattern: ^(((([A-Za-z0-9]+[-A-Za-z0-9]?)*[A-Za-z0-9])\.)(kk|kube))$ 99 | type: string 100 | required: 101 | - namespace 102 | type: object 103 | type: array 104 | selector: 105 | additionalProperties: 106 | type: string 107 | description: selector is a set of label selectors 108 | type: object 109 | required: 110 | - selector 111 | served: true 112 | storage: true 113 | status: 114 | acceptedNames: 115 | kind: NetworkPolicy 116 | listKind: NetworkPolicyList 117 | plural: networkpolicies 118 | shortNames: 119 | - anp 120 | - anps 121 | singular: networkpolicy 122 | storedVersions: 123 | - v1alpha1 -------------------------------------------------------------------------------- /tests/crds_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2020, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tests 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "io/fs" 21 | "os" 22 | "os/exec" 23 | "path/filepath" 24 | "testing" 25 | 26 | "github.com/pulumi/crd2pulumi/cmd" 27 | "github.com/pulumi/crd2pulumi/pkg/codegen" 28 | "github.com/stretchr/testify/assert" 29 | "github.com/stretchr/testify/require" 30 | ) 31 | 32 | var languages = []string{"dotnet", "go", "nodejs", "python", "java"} 33 | 34 | // execCrd2Pulumi runs the crd2pulumi binary in a temporary directory 35 | func execCrd2Pulumi(t *testing.T, lang, path string, additionalValidation func(t *testing.T, path string)) { 36 | tmpdir := t.TempDir() 37 | langFlag := fmt.Sprintf("--%sPath", lang) // e.g. --dotnetPath 38 | 39 | cmd := cmd.New() 40 | stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} 41 | cmd.SetArgs([]string{langFlag, tmpdir, "--force", path}) 42 | cmd.SetOut(stdout) 43 | cmd.SetErr(stderr) 44 | 45 | t.Logf("crd2pulumi %s=%s %s: running", langFlag, tmpdir, path) 46 | err := cmd.Execute() 47 | t.Logf("%s=%s %s: output=\n%s", langFlag, tmpdir, path, stdout.String()+stderr.String()) 48 | if err != nil { 49 | t.Fatalf("expected crd2pulumi for '%s=%s %s' to succeed", langFlag, tmpdir, path) 50 | } 51 | 52 | // Run additional validation if provided. 53 | if additionalValidation != nil { 54 | additionalValidation(t, tmpdir) 55 | } 56 | } 57 | 58 | // TestCRDsFromFile enumerates all CRD YAML files, and generates them in each language. 59 | func TestCRDsFromFile(t *testing.T) { 60 | filepath.WalkDir("crds", func(path string, d fs.DirEntry, err error) error { 61 | if !d.IsDir() && (filepath.Ext(path) == ".yml" || filepath.Ext(path) == ".yaml") { 62 | for _, lang := range languages { 63 | lang := lang 64 | name := fmt.Sprintf("%s-%s", lang, filepath.Base(path)) 65 | t.Run(name, func(t *testing.T) { 66 | t.Parallel() 67 | execCrd2Pulumi(t, lang, path, nil) 68 | }) 69 | } 70 | } 71 | return nil 72 | }) 73 | } 74 | 75 | // TestCRDsFromUrl pulls the CRD YAML file from a URL and generates it in each language 76 | func TestCRDsFromUrl(t *testing.T) { 77 | validateNodeCompiles := func(t *testing.T, path string) { 78 | withDir(t, path, func() { 79 | runRequireNoError(t, exec.Command("npm", "install")) 80 | runRequireNoError(t, exec.Command("npm", "run", "build")) 81 | }) 82 | } 83 | 84 | validateGolangCompiles := func(t *testing.T, path string) { 85 | withDir(t, path, func() { 86 | runRequireNoError(t, exec.Command("go", "mod", "init", "fakepackage")) 87 | runRequireNoError(t, exec.Command("go", "mod", "tidy")) 88 | runRequireNoError(t, exec.Command("go", "vet", "./...")) 89 | }) 90 | } 91 | 92 | validateDotnetCompiles := func(t *testing.T, path string) { 93 | withDir(t, path, func() { 94 | runRequireNoError(t, exec.Command("dotnet", "build")) 95 | }) 96 | } 97 | 98 | // TODO(#145): Also run compilation tests for java and python. 99 | compileValidationFn := map[string]func(t *testing.T, path string){ 100 | "nodejs": validateNodeCompiles, 101 | "go": validateGolangCompiles, 102 | "python": nil, 103 | "java": nil, 104 | "dotnet": validateDotnetCompiles, 105 | } 106 | 107 | tests := []struct { 108 | name string 109 | url string 110 | }{ 111 | { 112 | name: "GKEManagedCerts", 113 | url: "https://raw.githubusercontent.com/GoogleCloudPlatform/gke-managed-certs/c514101/deploy/managedcertificates-crd.yaml", 114 | }, 115 | { 116 | name: "VictoriaMetrics", 117 | url: "https://raw.githubusercontent.com/VictoriaMetrics/helm-charts/fdb7dfe/charts/victoria-metrics-operator/crd.yaml", 118 | }, 119 | { 120 | name: "GatewayClasses", 121 | url: "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v0.3.0/config/crd/bases/networking.x-k8s.io_gatewayclasses.yaml", 122 | }, 123 | { 124 | name: "Contours", 125 | url: "https://raw.githubusercontent.com/projectcontour/contour-operator/f8c07498803d062e30c255976270cbc82cd619b0/config/crd/bases/operator.projectcontour.io_contours.yaml", 126 | }, 127 | { 128 | // https://github.com/pulumi/crd2pulumi/issues/141 129 | name: "ElementDeployment", 130 | url: "https://raw.githubusercontent.com/element-hq/ess-starter-edition-core/d7e792bf8a872f06f02f59d807a1c16ee933862b/roles/elementdeployment/files/elementdeployment-schema.yaml", 131 | }, 132 | { 133 | // https://github.com/pulumi/crd2pulumi/issues/142 134 | name: "Keycloak", 135 | url: "https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/25.0.4/kubernetes/keycloaks.k8s.keycloak.org-v1.yml", 136 | }, 137 | { 138 | // https://github.com/pulumi/crd2pulumi/issues/115 139 | name: "CertManager", 140 | url: "https://gist.githubusercontent.com/RouxAntoine/b7dfb9ce327a4ad40a76ff6552c7fd5e/raw/4b5922da11643e14d04e6b52f7a0fca982e4dace/1-crds.yaml", 141 | }, 142 | { 143 | // https://github.com/pulumi/crd2pulumi/issues/104 144 | name: "TracingPolicies", 145 | url: "https://raw.githubusercontent.com/cilium/tetragon/v1.2.0/install/kubernetes/tetragon/crds-yaml/cilium.io_tracingpolicies.yaml", 146 | }, 147 | { 148 | // https://github.com/pulumi/crd2pulumi/issues/104 149 | name: "Argo Rollouts", 150 | url: "https://raw.githubusercontent.com/argoproj/argo-rollouts/74c1a947ab36670ae01a45993a0c5abb44af4677/manifests/crds/rollout-crd.yaml", 151 | }, 152 | { 153 | // https://github.com/pulumi/crd2pulumi/issues/92 154 | name: "Grafana", 155 | url: "https://raw.githubusercontent.com/bitnami/charts/refs/tags/grafana-operator/4.9.0/bitnami/grafana-operator/crds/grafanas.integreatly.org.yaml", 156 | }, 157 | { 158 | // https://github.com/pulumi/crd2pulumi/issues/70 159 | name: "Percona", 160 | url: "https://raw.githubusercontent.com/percona/percona-server-mongodb-operator/main/deploy/crd.yaml", 161 | }, 162 | { 163 | // https://github.com/pulumi/crd2pulumi/issues/49 164 | name: "Traefik", 165 | url: "https://raw.githubusercontent.com/traefik/traefik/eb99c8c/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml", 166 | }, 167 | { 168 | // https://github.com/pulumi/crd2pulumi/issues/29 169 | name: "Istio", 170 | url: "https://raw.githubusercontent.com/istio/istio/c132663/manifests/charts/base/crds/crd-all.gen.yaml", 171 | }, 172 | { 173 | name: "Argo Application Set", 174 | url: "https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/crds/applicationset-crd.yaml", 175 | }, 176 | { 177 | // https://github.com/pulumi/crd2pulumi/issues/147 178 | name: "Prometheus Operator", 179 | url: "https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.76.2/bundle.yaml", 180 | }, 181 | } 182 | 183 | for _, tt := range tests { 184 | t.Run(tt.name, func(t *testing.T) { 185 | for _, lang := range languages { 186 | t.Run(lang, func(t *testing.T) { 187 | if lang == "dotnet" { 188 | if tt.name == "CertManager" || tt.name == "GKEManagedCerts" { 189 | t.Skip("Skipping compilation for dotnet. See https://github.com/pulumi/crd2pulumi/issues/17") 190 | } 191 | 192 | if tt.name == "Percona" { 193 | t.Skip("Skipping dotnet compilation for Percona as we generate invalid code with hyphens that are not allowed in C# identifiers.") 194 | } 195 | 196 | } 197 | 198 | execCrd2Pulumi(t, lang, tt.url, compileValidationFn[lang]) 199 | }) 200 | } 201 | }) 202 | } 203 | } 204 | 205 | // TestCRDsWithUnderscore tests that CRDs with underscores field names are camelCased for the 206 | // generated types. Currently this test only runs for Python, and we're hardcoding the field name 207 | // detection logic in the test for simplicity. This is brittle and we should improve this in the 208 | // future. 209 | // TODO: properly detect field names in the generated Python code instead of grep'ing for them. 210 | func TestCRDsWithUnderscore(t *testing.T) { 211 | // Callback function to run additional validation on the generated Python code after running 212 | // crd2pulumi. 213 | validateUnderscore := func(t *testing.T, path string) { 214 | // Ensure inputs are camelCased. 215 | filename := filepath.Join(path, "pulumi_crds", "juice", "v1alpha1", "_inputs.py") 216 | t.Logf("validating underscored field names in %s", filename) 217 | pythonInputs, err := os.ReadFile(filename) 218 | if err != nil { 219 | t.Fatalf("expected to read generated Python code: %s", err) 220 | } 221 | assert.Contains(t, string(pythonInputs), "NetworkPolicySpecAppsIncomingArgs", "expected to find camelCased field name in generated Python code") 222 | assert.NotContains(t, string(pythonInputs), "NetworkPolicySpecApps_incomingArgs", "expected to not find underscored field name in generated Python code") 223 | 224 | // Ensure outputs are camelCased. 225 | filename = filepath.Join(path, "pulumi_crds", "juice", "v1alpha1", "outputs.py") 226 | t.Logf("validating underscored field names in %s", filename) 227 | pythonInputs, err = os.ReadFile(filename) 228 | if err != nil { 229 | t.Fatalf("expected to read generated Python code: %s", err) 230 | } 231 | assert.Contains(t, string(pythonInputs), "NetworkPolicySpecAppsIncoming", "expected to find camelCased field name in generated Python code") 232 | assert.NotContains(t, string(pythonInputs), "NetworkPolicySpecApps_incoming", "expected to not find underscored field name in generated Python code") 233 | } 234 | 235 | execCrd2Pulumi(t, "python", "crds/underscored-types/networkpolicy.yaml", validateUnderscore) 236 | } 237 | 238 | func TestKubernetesVersionNodeJs(t *testing.T) { 239 | validateVersion := func(t *testing.T, path string) { 240 | // enter and build the generated package 241 | withDir(t, path, func() { 242 | runRequireNoError(t, exec.Command("npm", "install")) 243 | runRequireNoError(t, exec.Command("npm", "run", "build")) 244 | 245 | // extract the version returned by a resource 246 | appendFile(t, "bin/index.js", "\nconsole.log((new k8sversion.test.TestResource('test')).__version)") 247 | 248 | version, err := exec.Command("node", "bin/index.js").Output() 249 | require.NoError(t, err) 250 | assert.Equal(t, codegen.KubernetesProviderVersion+"\n", string(version)) 251 | }) 252 | } 253 | 254 | execCrd2Pulumi(t, "nodejs", "crds/k8sversion/mock_crd.yaml", validateVersion) 255 | } 256 | 257 | func TestNodeJsObjectMeta(t *testing.T) { 258 | validateVersion := func(t *testing.T, path string) { 259 | // enter and build the generated package 260 | withDir(t, path, func() { 261 | runRequireNoError(t, exec.Command("npm", "install")) 262 | runRequireNoError(t, exec.Command("npm", "run", "build")) 263 | 264 | filename := filepath.Join(path, "k8sversion", "test", "testResource.ts") 265 | t.Logf("validating objectmeta type in %s", filename) 266 | 267 | testResource, err := os.ReadFile(filename) 268 | if err != nil { 269 | t.Fatalf("expected to read generated NodeJS code: %s", err) 270 | } 271 | 272 | assert.Contains(t, string(testResource), "public readonly metadata!: pulumi.Output;", "expected metadata output type") 273 | assert.Contains(t, string(testResource), "metadata?: pulumi.Input;", "expected metadata input type") 274 | }) 275 | } 276 | 277 | execCrd2Pulumi(t, "nodejs", "crds/k8sversion/mock_crd.yaml", validateVersion) 278 | } 279 | 280 | func withDir(t *testing.T, dir string, f func()) { 281 | pwd, err := os.Getwd() 282 | require.NoError(t, err) 283 | defer os.Chdir(pwd) 284 | 285 | require.NoError(t, os.Chdir(dir)) 286 | 287 | f() 288 | } 289 | 290 | func appendFile(t *testing.T, filename, content string) { 291 | // extract the version returned by a resource 292 | f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0o600) 293 | require.NoError(t, err) 294 | defer f.Close() 295 | 296 | _, err = f.WriteString(content) 297 | require.NoError(t, err) 298 | } 299 | 300 | func runRequireNoError(t *testing.T, cmd *exec.Cmd) { 301 | t.Helper() 302 | bytes, err := cmd.CombinedOutput() 303 | if err != nil { 304 | t.Log(string(bytes)) 305 | } 306 | require.NoError(t, err) 307 | } 308 | -------------------------------------------------------------------------------- /tests/schema_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tests 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "fmt" 21 | "os" 22 | "testing" 23 | 24 | "github.com/pulumi/crd2pulumi/pkg/codegen" 25 | pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 26 | "github.com/stretchr/testify/assert" 27 | "k8s.io/apimachinery/pkg/util/yaml" 28 | ) 29 | 30 | const TestCombineSchemasYAML = "test-combineschemas.yaml" 31 | const TestGetTypeSpecYAML = "test-gettypespec.yaml" 32 | const TestGetTypeSpecJSON = "test-gettypespec.json" 33 | 34 | // UnmarshalYaml un-marshals one and only one YAML document from a file 35 | func UnmarshalYaml(yamlFile []byte) (map[string]any, error) { 36 | dec := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(yamlFile), 128) 37 | var value map[string]any 38 | if err := dec.Decode(&value); err != nil { 39 | return nil, fmt.Errorf("failed to unmarshal YAML: %w", err) 40 | } 41 | return value, nil 42 | } 43 | 44 | func UnmarshalSchemas(yamlPath string) (map[string]any, error) { 45 | yamlFile, err := os.ReadFile(yamlPath) 46 | if err != nil { 47 | return nil, err 48 | } 49 | return UnmarshalYaml(yamlFile) 50 | } 51 | 52 | func UnmarshalTypeSpecJSON(jsonPath string) (map[string]pschema.TypeSpec, error) { 53 | jsonFile, err := os.ReadFile(jsonPath) 54 | if err != nil { 55 | return nil, fmt.Errorf("could not read file %s: %w", jsonPath, err) 56 | } 57 | var v map[string]pschema.TypeSpec 58 | err = json.Unmarshal(jsonFile, &v) 59 | if err != nil { 60 | return nil, fmt.Errorf("could not unmarshal %s: %w", jsonPath, err) 61 | } 62 | return v, nil 63 | } 64 | 65 | func TestCombineSchemas(t *testing.T) { 66 | // Test that CombineSchemas on no schemas returns nil 67 | assert.Nil(t, codegen.CombineSchemas(false)) 68 | assert.Nil(t, codegen.CombineSchemas(true)) 69 | 70 | // Unmarshal some testing schemas 71 | schemas, err := UnmarshalSchemas(TestCombineSchemasYAML) 72 | assert.NoError(t, err) 73 | person := schemas["person"].(map[string]any) 74 | employee := schemas["employee"].(map[string]any) 75 | 76 | // Test that CombineSchemas with 1 schema returns the same schema 77 | assert.Equal(t, person, codegen.CombineSchemas(true, person)) 78 | assert.Equal(t, person, codegen.CombineSchemas(false, person)) 79 | 80 | // Test CombineSchemas with 2 schemas and combineSchemas = true 81 | personAndEmployeeWithRequiredExpected := schemas["personAndEmployeeWithRequired"].(map[string]any) 82 | personAndEmployeeWithRequiredActual := codegen.CombineSchemas(true, person, employee) 83 | assert.EqualValues(t, personAndEmployeeWithRequiredExpected, personAndEmployeeWithRequiredActual) 84 | 85 | // Test CombineSchemas with 2 schemas and combineSchemas = false 86 | personAndEmployeeWithoutRequiredExpected := schemas["personAndEmployeeWithoutRequired"].(map[string]any) 87 | personAndEmployeeWithoutRequiredActual := codegen.CombineSchemas(false, person, employee) 88 | assert.EqualValues(t, personAndEmployeeWithoutRequiredExpected, personAndEmployeeWithoutRequiredActual) 89 | } 90 | 91 | func TestGetTypeSpec(t *testing.T) { 92 | // codegen.GetTypeSpec wants us to pass in a types map 93 | // (map[string]pschema.ObjectTypeSpec{}) to add object refs when we see 94 | // them. However we only want the returned pschema.TypeSpec, so this 95 | // wrapper function creates a placeholder types map and just returns 96 | // the pschema.TypeSpec. Since our initial name arg is "", this causes all 97 | // objects to have the ref "#/types/" 98 | getOnlyTypeSpec := func(schema map[string]any) pschema.TypeSpec { 99 | placeholderTypes := map[string]pschema.ComplexTypeSpec{} 100 | return codegen.GetTypeSpec(schema, "", placeholderTypes) 101 | } 102 | 103 | // Load YAML schemas 104 | schemas, err := UnmarshalSchemas(TestGetTypeSpecYAML) 105 | assert.NoError(t, err) 106 | 107 | // Load expected TypeSpec outputs as JSON 108 | typeSpecs, err := UnmarshalTypeSpecJSON(TestGetTypeSpecJSON) 109 | assert.NoError(t, err) 110 | 111 | for name := range schemas { 112 | expected, ok := typeSpecs[name] 113 | assert.True(t, ok) 114 | 115 | schema := schemas[name].(map[string]any) 116 | actual := getOnlyTypeSpec(schema) 117 | 118 | assert.EqualValues(t, expected, actual) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/test-combineschemas.yaml: -------------------------------------------------------------------------------- 1 | person: 2 | type: object 3 | description: represents a person 4 | properties: 5 | name: 6 | type: string 7 | hometown: 8 | type: string 9 | age: 10 | type: integer 11 | required: 12 | - name 13 | - age 14 | employee: 15 | type: object 16 | properties: 17 | employeeID: 18 | type: integer 19 | company: 20 | type: object 21 | description: represents a company 22 | properties: 23 | name: 24 | type: string 25 | address: 26 | type: string 27 | required: 28 | - name 29 | required: 30 | - employeeID 31 | personAndEmployeeWithRequired: 32 | type: object 33 | properties: 34 | name: 35 | type: string 36 | hometown: 37 | type: string 38 | age: 39 | type: integer 40 | employeeID: 41 | type: integer 42 | company: 43 | type: object 44 | description: represents a company 45 | properties: 46 | name: 47 | type: string 48 | address: 49 | type: string 50 | required: 51 | - name 52 | required: 53 | - name 54 | - age 55 | - employeeID 56 | personAndEmployeeWithoutRequired: 57 | type: object 58 | properties: 59 | name: 60 | type: string 61 | hometown: 62 | type: string 63 | age: 64 | type: integer 65 | employeeID: 66 | type: integer 67 | company: 68 | type: object 69 | description: represents a company 70 | properties: 71 | name: 72 | type: string 73 | address: 74 | type: string 75 | required: 76 | - name 77 | 78 | -------------------------------------------------------------------------------- /tests/test-gettypespec.json: -------------------------------------------------------------------------------- 1 | { 2 | "-": { 3 | "type": "string" 4 | }, 5 | "anyType": { 6 | "$ref": "pulumi.json#/Any" 7 | }, 8 | "integer": { 9 | "type": "integer" 10 | }, 11 | "number": { 12 | "type": "number" 13 | }, 14 | "string": { 15 | "type": "string" 16 | }, 17 | "boolean": { 18 | "type": "boolean" 19 | }, 20 | "x-kubernetes-int-or-string": { 21 | "oneOf": [ 22 | { "type": "integer" }, 23 | { "type": "string" } 24 | ] 25 | }, 26 | "x-kubernetes-preserve-unknown-fields": { 27 | "type": "object", 28 | "additionalProperties": { 29 | "$ref": "pulumi.json#/Any" 30 | } 31 | }, 32 | "object": { 33 | "type": "object", 34 | "$ref": "#/types/" 35 | }, 36 | "object-additionalproperties-true": { 37 | "type": "object", 38 | "additionalProperties": { 39 | "$ref": "pulumi.json#/Any" 40 | } 41 | }, 42 | "object-integer": { 43 | "type": "object", 44 | "additionalProperties": { 45 | "type": "integer" 46 | } 47 | }, 48 | "object-object": { 49 | "type": "object", 50 | "additionalProperties": { 51 | "type": "object", 52 | "$ref": "#/types/" 53 | } 54 | }, 55 | "object-array": { 56 | "type": "object", 57 | "additionalProperties": { 58 | "type": "array", 59 | "items": { 60 | "type": "integer" 61 | } 62 | } 63 | }, 64 | "array-integer": { 65 | "type": "array", 66 | "items": { 67 | "type": "integer" 68 | } 69 | }, 70 | "array-number": { 71 | "type": "array", 72 | "items": { 73 | "type": "number" 74 | } 75 | }, 76 | "array-string": { 77 | "type": "array", 78 | "items": { 79 | "type": "string" 80 | } 81 | }, 82 | "array-boolean": { 83 | "type": "array", 84 | "items": { 85 | "type": "boolean" 86 | } 87 | }, 88 | "array-array-boolean": { 89 | "type": "array", 90 | "items": { 91 | "type": "array", 92 | "items": { 93 | "type": "boolean" 94 | } 95 | } 96 | }, 97 | "array-object-boolean": { 98 | "type": "array", 99 | "items": { 100 | "type": "object", 101 | "$ref": "#/types/" 102 | } 103 | }, 104 | "array-any": { 105 | "type": "array", 106 | "items": { 107 | "$ref": "pulumi.json#/Any" 108 | } 109 | }, 110 | "oneOf-basic": { 111 | "oneOf": [ 112 | { "type": "integer" }, 113 | { "type": "number" }, 114 | { "type": "string" }, 115 | { "type": "boolean" } 116 | ] 117 | }, 118 | "anyOf-single": { 119 | "type": "object", 120 | "$ref": "#/types/" 121 | }, 122 | "anyOf-double": { 123 | "type": "object", 124 | "$ref": "#/types/" 125 | }, 126 | "allOf": { 127 | "type": "object", 128 | "$ref": "#/types/" 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /tests/test-gettypespec.yaml: -------------------------------------------------------------------------------- 1 | anyType: {} 2 | integer: 3 | type: integer 4 | number: 5 | type: number 6 | string: 7 | type: string 8 | boolean: 9 | type: boolean 10 | x-kubernetes-int-or-string: 11 | x-kubernetes-int-or-string: true 12 | x-kubernetes-preserve-unknown-fields: 13 | x-kubernetes-preserve-unknown-fields: true 14 | object: 15 | type: object 16 | properties: 17 | prop1: 18 | type: integer 19 | object-additionalproperties-true: 20 | type: object 21 | additionalProperties: true 22 | object-integer: 23 | type: object 24 | additionalProperties: 25 | type: integer 26 | "-": 27 | type: string 28 | object-object: 29 | type: object 30 | additionalProperties: 31 | type: object 32 | properties: 33 | prop1: 34 | type: integer 35 | object-array: 36 | type: object 37 | additionalProperties: 38 | type: array 39 | items: 40 | type: integer 41 | array-integer: 42 | type: array 43 | items: 44 | type: integer 45 | array-number: 46 | type: array 47 | items: 48 | type: number 49 | array-string: 50 | type: array 51 | items: 52 | type: string 53 | array-boolean: 54 | type: array 55 | items: 56 | type: boolean 57 | array-array-boolean: 58 | type: array 59 | items: 60 | type: array 61 | items: 62 | type: boolean 63 | array-object-boolean: 64 | type: array 65 | items: 66 | type: object 67 | properties: 68 | boolean: 69 | type: boolean 70 | array-any: 71 | type: array 72 | items: {} 73 | oneOf-basic: 74 | oneOf: 75 | - type: integer 76 | - type: number 77 | - type: string 78 | - type: boolean 79 | anyOf-single: 80 | anyOf: 81 | - type: object 82 | description: object1 83 | properties: 84 | prop1: 85 | type: string 86 | prop2: 87 | type: int 88 | required: 89 | - prop1 90 | anyOf-double: 91 | anyOf: 92 | - type: object 93 | description: object1 94 | properties: 95 | prop1: 96 | type: string 97 | prop2: 98 | type: int 99 | required: 100 | - prop1 101 | - type: object 102 | description: object2 103 | properties: 104 | prop2: 105 | type: boolean 106 | prop3: 107 | type: number 108 | required: 109 | - prop2 110 | - prop3 111 | allOf: 112 | allOf: 113 | - type: object 114 | description: object1 115 | properties: 116 | prop1: 117 | type: string 118 | prop2: 119 | type: int 120 | required: 121 | - prop1 122 | - type: object 123 | description: object2 124 | properties: 125 | prop2: 126 | type: boolean 127 | prop3: 128 | type: number 129 | required: 130 | - prop2 131 | - prop3 132 | -------------------------------------------------------------------------------- /tests/unneeded_go_files_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/pulumi/crd2pulumi/pkg/codegen" 9 | ) 10 | 11 | func TestUnneededGoFiles(t *testing.T) { 12 | mockCRDYaml := `--- 13 | apiVersion: apiextensions.k8s.io/v1 14 | kind: CustomResourceDefinition 15 | spec: 16 | group: uneeded-go-files-test.pulumi.com 17 | names: 18 | plural: testresources 19 | singular: testresource 20 | kind: TestResource 21 | scope: Namespaced 22 | versions: 23 | - test: 24 | storage: true 25 | served: true 26 | name: test 27 | schema: 28 | openAPIV3Schema: 29 | properties: 30 | testProperty: 31 | type: string` 32 | 33 | yamlSources := []io.ReadCloser{ 34 | io.NopCloser(strings.NewReader(mockCRDYaml)), 35 | } 36 | 37 | // Invoke ReadPackagesFromSource 38 | pg, err := codegen.ReadPackagesFromSource("", yamlSources) 39 | if err != nil { 40 | t.Fatalf("ReadPackagesFromSource failed: %v", err) 41 | } 42 | 43 | // Pick a generated file we want to exclude 44 | uneededGoFile := "uneededgofilestest/test/testResource.go" 45 | codegen.UnneededGoFiles.Add(uneededGoFile) 46 | 47 | // Generate the code from the mocked CRD 48 | buffers, err := codegen.GenerateGo(pg, "crds") 49 | if err != nil { 50 | t.Fatalf("GenerateGo failed: %v", err) 51 | } 52 | 53 | // Assert that buffers do not contain unneeded file 54 | if _, exists := buffers["../kubernetes/"+uneededGoFile]; exists { 55 | t.Errorf("Uneeded GO file was not excluded by GoGenerate, %s", uneededGoFile) 56 | } 57 | } 58 | --------------------------------------------------------------------------------