├── .github └── workflows │ └── release.yaml ├── .gitignore ├── .goreleaser.yml ├── ARCHITECTURE.md ├── CHANGELOG.md ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── ROADMAP.md ├── SERVER_VERSION ├── cmd └── flamingo │ ├── add_cluster.go │ ├── candidate_info.go │ ├── delete.go │ ├── generate_app.go │ ├── generate_app_hr.go │ ├── generate_app_ks.go │ ├── get.go │ ├── install.go │ ├── install_templates.go │ ├── list_candidates.go │ ├── list_cluster.go │ ├── log.go │ ├── main.go │ ├── port_fwd.go │ ├── show_init_password.go │ └── version.go ├── go.mod ├── go.sum ├── index └── index.json ├── install ├── README.md └── flamingo.sh └── pkg └── utils ├── apply.go ├── kubeclient.go ├── kubeclient_for_leaf_cluster.go └── kubeconfig.go /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: [ 'v*' ] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | release-flamingo-cli: 12 | outputs: 13 | hashes: ${{ steps.slsa.outputs.hashes }} 14 | image_url: ${{ steps.slsa.outputs.image_url }} 15 | image_digest: ${{ steps.slsa.outputs.image_digest }} 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: write # needed to write releases 19 | id-token: write # needed for keyless signing 20 | packages: write # needed for ghcr access 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 24 | - name: Unshallow 25 | run: git fetch --prune --unshallow 26 | - name: Setup Go 27 | uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 28 | with: 29 | go-version: 1.20.x 30 | cache: false 31 | - name: Setup QEMU 32 | uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 33 | - name: Setup Syft 34 | uses: anchore/sbom-action/download-syft@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 # v0.14.3 35 | - name: Setup Cosign 36 | uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 # v3.1.1 37 | - name: Generate release manifests 38 | run: | 39 | mkdir -p output 40 | echo '[CHANGELOG](https://github.com/flux-subsystem-argo/flamingo/blob/main/CHANGELOG.md)' > output/notes.md 41 | - name: Get server version 42 | id: get-server-version 43 | run: | 44 | echo "::set-output name=server-version::$(cat SERVER_VERSION)" 45 | - name: Run GoReleaser 46 | id: run-goreleaser 47 | uses: goreleaser/goreleaser-action@3fa32b8bb5620a2c1afe798654bbad59f9da4906 # v4.4.0 48 | with: 49 | version: latest 50 | args: release --release-notes=output/notes.md --skip-validate 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} 53 | HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} 54 | SERVER_VERSION: ${{ steps.get-server-version.outputs.server-version }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | bin/ 3 | *.py 4 | 5 | cmd/flamingo/flamingo 6 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: flamingo 2 | builds: 3 | - <<: &build_defaults 4 | binary: flamingo 5 | main: ./cmd/flamingo 6 | ldflags: 7 | - -s -w -X main.Version={{ .Version }} -X main.ServerVersion={{ .Env.SERVER_VERSION }} 8 | env: 9 | - CGO_ENABLED=0 10 | id: linux 11 | goos: 12 | - linux 13 | goarch: 14 | - amd64 15 | - arm64 16 | - arm 17 | goarm: 18 | - 7 19 | - <<: *build_defaults 20 | id: darwin 21 | goos: 22 | - darwin 23 | goarch: 24 | - amd64 25 | - arm64 26 | - <<: *build_defaults 27 | id: windows 28 | goos: 29 | - windows 30 | archives: 31 | - name_template: "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 32 | id: nix 33 | builds: [linux, darwin] 34 | format: tar.gz 35 | files: 36 | - none* 37 | - name_template: "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 38 | id: windows 39 | builds: [windows] 40 | format: zip 41 | files: 42 | - none* 43 | brews: 44 | - name: flamingo 45 | tap: 46 | owner: flux-subsystem-argo 47 | name: homebrew-tap 48 | token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" 49 | folder: Formula 50 | homepage: "https://flux-subsystem-argo.github.io/website/" 51 | description: "Flamingo CLI" 52 | install: | 53 | bin.install "flamingo" 54 | 55 | generate_completions_from_executable(bin/"flamingo", "completion") 56 | test: | 57 | system "#{bin}/flamingo --version" 58 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Flamingo's Architectural Approach: Centralized vs. Decentralized Setups 2 | 3 | ## Introduction 4 | 5 | Flamingo, offering the synergy between Flux and Argo CD, provides a powerful platform for automating deployments and managing workloads in Kubernetes clusters using GitOps principles. Although inherently designed to manage single-cluster environments effectively, the architectural flexibility of Flamingo allows it to be adapted to various setups. Here, we explore two architectural setups: a centralized setup where a single Flamingo instance controls the whole cluster, and a decentralized setup where Flamingo is instantiated per namespace. 6 | 7 | ### I. Centralized Setup: A Singular Control Point 8 | 9 | #### A. Overview 10 | In a centralized Flamingo architecture, a singular Flamingo instance is deployed, which takes charge of managing resources, configurations, and workloads across the entire Kubernetes cluster. This approach simplifies overall management but consolidates control and resource allocation to a single point. 11 | 12 | #### B. Key Architectural Components 13 | - *Single Flamingo Instance:* One Flamingo instance is deployed cluster-wide, managing resources and configurations across all namespaces and reducing the operational overhead. 14 | 15 | - *Centralized Resource Management:* Centralized control and management of all applications, policies, and resources across various namespaces within the cluster. 16 | 17 | #### C. Architecture Workflow 18 | 19 | - *Deployment:* One Flamingo instance is deployed, becoming the pivotal point for managing all deployments and configurations across the cluster. 20 | 21 | - *Unified Management:* The centralized Flamingo manages all resources, ensuring a unified policy and configuration application while streamlining administration. 22 | 23 | - *Scalability and Performance:* It’s crucial to monitor the performance and scalability aspects, as the centralized Flamingo instance will bear the load of managing the entire cluster. 24 | 25 | ### II. Decentralized Setup: Implementing Tenant per Namespace 26 | 27 | #### A. Overview 28 | 29 | In contrast, the decentralized setup involves deploying individual Flamingo instances within each namespace, ensuring isolated, autonomous, and dedicated management of resources and workflows within its scope. This approach maximizes autonomy and minimizes the risk of operational interference among namespaces. 30 | 31 | #### B. Key Architectural Components 32 | 33 | - *Flamingo Namespace Tenants:* Each namespace is endowed with its own Flamingo instance, ensuring management is localized and isolated to that specific namespace. 34 | 35 | - *Independent Resource Management:* Each Flamingo instance manages only the resources within its namespace, ensuring policies and configurations are contained and do not impact other namespaces. 36 | 37 | #### C. Architecture Workflow 38 | 39 | - *Individualized Deployment:* A Flamingo instance is deployed per namespace, each one autonomously managing its resources and workloads. 40 | 41 | - *Isolated Management:* Management is entirely localized, ensuring that configurations, policies, and resources are managed in isolation, providing a safeguard against unintentional impact on other namespaces. 42 | 43 | - *Autonomy and Security:* Autonomous operation bolsters security by isolating potential impact and reducing the attack surface. 44 | 45 | ## Conclusion 46 | 47 | Choosing between a centralized and decentralized setup should stem from the specific use-case, organizational structure, security considerations, and management preference. Flamingo's inherent architectural flexibility provides a robust platform to implement either centralized or decentralized setups, each offering its unique advantages and potential challenges. A centralized setup simplifies overall management but consolidates control and resource allocation, while a decentralized setup maximizes autonomy and operational isolation. 48 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Changes of the notable versions of the Flamingo CLI are documented in this file. 4 | 5 | ## v0.10.2 (2024-03-05) 6 | 7 | **New Features and Bug Fixes** 8 | 9 | * Add new FSA image **v2.10.2** as the default version. 10 | * Promoted `v2.10.2-fl.23-main-d2c9a8cb` to the default version. 11 | * Implement `flamingo/{kustomize,helmrelease,gitrepository,ocirepository,helmrepository}-override` annotations for Application CR to override the default configuration in the auto-create mode. 12 | * Implement `flamingo/{kustomize,helmrelease,gitrepository,ocirepository,helmrepository}-replace` annotations for Application CR to replace the default configuration in the auto-create mode. 13 | * Multi-cluster support for remote Flux clusters. The multi-cluster support works with any kinds of cluster configurations, but there are some limitation in the Flamingo CLI commands, see below. 14 | * Implement `flamingo add-cluster` to generate a cluster secret for the remote Flux cluster. This command currently supports only the static cluster configuration. 15 | * Implement `flamingo generate-app cluster/kind/resource` to generate an Application CR for resources in the remote Flux cluster. This command currently supports only the static cluster configuration. 16 | * Add `flamingo install --mode=helmrelease` to install Flamingo with HelmRelease. This command also support `--export` flag to export the HelmRelease CR. 17 | * Add documentation for the multi-cluster support. 18 | * Add documentation for the `flamingo install --mode=helmrelease` command. 19 | 20 | ## v0.9.0 21 | 22 | **New Features and Bug Fixes** 23 | 24 | * Started to implement multi-cluster support. 25 | * Promoted `v2.9.6-fl.22-main-402c9e49` to the default version. 26 | 27 | ## v0.8.3 (2024-02-16) 28 | 29 | **New Features and Bug Fixes** 30 | 31 | * Add new FSA image **v2.9.6** that supports HelmRelease v2beta2. 32 | 33 | ## v0.8.2 (2024-02-10) 34 | 35 | **New Features and Bug Fixes** 36 | 37 | * Promote FSA image **v2.9.6** to the default version. 38 | 39 | ## v0.8.1 (2024-02-10) 40 | 41 | **New Features and Bug Fixes** 42 | 43 | * Fix health check for OCI HelmRepository in Flux v2.2.x. 44 | 45 | ## v0.8.0 (2024-02-10) 46 | 47 | **New Features and Bug Fixes** 48 | 49 | * Add FSA image **v2.9.6** to the index 50 | * Fix empty path in Kustomization (generate-app ks) 51 | 52 | ## v0.7.0 (2023-12-30) 53 | 54 | **New Features and Bug Fixes** 55 | 56 | * Promote FSA image **v2.9.3** to the default version 57 | 58 | ## v0.6.1 (2023-11-08) 59 | 60 | **New Features and Bug Fixes** 61 | 62 | * SSA wait timeout is changed from 1 to 5 minutes 63 | 64 | ## v0.6.0 (2023-11-07) 65 | 66 | **New Features and Bug Fixes** 67 | 68 | * Promote FSA image **v2.8.6** to the default version 69 | * Add FSA image **v2.9.0** to the dev channel 70 | * Show the version during the installation process 71 | * Timeout default value is now 10 minutes 72 | 73 | ## v0.5.1 (2023-11-02) 74 | 75 | **New Features and Bug Fixes** 76 | * Promote FSA image **v2.8.5** to the default version 77 | * Add FSA image **v2.8.6** to the dev channel 78 | * Add HelmRelease quickstart to the docs 79 | * Fix installation script 80 | * Fix `install` command help to show the latest FSA image 81 | * Prefix `v` to the FSA image version for the `install` command 82 | 83 | ## v0.5.0 (2023-10-30) 84 | 85 | **New Features and Bug Fixes** 86 | * Promote FSA image **v2.8.4** to the default version 87 | * Add FSA image **v2.8.5** and **v2.9.0-rc3** to the dev channel 88 | * Add Flux v2.1.2 support to the dev channel 89 | * Add `lc` alias for `list-candidates` command 90 | * Remove `--dev` flag out of the `install` command 91 | * CLI repository migration from `flux-subsystem-argo/cli` to `flux-subsystem-argo/flamingo` 92 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | In alphabetical order: 2 | 3 | Chanwit Kaewkasi (github: @chanwit) 4 | Kingdon Barrett (github: @kingdonb) 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Constants 2 | CLI_VERSION := dev 3 | FLAMINGO_VERSION := $(shell cat SERVER_VERSION) 4 | BINARY_NAME := flamingo 5 | BIN_DIR := bin 6 | CMD_DIR := ./cmd/$(BINARY_NAME)/ 7 | BUILD_FLAGS := -ldflags="-s -w -X main.Version=$(CLI_VERSION) -X main.ServerVersion=$(FLAMINGO_VERSION)" 8 | OUTPUT_PATH := $(BIN_DIR)/$(BINARY_NAME) 9 | 10 | build: 11 | mkdir -p $(BIN_DIR) 12 | go fmt ./... 13 | CGO_ENABLED=0 go build $(BUILD_FLAGS) -o $(OUTPUT_PATH) $(CMD_DIR) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flamingo - the Flux Subsystem for Argo 2 | 3 | **This project is looking for sponsorship for its continuation. Please feel free to contact chanwit at gmail, if you want to support this project.** 4 | 5 | > 🚀🚀🚀 **BREAKING:** Introducing the new [Flamingo CLI](https://github.com/flux-subsystem-argo/flamingo)! 🚀🚀🚀 6 | 7 | Flamingo is the **Flux Subsystem for Argo** (FSA). Flamingo's container image can be used as a drop-in extension for the equivalent ArgoCD version to visualize, and manage Flux workloads, alongside ArgoCD. You can also ensure that upstream CVEs in Argo CD are quickly backported to Flamingo, maintaining a secure and stable environment. 8 | 9 | ## Why use Flamingo? 10 | 11 | Flamingo is a tool that combines Flux and Argo CD to provide the best of both worlds for implementing GitOps on Kubernetes clusters. With Flamingo, you can: 12 | 13 | 1. Automate the deployment of your applications to Kubernetes clusters and benefit from the improved collaboration and deployment speed and reliability that GitOps offers. 14 | 15 | 2. Enjoy a seamless and integrated experience for managing deployments, with the automation capabilities of Flux embedded inside the user-friendly interface of Argo CD. 16 | 17 | 3. Take advantage of additional features and capabilities that are not available in either Flux or Argo CD individually, such the robust Helm support from Flux, Flux OCI Repository, Weave GitOps Terraform Controller for Infrastructure as Code, Weave Policy Engine, or Argo CD ApplicationSet for Flux-managed resources. 18 | 19 | Try Flamingo today and see how it can improve your GitOps workflow on Kubernetes. 20 | 21 | This provides a brief overview of the benefits of using Flamingo and why it could be a useful tool for implementing GitOps on Kubernetes clusters. Of course, you may want to tailor this to your specific use case and requirements, but this should give you a good starting point. 22 | 23 | ## How does it work? 24 | 25 | **Loopback Reconciliation** is a feature of Flamingo that helps to synchronize applications deployed using the GitOps approach. It is activated when the "FluxSubsystem" feature is enabled in the ArgoCD user interface (UI). 26 | 27 | Here's how Loopback Reconciliation works: 28 | 29 | 1. An ArgoCD application manifest is created and deployed to a cluster, either in Kustomization or Helm mode. 30 | 31 | 2. Flamingo converts the ArgoCD application manifest into the equivalent Flux object, either a Kustomization object or a HelmRelease object with a Source, depending on the mode used in the ArgoCD manifest. If Flux objects already exist for the application, Flamingo will use them as references instead of creating new ones. 32 | 33 | 3. Flamingo synchronizes or reconciles the state of the ArgoCD application with its Flux counterparts by using the state of the Flux objects as the desired state. To do this, the Loopback Reconciliation mechanism bypasses the native reconciliation process in ArgoCD and relies on Flux reconciliation instead. It then uses the result from the Flux objects to report back to ArgoCD. 34 | 35 | Loopback Reconciliation helps to ensure the reliability and consistency of GitOps-based deployments by keeping the state of applications in sync with their desired state defined in the Flux objects. The technique gets its name because it involves "looping back" to the desired state defined in the Flux objects as references to reconcile the state of the application. 36 | 37 | ![FSA (2)](https://user-images.githubusercontent.com/10666/159503288-5faeda59-8b54-40f0-95ca-b46c22742e30.png) 38 | 39 | ## Getting Started with Flamingo CLI 40 | 41 | This guide will provide a step-by-step process for setting up a GitOps environment using Flux and ArgoCD, via Flamingo. By the end of this guide, you will have Flamingo running locally on your KIND cluster. You will create a `podinfo` application with a Flux Kustomization, and generate a Flamingo app from this Flux object. 42 | 43 | ### Install CLIs 44 | 45 | - [KIND CLI](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) 46 | - [Flux CLI](https://fluxcd.io/docs/cmd/) 47 | - [Flamingo CLI](https://github.com/flux-subsystem-argo/flamingo) 48 | 49 | Example install in macOS or Linux via [homebrew](https://brew.sh/) 50 | 51 | ```shell 52 | # install KIND cli 53 | brew install kind 54 | 55 | # install Flux CLI 56 | brew install fluxcd/tap/flux 57 | 58 | # install Flamingo CLI 59 | # with Homebrew 60 | brew install flux-subsystem-argo/tap/flamingo 61 | 62 | # or with cURL 63 | curl -sL https://raw.githubusercontent.com/flux-subsystem-argo/flamingo/main/install/flamingo.sh | sudo bash 64 | ``` 65 | 66 | ### Create a fresh KIND cluster 67 | 68 | ```shell 69 | kind create cluster 70 | ``` 71 | 72 | ### Install **Flux** 73 | 74 | ```shell 75 | flux install 76 | ``` 77 | 78 | ### Install **Flamingo** 79 | 80 | #### Install **Flamingo** via CLI 81 | 82 | ```shell 83 | flamingo install 84 | ``` 85 | 86 | ```shell 87 | # or with a specific version 88 | flamingo install --version=v2.8.4 89 | ``` 90 | 91 | #### Install **Flamingo** with HelmRelease 92 | 93 | ```yaml 94 | cat << EOF | kubectl apply -f - 95 | --- 96 | apiVersion: v1 97 | kind: Namespace 98 | metadata: 99 | name: argocd 100 | --- 101 | apiVersion: source.toolkit.fluxcd.io/v1beta2 102 | kind: HelmRepository 103 | metadata: 104 | name: argo-helm-repo 105 | namespace: argocd 106 | spec: 107 | interval: 10m 108 | url: https://argoproj.github.io/argo-helm 109 | --- 110 | apiVersion: helm.toolkit.fluxcd.io/v2beta2 111 | kind: HelmRelease 112 | metadata: 113 | name: flamingo 114 | namespace: argocd 115 | spec: 116 | interval: 10m 117 | targetNamespace: argocd 118 | releaseName: argocd 119 | chart: 120 | spec: 121 | chart: argo-cd 122 | version: '*' 123 | sourceRef: 124 | kind: HelmRepository 125 | name: argo-helm-repo 126 | values: 127 | global: 128 | image: 129 | repository: ghcr.io/flux-subsystem-argo/fsa/argocd 130 | tag: v2.10.2-fl.23-main-d2c9a8cb # replace with the latest version 131 | EOF 132 | ``` 133 | 134 | If you want to see Flamingo itself as an application, run: 135 | 136 | ```shell 137 | flamingo gen-app --app-name=flamingo -n argocd hr/flamingo 138 | ``` 139 | 140 | ### Example workloads 141 | 142 | #### Create a **Flux Kustomization** 143 | 144 | ```yaml 145 | cat << EOF | kubectl apply -f - 146 | --- 147 | apiVersion: v1 148 | kind: Namespace 149 | metadata: 150 | name: podinfo-kustomize 151 | --- 152 | apiVersion: source.toolkit.fluxcd.io/v1beta2 153 | kind: OCIRepository 154 | metadata: 155 | name: podinfo 156 | namespace: podinfo-kustomize 157 | spec: 158 | interval: 10m 159 | url: oci://ghcr.io/stefanprodan/manifests/podinfo 160 | ref: 161 | tag: latest 162 | --- 163 | apiVersion: kustomize.toolkit.fluxcd.io/v1 164 | kind: Kustomization 165 | metadata: 166 | name: podinfo 167 | namespace: podinfo-kustomize 168 | spec: 169 | interval: 10m 170 | targetNamespace: podinfo-kustomize 171 | prune: true 172 | sourceRef: 173 | kind: OCIRepository 174 | name: podinfo 175 | path: ./ 176 | EOF 177 | ``` 178 | 179 | To generate a Flamingo application to visualize the `podinfo` objects, run: 180 | 181 | ```shell 182 | flamingo generate-app \ 183 | --app-name=podinfo-ks \ 184 | -n podinfo-kustomize ks/podinfo 185 | ``` 186 | 187 | #### Create a **Flux HelmRelease** 188 | 189 | ```shell 190 | cat << EOF | kubectl apply -f - 191 | --- 192 | apiVersion: v1 193 | kind: Namespace 194 | metadata: 195 | name: podinfo-helm 196 | --- 197 | apiVersion: source.toolkit.fluxcd.io/v1beta2 198 | kind: HelmRepository 199 | metadata: 200 | name: podinfo 201 | namespace: podinfo-helm 202 | spec: 203 | interval: 10m 204 | type: oci 205 | url: oci://ghcr.io/stefanprodan/charts 206 | --- 207 | apiVersion: helm.toolkit.fluxcd.io/v2beta2 208 | kind: HelmRelease 209 | metadata: 210 | name: podinfo 211 | namespace: podinfo-helm 212 | spec: 213 | interval: 10m 214 | targetNamespace: podinfo-helm 215 | chart: 216 | spec: 217 | chart: podinfo 218 | version: '*' 219 | sourceRef: 220 | kind: HelmRepository 221 | name: podinfo 222 | verify: 223 | provider: cosign # keyless 224 | EOF 225 | ``` 226 | 227 | ```shell 228 | flamingo generate-app \ 229 | --app-name=podinfo-hr \ 230 | -n podinfo-helm hr/podinfo 231 | ``` 232 | 233 | Like a normal Argo CD instance, please firstly obtain the initial password by running the following command to login. 234 | The default username is `admin`. 235 | 236 | ```shell 237 | flamingo show-init-password 238 | ``` 239 | 240 | After that you can port forward and open your browser to http://localhost:8080 241 | 242 | ```shell 243 | kubectl -n argocd port-forward svc/argocd-server 8080:443 244 | ``` 245 | 246 | ## Multi-cluster support 247 | 248 | ### What is Flamingo multi-cluster support? 249 | 250 | Flamingo multi-cluster is a feature designed to visualize multiple Flux clusters in the Flamingo UI. 251 | We use ArgoCD cluster secrets to store cluster information for Flamingo multi-cluster support. 252 | To list clusters, use the following command: 253 | 254 | ```shell 255 | flamingo list-clusters 256 | ``` 257 | 258 | To register a Kubernetes cluster with Flamingo, simply use the `add-cluster` command. 259 | For example, the following command adds the `dev-1` cluster definition from the `KUBECONFIG` file, then overrides the server name and server address, and sets it to skip TLS verification. 260 | 261 | ```shell 262 | flamingo add-cluster dev-1 \ 263 | --server-name=dev-1.vcluster-dev-1 \ 264 | --server-addr=https://dev-1.vcluster-dev-1.svc \ 265 | --insecure 266 | ``` 267 | 268 | This `add-cluster` command currently supports adding a cluster with static credentials, such as client certs and client keys (like Vclusters), but does not yet support authentication via KubeClient plugins. For clusters like EKS and others, you need to create cluster secrets manually. 269 | 270 | To generate applications from Flux workloads on leaf clusters, the flamingo `generate-app` command has been extended to support the resource format as `cluster/kind/object-name`, for example: 271 | 272 | ```shell 273 | flamingo generate-app \ 274 | dev-1/ks/podinfo 275 | ``` 276 | 277 | The above command will generate an application named `podinfo` from the `ks` resource on the `dev-1` cluster. 278 | This `generate-app` command uses Flamingo cluster information to connect to the leaf cluster and generate the application. 279 | Currently, this command only supports static cluster credentials with client certs and keys. 280 | For dynamic cluster credentials like in EKS and others, you would use the `--context` flag to select the Kube's context for the leaf cluster and generate the application with the `--server` flag to override the destination cluster, like: 281 | 282 | ```shell 283 | flamingo generate-app \ 284 | --context=dev-1 \ 285 | --server=https://dev-1.vcluster-dev-1.svc \ 286 | ks/podinfo --export | kubectl apply -f - 287 | ``` 288 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## Q3 2023 4 | - [ ] Flux specification as application annotations 5 | - [ ] Cluster-scope resource virtualization 6 | - [ ] Flux v2 GA support 7 | 8 | ## Q4 2023 9 | - [ ] History and Rollback support 10 | 11 | ## Q1 2024 12 | - [ ] TBD 13 | -------------------------------------------------------------------------------- /SERVER_VERSION: -------------------------------------------------------------------------------- 1 | v2.10.2 2 | -------------------------------------------------------------------------------- /cmd/flamingo/add_cluster.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | ctx "context" 5 | "encoding/base64" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/flux-subsystem-argo/flamingo/pkg/utils" 10 | "github.com/spf13/cobra" 11 | "k8s.io/client-go/tools/clientcmd" 12 | ) 13 | 14 | var addClusterCmd = &cobra.Command{ 15 | Use: "add-cluster CONTEXT_NAME", 16 | Short: "Add a cluster to Flamingo", 17 | Long: ` 18 | # Add a cluster to Flamingo 19 | flamingo add-cluster my-cluster 20 | 21 | # Add cluster dev-1, override the name and address, and skip TLS verification 22 | flamingo add-cluster dev-1 \ 23 | --server-name=dev-1.example.com \ 24 | --server-addr=https://dev-1.example.com:6443 \ 25 | --insecure 26 | `, 27 | Args: cobra.ExactArgs(1), 28 | RunE: addClusterCmdRun, 29 | } 30 | 31 | var addClusterFlags struct { 32 | insecureSkipTLSVerify bool 33 | serverName string 34 | serverAddress string 35 | export bool 36 | } 37 | 38 | func init() { 39 | addClusterCmd.Flags().BoolVar(&addClusterFlags.insecureSkipTLSVerify, "insecure", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure") 40 | addClusterCmd.Flags().StringVar(&addClusterFlags.serverName, "server-name", "", "If set, this overrides the hostname used to validate the server certificate") 41 | addClusterCmd.Flags().StringVar(&addClusterFlags.serverAddress, "server-addr", "", "If set, this overrides the server address used to connect to the cluster") 42 | addClusterCmd.Flags().BoolVar(&addClusterFlags.export, "export", false, "export manifests instead of installing") 43 | 44 | rootCmd.AddCommand(addClusterCmd) 45 | } 46 | 47 | func addClusterCmdRun(cmd *cobra.Command, args []string) error { 48 | leafClusterContext := args[0] 49 | 50 | kubeconfig := "" 51 | if *kubeconfigArgs.KubeConfig == "" { 52 | kubeconfig = clientcmd.RecommendedHomeFile 53 | } else { 54 | kubeconfig = *kubeconfigArgs.KubeConfig 55 | } 56 | 57 | // Load the kubeconfig file 58 | config, err := clientcmd.LoadFromFile(kubeconfig) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | // Specify the context name 64 | contextName := leafClusterContext 65 | 66 | // Get the context 67 | context, exists := config.Contexts[contextName] 68 | if !exists { 69 | return fmt.Errorf("context not found") 70 | } 71 | 72 | // Get the cluster info 73 | cluster, exists := config.Clusters[context.Cluster] 74 | if !exists { 75 | return fmt.Errorf("cluster not found") 76 | } 77 | 78 | // Get the user info 79 | user, exists := config.AuthInfos[context.AuthInfo] 80 | if !exists { 81 | return fmt.Errorf("user not found") 82 | } 83 | 84 | template := `apiVersion: v1 85 | kind: Secret 86 | metadata: 87 | name: %s-cluster 88 | namespace: argocd 89 | labels: 90 | argocd.argoproj.io/secret-type: cluster 91 | flamingo/cluster: "true" 92 | annotations: 93 | flamingo/external-address: "%s" 94 | flamingo/internal-address: "%s" 95 | type: Opaque 96 | stringData: 97 | name: %s 98 | server: %s 99 | config: | 100 | { 101 | "tlsClientConfig": { 102 | "insecure": %v, 103 | "certData": "%s", 104 | "keyData": "%s", 105 | "serverName": "%s" 106 | } 107 | } 108 | ` 109 | serverAddress := addClusterFlags.serverAddress 110 | if serverAddress == "" { 111 | serverAddress = cluster.Server 112 | } 113 | 114 | result := fmt.Sprintf(template, 115 | contextName, 116 | cluster.Server, // external address (known to the user via kubectl config view) 117 | serverAddress, // internal address 118 | contextName, 119 | serverAddress, 120 | addClusterFlags.insecureSkipTLSVerify, 121 | base64.StdEncoding.EncodeToString(user.ClientCertificateData), 122 | base64.StdEncoding.EncodeToString(user.ClientKeyData), 123 | addClusterFlags.serverName, 124 | ) 125 | 126 | if addClusterFlags.export { 127 | fmt.Print(result) 128 | return nil 129 | } else { 130 | logger.Actionf("applying generated cluster secret %s in %s namespace", contextName+"-cluster", rootArgs.applicationNamespace) 131 | applyOutput, err := utils.Apply(ctx.Background(), kubeconfigArgs, kubeclientOptions, []byte(result)) 132 | if err != nil { 133 | return fmt.Errorf("apply failed: %w", err) 134 | } 135 | fmt.Fprintln(os.Stderr, applyOutput) 136 | } 137 | 138 | return nil 139 | } 140 | -------------------------------------------------------------------------------- /cmd/flamingo/candidate_info.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Candidate struct { 4 | Flamingo string `json:"flamingo"` 5 | ArgoCD string `json:"argocd"` 6 | Image string `json:"image"` 7 | Flux string `json:"flux"` 8 | } 9 | 10 | type CandidateList struct { 11 | Candidates []Candidate `json:"candidates"` 12 | } 13 | -------------------------------------------------------------------------------- /cmd/flamingo/delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/flux-subsystem-argo/flamingo/pkg/utils" 7 | "github.com/spf13/cobra" 8 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 9 | "k8s.io/apimachinery/pkg/runtime/schema" 10 | ) 11 | 12 | var deleteCmd = &cobra.Command{ 13 | Use: "delete", 14 | Args: cobra.ExactArgs(1), 15 | Short: "Delete Flamingo applications", 16 | Long: `Delete Flamingo applications`, 17 | RunE: deleteCmdRun, 18 | } 19 | 20 | func init() { 21 | rootCmd.AddCommand(deleteCmd) 22 | } 23 | 24 | func deleteCmdRun(cmd *cobra.Command, args []string) error { 25 | cli, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) 26 | if err != nil { 27 | return err 28 | } 29 | gvk := schema.GroupVersionKind{ 30 | Group: "argoproj.io", 31 | Version: "v1alpha1", 32 | Kind: "Application", 33 | } 34 | 35 | namespace := *kubeconfigArgs.Namespace 36 | labelSelector := map[string]string{ 37 | "app.kubernetes.io/managed-by": "flamingo", 38 | "flamingo/destination-namespace": namespace, 39 | } 40 | if getCmdFlags.all { 41 | delete(labelSelector, "flamingo/destination-namespace") 42 | } 43 | app := &unstructured.Unstructured{} 44 | app.SetName(args[0]) 45 | app.SetNamespace(namespace) 46 | app.SetGroupVersionKind(gvk) 47 | 48 | if err := cli.Delete(context.Background(), app); err != nil { 49 | return err 50 | } 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /cmd/flamingo/generate_app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "os" 8 | "strings" 9 | 10 | "github.com/flux-subsystem-argo/flamingo/pkg/utils" 11 | helmv2b1 "github.com/fluxcd/helm-controller/api/v2beta1" 12 | kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var generateAppCmd = &cobra.Command{ 17 | Use: "generate-app NAME", 18 | Aliases: []string{"gen-app"}, 19 | Args: cobra.ExactArgs(1), 20 | Short: "Generate a Flamingo application from Flux resources (Kustomization or HelmRelease)", 21 | Long: ` 22 | # Generate a Flamingo application from a Flux Kustomization podinfo in the current namespace (flux-system). 23 | # The generated application is put in the argocd namespace by default. 24 | flamingo generate-app ks/podinfo 25 | 26 | # Generate a Flamingo application from a Flux Kustomization podinfo in the podinfo namespace. 27 | flamingo generate-app -n podinfo ks/podinfo 28 | 29 | # Generate a Flamingo application from a HelmRelease podinfo in the current namespace (flux-system). 30 | flamingo generate-app hr/podinfo 31 | 32 | # Generate a Flamingo application from a HelmRelease podinfo in the podinfo namespace. 33 | flamingo generate-app -n podinfo hr/podinfo 34 | 35 | # Generate a Flamingo application named podinfo-ks, from a Flux Kustomization podinfo in the podinfo-kustomize namespace of the dev-1 cluster. 36 | # The generated application is put in the argocd namespace of the current cluster. 37 | flamingo generate-app \ 38 | --app-name=podinfo-ks \ 39 | -n podinfo-kustomize dev-1/ks/podinfo 40 | `, 41 | RunE: generateAppCmdRun, 42 | } 43 | 44 | var generateAppFlags struct { 45 | appName string 46 | server string 47 | export bool 48 | } 49 | 50 | func init() { 51 | // app name should be default to the resource name 52 | generateAppCmd.Flags().StringVar(&generateAppFlags.appName, "app-name", "", "name of the generated application") 53 | generateAppCmd.Flags().StringVar(&generateAppFlags.server, "server", "", "server URL to override the destination cluster") 54 | generateAppCmd.Flags().BoolVar(&generateAppFlags.export, "export", false, "export the generated application to stdout") 55 | 56 | rootCmd.AddCommand(generateAppCmd) 57 | } 58 | 59 | func generateAppCmdRun(_ *cobra.Command, args []string) error { 60 | isValid := false 61 | clusterName := "" 62 | kindName := "" 63 | objectName := "" 64 | 65 | kindSlashName := "" 66 | // FQN: fully qualified name is in the format of kind/name cluster-name/kind/name 67 | fqn := args[0] 68 | if strings.Count(fqn, "/") == 1 { 69 | clusterName = "in-cluster" 70 | kindSlashName = fqn 71 | } else if strings.Count(fqn, "/") == 2 { 72 | clusterName = strings.SplitN(fqn, "/", 2)[0] 73 | kindSlashName = strings.SplitN(fqn, "/", 2)[1] 74 | } else { 75 | return fmt.Errorf("not a valid Kustomization or HelmRelease resource") 76 | } 77 | 78 | // Define a map for valid kinds with their short and full names 79 | var validKinds = map[string]string{ 80 | "ks": kustomizev1.KustomizationKind, 81 | "kustomization": kustomizev1.KustomizationKind, 82 | "kustomizations": kustomizev1.KustomizationKind, 83 | "hr": helmv2b1.HelmReleaseKind, 84 | "helmrelease": helmv2b1.HelmReleaseKind, 85 | "helmreleases": helmv2b1.HelmReleaseKind, 86 | } 87 | // Check if the kindSlashName starts with any of the valid prefixes 88 | for shortName, fullName := range validKinds { 89 | if strings.HasPrefix(kindSlashName, shortName+"/") { 90 | isValid = true 91 | kindName = fullName 92 | break 93 | } else if strings.HasPrefix(kindSlashName, fullName+"/") { 94 | isValid = true 95 | kindName = fullName 96 | break 97 | } 98 | } 99 | 100 | if !isValid { 101 | return fmt.Errorf("not a valid Kustomization or HelmRelease resource") 102 | } 103 | 104 | objectName = strings.Split(kindSlashName, "/")[1] 105 | appName := generateAppFlags.appName 106 | if appName == "" { 107 | appName = objectName 108 | } 109 | 110 | mgmtCli, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) 111 | if err != nil { 112 | return err 113 | } 114 | 115 | leafCli := mgmtCli 116 | var clusterConfig *utils.ClusterConfig 117 | if clusterName != "in-cluster" && clusterName != "" { 118 | leafCli, clusterConfig, err = utils.KubeClientForLeafCluster(mgmtCli, clusterName, kubeclientOptions) 119 | if err != nil { 120 | return err 121 | } 122 | } else { 123 | clusterConfig = &utils.ClusterConfig{ 124 | Server: "https://kubernetes.default.svc", 125 | } 126 | } 127 | 128 | // Override the server URL if provided 129 | if generateAppFlags.server != "" { 130 | clusterConfig.Server = generateAppFlags.server 131 | } 132 | 133 | var tpl bytes.Buffer 134 | if kindName == kustomizev1.KustomizationKind { 135 | if err := generateKustomizationApp(leafCli, appName, objectName, kindName, clusterName, clusterConfig.Server, &tpl); err != nil { 136 | return err 137 | } 138 | } else if kindName == helmv2b1.HelmReleaseKind { 139 | if err := generateHelmReleaseApp(leafCli, appName, objectName, kindName, clusterName, clusterConfig.Server, &tpl); err != nil { 140 | return err 141 | } 142 | } 143 | 144 | if generateAppFlags.export { 145 | fmt.Print(tpl.String()) 146 | return nil 147 | } else { 148 | logger.Actionf("applying generated application %s in %s namespace", objectName, rootArgs.applicationNamespace) 149 | applyOutput, err := utils.Apply(context.Background(), kubeconfigArgs, kubeclientOptions, tpl.Bytes()) 150 | if err != nil { 151 | return fmt.Errorf("install failed: %w", err) 152 | } 153 | fmt.Fprintln(os.Stderr, applyOutput) 154 | } 155 | 156 | return nil 157 | } 158 | -------------------------------------------------------------------------------- /cmd/flamingo/generate_app_hr.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "text/template" 8 | 9 | helmv2b1 "github.com/fluxcd/helm-controller/api/v2beta1" 10 | sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | ) 14 | 15 | const hrAppTemplate = `--- 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Application 18 | metadata: 19 | name: {{ .Name }} 20 | namespace: {{ .Namespace }} 21 | labels: 22 | app.kubernetes.io/managed-by: flamingo 23 | flamingo/workload-name: {{ .WorkloadName }} 24 | flamingo/workload-type: "{{ .WorkloadType }}" 25 | flamingo/source-type: "{{ .SourceType }}" 26 | flamingo/destination-namespace: "{{ .DestinationNamespace }}" 27 | flamingo/cluster-name: "{{ .ClusterName }}" 28 | annotations: 29 | weave.gitops.flamingo/base-url: "http://localhost:9001" 30 | weave.gitops.flamingo/cluster-name: "Default" 31 | spec: 32 | destination: 33 | namespace: {{ .DestinationNamespace }} 34 | server: {{ .Server }} 35 | project: default 36 | source: 37 | chart: {{ .ChartName }} 38 | repoURL: {{ .ChartURL }} 39 | targetRevision: "{{ .ChartRevision }}" 40 | helm: 41 | releaseName: {{ .ReleaseName }} 42 | syncPolicy: 43 | syncOptions: 44 | - ApplyOutOfSyncOnly=true 45 | - FluxSubsystem=true 46 | ` 47 | 48 | func generateHelmReleaseApp(c client.Client, appName string, objectName string, kindName string, clusterName string, server string, tpl *bytes.Buffer) error { 49 | object := helmv2b1.HelmRelease{ 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Name: objectName, 52 | Namespace: *kubeconfigArgs.Namespace, 53 | }, 54 | } 55 | key := client.ObjectKeyFromObject(&object) 56 | if err := c.Get(context.Background(), key, &object); err != nil { 57 | return fmt.Errorf("%w in namespace %q", err, object.Namespace) 58 | } 59 | 60 | sourceKind := object.Spec.Chart.Spec.SourceRef.Kind 61 | sourceName := object.Spec.Chart.Spec.SourceRef.Name 62 | sourceNamespace := object.Spec.Chart.Spec.SourceRef.Namespace 63 | if sourceNamespace == "" { 64 | sourceNamespace = *kubeconfigArgs.Namespace 65 | } 66 | 67 | t, err := template.New("template").Parse(hrAppTemplate) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | var params struct { 73 | Name string 74 | Namespace string 75 | WorkloadName string 76 | WorkloadType string 77 | SourceType string 78 | DestinationNamespace string 79 | ClusterName string 80 | 81 | Server string 82 | 83 | ChartName string 84 | ChartURL string 85 | ChartRevision string 86 | ReleaseName string 87 | } 88 | 89 | params.Name = appName 90 | params.Namespace = rootArgs.applicationNamespace 91 | params.DestinationNamespace = *kubeconfigArgs.Namespace 92 | 93 | params.WorkloadName = object.Name 94 | params.WorkloadType = kindName 95 | params.SourceType = sourceKind 96 | params.ClusterName = clusterName 97 | 98 | params.Server = server 99 | 100 | params.ChartName = object.Spec.Chart.Spec.Chart 101 | params.ChartRevision = object.Spec.Chart.Spec.Version 102 | params.ReleaseName = object.Spec.ReleaseName 103 | if params.ReleaseName == "" { 104 | params.ReleaseName = object.Name 105 | } 106 | 107 | switch sourceKind { 108 | case sourcev1b2.HelmRepositoryKind: 109 | sourceObj := sourcev1b2.HelmRepository{ 110 | ObjectMeta: metav1.ObjectMeta{ 111 | Name: sourceName, 112 | Namespace: sourceNamespace, 113 | }, 114 | } 115 | sourceKey := client.ObjectKeyFromObject(&sourceObj) 116 | if err := c.Get(context.Background(), sourceKey, &sourceObj); err != nil { 117 | return err 118 | } 119 | params.ChartURL = sourceObj.Spec.URL 120 | } 121 | 122 | if err := t.Execute(tpl, params); err != nil { 123 | return err 124 | } 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /cmd/flamingo/generate_app_ks.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "strings" 8 | "text/template" 9 | 10 | kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" 11 | sourcev1 "github.com/fluxcd/source-controller/api/v1" 12 | sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | ) 16 | 17 | const ksAppTemplate = `--- 18 | apiVersion: argoproj.io/v1alpha1 19 | kind: Application 20 | metadata: 21 | name: {{ .Name }} 22 | namespace: {{ .Namespace }} 23 | labels: 24 | app.kubernetes.io/managed-by: flamingo 25 | flamingo/workload-name: {{ .WorkloadName }} 26 | flamingo/workload-type: "{{ .WorkloadType }}" 27 | flamingo/source-type: "{{ .SourceType }}" 28 | flamingo/destination-namespace: "{{ .DestinationNamespace }}" 29 | flamingo/cluster-name: "{{ .ClusterName }}" 30 | annotations: 31 | weave.gitops.flamingo/base-url: "http://localhost:9001" 32 | weave.gitops.flamingo/cluster-name: "Default" 33 | spec: 34 | destination: 35 | namespace: {{ .DestinationNamespace }} 36 | server: {{ .Server }} 37 | project: default 38 | source: 39 | path: {{ .Path }} 40 | repoURL: {{ .SourceURL }} 41 | targetRevision: "{{ .SourceRevision }}" 42 | syncPolicy: 43 | syncOptions: 44 | - ApplyOutOfSyncOnly=true 45 | - FluxSubsystem=true 46 | ` 47 | 48 | func generateKustomizationApp(c client.Client, appName string, objectName string, kindName string, clusterName string, server string, tpl *bytes.Buffer) error { 49 | object := kustomizev1.Kustomization{ 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Name: objectName, 52 | Namespace: *kubeconfigArgs.Namespace, 53 | }, 54 | } 55 | key := client.ObjectKeyFromObject(&object) 56 | if err := c.Get(context.Background(), key, &object); err != nil { 57 | return fmt.Errorf("%w in namespace %q", err, object.Namespace) 58 | } 59 | 60 | sourceKind := object.Spec.SourceRef.Kind 61 | sourceName := object.Spec.SourceRef.Name 62 | sourceNamespace := object.Spec.SourceRef.Namespace 63 | if sourceNamespace == "" { 64 | sourceNamespace = *kubeconfigArgs.Namespace 65 | } 66 | 67 | t, err := template.New("template").Parse(ksAppTemplate) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | var params struct { 73 | Name string 74 | Namespace string 75 | WorkloadType string 76 | WorkloadName string 77 | SourceType string 78 | DestinationNamespace string 79 | ClusterName string 80 | 81 | Server string 82 | 83 | Path string 84 | SourceURL string 85 | SourceRevision string 86 | } 87 | 88 | params.Name = appName 89 | params.Namespace = rootArgs.applicationNamespace 90 | params.DestinationNamespace = *kubeconfigArgs.Namespace 91 | 92 | params.WorkloadName = object.Name 93 | params.WorkloadType = kindName 94 | params.SourceType = sourceKind 95 | params.ClusterName = clusterName 96 | 97 | params.Server = server 98 | 99 | // The default path is '.' unless provided by the object 100 | params.Path = "." 101 | if object.Spec.Path != "" { 102 | params.Path = object.Spec.Path 103 | } 104 | 105 | switch sourceKind { 106 | case sourcev1.GitRepositoryKind: 107 | sourceObj := sourcev1.GitRepository{ 108 | ObjectMeta: metav1.ObjectMeta{ 109 | Name: sourceName, 110 | Namespace: sourceNamespace, 111 | }, 112 | } 113 | sourceKey := client.ObjectKeyFromObject(&sourceObj) 114 | if err := c.Get(context.Background(), sourceKey, &sourceObj); err != nil { 115 | return err 116 | } 117 | params.SourceURL = sourceObj.Spec.URL 118 | params.SourceRevision = getGitRepositorySourceRevision(sourceObj.Spec.Reference) 119 | case sourcev1b2.BucketKind: 120 | sourceObj := sourcev1b2.Bucket{ 121 | ObjectMeta: metav1.ObjectMeta{ 122 | Name: sourceName, 123 | Namespace: sourceNamespace, 124 | }, 125 | } 126 | sourceKey := client.ObjectKeyFromObject(&sourceObj) 127 | if err := c.Get(context.Background(), sourceKey, &sourceObj); err != nil { 128 | return err 129 | } 130 | params.SourceURL = getBucketURL(sourceObj.Spec) 131 | params.SourceRevision = "HEAD" 132 | case sourcev1b2.OCIRepositoryKind: 133 | sourceObj := sourcev1b2.OCIRepository{ 134 | ObjectMeta: metav1.ObjectMeta{ 135 | Name: sourceName, 136 | Namespace: sourceNamespace, 137 | }, 138 | } 139 | sourceKey := client.ObjectKeyFromObject(&sourceObj) 140 | if err := c.Get(context.Background(), sourceKey, &sourceObj); err != nil { 141 | return err 142 | } 143 | params.SourceURL = sourceObj.Spec.URL 144 | params.SourceRevision = getOCIRepositorySourceRevision(sourceObj.Spec.Reference) 145 | } 146 | 147 | if err := t.Execute(tpl, params); err != nil { 148 | return err 149 | } 150 | return nil 151 | } 152 | 153 | func getBucketURL(bs sourcev1b2.BucketSpec) string { 154 | switch bs.Provider { 155 | case "aws": 156 | if bs.Region == "" { 157 | return "" 158 | } 159 | return fmt.Sprintf("https://%s.s3.%s.amazonaws.com/", bs.BucketName, bs.Region) 160 | case "gcp": 161 | return fmt.Sprintf("https://storage.googleapis.com/%s/", bs.BucketName) 162 | case "azure": 163 | return fmt.Sprintf("https://%s.blob.core.windows.net/", bs.BucketName) 164 | case "generic": 165 | protocol := "https" 166 | if bs.Insecure { 167 | protocol = "http" 168 | } 169 | // Ensure the endpoint doesn't have a trailing slash. 170 | endpoint := strings.TrimRight(bs.Endpoint, "/") 171 | return fmt.Sprintf("%s://%s/%s/", protocol, endpoint, bs.BucketName) 172 | default: 173 | return "" 174 | } 175 | } 176 | 177 | func getGitRepositorySourceRevision(reference *sourcev1.GitRepositoryRef) string { 178 | if reference == nil { 179 | return "master" 180 | } 181 | 182 | if reference.Commit != "" { 183 | return reference.Commit 184 | } 185 | if reference.Name != "" { 186 | return reference.Name 187 | } 188 | if reference.SemVer != "" { 189 | return reference.SemVer 190 | } 191 | if reference.Tag != "" { 192 | return reference.Tag 193 | } 194 | if reference.Branch != "" { 195 | return reference.Branch 196 | } 197 | 198 | // the default value of the branch is master 199 | return "master" 200 | } 201 | 202 | func getOCIRepositorySourceRevision(reference *sourcev1b2.OCIRepositoryRef) string { 203 | if reference == nil { 204 | return "latest" 205 | } 206 | if reference.Digest != "" { 207 | return reference.Digest 208 | } 209 | if reference.SemVer != "" { 210 | return reference.SemVer 211 | } 212 | if reference.Tag != "" { 213 | return reference.Tag 214 | } 215 | 216 | return "latest" 217 | } 218 | -------------------------------------------------------------------------------- /cmd/flamingo/get.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/flux-subsystem-argo/flamingo/pkg/utils" 7 | "github.com/spf13/cobra" 8 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 9 | "k8s.io/apimachinery/pkg/runtime/schema" 10 | "os" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | "text/tabwriter" 13 | ) 14 | 15 | var getCmd = &cobra.Command{ 16 | Use: "get", 17 | Short: "Get Flamingo applications", 18 | Long: `Get Flamingo applications 19 | 20 | # List all Flamingo applications in the given namespace 21 | flamingo get --namespace=default 22 | NAMESPACE APP APP-TYPE REVISION SUSPENDED READY MESSAGE 23 | default podinfo kustomize latest@sha256:3f432793 False True Applied revision: latest@sha256:3f432793 24 | `, 25 | RunE: getCmdRun, 26 | } 27 | 28 | var getCmdFlags struct { 29 | all bool 30 | } 31 | 32 | func init() { 33 | getCmd.Flags().BoolVarP(&getCmdFlags.all, "all-namespaces", "A", false, "list all Flamingo applications in all namespaces") 34 | 35 | rootCmd.AddCommand(getCmd) 36 | } 37 | 38 | func getCmdRun(cmd *cobra.Command, args []string) error { 39 | cli, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | gvk := schema.GroupVersionKind{ 45 | Group: "argoproj.io", 46 | Version: "v1alpha1", 47 | Kind: "Application", 48 | } 49 | 50 | namespace := *kubeconfigArgs.Namespace 51 | labelSelector := map[string]string{ 52 | "app.kubernetes.io/managed-by": "flamingo", 53 | "flamingo/destination-namespace": namespace, 54 | } 55 | if getCmdFlags.all { 56 | delete(labelSelector, "flamingo/destination-namespace") 57 | } 58 | 59 | list := &unstructured.UnstructuredList{} 60 | list.SetGroupVersionKind(gvk) 61 | if err := cli.List(context.TODO(), list, client.InNamespace(rootArgs.applicationNamespace), client.MatchingLabels(labelSelector)); err != nil { 62 | return err 63 | } 64 | 65 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 66 | fmt.Fprintln(w, "NAMESPACE\tAPP-NS\tAPP\tFLUX-TYPE\tSOURCE-TYPE\tCLUSTER\tSTATUS\tMESSAGE") 67 | for _, item := range list.Items { 68 | labels := item.GetLabels() 69 | // Extract the necessary fields from the Unstructured object 70 | // This is just an example, you'll need to adjust based on the actual structure of your Argo CD objects 71 | appType := labels["flamingo/workload-type"] 72 | sourceType := labels["flamingo/source-type"] 73 | objectNs := labels["flamingo/destination-namespace"] 74 | clusterName := labels["flamingo/cluster-name"] 75 | status, err := extractStatus(&item) 76 | if err != nil { 77 | status = err.Error() 78 | } 79 | message, err := extractMessage(&item) 80 | if err != nil { 81 | message = err.Error() 82 | } 83 | if len(message) > 40 { 84 | message = message[:40] + " ..." 85 | } 86 | fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 87 | objectNs, 88 | item.GetNamespace(), 89 | item.GetName(), 90 | appType, 91 | sourceType, 92 | clusterName, 93 | status, 94 | message) 95 | } 96 | w.Flush() 97 | 98 | return nil 99 | } 100 | 101 | func extractMessage(item *unstructured.Unstructured) (string, error) { 102 | status, found, err := unstructured.NestedMap(item.Object, "status") 103 | if !found || err != nil { 104 | return "", fmt.Errorf("status field not found or error occurred: %v", err) 105 | } 106 | 107 | resources, found, err := unstructured.NestedSlice(status, "resources") 108 | if !found || err != nil { 109 | return "", fmt.Errorf("resources field not found or error occurred: %v", err) 110 | } 111 | 112 | for _, resource := range resources { 113 | resourceMap, ok := resource.(map[string]interface{}) 114 | if !ok { 115 | continue 116 | } 117 | 118 | health, found, err := unstructured.NestedMap(resourceMap, "health") 119 | if found && err == nil { 120 | message, found, err := unstructured.NestedString(health, "message") 121 | if found && err == nil { 122 | return message, nil 123 | } 124 | } 125 | } 126 | 127 | return "", fmt.Errorf("message not found") 128 | } 129 | 130 | func extractStatus(item *unstructured.Unstructured) (string, error) { 131 | status, found, err := unstructured.NestedMap(item.Object, "status") 132 | if !found || err != nil { 133 | return "", fmt.Errorf("status field not found or error occurred: %v", err) 134 | } 135 | 136 | healthStatus, found, err := unstructured.NestedString(status, "health", "status") 137 | if !found || err != nil { 138 | return "", fmt.Errorf("health.status field not found or error occurred: %v", err) 139 | } 140 | 141 | return healthStatus, nil 142 | } 143 | -------------------------------------------------------------------------------- /cmd/flamingo/install.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "os" 11 | "strings" 12 | "text/template" 13 | "time" 14 | 15 | "github.com/flux-subsystem-argo/flamingo/pkg/utils" 16 | "github.com/fluxcd/flux2/v2/pkg/status" 17 | "github.com/spf13/cobra" 18 | "k8s.io/apimachinery/pkg/runtime/schema" 19 | "sigs.k8s.io/cli-utils/pkg/object" 20 | "sigs.k8s.io/kustomize/api/filesys" 21 | "sigs.k8s.io/kustomize/api/krusty" 22 | ) 23 | 24 | var installCmd = &cobra.Command{ 25 | Use: "install", 26 | Short: "Install the Flux Subsystem for Argo", 27 | Long: fmt.Sprintf(` 28 | # Install the Flux Subsystem for Argo with the default version 29 | flamingo install 30 | 31 | # Install the Flux Subsystem for Argo 32 | flamingo install --version=%s 33 | 34 | # Install the Flux Subsystem for Argo with the anonymous UI enabled 35 | flamingo install --version=%s --anonymous 36 | 37 | # Install the Flux Subsystem for Argo with HelmRelease 38 | flamingo install --mode=helmrelease 39 | `, ServerVersion, ServerVersion), 40 | RunE: installCmdRun, 41 | } 42 | 43 | var installFlags struct { 44 | version string 45 | dev bool 46 | anonymous bool 47 | export bool 48 | mode string 49 | } 50 | 51 | const ( 52 | CRDsOnlyMode = "crds-only" 53 | AllMode = "all" 54 | TenantMode = "tenant" 55 | HelmReleaseMode = "helmrelease" 56 | ) 57 | 58 | var validModes = map[string]bool{ 59 | CRDsOnlyMode: true, 60 | AllMode: true, 61 | TenantMode: true, 62 | HelmReleaseMode: true, 63 | } 64 | 65 | func init() { 66 | installCmd.Flags().StringVarP(&installFlags.version, "version", "v", ServerVersion, "version of Flamingo to install") 67 | installCmd.Flags().BoolVar(&installFlags.anonymous, "anonymous", false, "enable anonymous UI") 68 | installCmd.Flags().StringVar(&installFlags.mode, "mode", AllMode, "installation mode [crds-only, all, tenant, helmrelease]") 69 | installCmd.Flags().BoolVar(&installFlags.export, "export", false, "export manifests instead of installing") 70 | 71 | rootCmd.AddCommand(installCmd) 72 | } 73 | 74 | func installCmdRun(cmd *cobra.Command, args []string) error { 75 | if _, valid := validModes[installFlags.mode]; !valid { 76 | return fmt.Errorf("invalid mode: %s", installFlags.mode) 77 | } 78 | 79 | if installFlags.export { 80 | logger.stderr = io.Discard 81 | } 82 | 83 | if installFlags.version == "" { 84 | return cmd.Help() 85 | } 86 | if strings.HasSuffix(installFlags.version, "-dev") { 87 | installFlags.dev = true 88 | } 89 | // prefix version with "v" if it doesn't start with it 90 | // so we can use this command like this: flamingo install -v2.0.0 91 | if strings.HasPrefix(installFlags.version, "v") == false { 92 | installFlags.version = "v" + installFlags.version 93 | } 94 | 95 | logger.Actionf("obtaining version info %s", installFlags.version) 96 | 97 | client := &http.Client{} 98 | 99 | // Create a new request 100 | req, err := http.NewRequest("GET", url, nil) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | // Set headers to prevent caching 106 | req.Header.Set("Cache-Control", "no-cache, no-store, must-revalidate") 107 | req.Header.Set("Pragma", "no-cache") 108 | 109 | // Execute the request 110 | resp, err := client.Do(req) 111 | if err != nil { 112 | return err 113 | } 114 | defer resp.Body.Close() 115 | 116 | // Read the entire response body into a byte buffer 117 | buf, err := io.ReadAll(resp.Body) 118 | if err != nil { 119 | return err 120 | } 121 | 122 | var candidates CandidateList 123 | if err := json.Unmarshal(buf, &candidates); err != nil { 124 | return err 125 | } 126 | 127 | // filter out .Flamingo that ends with -dev if --dev flag is not set 128 | if !installFlags.dev { 129 | var filteredCandidates []Candidate 130 | for _, candidate := range candidates.Candidates { 131 | if !isDev(candidate) { 132 | filteredCandidates = append(filteredCandidates, candidate) 133 | } 134 | } 135 | candidates.Candidates = filteredCandidates 136 | } 137 | 138 | var candidate *Candidate 139 | for i, c := range candidates.Candidates { 140 | if c.Flamingo == installFlags.version { 141 | candidate = &candidates.Candidates[i] 142 | break 143 | } 144 | } 145 | if candidate == nil { 146 | return fmt.Errorf("version %s not found", installFlags.version) 147 | } 148 | 149 | if err := installFluxSubsystemForArgo(*candidate, installFlags.mode, installFlags.anonymous, installFlags.export); err != nil { 150 | return err 151 | } 152 | 153 | if installFlags.export || installFlags.mode == CRDsOnlyMode { 154 | // do not verify the installation if we are exporting the manifests or installing CRDs only 155 | } else { 156 | if err := verifyTheInstallation(); err != nil { 157 | return err 158 | } 159 | } 160 | 161 | return nil 162 | } 163 | 164 | func buildComponentObjectRefs(namespace string, components ...string) ([]object.ObjMetadata, error) { 165 | var objRefs []object.ObjMetadata 166 | for _, deployment := range components { 167 | objRefs = append(objRefs, object.ObjMetadata{ 168 | Namespace: namespace, 169 | Name: deployment, 170 | GroupKind: schema.GroupKind{Group: "apps", Kind: "Deployment"}, 171 | }) 172 | } 173 | return objRefs, nil 174 | } 175 | 176 | func verifyTheInstallation() error { 177 | logger.Waitingf("verifying installation") 178 | 179 | kubeConfig, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions) 180 | if err != nil { 181 | return fmt.Errorf("install failed: %w", err) 182 | } 183 | 184 | statusChecker, err := status.NewStatusChecker(kubeConfig, 5*time.Second, rootArgs.timeout, logger) 185 | if err != nil { 186 | return fmt.Errorf("install failed: %w", err) 187 | } 188 | 189 | components := []string{ 190 | "argocd-redis", 191 | "argocd-dex-server", 192 | "argocd-repo-server", 193 | "argocd-server", 194 | "argocd-notifications-controller", 195 | "argocd-applicationset-controller", 196 | } 197 | 198 | // we install Argo CD components in the application namespace (argocd), not the flux-system namespace 199 | objectRefs, err := buildComponentObjectRefs( 200 | rootArgs.applicationNamespace, 201 | components..., 202 | ) 203 | 204 | if err != nil { 205 | return fmt.Errorf("install failed: %w", err) 206 | } 207 | 208 | if err := statusChecker.Assess(objectRefs...); err != nil { 209 | return fmt.Errorf("install failed: %w", err) 210 | } 211 | 212 | logger.Successf("install finished") 213 | return nil 214 | } 215 | 216 | func installFluxSubsystemForArgo(candidate Candidate, installMode string, anonymous bool, export bool) error { 217 | logger.Generatef("generating manifests") 218 | 219 | var tmpl string 220 | switch installMode { 221 | case AllMode: 222 | tmpl = allInstallTemplate 223 | case CRDsOnlyMode: 224 | tmpl = crdsOnlyInstallTemplate 225 | case TenantMode: 226 | tmpl = namespaceInstallTemplate 227 | case HelmReleaseMode: 228 | tmpl = helmReleaseInstallTemplate 229 | } 230 | 231 | patches := "" 232 | if anonymous { 233 | patches = anonymousPatches 234 | } 235 | 236 | var tpl bytes.Buffer 237 | t, err := template.New("template").Parse(tmpl) 238 | if err != nil { 239 | return err 240 | } 241 | 242 | if err := t.Execute(&tpl, struct { 243 | Flamingo string 244 | ArgoCD string 245 | Image string 246 | Namespace string 247 | AnonymousPatches string 248 | }{ 249 | Flamingo: candidate.Flamingo, 250 | ArgoCD: candidate.ArgoCD, 251 | Image: candidate.Image, 252 | Namespace: rootArgs.applicationNamespace, 253 | AnonymousPatches: patches, 254 | }); err != nil { 255 | return err 256 | } 257 | 258 | var yamlOutput []byte 259 | if installMode == HelmReleaseMode { 260 | yamlOutput = tpl.Bytes() 261 | } else { 262 | // Use Kustomize (krusty) to build the kustomization 263 | fSys := filesys.MakeFsInMemory() 264 | kustomizationPath := "/app/kustomization.yaml" 265 | fSys.WriteFile(kustomizationPath, tpl.Bytes()) 266 | 267 | namespacePath := "/app/namespace.yaml" 268 | if installMode == CRDsOnlyMode { 269 | fSys.WriteFile(namespacePath, []byte("# empty")) 270 | } else { 271 | fSys.WriteFile(namespacePath, []byte(fmt.Sprintf(namespaceTemplate, rootArgs.applicationNamespace))) 272 | } 273 | 274 | clusterPath := "/app/cluster.yaml" 275 | if installMode == TenantMode { 276 | fSys.WriteFile(clusterPath, []byte( 277 | fmt.Sprintf(defaultClusterSecretTemplate, 278 | rootArgs.applicationNamespace, 279 | rootArgs.applicationNamespace, 280 | rootArgs.applicationNamespace, 281 | rootArgs.applicationNamespace, 282 | rootArgs.applicationNamespace, 283 | ))) 284 | } else { 285 | fSys.WriteFile(clusterPath, []byte("# empty")) 286 | } 287 | 288 | opts := krusty.MakeDefaultOptions() 289 | opts.Reorder = krusty.ReorderOptionLegacy 290 | k := krusty.MakeKustomizer(opts) 291 | 292 | m, err := k.Run(fSys, "/app") 293 | if err != nil { 294 | return err 295 | } 296 | 297 | yamlOutput, err = m.AsYaml() 298 | if err != nil { 299 | return err 300 | } 301 | logger.Successf("manifests build completed") 302 | } 303 | 304 | if export { 305 | fmt.Println(string(yamlOutput)) 306 | return nil 307 | } 308 | 309 | if installMode == CRDsOnlyMode { 310 | // install CRDs only 311 | logger.Actionf("installing CRDs only") 312 | } else { 313 | // install everything 314 | logger.Actionf("installing components in %s namespace", rootArgs.applicationNamespace) 315 | } 316 | 317 | ctx, cancelFn := context.WithTimeout(context.Background(), rootArgs.timeout) 318 | defer cancelFn() 319 | applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, yamlOutput) 320 | if err != nil { 321 | return fmt.Errorf("install failed: %w", err) 322 | } 323 | fmt.Fprintln(os.Stderr, applyOutput) 324 | 325 | return nil 326 | } 327 | -------------------------------------------------------------------------------- /cmd/flamingo/install_templates.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // There are two components currently in the Flamingo project: 4 | // - the ArgoCD controllers 5 | // - the Flamingo controller 6 | 7 | const allInstallTemplate = ` 8 | apiVersion: kustomize.config.k8s.io/v1beta1 9 | kind: Kustomization 10 | namespace: {{ .Namespace }} 11 | resources: 12 | - namespace.yaml 13 | - "https://raw.githubusercontent.com/argoproj/argo-cd/{{ .ArgoCD }}/manifests/install.yaml" 14 | images: 15 | - name: quay.io/argoproj/argocd:{{ .ArgoCD }} 16 | newName: ghcr.io/flux-subsystem-argo/fsa/argocd 17 | newTag: {{ .Image }} 18 | {{ .AnonymousPatches }} 19 | ` 20 | 21 | const crdsOnlyInstallTemplate = ` 22 | apiVersion: kustomize.config.k8s.io/v1beta1 23 | kind: Kustomization 24 | resources: 25 | - "https://raw.githubusercontent.com/argoproj/argo-cd/{{ .ArgoCD }}/manifests/crds/application-crd.yaml" 26 | - "https://raw.githubusercontent.com/argoproj/argo-cd/{{ .ArgoCD }}/manifests/crds/applicationset-crd.yaml" 27 | - "https://raw.githubusercontent.com/argoproj/argo-cd/{{ .ArgoCD }}/manifests/crds/appproject-crd.yaml" 28 | ` 29 | 30 | const namespaceInstallTemplate = ` 31 | apiVersion: kustomize.config.k8s.io/v1beta1 32 | kind: Kustomization 33 | namespace: {{ .Namespace }} 34 | resources: 35 | - namespace.yaml 36 | - "https://raw.githubusercontent.com/argoproj/argo-cd/{{ .ArgoCD }}/manifests/namespace-install.yaml" 37 | - cluster.yaml 38 | images: 39 | - name: quay.io/argoproj/argocd:{{ .ArgoCD }} 40 | newName: ghcr.io/flux-subsystem-argo/fsa/argocd 41 | newTag: {{ .Image }} 42 | {{ .AnonymousPatches }} 43 | ` 44 | 45 | const anonymousPatches = ` 46 | patches: 47 | - patch: |- 48 | apiVersion: v1 49 | kind: ConfigMap 50 | metadata: 51 | name: argocd-cm 52 | labels: 53 | app.kubernetes.io/name: argocd-cm 54 | app.kubernetes.io/part-of: argocd 55 | data: 56 | users.anonymous.enabled: "true" 57 | target: 58 | kind: ConfigMap 59 | name: argocd-cm 60 | - patch: |- 61 | apiVersion: v1 62 | kind: ConfigMap 63 | metadata: 64 | labels: 65 | app.kubernetes.io/name: argocd-rbac-cm 66 | app.kubernetes.io/part-of: argocd 67 | name: argocd-rbac-cm 68 | data: 69 | policy.default: role:readonly 70 | policy.csv: | 71 | p, role:readonly, applications, get, default/*, allow 72 | p, role:readonly, applications, sync, default/*, allow 73 | p, role:readonly, clusters, get, *, allow 74 | p, role:readonly, repositories, get, *, allow 75 | p, role:readonly, project, get, default/*, allow 76 | target: 77 | kind: ConfigMap 78 | name: argocd-rbac-cm 79 | ` 80 | 81 | var namespaceTemplate = ` 82 | apiVersion: v1 83 | kind: Namespace 84 | metadata: 85 | name: %s 86 | ` 87 | 88 | const defaultClusterSecretTemplate = ` 89 | --- 90 | apiVersion: v1 91 | kind: Secret 92 | metadata: 93 | name: cluster-kubernetes.default.svc 94 | namespace: %s 95 | annotations: 96 | managed-by: argocd.argoproj.io 97 | labels: 98 | argocd.argoproj.io/secret-type: cluster 99 | type: Opaque 100 | stringData: 101 | config: '{"tlsClientConfig":{"insecure":false}}' 102 | name: in-cluster 103 | namespaces: %s 104 | server: https://kubernetes.default.svc 105 | --- 106 | apiVersion: rbac.authorization.k8s.io/v1 107 | kind: RoleBinding 108 | metadata: 109 | labels: 110 | toolkit.fluxcd.io/tenant: %s 111 | name: flamingo-reconciler 112 | namespace: %s 113 | roleRef: 114 | apiGroup: rbac.authorization.k8s.io 115 | kind: ClusterRole 116 | name: cluster-admin 117 | subjects: 118 | - kind: ServiceAccount 119 | name: argocd-application-controller 120 | namespace: %s 121 | ` 122 | 123 | const helmReleaseInstallTemplate = ` 124 | --- 125 | apiVersion: v1 126 | kind: Namespace 127 | metadata: 128 | name: {{ .Namespace }} 129 | --- 130 | apiVersion: source.toolkit.fluxcd.io/v1beta2 131 | kind: HelmRepository 132 | metadata: 133 | name: argo-helm-repo 134 | namespace: {{ .Namespace }} 135 | spec: 136 | interval: 10m 137 | url: https://argoproj.github.io/argo-helm 138 | --- 139 | apiVersion: helm.toolkit.fluxcd.io/v2beta2 140 | kind: HelmRelease 141 | metadata: 142 | name: flamingo 143 | namespace: {{ .Namespace }} 144 | spec: 145 | interval: 10m 146 | targetNamespace: argocd 147 | releaseName: argocd 148 | chart: 149 | spec: 150 | chart: argo-cd 151 | version: '*' 152 | sourceRef: 153 | kind: HelmRepository 154 | name: argo-helm-repo 155 | values: 156 | global: 157 | image: 158 | repository: ghcr.io/flux-subsystem-argo/fsa/argocd 159 | tag: {{ .Image }} 160 | ` 161 | -------------------------------------------------------------------------------- /cmd/flamingo/list_candidates.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "strings" 10 | "text/tabwriter" 11 | 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | const url = "https://raw.githubusercontent.com/flux-subsystem-argo/flamingo/main/index/index.json" 16 | 17 | var listCandidates = &cobra.Command{ 18 | Use: "list-candidates", 19 | Aliases: []string{"list-candidate", "candidates", "candidate"}, 20 | Short: "List installation candidates", 21 | RunE: listCmdRun, 22 | } 23 | 24 | var listCandidatesFlags struct { 25 | dev bool 26 | } 27 | 28 | func init() { 29 | listCandidates.Flags().BoolVar(&listCandidatesFlags.dev, "dev", false, "list development candidates") 30 | 31 | rootCmd.AddCommand(listCandidates) 32 | } 33 | 34 | func listCmdRun(cmd *cobra.Command, args []string) error { 35 | client := &http.Client{} 36 | 37 | // Create a new request 38 | req, err := http.NewRequest("GET", url, nil) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | // Set headers to prevent caching 44 | req.Header.Set("Cache-Control", "no-cache, no-store, must-revalidate") 45 | req.Header.Set("Pragma", "no-cache") 46 | 47 | // Execute the request 48 | resp, err := client.Do(req) 49 | if err != nil { 50 | return err 51 | } 52 | defer resp.Body.Close() 53 | 54 | // Read the entire response body into a byte buffer 55 | buf, err := io.ReadAll(resp.Body) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | var candidates CandidateList 61 | if err := json.Unmarshal(buf, &candidates); err != nil { 62 | return err 63 | } 64 | 65 | // Use tabwriter to print in table format 66 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.TabIndent) 67 | 68 | fmt.Fprintln(w, "FLAMINGO\tFSA-IMAGE\tSUPPORTED FLUX") 69 | for _, candidate := range candidates.Candidates { 70 | if !listCandidatesFlags.dev && isDev(candidate) { 71 | continue 72 | } 73 | fmt.Fprintf(w, "%s\t%s\t%s\n", candidate.Flamingo, candidate.Image, candidate.Flux) 74 | } 75 | w.Flush() 76 | 77 | return nil 78 | } 79 | 80 | func isDev(candidate Candidate) bool { 81 | return strings.HasSuffix(candidate.Flamingo, "-dev") 82 | } 83 | -------------------------------------------------------------------------------- /cmd/flamingo/list_cluster.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | ctx "context" 5 | "fmt" 6 | "github.com/flux-subsystem-argo/flamingo/pkg/utils" 7 | "github.com/spf13/cobra" 8 | corev1 "k8s.io/api/core/v1" 9 | "k8s.io/apimachinery/pkg/labels" 10 | "os" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | "text/tabwriter" 13 | ) 14 | 15 | var listClusterCmd = &cobra.Command{ 16 | Use: "list-clusters", 17 | Aliases: []string{"list-cluster", "lc"}, 18 | Short: "List clusters", 19 | Long: ` 20 | # List clusters 21 | flamingo list-clusters 22 | `, 23 | Args: cobra.NoArgs, 24 | RunE: listClusterCmdRun, 25 | } 26 | 27 | func init() { 28 | rootCmd.AddCommand(listClusterCmd) 29 | } 30 | 31 | func listClusterCmdRun(_ *cobra.Command, _ []string) error { 32 | cli, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) 33 | if err != nil { 34 | return err 35 | } 36 | // list clusters 37 | list := &corev1.SecretList{} 38 | opts := &client.ListOptions{ 39 | Namespace: rootArgs.applicationNamespace, 40 | LabelSelector: labels.SelectorFromSet(map[string]string{ 41 | "flamingo/cluster": "true", 42 | }), 43 | } 44 | if err := cli.List(ctx.Background(), list, opts); err != nil { 45 | return err 46 | } 47 | 48 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) 49 | fmt.Fprintln(w, "NAME\tEXTERNAL ADDRESS\tINTERNAL ADDRESS") 50 | // hard code in-cluster info 51 | fmt.Fprintf(w, "%s\t%s\t%s\n", "in-cluster", "-", "https://kubernetes.default.svc") 52 | for _, s := range list.Items { 53 | fmt.Fprintf(w, "%s\t%s\t%s\n", string(s.Data["name"]), s.Annotations["flamingo/external-address"], s.Annotations["flamingo/internal-address"]) 54 | } 55 | w.Flush() 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /cmd/flamingo/log.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Flux authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | ) 23 | 24 | type stderrLogger struct { 25 | stderr io.Writer 26 | } 27 | 28 | func (l stderrLogger) Actionf(format string, a ...interface{}) { 29 | fmt.Fprintln(l.stderr, `►`, fmt.Sprintf(format, a...)) 30 | } 31 | 32 | func (l stderrLogger) Generatef(format string, a ...interface{}) { 33 | fmt.Fprintln(l.stderr, `✚`, fmt.Sprintf(format, a...)) 34 | } 35 | 36 | func (l stderrLogger) Waitingf(format string, a ...interface{}) { 37 | fmt.Fprintln(l.stderr, `◎`, fmt.Sprintf(format, a...)) 38 | } 39 | 40 | func (l stderrLogger) Successf(format string, a ...interface{}) { 41 | fmt.Fprintln(l.stderr, `✔`, fmt.Sprintf(format, a...)) 42 | } 43 | 44 | func (l stderrLogger) Warningf(format string, a ...interface{}) { 45 | fmt.Fprintln(l.stderr, `⚠️`, fmt.Sprintf(format, a...)) 46 | } 47 | 48 | func (l stderrLogger) Failuref(format string, a ...interface{}) { 49 | fmt.Fprintln(l.stderr, `✗`, fmt.Sprintf(format, a...)) 50 | } 51 | -------------------------------------------------------------------------------- /cmd/flamingo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | runclient "github.com/fluxcd/pkg/runtime/client" 10 | "github.com/go-logr/logr" 11 | "github.com/spf13/cobra" 12 | "k8s.io/apimachinery/pkg/util/validation" 13 | "k8s.io/cli-runtime/pkg/genericclioptions" 14 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 15 | ) 16 | 17 | var rootCmd = &cobra.Command{ 18 | Use: "flamingo", 19 | Version: Version, 20 | SilenceUsage: true, 21 | SilenceErrors: true, 22 | Short: "CLI of Flamingo - the Flux Subsystem for Argo", 23 | Long: `CLI of Flamingo - the Flux Subsystem for Argo 24 | # List all Flamingo candidates. 25 | flamingo list-candidates 26 | 27 | # List all Flamingo candidates including development versions. 28 | flamingo list-candidates --dev 29 | 30 | # Install Flamingo in the argocd namespace. 31 | flamingo install 32 | 33 | # Install Flamingo in the argocd namespace with the anonymous UI enabled. 34 | flamingo install --anonymous 35 | 36 | # Install Flamingo in the argocd namespace with the development version. 37 | flamingo install --dev --version=v2.8.3-dev 38 | 39 | # Install CRDs only at the cluster level. Required only once per cluster before installing Flamingo tenants. 40 | flamingo install --mode=crds-only 41 | 42 | # Install Flamingo in the Tenant mode in the dev-team namespace (requires the CRDs to be installed first). 43 | flamingo install --app-ns=dev-team --mode=tenant 44 | 45 | # Show initial password for the admin user. 46 | flamingo show-init-password 47 | 48 | # Generate a Flamingo application from a Flux Kustomization podinfo in the current namespace (flux-system). 49 | # The generated application is put in the argocd namespace by default. 50 | flamingo generate-app ks/podinfo 51 | 52 | # Generate a Flamingo application from a Flux Kustomization podinfo in the podinfo namespace. 53 | # The generated application is put in the argocd namespace by default. 54 | flamingo generate-app -n podinfo ks/podinfo 55 | 56 | # Generate a Flamingo application from a HelmRelease podinfo in the current namespace (flux-system). 57 | # The generated application is put in the argocd namespace by default. 58 | flamingo generate-app hr/podinfo 59 | 60 | # Generate a Flamingo application (Tenant mode) from a Flux Kustomization podinfo in the dev-team namespace. 61 | # The generated application is put in the dev-team namespace. 62 | flamingo generate-app \ 63 | --app-ns=dev-team \ 64 | -n dev-team \ 65 | --app-name=dev-team-podinfo \ 66 | ks/podinfo 67 | 68 | # List all Flamingo applications in the given namespace 69 | flamingo get --namespace=default 70 | 71 | # List all Flamingo applications in all namespaces 72 | flamingo get --all-namespaces 73 | `, 74 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 75 | ns, err := cmd.Flags().GetString("namespace") 76 | if err != nil { 77 | return fmt.Errorf("error getting namespace: %w", err) 78 | } 79 | 80 | if e := validation.IsDNS1123Label(ns); len(e) > 0 { 81 | return fmt.Errorf("namespace must be a valid DNS label: %q", ns) 82 | } 83 | 84 | return nil 85 | }, 86 | } 87 | 88 | var logger = stderrLogger{stderr: os.Stderr} 89 | 90 | type rootFlags struct { 91 | timeout time.Duration 92 | verbose bool 93 | pollInterval time.Duration 94 | applicationNamespace string 95 | } 96 | 97 | const defaultNamespace = "flux-system" 98 | const defaultApplicationName = "argocd" 99 | 100 | var ( 101 | rootArgs = newRootFlags() 102 | kubeconfigArgs = genericclioptions.NewConfigFlags(false) 103 | kubeclientOptions = new(runclient.Options) 104 | ) 105 | 106 | func init() { 107 | rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 10*time.Minute, "timeout for this operation") 108 | rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects") 109 | rootCmd.PersistentFlags().StringVarP(&rootArgs.applicationNamespace, "app-ns", "N", defaultApplicationName, "namespace where Flamingo and applications are located") 110 | 111 | configureDefaultNamespace() 112 | kubeconfigArgs.APIServer = nil // prevent AddFlags from configuring --server flag 113 | kubeconfigArgs.Timeout = nil // prevent AddFlags from configuring --request-timeout flag, we have --timeout instead 114 | kubeconfigArgs.AddFlags(rootCmd.PersistentFlags()) 115 | 116 | // Since some subcommands use the `-s` flag as a short version for `--silent`, we manually configure the server flag 117 | // without the `-s` short version. While we're no longer on par with kubectl's flags, we maintain backwards compatibility 118 | // on the CLI interface. 119 | apiServer := "" 120 | kubeconfigArgs.APIServer = &apiServer 121 | rootCmd.PersistentFlags().StringVar(kubeconfigArgs.APIServer, "server", *kubeconfigArgs.APIServer, "The address and port of the Kubernetes API server") 122 | 123 | kubeclientOptions.BindFlags(rootCmd.PersistentFlags()) 124 | 125 | rootCmd.DisableAutoGenTag = true 126 | rootCmd.SetOut(os.Stdout) 127 | } 128 | 129 | func newRootFlags() rootFlags { 130 | rf := rootFlags{ 131 | pollInterval: 2 * time.Second, 132 | } 133 | return rf 134 | } 135 | 136 | func configureDefaultNamespace() { 137 | *kubeconfigArgs.Namespace = defaultNamespace 138 | fromEnv := os.Getenv("FLUX_SYSTEM_NAMESPACE") 139 | if fromEnv != "" { 140 | // namespace must be a valid DNS label. Assess against validation 141 | // used upstream, and ignore invalid values as environment vars 142 | // may not be actively provided by end-user. 143 | if e := validation.IsDNS1123Label(fromEnv); len(e) > 0 { 144 | logger.Failuref(" ignoring invalid FLUX_SYSTEM_NAMESPACE: %v", fromEnv) 145 | return 146 | } 147 | 148 | kubeconfigArgs.Namespace = &fromEnv 149 | } 150 | } 151 | 152 | func main() { 153 | log.SetFlags(0) 154 | 155 | // This is required because controller-runtime expects its consumers to 156 | // set a logger through log.SetLogger within 30 seconds of the program's 157 | // initalization. If not set, the entire debug stack is printed as an 158 | // error, see: https://github.com/kubernetes-sigs/controller-runtime/blob/ed8be90/pkg/log/log.go#L59 159 | // Since we have our own logging and don't care about controller-runtime's 160 | // logger, we configure it's logger to do nothing. 161 | ctrllog.SetLogger(logr.New(ctrllog.NullLogSink{})) 162 | 163 | if err := rootCmd.Execute(); err != nil { 164 | logger.Failuref("%v", err) 165 | os.Exit(1) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /cmd/flamingo/port_fwd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "os" 6 | "os/exec" 7 | ) 8 | 9 | var portFwdCmd = &cobra.Command{ 10 | Use: "port-fwd", 11 | Args: cobra.NoArgs, 12 | Aliases: []string{"port-forward", "pf"}, 13 | Short: "Port forward to the Flamingo service", 14 | RunE: portFwdCmdRun, 15 | } 16 | 17 | var portFwdFlags struct { 18 | port string 19 | } 20 | 21 | func init() { 22 | portFwdCmd.Flags().StringVar(&portFwdFlags.port, "port", "8080", "port to forward to") 23 | 24 | rootCmd.AddCommand(portFwdCmd) 25 | } 26 | 27 | func portFwdCmdRun(_ *cobra.Command, _ []string) error { 28 | cmd := exec.Command("kubectl", "-n", rootArgs.applicationNamespace, "port-forward", "svc/argocd-server", portFwdFlags.port+":443") 29 | cmd.Stdout = os.Stdout 30 | cmd.Stderr = os.Stderr 31 | logger.Actionf("running kubectl -n %s port-forward svc/argocd-server %s:443", rootArgs.applicationNamespace, portFwdFlags.port) 32 | logger.Waitingf("address: https://localhost:%s", portFwdFlags.port) 33 | return cmd.Run() 34 | } 35 | -------------------------------------------------------------------------------- /cmd/flamingo/show_init_password.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/flux-subsystem-argo/flamingo/pkg/utils" 8 | "github.com/spf13/cobra" 9 | corev1 "k8s.io/api/core/v1" 10 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | ) 13 | 14 | var showInitPasswordCmd = &cobra.Command{ 15 | Use: "show-init-password", 16 | Aliases: []string{"show-init-pass", "show-init-pwd"}, 17 | Short: "Show the init password", 18 | Long: ` 19 | # Show the init password 20 | flamingo show-init-password 21 | `, 22 | RunE: runShowInitPassword, 23 | } 24 | 25 | func init() { 26 | rootCmd.AddCommand(showInitPasswordCmd) 27 | } 28 | 29 | func runShowInitPassword(cmd *cobra.Command, args []string) error { 30 | return showInitPassword() 31 | } 32 | 33 | // showInitPassword shows the init password 34 | func showInitPassword() error { 35 | cli, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) 36 | if err != nil { 37 | return err 38 | } 39 | secret := corev1.Secret{ 40 | ObjectMeta: v1.ObjectMeta{ 41 | Name: "argocd-initial-admin-secret", 42 | Namespace: rootArgs.applicationNamespace, 43 | }, 44 | } 45 | 46 | if err := cli.Get(context.Background(), client.ObjectKeyFromObject(&secret), &secret); err != nil { 47 | return err 48 | } 49 | fmt.Println(string(secret.Data["password"])) 50 | 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /cmd/flamingo/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | var ServerVersion = "dev" 6 | var Version = "dev" 7 | 8 | var versionCmd = &cobra.Command{ 9 | Use: "version", 10 | Short: "Print the version number of Flamingo", 11 | Long: ` 12 | # Print the version numbers of Flamingo CLI and Server 13 | flamingo version 14 | `, 15 | Run: func(cmd *cobra.Command, args []string) { 16 | cmd.Printf("Flamingo CLI version %s - Server version %s\n", Version, ServerVersion) 17 | }, 18 | } 19 | 20 | func init() { 21 | rootCmd.AddCommand(versionCmd) 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/flux-subsystem-argo/flamingo 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/fluxcd/flux2/v2 v2.0.1 7 | github.com/fluxcd/helm-controller/api v0.35.0 8 | github.com/fluxcd/kustomize-controller/api v1.0.0 9 | github.com/fluxcd/pkg/runtime v0.40.0 10 | github.com/fluxcd/pkg/ssa v0.32.0 11 | github.com/fluxcd/source-controller/api v1.0.0 12 | github.com/go-logr/logr v1.2.4 13 | github.com/spf13/cobra v1.7.0 14 | k8s.io/api v0.27.4 15 | k8s.io/apiextensions-apiserver v0.27.4 16 | k8s.io/apimachinery v0.27.4 17 | k8s.io/cli-runtime v0.27.4 18 | k8s.io/client-go v0.27.4 19 | sigs.k8s.io/cli-utils v0.35.0 20 | sigs.k8s.io/controller-runtime v0.15.1 21 | sigs.k8s.io/kustomize/api v0.13.4 22 | ) 23 | 24 | require ( 25 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect 26 | github.com/MakeNowJust/heredoc v1.0.0 // indirect 27 | github.com/beorn7/perks v1.0.1 // indirect 28 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 29 | github.com/chai2010/gettext-go v1.0.2 // indirect 30 | github.com/davecgh/go-spew v1.1.1 // indirect 31 | github.com/emicklei/go-restful/v3 v3.10.0 // indirect 32 | github.com/evanphx/json-patch v5.6.0+incompatible // indirect 33 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 34 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect 35 | github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect 36 | github.com/fluxcd/pkg/apis/kustomize v1.1.1 // indirect 37 | github.com/fluxcd/pkg/apis/meta v1.1.1 // indirect 38 | github.com/fsnotify/fsnotify v1.6.0 // indirect 39 | github.com/go-errors/errors v1.4.2 // indirect 40 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 41 | github.com/go-openapi/jsonreference v0.20.2 // indirect 42 | github.com/go-openapi/swag v0.22.3 // indirect 43 | github.com/gogo/protobuf v1.3.2 // indirect 44 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 45 | github.com/golang/protobuf v1.5.3 // indirect 46 | github.com/google/btree v1.1.2 // indirect 47 | github.com/google/gnostic v0.6.9 // indirect 48 | github.com/google/go-cmp v0.5.9 // indirect 49 | github.com/google/gofuzz v1.2.0 // indirect 50 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 51 | github.com/google/uuid v1.3.0 // indirect 52 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 53 | github.com/imdario/mergo v0.3.15 // indirect 54 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 55 | github.com/josharian/intern v1.0.0 // indirect 56 | github.com/json-iterator/go v1.1.12 // indirect 57 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 58 | github.com/mailru/easyjson v0.7.7 // indirect 59 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 60 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 61 | github.com/moby/spdystream v0.2.0 // indirect 62 | github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect 63 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 64 | github.com/modern-go/reflect2 v1.0.2 // indirect 65 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 66 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 67 | github.com/onsi/ginkgo/v2 v2.11.0 // indirect 68 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 69 | github.com/pkg/errors v0.9.1 // indirect 70 | github.com/prometheus/client_golang v1.16.0 // indirect 71 | github.com/prometheus/client_model v0.4.0 // indirect 72 | github.com/prometheus/common v0.42.0 // indirect 73 | github.com/prometheus/procfs v0.10.1 // indirect 74 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 75 | github.com/spf13/pflag v1.0.5 // indirect 76 | github.com/xlab/treeprint v1.2.0 // indirect 77 | go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect 78 | golang.org/x/net v0.13.0 // indirect 79 | golang.org/x/oauth2 v0.8.0 // indirect 80 | golang.org/x/sync v0.3.0 // indirect 81 | golang.org/x/sys v0.10.0 // indirect 82 | golang.org/x/term v0.10.0 // indirect 83 | golang.org/x/text v0.11.0 // indirect 84 | golang.org/x/time v0.3.0 // indirect 85 | gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect 86 | google.golang.org/appengine v1.6.7 // indirect 87 | google.golang.org/protobuf v1.30.0 // indirect 88 | gopkg.in/inf.v0 v0.9.1 // indirect 89 | gopkg.in/yaml.v2 v2.4.0 // indirect 90 | gopkg.in/yaml.v3 v3.0.1 // indirect 91 | k8s.io/component-base v0.27.4 // indirect 92 | k8s.io/klog/v2 v2.100.1 // indirect 93 | k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515 // indirect 94 | k8s.io/kubectl v0.27.3 // indirect 95 | k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect 96 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 97 | sigs.k8s.io/kustomize/kyaml v0.14.2 // indirect 98 | sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect 99 | sigs.k8s.io/yaml v1.3.0 // indirect 100 | ) 101 | 102 | // Important fix 103 | // go get k8s.io/kube-openapi@2546d827e515dca59571ec245eef2302e11018e1 && go mod tidy 104 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 4 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 6 | github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= 7 | github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 8 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 9 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 10 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 11 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 12 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 13 | github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= 14 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 15 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 16 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 17 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 18 | github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= 19 | github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= 20 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 21 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 22 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 23 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 24 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 25 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 26 | github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 27 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 28 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 29 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 30 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 32 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 33 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 34 | github.com/emicklei/go-restful/v3 v3.10.0 h1:X4gma4HM7hFm6WMeAsTfqA0GOfdNoCzBIkHGoRLGXuM= 35 | github.com/emicklei/go-restful/v3 v3.10.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 36 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 37 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 38 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 39 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 40 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= 41 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 42 | github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= 43 | github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 44 | github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= 45 | github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= 46 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= 47 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= 48 | github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= 49 | github.com/fluxcd/flux2/v2 v2.0.1 h1:rovomePS9x1UgLOr+3n7V+Nzhi1CZpMAPmFYwrMtar8= 50 | github.com/fluxcd/flux2/v2 v2.0.1/go.mod h1:pKycTpib6Unm5qxuZTcDgxrzO0zylU04+WzwwB8576c= 51 | github.com/fluxcd/helm-controller/api v0.35.0 h1:UyhKXPni5z69DzPW7GtECGGdUwKsB+OTI0A/wc7HmFY= 52 | github.com/fluxcd/helm-controller/api v0.35.0/go.mod h1:CdHMtr5wM0xgDt/PS147H7QQS+zDxAFgDW3ZN8MnUlU= 53 | github.com/fluxcd/kustomize-controller/api v1.0.0 h1:BVz6lEpfGbny0ppQ82LloLSK0OoEGC51YQaw31j4vT8= 54 | github.com/fluxcd/kustomize-controller/api v1.0.0/go.mod h1:rYUovoofr3bVPgQowWj/CSGw73qoH0tOCopJ3oNh7lM= 55 | github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q= 56 | github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8= 57 | github.com/fluxcd/pkg/apis/kustomize v1.1.1 h1:MSGn4z0R9PptmoPFHnx2nEZ8Jtl1sKfw0cuDQY2HYwM= 58 | github.com/fluxcd/pkg/apis/kustomize v1.1.1/go.mod h1:0pCu0ecIY+ZM0iE/hOHYwCMZ3b0SpBrjJ1SH3FFyYdE= 59 | github.com/fluxcd/pkg/apis/meta v1.1.1 h1:sLAKLbEu7rRzJ+Mytffu3NcpfdbOBTa6hcpOQzFWm+M= 60 | github.com/fluxcd/pkg/apis/meta v1.1.1/go.mod h1:soCfzjFWbm1mqybDcOywWKTCEYlH3skpoNGTboVk234= 61 | github.com/fluxcd/pkg/runtime v0.40.0 h1:uGiiEbMZwd7xmbKaVmcH7iilCFW9betWbz0r1taK3G0= 62 | github.com/fluxcd/pkg/runtime v0.40.0/go.mod h1:BqHEOVrZmt19p0q1OlGFWAYh3rZ28+IBpxLB2yPjjQ4= 63 | github.com/fluxcd/pkg/ssa v0.32.0 h1:RBqs9DNrbJkFHjpfsiKilyean7gwqWFspSBTLOaBIHs= 64 | github.com/fluxcd/pkg/ssa v0.32.0/go.mod h1:+Kf5euYAbvgJX645bo+IL7V/NlH0X7kGgFTr1W++I3c= 65 | github.com/fluxcd/source-controller/api v1.0.0 h1:lPjmCXmEiI3tY4pReeVQBMuyLgdH8462W5ewUa9kgYM= 66 | github.com/fluxcd/source-controller/api v1.0.0/go.mod h1:rAY5FRFGZUKpIFNyYANHIgPgJPvbALBHWsq/zHw/cXQ= 67 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 68 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 69 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 70 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 71 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 72 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 73 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 74 | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 75 | github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= 76 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 77 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 78 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 79 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 80 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= 81 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 82 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 83 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 84 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 85 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 86 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 87 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 88 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 89 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 90 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 91 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 92 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 93 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 94 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 95 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 96 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 97 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 98 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 99 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 100 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 101 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 102 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 103 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 104 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 105 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 106 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 107 | github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= 108 | github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= 109 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 110 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 111 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 112 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 113 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 114 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 115 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 116 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 117 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 118 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 119 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 120 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 121 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 122 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 123 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 124 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 125 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 126 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 127 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 128 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= 129 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 130 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 131 | github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= 132 | github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 133 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 134 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 135 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 136 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 137 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 138 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 139 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 140 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 141 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 142 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 143 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 144 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 145 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 146 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 147 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 148 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 149 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= 150 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 151 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 152 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 153 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 154 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 155 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 156 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 157 | github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= 158 | github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= 159 | github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= 160 | github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 161 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 162 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 163 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 164 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 165 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 166 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= 167 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 168 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 169 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 170 | github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= 171 | github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= 172 | github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= 173 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 174 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 175 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 176 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 177 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 178 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 179 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 180 | github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= 181 | github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= 182 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 183 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 184 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 185 | github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= 186 | github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= 187 | github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= 188 | github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= 189 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 190 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 191 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 192 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 193 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 194 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 195 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 196 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 197 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 198 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 199 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 200 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 201 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 202 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 203 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 204 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 205 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 206 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 207 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 208 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 209 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 210 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 211 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 212 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 213 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 214 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 215 | github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= 216 | github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 217 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 218 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 219 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 220 | go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= 221 | go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= 222 | go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= 223 | go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= 224 | go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= 225 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= 226 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 227 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 228 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 229 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 230 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 231 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 232 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 233 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 234 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 235 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 236 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 237 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 238 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 239 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 240 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 241 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 242 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 243 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 244 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 245 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 246 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 247 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 248 | golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= 249 | golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 250 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 251 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 252 | golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= 253 | golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 254 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 255 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 256 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 257 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 258 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 259 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 260 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 261 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 262 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 263 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 264 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 265 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 266 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 267 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 268 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 269 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 270 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 271 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 272 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 273 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 274 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 275 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 276 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 277 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 278 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 279 | golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= 280 | golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= 281 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 282 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 283 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 284 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 285 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 286 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= 287 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 288 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 289 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 290 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 291 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 292 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 293 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 294 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 295 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 296 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 297 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 298 | golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= 299 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 300 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 301 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 302 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 303 | gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= 304 | gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 305 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 306 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 307 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 308 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 309 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 310 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 311 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 312 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 313 | google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= 314 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 315 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 316 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 317 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 318 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 319 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 320 | google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= 321 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 322 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 323 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 324 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 325 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 326 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 327 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 328 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 329 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 330 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 331 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 332 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 333 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 334 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 335 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 336 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 337 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 338 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 339 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 340 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 341 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 342 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 343 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 344 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 345 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 346 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 347 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 348 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 349 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 350 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 351 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 352 | k8s.io/api v0.27.4 h1:0pCo/AN9hONazBKlNUdhQymmnfLRbSZjd5H5H3f0bSs= 353 | k8s.io/api v0.27.4/go.mod h1:O3smaaX15NfxjzILfiln1D8Z3+gEYpjEpiNA/1EVK1Y= 354 | k8s.io/apiextensions-apiserver v0.27.4 h1:ie1yZG4nY/wvFMIR2hXBeSVq+HfNzib60FjnBYtPGSs= 355 | k8s.io/apiextensions-apiserver v0.27.4/go.mod h1:KHZaDr5H9IbGEnSskEUp/DsdXe1hMQ7uzpQcYUFt2bM= 356 | k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs= 357 | k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= 358 | k8s.io/cli-runtime v0.27.4 h1:Zb0eci+58eHZNnoHhjRFc7W88s8dlG12VtIl3Nv2Hto= 359 | k8s.io/cli-runtime v0.27.4/go.mod h1:k9Z1xiZq2xNplQmehpDquLgc+rE+pubpO1cK4al4Mlw= 360 | k8s.io/client-go v0.27.4 h1:vj2YTtSJ6J4KxaC88P4pMPEQECWMY8gqPqsTgUKzvjk= 361 | k8s.io/client-go v0.27.4/go.mod h1:ragcly7lUlN0SRPk5/ZkGnDjPknzb37TICq07WhI6Xc= 362 | k8s.io/component-base v0.27.4 h1:Wqc0jMKEDGjKXdae8hBXeskRP//vu1m6ypC+gwErj4c= 363 | k8s.io/component-base v0.27.4/go.mod h1:hoiEETnLc0ioLv6WPeDt8vD34DDeB35MfQnxCARq3kY= 364 | k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= 365 | k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 366 | k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515 h1:OmK1d0WrkD3IPfkskvroRykOulHVHf0s0ZIFRjyt+UI= 367 | k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515/go.mod h1:kzo02I3kQ4BTtEfVLaPbjvCkX97YqGve33wzlb3fofQ= 368 | k8s.io/kubectl v0.27.3 h1:HyC4o+8rCYheGDWrkcOQHGwDmyLKR5bxXFgpvF82BOw= 369 | k8s.io/kubectl v0.27.3/go.mod h1:g9OQNCC2zxT+LT3FS09ZYqnDhlvsKAfFq76oyarBcq4= 370 | k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= 371 | k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 372 | sigs.k8s.io/cli-utils v0.35.0 h1:dfSJaF1W0frW74PtjwiyoB4cwdRygbHnC7qe7HF0g/Y= 373 | sigs.k8s.io/cli-utils v0.35.0/go.mod h1:ITitykCJxP1vaj1Cew/FZEaVJ2YsTN9Q71m02jebkoE= 374 | sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c= 375 | sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= 376 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 377 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 378 | sigs.k8s.io/kustomize/api v0.13.4 h1:E38Hfx0G9R9v7vRgKshviPotJQETG0S2gD3JdHLCAsI= 379 | sigs.k8s.io/kustomize/api v0.13.4/go.mod h1:Bkaavz5RKK6ZzP0zgPrB7QbpbBJKiHuD3BB0KujY7Ls= 380 | sigs.k8s.io/kustomize/kyaml v0.14.2 h1:9WSwztbzwGszG1bZTziQUmVMrJccnyrLb5ZMKpJGvXw= 381 | sigs.k8s.io/kustomize/kyaml v0.14.2/go.mod h1:AN1/IpawKilWD7V+YvQwRGUvuUOOWpjsHu6uHwonSF4= 382 | sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= 383 | sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 384 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 385 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 386 | -------------------------------------------------------------------------------- /index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "candidates": [ 3 | { 4 | "flamingo": "v2.10.2", 5 | "argocd": "v2.10.2", 6 | "image": "v2.10.2-fl.23-main-d2c9a8cb", 7 | "flux": "v2.2.3" 8 | }, 9 | { 10 | "flamingo": "v2.9.7", 11 | "argocd": "v2.9.7", 12 | "image": "v2.9.7-fl.23-main-909efcbe", 13 | "flux": "v2.2.3" 14 | }, 15 | { 16 | "flamingo": "v2.9.6", 17 | "argocd": "v2.9.6", 18 | "image": "v2.9.6-fl.22-main-402c9e49", 19 | "flux": "v2.2.3" 20 | }, 21 | { 22 | "flamingo": "v2.9.3", 23 | "argocd": "v2.9.3", 24 | "image": "v2.9.3-fl.21-main-a6eaf3af", 25 | "flux": "v2.1.2" 26 | }, 27 | { 28 | "flamingo": "v2.9.2", 29 | "argocd": "v2.9.2", 30 | "image": "v2.9.2-fl.21-main-ca114998", 31 | "flux": "v2.1.2" 32 | }, 33 | { 34 | "flamingo": "v2.9.1", 35 | "argocd": "v2.9.1", 36 | "image": "v2.9.1-fl.21-main-794f2ef5", 37 | "flux": "v2.1.2" 38 | }, 39 | { 40 | "flamingo": "v2.9.0", 41 | "argocd": "v2.9.0", 42 | "image": "v2.9.0-fl.21-main-b55851f8", 43 | "flux": "v2.1.2" 44 | }, 45 | { 46 | "flamingo": "v2.9.0-rc3-dev", 47 | "argocd": "v2.9.0-rc3", 48 | "image": "v2.9.0-rc3-fl.21-main-1fecd6b1", 49 | "flux": "v2.1.2" 50 | }, 51 | { 52 | "flamingo": "v2.8.10", 53 | "argocd": "v2.8.10", 54 | "image": "v2.8.10-fl.21-main-7ef6c602", 55 | "flux": "v2.1.2" 56 | }, 57 | { 58 | "flamingo": "v2.8.6", 59 | "argocd": "v2.8.6", 60 | "image": "v2.8.6-fl.21-main-ff4f071a", 61 | "flux": "v2.1.2" 62 | }, 63 | { 64 | "flamingo": "v2.8.5", 65 | "argocd": "v2.8.5", 66 | "image": "v2.8.5-fl.21-main-2670d8c2", 67 | "flux": "v2.1.2" 68 | }, 69 | { 70 | "flamingo": "v2.8.4", 71 | "argocd": "v2.8.4", 72 | "image": "v2.8.4-fl.21-main-6c72e98c", 73 | "flux": "v2.1.1" 74 | }, 75 | { 76 | "flamingo": "v2.8.3", 77 | "argocd": "v2.8.3", 78 | "image": "v2.8.3-fl.21-main-3155e06d", 79 | "flux": "v2.1.0" 80 | }, 81 | { 82 | "flamingo": "v2.8.1", 83 | "argocd": "v2.8.1", 84 | "image": "v2.8.1-fl.21-main-7242d1b4", 85 | "flux": "v2.1.0" 86 | }, 87 | { 88 | "flamingo": "v2.7.17", 89 | "argocd": "v2.7.17", 90 | "image": "v2.7.17-fl.15-main-f762c2ac", 91 | "flux": "v2.0.1" 92 | }, 93 | { 94 | "flamingo": "v2.7.14", 95 | "argocd": "v2.7.14", 96 | "image": "v2.7.14-fl.15-main-79945e4b", 97 | "flux": "v2.0.1" 98 | }, 99 | { 100 | "flamingo": "v2.7.12", 101 | "argocd": "v2.7.12", 102 | "image": "v2.7.12-fl.15-main-1072aa5e", 103 | "flux": "v2.0.1" 104 | }, 105 | { 106 | "flamingo": "v2.8.0", 107 | "argocd": "v2.8.0", 108 | "image": "v2.8.0-fl.15-main-aef83705", 109 | "flux": "v2.0.1" 110 | }, 111 | { 112 | "flamingo": "v2.7.11", 113 | "argocd": "v2.7.11", 114 | "image": "v2.7.11-fl.15-main-32b9702c", 115 | "flux": "v2.0.1" 116 | }, 117 | { 118 | "flamingo": "v2.6.15", 119 | "argocd": "v2.6.15", 120 | "image": "v2.6.15-fl.15-main-10ea1bf3", 121 | "flux": "v2.0.1" 122 | }, 123 | { 124 | "flamingo": "v2.6.14", 125 | "argocd": "v2.6.14", 126 | "image": "v2.6.14-fl.15-main-e9a0660c", 127 | "flux": "v2.0.1" 128 | }, 129 | { 130 | "flamingo": "v2.5.22", 131 | "argocd": "v2.5.22", 132 | "image": "v2.5.22-fl.12-main-0f9b4dbc", 133 | "flux": "v0.41.2" 134 | } 135 | ] 136 | } 137 | -------------------------------------------------------------------------------- /install/README.md: -------------------------------------------------------------------------------- 1 | # flamingo CLI installation 2 | 3 | ```bash 4 | curl -s https://raw.githubusercontent.com/flux-subsystem-argo/flamingo/main/install/flamingo.sh | sudo bash 5 | ``` 6 | -------------------------------------------------------------------------------- /install/flamingo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | DEFAULT_BIN_DIR="/usr/local/bin" 5 | BIN_DIR=${1:-"${DEFAULT_BIN_DIR}"} 6 | GITHUB_REPO="flux-subsystem-argo/flamingo" 7 | 8 | # Helper functions for logs 9 | info() { 10 | echo '[INFO] ' "$@" 11 | } 12 | 13 | warn() { 14 | echo '[WARN] ' "$@" >&2 15 | } 16 | 17 | fatal() { 18 | echo '[ERROR] ' "$@" >&2 19 | exit 1 20 | } 21 | 22 | # Set os, fatal if operating system not supported 23 | setup_verify_os() { 24 | if [[ -z "${OS}" ]]; then 25 | OS=$(uname) 26 | fi 27 | case ${OS} in 28 | Darwin) 29 | OS=darwin 30 | ;; 31 | Linux) 32 | OS=linux 33 | ;; 34 | *) 35 | fatal "Unsupported operating system ${OS}" 36 | esac 37 | } 38 | 39 | # Set arch, fatal if architecture not supported 40 | setup_verify_arch() { 41 | if [[ -z "${ARCH}" ]]; then 42 | ARCH=$(uname -m) 43 | fi 44 | case ${ARCH} in 45 | arm|armv6l|armv7l) 46 | ARCH=arm 47 | ;; 48 | arm64|aarch64|armv8l) 49 | ARCH=arm64 50 | ;; 51 | amd64) 52 | ARCH=amd64 53 | ;; 54 | x86_64) 55 | ARCH=amd64 56 | ;; 57 | *) 58 | fatal "Unsupported architecture ${ARCH}" 59 | esac 60 | } 61 | 62 | # Verify existence of downloader executable 63 | verify_downloader() { 64 | # Return failure if it doesn't exist or is no executable 65 | [[ -x "$(which "$1")" ]] || return 1 66 | 67 | # Set verified executable as our downloader program and return success 68 | DOWNLOADER=$1 69 | return 0 70 | } 71 | 72 | # Create tempory directory and cleanup when done 73 | setup_tmp() { 74 | TMP_DIR=$(mktemp -d -t flamingo-install.XXXXXXXXXX) 75 | TMP_METADATA="${TMP_DIR}/flamingo.json" 76 | TMP_HASH="${TMP_DIR}/flamingo.hash" 77 | TMP_BIN="${TMP_DIR}/flamingo.tar.gz" 78 | cleanup() { 79 | local code=$? 80 | set +e 81 | trap - EXIT 82 | rm -rf "${TMP_DIR}" 83 | exit ${code} 84 | } 85 | trap cleanup INT EXIT 86 | } 87 | 88 | # Find version from Github metadata 89 | get_release_version() { 90 | if [[ -n "${FLAMINGO_VERSION}" ]]; then 91 | SUFFIX_URL="tags/v${FLAMINGO_VERSION}" 92 | else 93 | SUFFIX_URL="latest" 94 | fi 95 | 96 | METADATA_URL="https://api.github.com/repos/${GITHUB_REPO}/releases/${SUFFIX_URL}" 97 | 98 | info "Downloading metadata ${METADATA_URL}" 99 | download "${TMP_METADATA}" "${METADATA_URL}" 100 | 101 | VERSION_FLAMINGO=$(grep '"tag_name":' "${TMP_METADATA}" | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-) 102 | if [[ -n "${VERSION_FLAMINGO}" ]]; then 103 | info "Using ${VERSION_FLAMINGO} as release" 104 | else 105 | fatal "Unable to determine release version" 106 | fi 107 | } 108 | 109 | # Download from file from URL 110 | download() { 111 | [[ $# -eq 2 ]] || fatal 'download needs exactly 2 arguments' 112 | 113 | case $DOWNLOADER in 114 | curl) 115 | curl -u user:$GITHUB_TOKEN -o "$1" -sfL "$2" 116 | ;; 117 | wget) 118 | wget --auth-no-challenge --user=user --password=$GITHUB_TOKEN -qO "$1" "$2" 119 | ;; 120 | *) 121 | fatal "Incorrect executable '${DOWNLOADER}'" 122 | ;; 123 | esac 124 | 125 | # Abort if download command failed 126 | [[ $? -eq 0 ]] || fatal 'Download failed' 127 | } 128 | 129 | # Version comparison 130 | # Returns 0 on '=', 1 on '>', and 2 on '<'. 131 | # Ref: https://stackoverflow.com/a/4025065 132 | vercomp () { 133 | if [[ $1 == $2 ]] 134 | then 135 | return 0 136 | fi 137 | local IFS=. 138 | local i ver1=($1) ver2=($2) 139 | # fill empty fields in ver1 with zeros 140 | for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)) 141 | do 142 | ver1[i]=0 143 | done 144 | for ((i=0; i<${#ver1[@]}; i++)) 145 | do 146 | if [[ -z ${ver2[i]} ]] 147 | then 148 | # fill empty fields in ver2 with zeros 149 | ver2[i]=0 150 | fi 151 | if ((10#${ver1[i]} > 10#${ver2[i]})) 152 | then 153 | return 1 154 | fi 155 | if ((10#${ver1[i]} < 10#${ver2[i]})) 156 | then 157 | return 2 158 | fi 159 | done 160 | return 0 161 | } 162 | 163 | # Download hash from Github URL 164 | download_hash() { 165 | HASH_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_FLAMINGO}/flamingo_${VERSION_FLAMINGO}_checksums.txt" 166 | # NB: support the checksum filename format prior to v0.0.1 167 | set +e 168 | vercomp ${VERSION_FLAMINGO} 0.0.1 169 | if [[ $? -eq 2 ]]; then 170 | HASH_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_FLAMINGO}/flamingo_${VERSION_FLAMINGO}_checksums.txt" 171 | fi 172 | set -e 173 | 174 | info "Downloading hash ${HASH_URL}" 175 | download "${TMP_HASH}" "${HASH_URL}" 176 | HASH_EXPECTED=$(grep " flamingo_${VERSION_FLAMINGO}_${OS}_${ARCH}.tar.gz$" "${TMP_HASH}") 177 | HASH_EXPECTED=${HASH_EXPECTED%%[[:blank:]]*} 178 | } 179 | 180 | # Download binary from Github URL 181 | download_binary() { 182 | BIN_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_FLAMINGO}/flamingo_${VERSION_FLAMINGO}_${OS}_${ARCH}.tar.gz" 183 | info "Downloading binary ${BIN_URL}" 184 | download "${TMP_BIN}" "${BIN_URL}" 185 | } 186 | 187 | compute_sha256sum() { 188 | cmd=$(which sha256sum shasum | head -n 1) 189 | case $(basename "$cmd") in 190 | sha256sum) 191 | sha256sum "$1" | cut -f 1 -d ' ' 192 | ;; 193 | shasum) 194 | shasum -a 256 "$1" | cut -f 1 -d ' ' 195 | ;; 196 | *) 197 | fatal "Can not find sha256sum or shasum to compute checksum" 198 | ;; 199 | esac 200 | } 201 | 202 | # Verify downloaded binary hash 203 | verify_binary() { 204 | info "Verifying binary download" 205 | HASH_BIN=$(compute_sha256sum "${TMP_BIN}") 206 | HASH_BIN=${HASH_BIN%%[[:blank:]]*} 207 | if [[ "${HASH_EXPECTED}" != "${HASH_BIN}" ]]; then 208 | fatal "Download sha256 does not match ${HASH_EXPECTED}, got ${HASH_BIN}" 209 | fi 210 | } 211 | 212 | # Setup permissions and move binary 213 | setup_binary() { 214 | chmod 755 "${TMP_BIN}" 215 | info "Installing flamingo to ${BIN_DIR}/flamingo" 216 | tar -xzof "${TMP_BIN}" -C "${TMP_DIR}" 217 | 218 | local CMD_MOVE="mv -f \"${TMP_DIR}/flamingo\" \"${BIN_DIR}\"" 219 | if [[ -w "${BIN_DIR}" ]]; then 220 | eval "${CMD_MOVE}" 221 | else 222 | eval "sudo ${CMD_MOVE}" 223 | fi 224 | } 225 | 226 | # Run the install process 227 | { 228 | setup_verify_os 229 | setup_verify_arch 230 | verify_downloader curl || verify_downloader wget || fatal 'Can not find curl or wget for downloading files' 231 | setup_tmp 232 | get_release_version 233 | download_hash 234 | download_binary 235 | verify_binary 236 | setup_binary 237 | } 238 | -------------------------------------------------------------------------------- /pkg/utils/apply.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | helmv2b1 "github.com/fluxcd/helm-controller/api/v2beta1" 8 | kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" 9 | sourcev1 "github.com/fluxcd/source-controller/api/v1" 10 | sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" 11 | "time" 12 | 13 | "github.com/fluxcd/pkg/ssa" 14 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 15 | "k8s.io/cli-runtime/pkg/genericclioptions" 16 | "sigs.k8s.io/cli-utils/pkg/kstatus/polling" 17 | 18 | runclient "github.com/fluxcd/pkg/runtime/client" 19 | 20 | appsv1 "k8s.io/api/apps/v1" 21 | corev1 "k8s.io/api/core/v1" 22 | networkingv1 "k8s.io/api/networking/v1" 23 | rbacv1 "k8s.io/api/rbac/v1" 24 | apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 25 | apiruntime "k8s.io/apimachinery/pkg/runtime" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | ) 28 | 29 | func Apply(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *runclient.Options, resources []byte) (string, error) { 30 | objs, err := ssa.ReadObjects(bytes.NewReader(resources)) 31 | if err != nil { 32 | return "", err 33 | } 34 | 35 | if len(objs) == 0 { 36 | return "", fmt.Errorf("no Kubernetes objects found") 37 | } 38 | 39 | if err := ssa.SetNativeKindsDefaults(objs); err != nil { 40 | return "", err 41 | } 42 | 43 | changeSet := ssa.NewChangeSet() 44 | 45 | // contains only CRDs and Namespaces 46 | var stageOne []*unstructured.Unstructured 47 | 48 | // contains all objects except for CRDs and Namespaces 49 | var stageTwo []*unstructured.Unstructured 50 | 51 | for _, u := range objs { 52 | if ssa.IsClusterDefinition(u) { 53 | stageOne = append(stageOne, u) 54 | } else { 55 | stageTwo = append(stageTwo, u) 56 | } 57 | } 58 | 59 | if len(stageOne) > 0 { 60 | cs, err := applySet(ctx, rcg, opts, stageOne) 61 | if err != nil { 62 | return "", err 63 | } 64 | changeSet.Append(cs.Entries) 65 | } 66 | 67 | if len(changeSet.Entries) > 0 { 68 | if err := waitForSet(rcg, opts, changeSet); err != nil { 69 | return "", err 70 | } 71 | } 72 | 73 | if len(stageTwo) > 0 { 74 | cs, err := applySet(ctx, rcg, opts, stageTwo) 75 | if err != nil { 76 | return "", err 77 | } 78 | changeSet.Append(cs.Entries) 79 | } 80 | 81 | if len(changeSet.Entries) > 0 { 82 | if err := waitForSet(rcg, opts, changeSet); err != nil { 83 | return "", err 84 | } 85 | } 86 | 87 | return changeSet.String(), nil 88 | } 89 | 90 | func NewScheme() *apiruntime.Scheme { 91 | scheme := apiruntime.NewScheme() 92 | _ = apiextensionsv1.AddToScheme(scheme) 93 | _ = corev1.AddToScheme(scheme) 94 | _ = rbacv1.AddToScheme(scheme) 95 | _ = appsv1.AddToScheme(scheme) 96 | _ = networkingv1.AddToScheme(scheme) 97 | _ = sourcev1.AddToScheme(scheme) 98 | _ = sourcev1b2.AddToScheme(scheme) 99 | _ = kustomizev1.AddToScheme(scheme) 100 | _ = helmv2b1.AddToScheme(scheme) 101 | return scheme 102 | } 103 | 104 | func newManager(rcg genericclioptions.RESTClientGetter, opts *runclient.Options) (*ssa.ResourceManager, error) { 105 | cfg, err := KubeConfig(rcg, opts) 106 | if err != nil { 107 | return nil, err 108 | } 109 | restMapper, err := rcg.ToRESTMapper() 110 | if err != nil { 111 | return nil, err 112 | } 113 | kubeClient, err := client.New(cfg, client.Options{Mapper: restMapper, Scheme: NewScheme()}) 114 | if err != nil { 115 | return nil, err 116 | } 117 | kubePoller := polling.NewStatusPoller(kubeClient, restMapper, polling.Options{}) 118 | 119 | return ssa.NewResourceManager(kubeClient, kubePoller, ssa.Owner{ 120 | Field: "flamingo", 121 | Group: "flamingo.io", 122 | }), nil 123 | 124 | } 125 | 126 | func applySet(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *runclient.Options, objects []*unstructured.Unstructured) (*ssa.ChangeSet, error) { 127 | man, err := newManager(rcg, opts) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | return man.ApplyAll(ctx, objects, ssa.DefaultApplyOptions()) 133 | } 134 | 135 | func waitForSet(rcg genericclioptions.RESTClientGetter, opts *runclient.Options, changeSet *ssa.ChangeSet) error { 136 | man, err := newManager(rcg, opts) 137 | if err != nil { 138 | return err 139 | } 140 | return man.WaitForSet(changeSet.ToObjMetadataSet(), ssa.WaitOptions{Interval: 2 * time.Second, Timeout: 5 * time.Minute}) 141 | } 142 | -------------------------------------------------------------------------------- /pkg/utils/kubeclient.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | runclient "github.com/fluxcd/pkg/runtime/client" 5 | "k8s.io/cli-runtime/pkg/genericclioptions" 6 | "sigs.k8s.io/controller-runtime/pkg/client" 7 | ) 8 | 9 | func KubeClient(rcg genericclioptions.RESTClientGetter, opts *runclient.Options) (client.Client, error) { 10 | cfg, err := KubeConfig(rcg, opts) 11 | if err != nil { 12 | return nil, err 13 | } 14 | restMapper, err := rcg.ToRESTMapper() 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return client.New(cfg, client.Options{Mapper: restMapper, Scheme: NewScheme()}) 20 | } 21 | -------------------------------------------------------------------------------- /pkg/utils/kubeclient_for_leaf_cluster.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | runclient "github.com/fluxcd/pkg/runtime/client" 9 | corev1 "k8s.io/api/core/v1" 10 | "k8s.io/client-go/rest" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | ) 13 | 14 | type TLSClientConfig struct { 15 | Insecure bool `json:"insecure"` 16 | CertData string `json:"certData"` 17 | KeyData string `json:"keyData"` 18 | ServerName string `json:"serverName"` 19 | } 20 | 21 | type SecretConfig struct { 22 | TLSClientConfig TLSClientConfig `json:"tlsClientConfig"` 23 | } 24 | 25 | type ClusterConfig struct { 26 | ExternalAddress string 27 | InternalAddress string 28 | Name string 29 | Server string 30 | TLSClientConfig TLSClientConfig 31 | } 32 | 33 | func KubeClientForLeafCluster(mgmt client.Client, clusterName string, opts *runclient.Options) (client.Client, *ClusterConfig, error) { 34 | secretName := fmt.Sprintf("%s-cluster", clusterName) 35 | secret := corev1.Secret{} 36 | err := mgmt.Get(context.Background(), client.ObjectKey{Namespace: "argocd", Name: secretName}, &secret) 37 | if err != nil { 38 | return nil, nil, err 39 | } 40 | 41 | // Parse the config block from the secret 42 | configData, ok := secret.Data["config"] 43 | if !ok { 44 | return nil, nil, fmt.Errorf("config block not found in secret") 45 | } 46 | 47 | var secretConfig SecretConfig 48 | err = json.Unmarshal(configData, &secretConfig) 49 | if err != nil { 50 | return nil, nil, err 51 | } 52 | 53 | clusterConfig := &ClusterConfig{} 54 | clusterConfig.ExternalAddress = secret.Annotations["flamingo/external-address"] 55 | clusterConfig.InternalAddress = secret.Annotations["flamingo/internal-address"] 56 | clusterConfig.Name = string(secret.Data["name"]) 57 | clusterConfig.Server = string(secret.Data["server"]) 58 | clusterConfig.TLSClientConfig = secretConfig.TLSClientConfig 59 | 60 | certData, err := base64.StdEncoding.DecodeString(clusterConfig.TLSClientConfig.CertData) 61 | if err != nil { 62 | return nil, nil, err 63 | } 64 | keyData, err := base64.StdEncoding.DecodeString(clusterConfig.TLSClientConfig.KeyData) 65 | if err != nil { 66 | return nil, nil, err 67 | } 68 | 69 | // Manually construct the Kubernetes rest.Config 70 | cfg := &rest.Config{ 71 | // Set the server address 72 | Host: clusterConfig.ExternalAddress, 73 | // Setup custom TLS config 74 | TLSClientConfig: rest.TLSClientConfig{ 75 | Insecure: clusterConfig.TLSClientConfig.Insecure, 76 | CertData: certData, 77 | KeyData: keyData, 78 | ServerName: clusterConfig.TLSClientConfig.ServerName, 79 | }, 80 | QPS: opts.QPS, 81 | Burst: opts.Burst, 82 | } 83 | 84 | // Ensure your scheme is properly configured with the required API types 85 | k8sClient, err := client.New(cfg, client.Options{Scheme: NewScheme()}) 86 | if err != nil { 87 | return nil, clusterConfig, err 88 | } 89 | 90 | return k8sClient, clusterConfig, nil 91 | } 92 | -------------------------------------------------------------------------------- /pkg/utils/kubeconfig.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fluxcd/pkg/runtime/client" 7 | "k8s.io/cli-runtime/pkg/genericclioptions" 8 | "k8s.io/client-go/rest" 9 | ) 10 | 11 | func KubeConfig(rcg genericclioptions.RESTClientGetter, opts *client.Options) (*rest.Config, error) { 12 | cfg, err := rcg.ToRESTConfig() 13 | if err != nil { 14 | return nil, fmt.Errorf("kubernetes configuration load failed: %w", err) 15 | } 16 | 17 | // avoid throttling request when some Flux CRDs are not registered 18 | cfg.QPS = opts.QPS 19 | cfg.Burst = opts.Burst 20 | 21 | return cfg, nil 22 | } 23 | --------------------------------------------------------------------------------