diff --git a/.env.example b/.env.example index c5f9e29b..abccecca 100644 --- a/.env.example +++ b/.env.example @@ -20,3 +20,7 @@ RELATED_IMAGE_DB_MIGRATOR_TOOL=quay.io/kubesmarts/incubator-kie-kogito-db-migrat # Image digest pinning tool (docker, podman, or skopeo) PIN_IMAGE_SHA_BUNDLE_TOOL=docker + +# CLI build metadata +QUARKUS_VERSION=3.8.1 +QUARKUS_PLATFORM_GROUP_ID=io.quarkus.platform diff --git a/.github/workflows/cli-build.yaml b/.github/workflows/cli-build.yaml new file mode 100644 index 00000000..1d99b14b --- /dev/null +++ b/.github/workflows/cli-build.yaml @@ -0,0 +1,112 @@ +name: CLI Build and Test + +on: + push: + branches: [main, master] + paths: + - 'cli/**' + - '.github/workflows/cli-build.yaml' + pull_request: + branches: [main, master] + paths: + - 'cli/**' + - '.github/workflows/cli-build.yaml' + +jobs: + build-and-test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + include: + - os: ubuntu-latest + binary: kn-workflow-linux-amd64 + - os: macos-latest + binary: kn-workflow-darwin-arm64 + - os: windows-latest + binary: kn-workflow-windows-amd64.exe + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.26' + cache: true + + - name: Copy .env configuration + shell: bash + run: cp .env.example .env + + - name: Run unit tests + working-directory: cli + run: go test -v ./... + + - name: Build CLI + working-directory: cli + shell: bash + run: | + if [ "$RUNNER_OS" == "Linux" ]; then + make build-linux-amd64 + elif [ "$RUNNER_OS" == "macOS" ]; then + make build-darwin-arm64 + elif [ "$RUNNER_OS" == "Windows" ]; then + make build-win32-amd64 + fi + + - name: Test CLI version command + working-directory: cli + shell: bash + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + ./dist/kn-workflow-windows-amd64.exe version + elif [ "$RUNNER_OS" == "macOS" ]; then + ./dist/kn-workflow-darwin-arm64 version + else + ./dist/kn-workflow-linux-amd64 version + fi + + - name: Upload binary + uses: actions/upload-artifact@v4 + with: + name: cli-${{ matrix.os }} + path: cli/dist/${{ matrix.binary }} + retention-days: 7 + + build-all-platforms: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.26' + cache: true + + - name: Copy .env configuration + run: cp .env.example .env + + - name: Build all platforms + working-directory: cli + run: make build-all + + - name: Verify all binaries + working-directory: cli + run: | + ls -lh dist/ + [ -f dist/kn-workflow-linux-amd64 ] + [ -f dist/kn-workflow-darwin-amd64 ] + [ -f dist/kn-workflow-darwin-arm64 ] + [ -f dist/kn-workflow-windows-amd64.exe ] + + - name: Upload all binaries + uses: actions/upload-artifact@v4 + with: + name: cli-all-platforms + path: cli/dist/* + retention-days: 30 diff --git a/.github/workflows/cli-e2e.yaml b/.github/workflows/cli-e2e.yaml new file mode 100644 index 00000000..ad46c08c --- /dev/null +++ b/.github/workflows/cli-e2e.yaml @@ -0,0 +1,88 @@ +name: CLI E2E Tests + +on: + push: + branches: [main, master] + paths: + - 'cli/**' + - '.github/workflows/cli-e2e.yaml' + pull_request: + branches: [main, master] + paths: + - 'cli/**' + - '.github/workflows/cli-e2e.yaml' + workflow_dispatch: + +jobs: + e2e-tests: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.26' + cache: true + + - name: Copy .env configuration + run: cp .env.example .env + + - name: Build CLI + working-directory: cli + run: make build + + - name: Run non-operator e2e tests + working-directory: cli + env: + SKIP_OPERATOR_INSTALL: "true" + run: | + mkdir -p dist-tests-e2e + # Run CLI e2e tests that don't require Kubernetes/operator + # These tests verify local CLI functionality: create, convert, gen-manifest + # + # Tests running (8 total): + # - TestCreateProjectSuccess (create workflow project) + # - TestCreateProjectFail (validate errors) + # - TestGenManifestProjectSuccess (generate K8s manifests) + # - TestQuarkusCreateProjectSuccess (create Quarkus project) + # - TestQuarkusCreateProjectFail (validate errors) + # - TestQuarkusConvertProjectSuccess (convert to Quarkus) + # - TestQuarkusConvertProjectFailed (validate errors) + # - TestQuarkusConvertProjectFailedAlreadyQuarkus (validate already converted) + # + # Tests skipped: + # - Deploy tests (require OLM-based operator installation) + # - Build/run tests (require Docker) + go test -v ./e2e-tests/... -tags e2e_tests \ + -run "TestCreateProject|TestQuarkusCreateProject|TestQuarkusConvertProject|TestGenManifestProject" \ + -timeout 10m 2>&1 | tee ./dist-tests-e2e/go-test-output-e2e.txt + + - name: Generate JUnit report + if: always() + working-directory: cli + run: | + if [ -f ./dist-tests-e2e/go-test-output-e2e.txt ]; then + go run github.com/jstemmer/go-junit-report/v2 \ + -in ./dist-tests-e2e/go-test-output-e2e.txt \ + -out ./dist-tests-e2e/junit-report.xml || true + fi + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: cli-e2e-test-results + path: cli/dist-tests-e2e/ + retention-days: 7 + + - name: Display test summary + if: always() + working-directory: cli + run: | + if [ -f ./dist-tests-e2e/go-test-output-e2e.txt ]; then + echo "=== Test Output Summary ===" + grep -E "PASS|FAIL|RUN" ./dist-tests-e2e/go-test-output-e2e.txt | tail -20 + fi diff --git a/.github/workflows/pr-checks.yaml b/.github/workflows/pr-checks.yaml index 0a67acc0..e241220d 100644 --- a/.github/workflows/pr-checks.yaml +++ b/.github/workflows/pr-checks.yaml @@ -99,3 +99,32 @@ jobs: make manifests make addheaders git diff --exit-code config/rbac/ config/crd/ + + cli-checks: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.26' + cache: true + + - name: Copy .env configuration + run: cp .env.example .env + + - name: Run CLI unit tests + working-directory: cli + run: go test -v ./... + + - name: Build CLI + working-directory: cli + run: make build + + - name: Verify CLI binary works + working-directory: cli + run: ./dist/kn-workflow version diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 00000000..c71c993a --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1,26 @@ +### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +debug/ +sonataflow-operator/ +TODO.txt +e2e-tests/temp-tests/ +dist-it-tests/ +dist/ +dist-tests/ +target/ diff --git a/cli/.vscode/launch.json b/cli/.vscode/launch.json new file mode 100644 index 00000000..fa24dd0f --- /dev/null +++ b/cli/.vscode/launch.json @@ -0,0 +1,254 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch create/version", + "type": "go", + "request": "launch", + "mode": "exec", + "preLaunchTask": "build", + "postDebugTask": "clean", + "program": "${workspaceRoot}/${input:bin}", + "args": ["${input:command}", "${input:command-args-create-version}"] + }, + { + "name": "Launch run", + "type": "go", + "request": "launch", + "mode": "exec", + "preLaunchTask": "build and create", + "postDebugTask": "clean", + "program": "${workspaceRoot}/${input:bin}", + "cwd": "${workspaceFolder}/debug", + "args": ["run", "${input:command-args-run}"] + }, + { + "name": "Launch deploy", + "type": "go", + "request": "launch", + "mode": "exec", + "preLaunchTask": "build and create", + "postDebugTask": "clean", + "program": "${workspaceRoot}/${input:bin}", + "cwd": "${workspaceFolder}/debug", + "args": [ + "deploy", + "${input:command-args-deploy-ns}", + "${input:command-args-deploy-supportFiles}", + "${input:command-args-deploy-manifest}" + ] + }, + { + "name": "Launch undeploy", + "type": "go", + "request": "launch", + "mode": "exec", + "preLaunchTask": "build and create", + "postDebugTask": "clean", + "program": "${workspaceRoot}/${input:bin}", + "cwd": "${workspaceFolder}/debug", + "args": ["undeploy", "${input:command-args-deploy-ns}", "${input:command-args-deploy-manifest}"] + }, + { + "name": "Launch quarkus create", + "type": "go", + "request": "launch", + "mode": "exec", + "preLaunchTask": "build", + "postDebugTask": "clean", + "program": "${workspaceRoot}/${input:bin}", + "args": [ + "quarkus", + "create", + "--name=debug", + "${input:command-args-quarkus-extensions}", + "${input:command-args-quarkus-pgi}", + "${input:command-args-quarkus-version}" + ] + }, + { + "name": "Launch quarkus convert", + "type": "go", + "request": "launch", + "mode": "exec", + "preLaunchTask": "build and create", + "postDebugTask": "clean", + "cwd": "${workspaceFolder}/debug", + "program": "${workspaceRoot}/${input:bin}", + "args": [ + "quarkus", + "convert", + "${input:command-args-quarkus-extensions}", + "${input:command-args-quarkus-pgi}", + "${input:command-args-quarkus-version}" + ] + }, + { + "name": "Launch quarkus deploy", + "type": "go", + "request": "launch", + "mode": "exec", + "preLaunchTask": "build and quarkus create", + "postDebugTask": "clean", + "cwd": "${workspaceFolder}/debug", + "program": "${workspaceRoot}/${input:bin}", + "args": ["quarkus", "deploy", "${input:command-args-quarkus-deploy-path}"] + }, + { + "name": "Launch quarkus build", + "type": "go", + "request": "launch", + "mode": "exec", + "preLaunchTask": "build and quarkus create", + "postDebugTask": "clean", + "cwd": "${workspaceFolder}/debug", + "program": "${workspaceRoot}/${input:bin}", + "args": [ + "quarkus", + "build", + "${input:command-args-quarkus-image}", + "${input:command-args-quarkus-image-registry}", + "${input:command-args-quarkus-image-repository}", + "${input:command-args-quarkus-image-name}", + "${input:command-args-quarkus-image-tag}", + "${input:command-args-quarkus-jib}", + "${input:command-args-quarkus-jib-podman}", + "${input:command-args-quarkus-push}", + "${input:command-args-quarkus-test}" + ] + } + ], + "inputs": [ + { + "id": "bin", + "type": "pickString", + "default": "dist/kn-workflow-linux-amd64", + "description": "The binary", + "options": [ + "dist/kn-workflow-linux-amd64", + "dist/kn-workflow-darwin-amd64", + "dist/kn-workflow-darwin-arm64", + "dist/kn-workflow-windows-amd64.exe" + ] + }, + { + "id": "command", + "type": "pickString", + "default": "version", + "description": "The command", + "options": ["create", "version"] + }, + { + "id": "command-args-create-version", + "type": "pickString", + "default": "", + "description": "The --name flag is used to specify a name for the project.", + "options": ["", "--name=debug"] + }, + { + "id": "command-args-run", + "type": "promptString", + "default": "--port=8080", + "description": "The --port is used to specify the port to run the project." + }, + { + "id": "command-args-deploy-ns", + "type": "promptString", + "default": "--namespace=default", + "description": "The --namespace is used to deploy the project to the said namespace." + }, + { + "id": "command-args-deploy-supportFiles", + "type": "promptString", + "default": "--supportFilesFolder=${workspaceRoot}/debug/specs", + "description": "The --supportFilesFolder is used to specify a path for the support files for deployment." + }, + { + "id": "command-args-deploy-manifest", + "type": "promptString", + "default": "", + "description": "The --manifestPath is used to specify a path for the mainfest files for deployment." + }, + { + "id": "command-args-quarkus-extensions", + "type": "promptString", + "default": "", + "description": "On Quarkus projects, setup project custom Maven extensions, separated with a comma. Usage --extension=${extensions}." + }, + { + "id": "command-args-quarkus-pgi", + "type": "promptString", + "default": "", + "description": "The quarkus platform group id. Empty value defaults to io.quarkus.platform. Usage --quarkus-platform-group-id=io.quarkus.platform." + }, + { + "id": "command-args-quarkus-version", + "type": "promptString", + "default": "", + "description": "The quarkus version. Usage --quarkus-version=${version}" + }, + { + "id": "command-args-quarkus-deploy-path", + "type": "promptString", + "default": "", + "description": "Path to knative deployment files. Usage --path=${path_to_k8_files}" + }, + { + "id": "command-args-quarkus-image", + "type": "promptString", + "default": "", + "description": "Full image name in the form of [registry]/[repository]/[name]:[tag]. Usage --image=${image_name}" + }, + { + "id": "command-args-quarkus-image-registry", + "type": "promptString", + "default": "", + "description": "Image registry, ex: quay.io, if the --image flag is in use this option overrides image [registry]. Usage --image-registry=${image_registry_name}" + }, + { + "id": "command-args-quarkus-image-repository", + "type": "promptString", + "default": "", + "description": "Image repository, ex: registry-user or registry-project, if the --image flag is in use, this option overrides image [repository]. Usage --image-repository=${image_repo_name}" + }, + { + "id": "command-args-quarkus-image-name", + "type": "promptString", + "default": "", + "description": "Image name, ex: new-project, if the --image flag is in use, this option overrides the image [name]. Usage --image-name=${image_name}" + }, + { + "id": "command-args-quarkus-image-tag", + "type": "promptString", + "default": "", + "description": "Image tag, ex: 1.0, if the --image flag is in use, this option overrides the image [tag]. Usage --image-tag=${image_tag_name}" + }, + { + "id": "command-args-quarkus-jib", + "type": "promptString", + "default": "", + "description": "Use Jib extension to generate the image (Docker is still required to save the generated image if push is not used). Usage --jib=true or --jib=false" + }, + { + "id": "command-args-quarkus-jib-podman", + "type": "promptString", + "default": "", + "description": "Use Jib extension to generate the image and save it in podman (can't use --push). Usage --jib-podman=true or --jib-podman=false" + }, + { + "id": "command-args-quarkus-push", + "type": "promptString", + "default": "", + "description": "Attempt to push the genereated image after being successfully built. Usage --push=true or --push=false" + }, + { + "id": "command-args-quarkus-test", + "type": "promptString", + "default": "", + "description": "Run the project tests. Usage --test=true or --test=false." + } + ] +} diff --git a/cli/.vscode/settings.json b/cli/.vscode/settings.json new file mode 100644 index 00000000..c72ae348 --- /dev/null +++ b/cli/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "go.testTimeout": "20m", + "go.buildTags": "e2e_tests" +} diff --git a/cli/.vscode/tasks.json b/cli/.vscode/tasks.json new file mode 100644 index 00000000..d309740e --- /dev/null +++ b/cli/.vscode/tasks.json @@ -0,0 +1,45 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "build:dev", + "group": "build", + "problemMatcher": [], + "label": "build", + "detail": "rimraf dist && pnpm build" + }, + { + "type": "npm", + "script": "debug:clean", + "group": "none", + "problemMatcher": [], + "label": "clean", + "detail": "rimraf debug" + }, + { + "type": "shell", + "group": "none", + "problemMatcher": [], + "label": "create", + "command": "kn workflow create --name='debug'" + }, + { + "type": "shell", + "group": "none", + "problemMatcher": [], + "label": "quarkus create", + "command": "kn workflow quarkus create --name='debug'" + }, + { + "label": "build and create", + "dependsOrder": "sequence", + "dependsOn": ["build", "create"] + }, + { + "label": "build and quarkus create", + "dependsOrder": "sequence", + "dependsOn": ["build", "quarkus create"] + } + ] +} diff --git a/cli/LICENSE b/cli/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/cli/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/cli/Makefile b/cli/Makefile new file mode 100644 index 00000000..5f176354 --- /dev/null +++ b/cli/Makefile @@ -0,0 +1,132 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Include parent .env file for build metadata +-include ../.env + +BIN := kn-workflow +BIN_DARWIN_AMD64 ?= $(BIN)-darwin-amd64 +BIN_DARWIN_ARM64 ?= $(BIN)-darwin-arm64 +BIN_LINUX ?= $(BIN)-linux-amd64 +BIN_WINDOWS ?= $(BIN)-windows-amd64.exe + +BIN_PATH := ./dist +TEST_PATH := ./dist-tests +MAIN_PATH := cmd/main.go + +# Version from git tag or VERSION env var +CLI_VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "$(VERSION)") + +# Build-time metadata injection via ldflags +METADATA_PATH := github.com/kubesmarts/logic-operator/cli/pkg/metadata +LDFLAGS := -X $(METADATA_PATH).PluginVersion=$(CLI_VERSION) \ + -X $(METADATA_PATH).QuarkusVersion=$(QUARKUS_VERSION) \ + -X $(METADATA_PATH).QuarkusPlatformGroupId=$(QUARKUS_PLATFORM_GROUP_ID) \ + -X $(METADATA_PATH).BuilderImage=$(RELATED_IMAGE_BASE_BUILDER) \ + -X $(METADATA_PATH).DevModeImage=$(RELATED_IMAGE_DEVMODE) + +KIND_VERSION ?= v0.20.0 +OLM_VERSION = v0.31.0 +KIND_CLUSTER ?= kind +KUBE_RBAC_PROXY_SRC := quay.io/brancz/kube-rbac-proxy:v0.13.1 +KUBE_RBAC_PROXY_DST := gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0 + +ARCH := $(shell uname -m) +ifeq ($(ARCH),arm64) + GOARCH = arm64 + BIN_BUILD_DARWIN=$(BIN_DARWIN_ARM64) +else + GOARCH = amd64 + BIN_BUILD_DARWIN=$(BIN_DARWIN_AMD64) +endif + +.PHONY: build +build: + CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o $(BIN_PATH)/$(BIN) $(MAIN_PATH) + +.PHONY: build-all +build-all: build-linux-amd64 build-darwin-amd64 build-darwin-arm64 build-win32-amd64 + +.PHONY: build-darwin +build-darwin: + CGO_ENABLED=0 GOOS=darwin GOARCH=$(GOARCH) go build -ldflags "$(LDFLAGS)" -o $(BIN_PATH)/$(BIN_BUILD_DARWIN) $(MAIN_PATH) + +.PHONY: build-darwin-amd64 +build-darwin-amd64: + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o $(BIN_PATH)/$(BIN_DARWIN_AMD64) $(MAIN_PATH) + +.PHONY: build-darwin-arm64 +build-darwin-arm64: + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o $(BIN_PATH)/$(BIN_DARWIN_ARM64) $(MAIN_PATH) + +.PHONY: build-linux-amd64 +build-linux-amd64: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o $(BIN_PATH)/$(BIN_LINUX) $(MAIN_PATH) + +.PHONY: build-win32-amd64 +build-win32-amd64: + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o $(BIN_PATH)/$(BIN_WINDOWS) $(MAIN_PATH) + +.PHONY: clean +clean: + go clean + rm -rf $(BIN_PATH) $(TEST_PATH) + +.PHONY: test +test: + go test -v ./... + +.PHONY: test-e2e +test-e2e: + @$(MAKE) install-kind + @$(MAKE) create-cluster + @$(MAKE) install-operator-framework + @$(MAKE) kind-preload-images + @$(MAKE) go-test-e2e + @$(MAKE) go-test-e2e-report + +.PHONY: install-kind +install-kind: + command -v kind >/dev/null || go install sigs.k8s.io/kind@$(KIND_VERSION) + +.PHONY: create-cluster +create-cluster: install-kind + kind create cluster + +.PHONY: install-operator-framework +install-operator-framework: + curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/$(OLM_VERSION)/install.sh | bash -s $(OLM_VERSION) + +.PHONY: go-test-e2e +go-test-e2e: + rm -rf dist-tests-e2e + mkdir dist-tests-e2e + go test -v ./e2e-tests/... -tags e2e_tests -run TestQuarkusRunCommand -timeout 20m 2>&1 | tee ./dist-tests-e2e/go-test-output-e2e.txt + +.PHONY: go-test-e2e-report +go-test-e2e-report: + go run github.com/jstemmer/go-junit-report/v2 \ + -set-exit-code \ + -in ./dist-tests-e2e/go-test-output-e2e.txt \ + -out ./dist-tests-e2e/junit-report-it.xml + +.PHONY: kind-preload-images +kind-preload-images: + @echo "Preloading kube-rbac-proxy image into kind..." + docker pull $(KUBE_RBAC_PROXY_SRC) + docker tag $(KUBE_RBAC_PROXY_SRC) $(KUBE_RBAC_PROXY_DST) + kind load docker-image --name $(KIND_CLUSTER) $(KUBE_RBAC_PROXY_DST) diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 00000000..13701099 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,143 @@ + + +# kn-workflow CLI + +`kn-workflow` is a command-line tool for creating, managing, and deploying SonataFlow projects. It enables users to quickly set up local SonataFlow projects and deploy them to Kubernetes via the Logic Operator. + +This CLI is part of the [Logic Operator](https://github.com/kubesmarts/logic-operator) project. + +[Read the documentation](https://sonataflow.org/serverlessworkflow/main/testing-and-troubleshooting/kn-plugin-workflow-overview.html) + +## Build from source + +### Prerequisites + +- Go `1.26.0+` _(To install, follow these instructions: https://go.dev/doc/install)_ +- Make + +### Building + +To build the CLI for your current platform: + +```bash +make build +``` + +The binary will be created at `./dist/kn-workflow`. + +### Building for all platforms + +To build the CLI for all supported platforms (Linux, macOS, Windows): + +```bash +make build-all +``` + +Artifacts are generated in the `dist/` directory: +- `kn-workflow-darwin-amd64` - macOS Intel +- `kn-workflow-darwin-arm64` - macOS Apple Silicon +- `kn-workflow-linux-amd64` - Linux +- `kn-workflow-windows-amd64.exe` - Windows + +### Configuration + +Build metadata (Quarkus version, container images) is inherited from the parent `.env` file. You can override these values by setting environment variables: + +```bash +QUARKUS_VERSION=3.8.1 make build +``` + +### Running tests + +Run unit tests: + +```bash +make test +``` + +Run end-to-end tests (requires Docker/Podman and KIND): + +```bash +make test-e2e +``` + +### Clean build artifacts + +```bash +make clean +``` + +## Installation + +After building, copy the binary to your PATH: + +```bash +# macOS/Linux +sudo cp dist/kn-workflow /usr/local/bin/ + +# Or install to user bin +cp dist/kn-workflow ~/.local/bin/ +``` + +## Usage + +```bash +# Create a new workflow project +kn-workflow create --name my-workflow + +# Deploy to Kubernetes +kn-workflow deploy --namespace my-namespace + +# Run in development mode +kn-workflow run + +# Show version +kn-workflow version +``` + +For more commands and options, run: + +```bash +kn-workflow --help +``` + +--- + +Apache KIE (incubating) is an effort undergoing incubation at The Apache Software +Foundation (ASF), sponsored by the name of Apache Incubator. Incubation is +required of all newly accepted projects until a further review indicates that +the infrastructure, communications, and decision making process have stabilized +in a manner consistent with other successful ASF projects. While incubation +status is not necessarily a reflection of the completeness or stability of the +code, it does indicate that the project has yet to be fully endorsed by the ASF. + +Some of the incubating project's releases may not be fully compliant with ASF +policy. For example, releases may have incomplete or un-reviewed licensing +conditions. What follows is a list of known issues the project is currently +aware of (note that this list, by definition, is likely to be incomplete): + +- Hibernate, an LGPL project, is being used. Hibernate is in the process of + relicensing to ASL v2 +- Some files, particularly test files, and those not supporting comments, may + be missing the ASF Licensing Header + +If you are planning to incorporate this work into your product/project, please +be aware that you will need to conduct a thorough licensing review to determine +the overall implications of including this work. For the current status of this +project through the Apache Incubator visit: +https://incubator.apache.org/projects/kie.html diff --git a/cli/cmd/main.go b/cli/cmd/main.go new file mode 100644 index 00000000..f8cec50f --- /dev/null +++ b/cli/cmd/main.go @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package main + +import ( + "fmt" + "os" + + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/kubesmarts/logic-operator/cli/pkg/root" +) + +func main() { + if err := root.NewRootCommand(root.RootCmdConfig{Name: "kn\u00A0workflow", Version: metadata.PluginVersion}).Execute(); err != nil { + if err.Error() != "subcommand is required" { + fmt.Fprintln(os.Stderr, err) + } + os.Exit(1) + } +} diff --git a/cli/e2e-tests/create_test.go b/cli/e2e-tests/create_test.go new file mode 100644 index 00000000..89e5363e --- /dev/null +++ b/cli/e2e-tests/create_test.go @@ -0,0 +1,120 @@ +//go:build e2e_tests + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/kubesmarts/logic-operator/cli/pkg/command" + "github.com/kubesmarts/logic-operator/cli/pkg/common" +) + +type CfgTestInputCreate struct { + input command.CreateCmdConfig +} + +var CfgTestInputCreate_Success = []CfgTestInputCreate{ + {input: command.CreateCmdConfig{}}, + {input: command.CreateCmdConfig{ + ProjectName: "my-project", + }}, +} + +var CfgTestInputCreate_Fail = []CfgTestInputCreate{ + {input: command.CreateCmdConfig{ + ProjectName: "wrong/project-name", + }}, +} + +func transformCreateCmdCfgToArgs(cfg command.CreateCmdConfig) []string { + args := []string{"create"} + if cfg.ProjectName != "" { + args = append(args, "--name", cfg.ProjectName) + } + return args +} + +func GetCreateProjectName(t *testing.T, config CfgTestInputCreate) string { + if config.input.ProjectName != "" { + return config.input.ProjectName + } else { + projectDefaultName, err := LookupFlagDefaultValue("name", command.NewCreateCommand()) + require.NoErrorf(t, err, "Error: %v", err) + return projectDefaultName + } +} + +func TestCreateProjectSuccess(t *testing.T) { + for testIndex, test := range CfgTestInputCreate_Success { + t.Run(fmt.Sprintf("Test create project success index: %d", testIndex), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + RunCreateTest(t, test) + }) + } +} + +func RunCreateTest(t *testing.T, test CfgTestInputCreate) string { + var err error + + projectName := GetCreateProjectName(t, test) + projectDir := filepath.Join(TempTestsPath, projectName) + + // Run `create` command + _, err = ExecuteKnWorkflow(transformCreateCmdCfgToArgs(test.input)...) + require.NoErrorf(t, err, "Expected nil error, got: %v", err) + + // Check if the project directory was created + require.DirExistsf(t, projectDir, "Expected project directory '%s' to be created", projectDir) + + expectedFiles := []string{"workflow.sw.json"} + VerifyFilesExist(t, projectDir, expectedFiles) + + // Verify the content of the file `workflow.sw.json` + workflowFileData, err := common.GetWorkflowTemplate(false) + expectedFileContent := string(workflowFileData) + VerifyFileContent(t, filepath.Join(projectDir, "workflow.sw.json"), expectedFileContent) + + return projectName +} + +func TestCreateProjectFail(t *testing.T) { + for testIndex, test := range CfgTestInputCreate_Fail { + t.Run(fmt.Sprintf("Test create project fail index: %d", testIndex), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + projectName := test.input.ProjectName + projectDir := filepath.Join(TempTestsPath, projectName) + + _, err := ExecuteKnWorkflow(transformCreateCmdCfgToArgs(test.input)...) + require.Errorf(t, err, "Expected error, got nil") + + // Check if the project directory was not created + require.NoDirExistsf(t, projectDir, "Expected project directory '%s' not to be created", projectDir) + + // Cleanup (if necessary) + common.DeleteFolderStructure(t, projectDir) + }) + } +} diff --git a/cli/e2e-tests/deploy_test.go b/cli/e2e-tests/deploy_test.go new file mode 100644 index 00000000..3ed088ff --- /dev/null +++ b/cli/e2e-tests/deploy_test.go @@ -0,0 +1,208 @@ +//go:build e2e_tests + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +import ( + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/kubesmarts/logic-operator/cli/pkg/command" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" +) + +type cfgTestInputDeploy struct { + input command.DeployUndeployCmdConfig +} + +type Config struct { + Kind string `yaml:"kind"` + Spec struct { + PodTemplate struct { + Container struct { + Image string `yaml:"image"` + } `yaml:"container"` + } `yaml:"podTemplate"` + } `yaml:"spec"` +} + +var cfgTestInputDeploy_Success = []cfgTestInputDeploy{ + {input: command.DeployUndeployCmdConfig{}}, +} + +var my_test_image = "my_test_image" + +func transformDeployCmdCfgToArgs(cfg command.DeployUndeployCmdConfig) []string { + args := []string{"deploy"} + return args +} + +func TestDeployProjectSuccess(t *testing.T) { + dir, err := os.Getwd() + require.NoError(t, err) + + var originalCheckCrds = command.CheckCRDs + defer func() { command.CheckCRDs = originalCheckCrds }() + + command.CheckCRDs = func(crds []string, typeName string) error { + return nil + } + + var executeApplyOriginal = common.ExecuteApply + defer func() { common.ExecuteApply = executeApplyOriginal }() + + common.ExecuteApply = func(crd, namespace string) error { + return nil + } + + defer os.Chdir(dir) + for testIndex := range cfgTestInputDeploy_Success { + t.Run(fmt.Sprintf("Test deploy project success index: %d", testIndex), func(t *testing.T) { + RunCreateTest(t, CfgTestInputCreate_Success[testIndex]) + projectName := GetCreateProjectName(t, CfgTestInputCreate_Success[testIndex]) + projectDir := filepath.Join(TempTestsPath, projectName) + defer os.RemoveAll(projectDir) + + err = os.Chdir(projectDir) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + cmd := command.NewDeployCommand() + err = cmd.Execute() + require.NoError(t, err) + }) + } +} + +func TestDeployProjectSuccessWithImageDefined(t *testing.T) { + dir, err := os.Getwd() + require.NoError(t, err) + + var originalCheckCrds = command.CheckCRDs + defer func() { command.CheckCRDs = originalCheckCrds }() + + command.CheckCRDs = func(crds []string, typeName string) error { + return nil + } + + var executeApplyOriginal = common.ExecuteApply + defer func() { common.ExecuteApply = executeApplyOriginal }() + + defer os.Chdir(dir) + for testIndex := range cfgTestInputDeploy_Success { + common.ExecuteApply = func(path, namespace string) error { + if cfgTestInputDeploy_Success[testIndex].input.Image != "" { + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to open file: %v", err) + } + defer file.Close() + data, err := io.ReadAll(file) + if err != nil { + t.Fatalf("❌ ERROR: Failed to read file: %v", err) + } + + var cfg Config + err = yaml.Unmarshal(data, &cfg) + if err != nil { + t.Fatalf("❌ ERROR: Failed to unmarshal file: %v", err) + } + + if cfg.Kind != "SonataFlow" { + return nil + } + + if cfg.Spec.PodTemplate.Container.Image != my_test_image { + t.Fatalf("❌ ERROR: Expected image %s, got %s", my_test_image, cfg.Spec.PodTemplate.Container.Image) + } + } + return nil + } + + t.Run(fmt.Sprintf("Test deploy project success index: %d", testIndex), func(t *testing.T) { + RunCreateTest(t, CfgTestInputCreate_Success[testIndex]) + projectName := GetCreateProjectName(t, CfgTestInputCreate_Success[testIndex]) + projectDir := filepath.Join(TempTestsPath, projectName) + defer os.RemoveAll(projectDir) + + err = os.Chdir(projectDir) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + cmd := command.NewDeployCommand() + cmd.SetArgs([]string{"--image", my_test_image}) + err = cmd.Execute() + require.NoError(t, err) + }) + } +} + +func TestDeployProjectSuccessWithoutResultEventRef(t *testing.T) { + _, file, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("cannot determine current test file path") + } + baseDir := filepath.Dir(file) + dataPath := filepath.Join(baseDir, "testdata", "lock.sw.yaml") + data, err := os.ReadFile(dataPath) + if err != nil { + t.Fatalf("❌ ERROR: Failed to read file %q: %v", dataPath, err) + } + + dir, err := os.Getwd() + require.NoError(t, err) + + var originalCheckCrds = command.CheckCRDs + defer func() { command.CheckCRDs = originalCheckCrds }() + + command.CheckCRDs = func(crds []string, typeName string) error { + return nil + } + + var executeApplyOriginal = common.ExecuteApply + defer func() { common.ExecuteApply = executeApplyOriginal }() + + common.ExecuteApply = func(path, namespace string) error { + return nil + } + + defer os.Chdir(dir) + + tmpRoot := t.TempDir() + destDir := filepath.Join(tmpRoot, "workspace") + require.NoError(t, os.MkdirAll(destDir, 0755)) + + dst := filepath.Join(destDir, "lock.sw.yaml") + + require.NoError(t, os.WriteFile(dst, data, 0644)) + + require.NoError(t, os.Chdir(destDir)) + + t.Run(fmt.Sprintf("Test deploy project with resultEventRef"), func(t *testing.T) { + cmd := command.NewDeployCommand() + err = cmd.Execute() + require.NoError(t, err) + }) +} diff --git a/cli/e2e-tests/gen_manifest_test.go b/cli/e2e-tests/gen_manifest_test.go new file mode 100644 index 00000000..1dde8e3b --- /dev/null +++ b/cli/e2e-tests/gen_manifest_test.go @@ -0,0 +1,128 @@ +//go:build e2e_tests + +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package e2e_tests + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/kubesmarts/logic-operator/cli/pkg/command" +) + +type GenManifestTestInputCreate struct { + args []string + checks []func(t *testing.T, content string) +} + +var tests = []GenManifestTestInputCreate{ + {args: []string{"gen-manifest", "--image", "my_image"}, + checks: []func(t *testing.T, content string){ + func(t *testing.T, content string) { + require.Contains(t, content, " image: my_image", "Expected image to be my_image") + }, + }, + }, + {args: []string{"gen-manifest", "--profile", "gitops"}, + checks: []func(t *testing.T, content string){ + func(t *testing.T, content string) { + require.Contains(t, content, " sonataflow.org/profile: gitops", "Expected profile to be gitops") + }, + }, + }, + {args: []string{"gen-manifest", "--profile", "dev"}, + checks: []func(t *testing.T, content string){ + func(t *testing.T, content string) { + require.Contains(t, content, " sonataflow.org/profile: dev", "Expected profile to be dev") + }, + }, + }, + {args: []string{"gen-manifest", "--profile", "preview"}, + checks: []func(t *testing.T, content string){ + func(t *testing.T, content string) { + require.Contains(t, content, " sonataflow.org/profile: preview", "Expected profile to be preview") + }, + }, + }, + {args: []string{"gen-manifest", "--namespace", "my_namespace", "--skip-namespace"}, + checks: []func(t *testing.T, content string){ + func(t *testing.T, content string) { + require.NotContains(t, content, " namespace: my_namespace", "Unexpected namespace: my_namespace") + }, + }, + }, + {args: []string{"gen-manifest", "--skip-namespace"}, + checks: []func(t *testing.T, content string){ + func(t *testing.T, content string) { + require.NotContains(t, content, " namespace: default", "Unexpected namespace: default") + }, + }, + }, +} + +func TestGenManifestProjectSuccess(t *testing.T) { + var test = CfgTestInputCreate{ + input: command.CreateCmdConfig{ProjectName: "new-project"}, + } + t.Run(fmt.Sprintf("Test gen-manifest success"), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + + RunCreateTest(t, test) + + projectName := GetCreateProjectName(t, CfgTestInputCreate{ + input: command.CreateCmdConfig{ProjectName: "new-project"}, + }) + projectDir := filepath.Join(TempTestsPath, projectName) + err := os.Chdir(projectDir) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + for _, run := range tests { + _, err = ExecuteKnWorkflow(run.args...) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + manifestDir := getGenManifestDir(projectDir, t) + + yaml := readFileAsString(t, filepath.Join(manifestDir, "01-sonataflow_hello.yaml")) + + for _, check := range run.checks { + check(t, yaml) + } + } + }) +} + +func getGenManifestDir(projectDir string, t *testing.T) string { + manifestDir := filepath.Join(projectDir, "manifests") + require.DirExistsf(t, manifestDir, "Expected project directory '%s' to be created", manifestDir) + + expectedFiles := []string{"01-sonataflow_hello.yaml"} + VerifyFilesExist(t, manifestDir, expectedFiles) + + return manifestDir +} + +func readFileAsString(t *testing.T, path string) string { + content, err := os.ReadFile(path) + if err != nil { + t.Fatalf("Failed to read file %s", path) + } + return string(content) +} diff --git a/cli/e2e-tests/helper_test.go b/cli/e2e-tests/helper_test.go new file mode 100644 index 00000000..4de18c50 --- /dev/null +++ b/cli/e2e-tests/helper_test.go @@ -0,0 +1,303 @@ +//go:build e2e_tests + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +import ( + "bufio" + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "testing" + + "github.com/kubesmarts/logic-operator/cli/pkg/command" + "github.com/kubesmarts/logic-operator/cli/pkg/command/quarkus" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" +) + +// Holds quarkus dependencies populated from environment variables +var quarkusDependencies = metadata.ResolveQuarkusDependencies() + +// ExecuteCommand executes a command with the given arguments and returns an error if the command fails. +func ExecuteCommand(command string, args ...string) error { + cmd := exec.Command(command, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// ExecuteKnWorkflow executes the 'kn-workflow' CLI tool with the given arguments and returns the command's output and possible error message. +func ExecuteKnWorkflow(args ...string) (string, error) { + cmd := exec.Command(KnExecutable) + return executeCommandWithOutput(cmd, args...) +} + +// ExecuteKnWorkflowQuarkus executes the 'kn-workflow' CLI tool with 'quarkus' command with the given arguments and returns the command's output and possible error message. +func ExecuteKnWorkflowQuarkus(args ...string) (string, error) { + newArgs := append([]string{"quarkus"}, args...) + cmd := exec.Command(KnExecutable) + return executeCommandWithOutput(cmd, newArgs...) +} + +// ExecuteKnWorkflowWithCmd executes the 'kn-workflow' CLI tool with the given arguments using the provided command and returns the command's output and possible error message. +func ExecuteKnWorkflowWithCmd(cmd *exec.Cmd, args ...string) (string, error) { + return executeCommandWithOutput(cmd, args...) +} + +// ExecuteKnWorkflowWithCmdAndStopContainer executes the 'kn-workflow' CLI tool with the given arguments using the provided command and returns the containerID and possible error message. +func ExecuteKnWorkflowWithCmdAndStopContainer(cmd *exec.Cmd, args ...string) (string, error) { + return executeCommandWithOutputAndStopContainer(cmd, args...) +} + +// ExecuteKnWorkflowQuarkusWithCmd executes the 'kn-workflow' CLI tool with 'quarkus' command with the given arguments using the provided command and returns the command's output and possible error message. +func ExecuteKnWorkflowQuarkusWithCmd(cmd *exec.Cmd, args ...string) (string, error) { + newArgs := append([]string{"quarkus"}, args...) + return executeCommandWithOutput(cmd, newArgs...) +} + +// executeCommandWithOutput executes a command with the given arguments using the provided command and captures its standard output and error streams. +// It returns the combined standard output as a string and an error if the command fails. +// It also prints out the standard output to the console if 'e2e_tests.testPrintCmdOutput' is set to 'true'. +func executeCommandWithOutput(cmd *exec.Cmd, args ...string) (string, error) { + cmd.Args = append([]string{cmd.Path}, args...) + var stdout bytes.Buffer + var stderr bytes.Buffer + if *TestPrintCmdOutput { + cmd.Stdout = io.MultiWriter(os.Stdout, &stdout) + } else { + cmd.Stdout = &stdout + } + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + return stdout.String(), err + } + return stdout.String(), nil +} + +func executeCommandWithOutputAndStopContainer(cmd *exec.Cmd, args ...string) (string, error) { + cmd.Args = append([]string{cmd.Path}, args...) + + var containerId string + var stderr bytes.Buffer + + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return "", fmt.Errorf("failed to create stdout pipe: %w", err) + } + defer stdoutPipe.Close() + + stdinPipe, err := cmd.StdinPipe() + if err != nil { + return "", fmt.Errorf("failed to create stdin pipe: %w", err) + } + defer stdinPipe.Close() + + cmd.Stderr = &stderr + errorCh := make(chan error, 1) + + go func() { + defer close(errorCh) + scanner := bufio.NewScanner(stdoutPipe) + for scanner.Scan() { + line := scanner.Text() + + if strings.HasPrefix(line, "Created container with ID ") { + id, ok := strings.CutPrefix(line, "Created container with ID ") + if !ok || id == "" { + errorCh <- fmt.Errorf("failed to parse container ID from output: %q", line) + return + } + containerId = id + } + + if line == command.StopContainerMsg { + _, err := io.WriteString(stdinPipe, "any\n") + if err != nil { + errorCh <- fmt.Errorf("failed to write to stdin: %w", err) + return + } + } + } + + if err := scanner.Err(); err != nil { + errorCh <- fmt.Errorf("error reading from stdout: %w", err) + return + } + }() + + err = cmd.Run() + if err != nil { + return "", fmt.Errorf("command run error: %w (stderr: %s)", err, stderr.String()) + } + + readErr := <-errorCh + if readErr != nil { + return "", readErr + } + + return containerId, nil +} + +// VerifyFileContent verifies that the content of a file matches the expected content. +func VerifyFileContent(t *testing.T, filePath string, expected string) { + actual, err := os.ReadFile(filePath) + require.NoErrorf(t, err, "Failed to read file: %s", filePath) + require.Equalf(t, expected, string(actual), "The content of the file '%s' is different than expected", filePath) +} + +// VerifyDirectoriesExist verifies that the specified directories exist within the given base directory. +func VerifyDirectoriesExist(t *testing.T, baseDir string, directories []string) { + for _, dir := range directories { + dirPath := filepath.Join(baseDir, dir) + require.DirExistsf(t, dirPath, "Expected directory '%s' to be present", dirPath) + } +} + +// VerifyFilesExist verifies that the specified files exist within the given base directory. +func VerifyFilesExist(t *testing.T, baseDir string, files []string) { + for _, file := range files { + filePath := filepath.Join(baseDir, file) + require.FileExistsf(t, filePath, "Expected file '%s' to be present", filePath) + } +} + +// ExpectedImageName returns the expected image name based on the provided quarkus.BuildCmdConfig. +func ExpectedImageName(cfg quarkus.BuildCmdConfig) string { + var outputName string + if cfg.Repository != "" { + splitter := func(r rune) bool { + return r == '/' || r == ':' + } + inputName := strings.FieldsFunc(cfg.Image, splitter) + outputNameArray := []string{inputName[0], cfg.Repository, inputName[1]} + outputName = strings.Join(outputNameArray, "/") + } else { + outputName = cfg.Image + } + if cfg.Tag != "" { + outputName = outputName + ":" + cfg.Tag + } + return outputName +} + +// LookupFlagDefaultValue looks up the default value of a flag within a given command. +func LookupFlagDefaultValue(flagName string, createCmd *cobra.Command) (string, error) { + flag := createCmd.Flags().Lookup(flagName) + if flag == nil { + return "", fmt.Errorf("flag '%s' not found", flagName) + } + return flag.DefValue, nil +} + +// IsSignalInterrupt checks if the given error is caused by a signal interrupt. +func IsSignalInterrupt(err error) bool { + if err == nil { + return false + } + if errors.Is(err, context.Canceled) { + return true + } + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { + return status.Signaled() && (status.Signal() == syscall.SIGINT || status.Signal() == syscall.SIGTERM) + } + } + return strings.Contains(err.Error(), "signal: interrupt") || strings.Contains(err.Error(), "signal: terminated") +} + +// cleanUpFolder removes the folder at the given path and recreates it with permissions set to 0750. +func cleanUpFolder(t *testing.T, path string) { + var err error + err = os.RemoveAll(path) + if err != nil { + t.Errorf("failed to delete '%s' folder: %v", path, err) + } + err = os.Mkdir(path, 0750) + if err != nil { + t.Errorf("failed to create '%s' create: %v", path, err) + } +} + +// CleanUpAndChdirTemp cleans up the 'temp-tests' folder by removing its contents and changes the current directory to it. +func CleanUpAndChdirTemp(t *testing.T) { + cleanUpFolder(t, TempTestsPath) + err := os.Chdir(TempTestsPath) + if err != nil { + t.Errorf("failed to change directory to temp: %v", err) + os.Exit(1) + } +} + +func AddSnapshotRepositoryDeclarationToPom(t *testing.T, projectDir string) { + VerifyFilesExist(t, projectDir, []string{"pom.xml"}) + pomFilePath := filepath.Join(projectDir, "pom.xml") + + file, err := os.Open(pomFilePath) + require.NoErrorf(t, err, "Expected nil error, got: %v", err) + + content, err := io.ReadAll(file) + require.NoErrorf(t, err, "Expected nil error, got: %v", err) + + err = file.Close() + require.NoErrorf(t, err, "Expected nil error, got: %v", err) + + xml := string(content) + insertPosition := strings.Index(xml, "") + len("") + + const repository = ` + + + central + https://repo.maven.apache.org/maven2 + + true + + + true + + + + logic-repo + https://maven.pkg.github.com/kubesmarts/logic-maven-repository + + true + + + true + + + ` + + modifiedXml := xml[:insertPosition] + repository + xml[insertPosition:] + err = os.WriteFile(pomFilePath, []byte(modifiedXml), 0644) + require.NoErrorf(t, err, "Expected nil error, got: %v", err) +} diff --git a/cli/e2e-tests/main_test.go b/cli/e2e-tests/main_test.go new file mode 100644 index 00000000..08488f3d --- /dev/null +++ b/cli/e2e-tests/main_test.go @@ -0,0 +1,214 @@ +//go:build e2e_tests + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" +) + +var parentPath string +var TempTestsPath string +var KnExecutable string + +var TestPrintCmdOutput = flag.Bool("logs", true, "Print command output during tests") + + +func TestMain(m *testing.M) { + + // Create temp directory for tests and switch inside it + workingPath, _ := os.Getwd() + parentPath = filepath.Dir(workingPath) + tempDirName := "temp-tests" + if fileExists(tempDirName) { + cleanUpTemp(workingPath, tempDirName) + } + setUpTempDir(tempDirName) + TempTestsPath = filepath.Join(workingPath, tempDirName) + + KnExecutable = getPlatformSpecificExecutablePath() + + checkAndBuildExecutable() + + // Skip operator installation if running without OLM + skipOperator := os.Getenv("SKIP_OPERATOR_INSTALL") + if skipOperator != "true" { + InstallOperator() + } + // Run tests + exitCode := m.Run() + + if skipOperator != "true" { + UninstallOperator() + } + + // Cleanup after tests + cleanUpTemp(workingPath, tempDirName) + + os.Exit(exitCode) +} + +func setUpTempDir(tempDirName string) { + var err error + err = os.Mkdir(tempDirName, 0750) + if err != nil { + fmt.Printf("Failed to create temp directory: %v", err) + os.Exit(1) + } + err = os.Chdir(tempDirName) + if err != nil { + fmt.Printf("Failed to change directory to temp: %v", err) + os.Exit(1) + } +} + +func cleanUpTemp(workingPath string, tempDirName string) { + var err error + err = os.Chdir(workingPath) + if err != nil { + fmt.Printf("Failed to change directory back from temp: %v", err) + os.Exit(1) + } + err = os.RemoveAll(tempDirName) + if err != nil { + fmt.Printf("Failed to remove temp directory: %v", err) + os.Exit(1) + } +} + +func getPlatformSpecificExecutablePath() string { + binaryDirPath := filepath.Join(parentPath, "dist") + buildOutput := filepath.Join(binaryDirPath, "/kn-workflow-") + switch osAndArch := strings.ToLower(runtime.GOOS); osAndArch { + case "darwin": + switch arch := strings.ToLower(runtime.GOARCH); arch { + case "amd64": + buildOutput += "darwin-amd64" + case "arm64": + buildOutput += "darwin-arm64" + default: + fmt.Println("Unsupported architecture:", arch) + os.Exit(1) + } + case "linux": + buildOutput += "linux-amd64" + case "windows": + switch arch := strings.ToLower(runtime.GOARCH); arch { + case "amd64": + buildOutput += "windows-amd64.exe" + default: + fmt.Println("Unsupported architecture:", arch) + os.Exit(1) + } + default: + fmt.Println("Unsupported OS:", osAndArch) + os.Exit(1) + } + return buildOutput +} + +func checkAndBuildExecutable() { + // Check if rebuilding the executable is needed + executableExists := fileExists(KnExecutable) + executableIsExecutable := isExecAny(KnExecutable) + lastBuildTimestamp, errTimestamp := getDistFolderTimestamp(filepath.Join(parentPath, "dist")) + codeModified, errModified := areSourceFilesModifiedSince(lastBuildTimestamp) + if executableExists == false || executableIsExecutable == false || codeModified == true || errTimestamp != nil || errModified != nil { + buildDev() // Build the executable for current platform (`build:dev`) + } +} + +func getDistFolderTimestamp(path string) (time.Time, error) { + fileInfo, err := os.Stat(path) + if err != nil { + return time.Time{}, err + } + + return fileInfo.ModTime(), nil +} + +func areSourceFilesModifiedSince(timestamp time.Time) (bool, error) { + excludedFolders := []string{"e2e-tests", "dist-tests", "dist"} + modificationsDetected := false + + err := filepath.Walk(parentPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + for _, excludedFolder := range excludedFolders { + if strings.Contains(path, excludedFolder) { + return filepath.SkipDir + } + } + return nil + } + + modifiedTime := info.ModTime() + if modifiedTime.After(timestamp) { + modificationsDetected = true + return filepath.SkipDir + } + + return nil + }) + + if err != nil { + return false, err + } + + return modificationsDetected, nil +} + +func buildDev() { + err := ExecuteCommand("pnpm", "build:dev") + if err != nil { + fmt.Println("Failed to build:", err) + os.Exit(1) + } + + err = ExecuteCommand("chmod", "+x", KnExecutable) + if err != nil { + fmt.Println("Failed to make the built executable file executable:", err) + os.Exit(1) + } + + return +} + +func fileExists(filePath string) bool { + _, err := os.Stat(filePath) + return err == nil +} + +func isExecAny(filePath string) bool { + fileInfo, _ := os.Stat(filePath) + fileMode := fileInfo.Mode() + return fileMode&0111 != 0 +} diff --git a/cli/e2e-tests/operator_helper.go b/cli/e2e-tests/operator_helper.go new file mode 100644 index 00000000..b17c0790 --- /dev/null +++ b/cli/e2e-tests/operator_helper.go @@ -0,0 +1,124 @@ +//go:build e2e_tests + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +import ( + "fmt" + "os" + "time" + + "github.com/kubesmarts/logic-operator/cli/pkg/command/operator" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +var operatorManager = common.NewOperatorManager("") + +func InstallOperator() { + installOperator() + waitForOperatorReady() + checkOperatorInstalled() +} + +func UninstallOperator() { + uninstallOperator() +} + +func installOperator() { + var install = operator.NewInstallOperatorCommand() + err := install.Execute() + if err != nil { + fmt.Println("Failed to install operator:", err) + os.Exit(1) + } +} + +func waitForOperatorReady() { + deployed := make(chan bool) + defer close(deployed) + timeoutCh := time.After(5 * time.Minute) + + go func() { + for { + select { + case <-timeoutCh: + fmt.Println("Timeout waiting for operator to be ready") + os.Exit(1) + default: + resources, err := operatorManager.ListOperatorResources(); + if err != nil { + fmt.Println("Failed to list operator resources:", err) + os.Exit(1) + } + + if(len(resources) == 0) { + continue + } + + var ready = true + for _, resource := range resources { + phase, found, err := unstructured.NestedString(resource.Object, "status", "phase") + if !found { + ready = false + } + if err != nil { + fmt.Println("Failed to get resource status:", err) + os.Exit(1) + } + if phase != "Succeeded" { + ready = false + } + } + + if ready { + deployed <- true + return + + } + time.Sleep(5 * time.Second) + } + } + }() + + select { + case <-deployed: + fmt.Printf(" - ✅ Operator is ready\n") + } +} + +func checkOperatorInstalled() { + var status = operator.NewStatusOperatorCommand() + err := status.Execute() + if err != nil { + fmt.Println("Failed to check operator status:", err) + os.Exit(1) + } +} + +func uninstallOperator() { + var uninstall = operator.NewUnInstallOperatorCommand() + err := uninstall.Execute() + if err != nil { + fmt.Println("Failed to uninstall operator:", err) + os.Exit(1) + } +} diff --git a/cli/e2e-tests/quarkus_build_test.go b/cli/e2e-tests/quarkus_build_test.go new file mode 100644 index 00000000..19ee8cee --- /dev/null +++ b/cli/e2e-tests/quarkus_build_test.go @@ -0,0 +1,155 @@ +//go:build e2e_tests + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/kubesmarts/logic-operator/cli/pkg/command/quarkus" +) + +var cfgTestInputPrepareQuarkusCreateBuild = CfgTestInputQuarkusCreate{ + input: quarkus.CreateQuarkusProjectConfig{ + ProjectName: "new-project", + }, +} + +type CfgTestInputQuarkusBuild struct { + input quarkus.BuildCmdConfig +} + +var cfgTestInputQuarkusBuild_Success = []CfgTestInputQuarkusBuild{ + {input: quarkus.BuildCmdConfig{ + Image: "dev.local/my-project", + }}, + {input: quarkus.BuildCmdConfig{ + Image: "my-user/my-project:1.0.0", + Repository: "other-user", + Tag: "1.0.1", + }}, + {input: quarkus.BuildCmdConfig{ + Image: "dev.local/my-project", + Jib: true, + }}, + //{input: quarkus.BuildCmdConfig{ + // Image: "dev.local/my-project", + // JibPodman: true, + //}}, + // {input: quarkus.BuildCmdConfig{ + // Image: "dev.local/my-project", + // Jib: true, + // Push: true, + // }}, + // {input: quarkus.BuildCmdConfig{ + // Image: "dev.local/my-project", + // Push: true, + // }}, +} + +func transformQuarkusBuildCmdCfgToArgs(cfg quarkus.BuildCmdConfig) []string { + args := []string{"build"} + if cfg.Image != "" { + args = append(args, "--image", cfg.Image) + } + if cfg.ImageName != "" { + args = append(args, "--image-name", cfg.ImageName) + } + if cfg.Registry != "" { + args = append(args, "--image-registry", cfg.Registry) + } + if cfg.Repository != "" { + args = append(args, "--image-repository", cfg.Repository) + } + if cfg.Tag != "" { + args = append(args, "--image-tag", cfg.Tag) + } + if cfg.Jib == true { + args = append(args, "--jib") + } + if cfg.JibPodman == true { + args = append(args, "--jib-podman") + } + if cfg.Push == true { + args = append(args, "--push") + } + if cfg.Test == true { + args = append(args, "--test") + } + return args +} + +func TestQuarkusBuildCommand(t *testing.T) { + for testIndex, test := range cfgTestInputQuarkusBuild_Success { + t.Run(fmt.Sprintf("Test build project success index: %d", testIndex), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + RunQuarkusBuildTest(t, cfgTestInputPrepareQuarkusCreateBuild, test, true) + }) + } +} + +func RunQuarkusBuildTest(t *testing.T, cfgTestInputQuarkusCreate CfgTestInputQuarkusCreate, test CfgTestInputQuarkusBuild, cleanUp bool) string { + var err error + + // Create the project + projectName := RunQuarkusCreateTest(t, cfgTestInputQuarkusCreate) + projectDir := filepath.Join(TempTestsPath, projectName) + + err = os.Chdir(projectDir) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + // Run `quarkus build` command + args := transformQuarkusBuildCmdCfgToArgs(test.input) + _, err = ExecuteKnWorkflowQuarkus(args...) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + require.FileExists(t, filepath.Join("target", "kubernetes", "knative.yml")) + + // Clean up images from docker and podman + if cleanUp { + CleanUpDockerPodman(t, test) + } + + return projectName +} + +func CleanUpDockerPodman(t *testing.T, test CfgTestInputQuarkusBuild) { + var err error + expectedImageName := ExpectedImageName(test.input) + var removeCmd *exec.Cmd + if test.input.JibPodman { + // Remove built image from podman + removeCmd = exec.Command("podman", "image", "rm", expectedImageName) // podman only takes `rm` for removing images + } else { + // Remove built image from docker + removeCmd = exec.Command("docker", "image", "rm", expectedImageName) // docker takes both `rm` and `remove` for removing images + } + t.Log("Removing image:", removeCmd.Args) + out, err := removeCmd.Output() + fmt.Print(string(out)) + require.NoErrorf(t, err, "Error when removing image: %s. Expected nil error, got %v", expectedImageName, err) +} diff --git a/cli/e2e-tests/quarkus_convert_test.go b/cli/e2e-tests/quarkus_convert_test.go new file mode 100644 index 00000000..fd0653a1 --- /dev/null +++ b/cli/e2e-tests/quarkus_convert_test.go @@ -0,0 +1,191 @@ +//go:build e2e_tests + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/kubesmarts/logic-operator/cli/pkg/command" + "github.com/kubesmarts/logic-operator/cli/pkg/command/quarkus" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" +) + +var cfgTestInputCreateConvert = CfgTestInputCreate{ + input: command.CreateCmdConfig{}, +} + +var cfgTestInputQuarkusCreateConvert = CfgTestInputQuarkusCreate{ + input: quarkus.CreateQuarkusProjectConfig{}, +} + +type CfgTestInputQuarkusConvert struct { + input quarkus.CreateQuarkusProjectConfig +} + +var cfgTestInputQuarkusConvert_Success = []CfgTestInputQuarkusConvert{ + {input: quarkus.CreateQuarkusProjectConfig{}}, + {input: quarkus.CreateQuarkusProjectConfig{ + Extensions: "quarkus-jsonp,quarkus-smallrye-openapi", + }}, + {input: quarkus.CreateQuarkusProjectConfig{ + Extensions: "quarkus-jsonp,quarkus-smallrye-openapi", + DependenciesVersion: metadata.DependenciesVersion{ + QuarkusPlatformGroupId: quarkusDependencies.QuarkusPlatformGroupId, + QuarkusVersion: quarkusDependencies.QuarkusVersion, + }, + }}, +} + +var cfgTestInputQuarkusConvert_Failed = []CfgTestInputQuarkusConvert{ + {input: quarkus.CreateQuarkusProjectConfig{ + Extensions: "nonexistent-extension", + }}, +} + +var cfgTestInputQuarkusConvert_FailedAlreadyQuarkus = []CfgTestInputQuarkusConvert{ + {input: quarkus.CreateQuarkusProjectConfig{}}, +} + +func transformQuarkusConvertCmdCfgToArgs(t *testing.T, cfg quarkus.CreateQuarkusProjectConfig) []string { + args := []string{"convert"} + require.Empty(t, cfg.ProjectName, "The project name can not be set in the test of `quarkus convert`.") + if cfg.Extensions != "" { + args = append(args, "--extension", cfg.Extensions) + } + if cfg.DependenciesVersion.QuarkusPlatformGroupId != "" { + args = append(args, "--quarkus-platform-group-id", cfg.DependenciesVersion.QuarkusPlatformGroupId) + } + if cfg.DependenciesVersion.QuarkusVersion != "" { + args = append(args, "--quarkus-version", cfg.DependenciesVersion.QuarkusVersion) + } + return args +} + +func TestQuarkusConvertProjectSuccess(t *testing.T) { + for testIndex, test := range cfgTestInputQuarkusConvert_Success { + t.Run(fmt.Sprintf("Test quarkus convert project success index: %d", testIndex), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + RunQuarkusConvertTest(t, cfgTestInputCreateConvert, test) + }) + } +} + +func RunQuarkusConvertTest(t *testing.T, cfgTestInputCreateConvert CfgTestInputCreate, test CfgTestInputQuarkusConvert) string { + var err error + + projectName := GetCreateProjectName(t, cfgTestInputCreateConvert) + projectDir := filepath.Join(TempTestsPath, projectName) + + // Create the project + RunCreateTest(t, cfgTestInputCreateConvert) + + err = os.Chdir(projectDir) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + // Run `quarkus convert` command + _, err = ExecuteKnWorkflowQuarkus(transformQuarkusConvertCmdCfgToArgs(t, test.input)...) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + // Check if the expected directories and files are present + expectedDirectories := []string{ + "src/main/java", + "src/main/resources", + "src/main/docker", + "src/main", + "src", + } + VerifyDirectoriesExist(t, projectDir, expectedDirectories) + expectedFiles := []string{ + "src/main/resources/application.properties", + "src/main/resources/workflow.sw.json", + "src/main/docker/Dockerfile.legacy-jar", + "src/main/docker/Dockerfile.jvm", + "src/main/docker/Dockerfile.native", + "src/main/docker/Dockerfile.native-micro", + ".gitignore", + "pom.xml", + "README.md", + ".dockerignore", + } + VerifyFilesExist(t, projectDir, expectedFiles) + + // Verify the content of the file `workflow.sw.json` + workflowFilePath := filepath.Join(projectDir, "src/main/resources/workflow.sw.json") + workflowFileData, err := common.GetWorkflowTemplate(false) + require.NoErrorf(t, err, "Error reading workflow template: %v", err) + expectedFileContent := string(workflowFileData) + VerifyFileContent(t, workflowFilePath, expectedFileContent) + + return projectName +} + +func TestQuarkusConvertProjectFailed(t *testing.T) { + for testIndex, test := range cfgTestInputQuarkusConvert_Failed { + t.Run(fmt.Sprintf("Test quarkus convert project fail index: %d", testIndex), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + + var err error + projectName := GetCreateProjectName(t, cfgTestInputCreateConvert) + projectDir := filepath.Join(TempTestsPath, projectName) + + // Create the project + RunCreateTest(t, cfgTestInputCreateConvert) + + err = os.Chdir(projectDir) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + // Run `quarkus convert` command + _, err = ExecuteKnWorkflowQuarkus(transformQuarkusConvertCmdCfgToArgs(t, test.input)...) + require.Errorf(t, err, "Expected error, got nil") + + common.DeleteFolderStructure(t, projectDir) + }) + } +} + +func TestQuarkusConvertProjectFailedAlreadyQuarkus(t *testing.T) { + for testIndex, test := range cfgTestInputQuarkusConvert_Failed { + t.Run(fmt.Sprintf("Test quarkus convert project fail already quarkus index: %d", testIndex), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + + var err error + projectName := GetQuarkusCreateProjectName(t, cfgTestInputQuarkusCreateConvert) + projectDir := filepath.Join(TempTestsPath, projectName) + + // Create the project + RunQuarkusCreateTest(t, cfgTestInputQuarkusCreateConvert) + + err = os.Chdir(projectDir) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + // Run `quarkus convert` command + _, err = ExecuteKnWorkflowQuarkus(transformQuarkusConvertCmdCfgToArgs(t, test.input)...) + require.Errorf(t, err, "Expected error, got nil") + }) + } +} diff --git a/cli/e2e-tests/quarkus_create_test.go b/cli/e2e-tests/quarkus_create_test.go new file mode 100644 index 00000000..41f3d72e --- /dev/null +++ b/cli/e2e-tests/quarkus_create_test.go @@ -0,0 +1,167 @@ +//go:build e2e_tests + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/kubesmarts/logic-operator/cli/pkg/command/quarkus" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" +) + +type CfgTestInputQuarkusCreate struct { + input quarkus.CreateQuarkusProjectConfig +} + +var cfgTestInputQuarkusCreate_Success = []CfgTestInputQuarkusCreate{ + {input: quarkus.CreateQuarkusProjectConfig{}}, + {input: quarkus.CreateQuarkusProjectConfig{ + ProjectName: "my-project", + }}, + {input: quarkus.CreateQuarkusProjectConfig{ + Extensions: "quarkus-jsonp,quarkus-smallrye-openapi", + }}, + {input: quarkus.CreateQuarkusProjectConfig{ + ProjectName: "serverless-workflow-hello-world", + Extensions: "quarkus-jsonp,quarkus-smallrye-openapi", + DependenciesVersion: metadata.DependenciesVersion{ + QuarkusPlatformGroupId: quarkusDependencies.QuarkusPlatformGroupId, + QuarkusVersion: quarkusDependencies.QuarkusVersion, + }, + }}, +} + +var cfgTestInputQuarkusCreate_Fail = []CfgTestInputQuarkusCreate{ + {input: quarkus.CreateQuarkusProjectConfig{ + ProjectName: "wrong/project-name", + }}, + {input: quarkus.CreateQuarkusProjectConfig{ + Extensions: "nonexistent-extension", + }}, +} + +func transformQuarkusCreateCmdCfgToArgs(cfg quarkus.CreateQuarkusProjectConfig) []string { + args := []string{"create"} + if cfg.ProjectName != "" { + args = append(args, "--name", cfg.ProjectName) + } + if cfg.Extensions != "" { + args = append(args, "--extension", cfg.Extensions) + } + if cfg.DependenciesVersion.QuarkusPlatformGroupId != "" { + args = append(args, "--quarkus-platform-group-id", cfg.DependenciesVersion.QuarkusPlatformGroupId) + } + if cfg.DependenciesVersion.QuarkusVersion != "" { + args = append(args, "--quarkus-version", cfg.DependenciesVersion.QuarkusVersion) + } + return args +} + +func GetQuarkusCreateProjectName(t *testing.T, config CfgTestInputQuarkusCreate) string { + if config.input.ProjectName != "" { + return config.input.ProjectName + } else { + projectDefaultName, err := LookupFlagDefaultValue("name", quarkus.NewCreateCommand()) + require.NoErrorf(t, err, "Error: %v", err) + return projectDefaultName + } +} + +func TestQuarkusCreateProjectSuccess(t *testing.T) { + for testIndex, test := range cfgTestInputQuarkusCreate_Success { + t.Run(fmt.Sprintf("Test quarkus create project success index: %d", testIndex), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + RunQuarkusCreateTest(t, test) + }) + } +} + +func RunQuarkusCreateTest(t *testing.T, test CfgTestInputQuarkusCreate) string { + var err error + + projectName := GetQuarkusCreateProjectName(t, test) + projectDir := filepath.Join(TempTestsPath, projectName) + + // Run `quarkus create` command + _, err = ExecuteKnWorkflowQuarkus(transformQuarkusCreateCmdCfgToArgs(test.input)...) + + err = os.Chdir(projectDir) + require.NoErrorf(t, err, "Expected nil error, got: %v", err) + + // Check if the project directory was created + require.DirExistsf(t, projectDir, "Expected project directory '%s' to be created", projectDir) + + // Check if the expected directories and files are present + expectedDirectories := []string{ + "src/main/java", + "src/main/resources", + "src/main/docker", + "src/main", + "src", + } + VerifyDirectoriesExist(t, projectDir, expectedDirectories) + expectedFiles := []string{ + "src/main/resources/application.properties", + "src/main/resources/workflow.sw.json", + "src/main/docker/Dockerfile.legacy-jar", + "src/main/docker/Dockerfile.jvm", + "src/main/docker/Dockerfile.native", + "src/main/docker/Dockerfile.native-micro", + ".gitignore", + "pom.xml", + "README.md", + ".dockerignore", + } + VerifyFilesExist(t, projectDir, expectedFiles) + + // Verify the content of the file `workflow.sw.json` + workflowFilePath := filepath.Join(projectDir, "src/main/resources/workflow.sw.json") + workflowFileData, err := common.GetWorkflowTemplate(false) + require.NoErrorf(t, err, "Error reading workflow template: %v", err) + expectedFileContent := string(workflowFileData) + VerifyFileContent(t, workflowFilePath, expectedFileContent) + return projectName +} + +func TestQuarkusCreateProjectFail(t *testing.T) { + for testIndex, test := range cfgTestInputQuarkusCreate_Fail { + t.Run(fmt.Sprintf("Test quarkus create project fail index: %d", testIndex), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + + projectName := GetQuarkusCreateProjectName(t, test) + projectDir := filepath.Join(TempTestsPath, projectName) + + // Run `quarkus create` command + _, err := ExecuteKnWorkflowQuarkus(transformQuarkusCreateCmdCfgToArgs(test.input)...) + require.Errorf(t, err, "Expected error, got nil") + + // Check if the project directory was not created + require.NoDirExistsf(t, projectDir, "Expected project directory '%s' not to be created", projectDir) + }) + } +} diff --git a/cli/e2e-tests/quarkus_run_test.go b/cli/e2e-tests/quarkus_run_test.go new file mode 100644 index 00000000..fe2c83ce --- /dev/null +++ b/cli/e2e-tests/quarkus_run_test.go @@ -0,0 +1,128 @@ +//go:build e2e_tests + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/kubesmarts/logic-operator/cli/pkg/command" + "github.com/kubesmarts/logic-operator/cli/pkg/command/quarkus" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/stretchr/testify/require" +) + +var cfgTestInputPrepareQuarkusCreateRun = CfgTestInputQuarkusCreate{ + input: quarkus.CreateQuarkusProjectConfig{ProjectName: "new-project"}, +} + +var cfgTestInputPrepareQuarkusBuildRun = CfgTestInputQuarkusBuild{ + input: quarkus.BuildCmdConfig{ + Image: "dev.local/new-project", + }, +} + +type cfgTestInputQuarkusRun struct { + input quarkus.RunCmdConfig +} + +var cfgTestInputQuarkusRun_Success = []cfgTestInputQuarkusRun{ + {input: quarkus.RunCmdConfig{PortMapping: "8081", OpenDevUI: false}}, + {input: quarkus.RunCmdConfig{OpenDevUI: true}}, +} + +func transformQuarkusRunCmdCfgToArgs(cfg quarkus.RunCmdConfig) []string { + args := []string{"run"} + if !cfg.OpenDevUI { + args = append(args, "--open-dev-ui=false") + } + if cfg.PortMapping != "" { + args = append(args, "--port", cfg.PortMapping) + } + return args +} + +func getRunQuarkusProjectPort(t *testing.T, config cfgTestInputQuarkusRun) string { + if config.input.PortMapping != "" { + return config.input.PortMapping + } else { + projectDefaultPort, err := LookupFlagDefaultValue("port", command.NewRunCommand()) + require.NoErrorf(t, err, "Error: %v", err) + return projectDefaultPort + } +} + +func TestQuarkusRunCommand(t *testing.T) { + for testIndex, test := range cfgTestInputQuarkusRun_Success { + t.Run(fmt.Sprintf("Test quarkus run project success index: %d", testIndex), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + RunQuarkusRunTest(t, cfgTestInputPrepareQuarkusCreateRun, cfgTestInputPrepareQuarkusBuildRun, test) + }) + } +} + +func RunQuarkusRunTest(t *testing.T, cfgTestInputPrepareQuarkusCreateRun CfgTestInputQuarkusCreate, cfgTestInputPrepareQuarkusBuild CfgTestInputQuarkusBuild, test cfgTestInputQuarkusRun) string { + var err error + + // Create and build the quarkus project + projectName := RunQuarkusCreateTest(t, cfgTestInputPrepareQuarkusCreateRun) + projectDir := filepath.Join(TempTestsPath, projectName) + + err = os.Chdir(projectDir) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + cmd := exec.Command(KnExecutable) + + var wg sync.WaitGroup + wg.Add(1) + + // Run the `quarkus run` command + go func() { + defer wg.Done() + _, err = ExecuteKnWorkflowQuarkusWithCmd(cmd, transformQuarkusRunCmdCfgToArgs(test.input)...) + require.Truef(t, err == nil || IsSignalInterrupt(err), "Expected nil error or signal interrupt, got %v", err) + }() + + // Check if the project is successfully run and accessible within a specified time limit. + readyCheckURL := fmt.Sprintf("http://localhost:%s/q/health/ready", getRunQuarkusProjectPort(t, test)) + pollInterval := 5 * time.Second + timeout := 10 * time.Minute + ready := make(chan bool) + t.Logf("Checking if project is ready at %s", readyCheckURL) + go common.PollReadyCheckURL(readyCheckURL, pollInterval, ready) + select { + case <-ready: + cmd.Process.Signal(os.Interrupt) + case <-time.After(timeout): + t.Fatalf("Test case timed out after %s. The project was not ready within the specified time.", timeout) + cmd.Process.Signal(os.Interrupt) + } + + wg.Wait() + + return projectName +} diff --git a/cli/e2e-tests/run_test.go b/cli/e2e-tests/run_test.go new file mode 100644 index 00000000..1afe63d1 --- /dev/null +++ b/cli/e2e-tests/run_test.go @@ -0,0 +1,139 @@ +//go:build e2e_tests + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/kubesmarts/logic-operator/cli/pkg/command" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var cfgTestInputPrepareCreateRun = CfgTestInputCreate{ + input: command.CreateCmdConfig{ProjectName: "new-project"}, +} + +type cfgTestInputRun struct { + input command.RunCmdConfig +} + +var cfgTestInputRun_Success = []cfgTestInputRun{ + {input: command.RunCmdConfig{PortMapping: "8081", OpenDevUI: false}}, + {input: command.RunCmdConfig{}}, +} + +func transformRunCmdCfgToArgs(cfg command.RunCmdConfig) []string { + args := []string{"run"} + if !cfg.OpenDevUI { + args = append(args, "--open-dev-ui=false") + } + if cfg.PortMapping != "" { + args = append(args, "--port", cfg.PortMapping) + } + return args +} + +func getRunProjectPort(t *testing.T, config cfgTestInputRun) string { + if config.input.PortMapping != "" { + return config.input.PortMapping + } else { + projectDefaultPort, err := LookupFlagDefaultValue("port", command.NewRunCommand()) + require.NoErrorf(t, err, "Error: %v", err) + return projectDefaultPort + } +} + +func TestRunCommand(t *testing.T) { + for testIndex, test := range cfgTestInputRun_Success { + t.Run(fmt.Sprintf("Test run project success index: %d", testIndex), func(t *testing.T) { + defer CleanUpAndChdirTemp(t) + RunRunTest(t, cfgTestInputPrepareCreateRun, test) + }) + } +} + +func RunRunTest(t *testing.T, cfgTestInputPrepareCreate CfgTestInputCreate, test cfgTestInputRun) string { + var err error + var containerId string + + // Create the project + RunCreateTest(t, cfgTestInputPrepareCreate) + + projectName := GetCreateProjectName(t, cfgTestInputPrepareCreateRun) + projectDir := filepath.Join(TempTestsPath, projectName) + + err = os.Chdir(projectDir) + require.NoErrorf(t, err, "Expected nil error, got %v", err) + + cmd := exec.Command(KnExecutable) + + var wg sync.WaitGroup + wg.Add(1) + + // Run the `run` command + go func() { + defer wg.Done() + containerId, err = ExecuteKnWorkflowWithCmdAndStopContainer(cmd, transformRunCmdCfgToArgs(test.input)...) + assert.NotNil(t, containerId, "Container ID is nil") + require.Truef(t, err == nil || IsSignalInterrupt(err), "Expected nil error or signal interrupt, got %v", err) + }() + + // Check if the project is successfully run and accessible within a specified time limit. + readyCheckURL := fmt.Sprintf("http://localhost:%s/q/health/ready", getRunProjectPort(t, test)) + pollInterval := 5 * time.Second + timeout := 10 * time.Minute + ready := make(chan bool) + t.Logf("Checking if project is ready at %s", readyCheckURL) + go common.PollReadyCheckURL(readyCheckURL, pollInterval, ready) + select { + case <-ready: + cmd.Process.Signal(os.Interrupt) + case <-time.After(timeout): + t.Fatalf("Test case timed out after %s. The project was not ready within the specified time.", timeout) + cmd.Process.Signal(os.Interrupt) + } + + wg.Wait() + + stopped := make(chan bool) + t.Logf("Checking if container is stopped") + assert.NotNil(t, containerId, "Container ID is nil") + // Check if the container is stopped within a specified time limit. + go common.PollContainerStoppedCheck(containerId, pollInterval, stopped) + select { + case <-stopped: + fmt.Println("Project is stopped") + case <-time.After(timeout): + t.Fatalf("Test case timed out after %s. The project was not stopped within the specified time.", timeout) + cmd.Process.Signal(os.Interrupt) + } + + return projectName +} diff --git a/cli/e2e-tests/test_env.go b/cli/e2e-tests/test_env.go new file mode 100644 index 00000000..78c12218 --- /dev/null +++ b/cli/e2e-tests/test_env.go @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package e2e_tests + +var testPrintCmdOutput = false diff --git a/cli/e2e-tests/testdata/lock.sw.yaml b/cli/e2e-tests/testdata/lock.sw.yaml new file mode 100644 index 00000000..f3984988 --- /dev/null +++ b/cli/e2e-tests/testdata/lock.sw.yaml @@ -0,0 +1,46 @@ +id: sendcloudeventonprovision +version: 1.0.0 +specVersion: "0.8" +name: Send CloudEvent on provision completion +start: ProvisionOrdersState +events: + - name: provisioningCompleteEvent + type: provisionCompleteType + kind: produced + - name: MakeVetAppointment + source: VetServiceSource + type: events.vet.appointments + kind: produced + - name: VetAppointmentInfo + source: VetServiceSource + type: events.vet.appointments + kind: consumed +functions: + - name: provisionOrderFunction + operation: http://myapis.org/provisioning.json#doProvision + - name: makeVetAppointmentFunction + operation: http://vetapi.org/appointments.json#makeAppointment +states: + - name: ProvisionOrdersState + type: foreach + inputCollection: ${ .orders } + iterationParam: singleorder + outputCollection: ${ .provisionedOrders } + actions: + - functionRef: + refName: provisionOrderFunction + arguments: + order: ${ .singleorder } + resultEventRef: provisioningCompleteEvent + end: + produceEvents: + - eventRef: provisioningCompleteEvent + data: ${ .provisionedOrders } + - eventRef: VetAppointmentInfo + - name: MakeVetAppointmentState + type: operation + actions: + - eventRef: + refName: MakeVetAppointment + triggerEventRef: VetAppointmentInfo + end: true diff --git a/cli/go.mod b/cli/go.mod new file mode 100644 index 00000000..230b0843 --- /dev/null +++ b/cli/go.mod @@ -0,0 +1,145 @@ +module github.com/kubesmarts/logic-operator/cli + +go 1.26.0 + +// Use local modules via go.work +replace ( + github.com/kubesmarts/logic-operator/api => ../api + github.com/kubesmarts/logic-operator/workflowproj => ../workflowproj +) + +require ( + github.com/beevik/etree v1.5.0 + github.com/distribution/reference v0.6.0 + github.com/docker/docker v28.5.2+incompatible + github.com/docker/go-connections v0.5.0 + github.com/getkin/kin-openapi v0.131.0 + github.com/jstemmer/go-junit-report/v2 v2.1.0 + github.com/kubesmarts/logic-operator/api v0.0.0 + github.com/kubesmarts/logic-operator/workflowproj v0.0.0 + github.com/ory/viper v1.7.5 + github.com/spf13/afero v1.12.0 + github.com/spf13/cobra v1.9.1 + github.com/stretchr/testify v1.11.1 + golang.org/x/sys v0.45.0 + gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 + k8s.io/api v0.31.6 + k8s.io/apiextensions-apiserver v0.31.6 + k8s.io/apimachinery v0.31.6 + k8s.io/client-go v0.31.6 +) + +require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.2.4 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/spdystream v0.4.0 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pb33f/libopenapi v0.10.1 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/relvacode/iso8601 v1.4.0 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/serverlessworkflow/sdk-go/v2 v2.5.0 // indirect + github.com/sosodev/duration v1.3.1 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/term v0.43.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.8.0 // indirect + golang.org/x/tools v0.44.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gotest.tools/v3 v3.5.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + knative.dev/pkg v0.0.0-20231023151236-29775d7c9e5c // indirect + sigs.k8s.io/controller-runtime v0.19.0 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) + +replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16 diff --git a/cli/go.sum b/cli/go.sum new file mode 100644 index 00000000..b1314981 --- /dev/null +++ b/cli/go.sum @@ -0,0 +1,473 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/beevik/etree v1.5.0 h1:iaQZFSDS+3kYZiGoc9uKeOkUY3nYMXOKLl6KIJxiJWs= +github.com/beevik/etree v1.5.0/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= +github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report/v2 v2.1.0 h1:X3+hPYlSczH9IMIpSC9CQSZA0L+BipYafciZUWHEmsc= +github.com/jstemmer/go-junit-report/v2 v2.1.0/go.mod h1:mgHVr7VUo5Tn8OLVr1cKnLuEy0M92wdRntM99h7RkgQ= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= +github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= +github.com/pb33f/libopenapi v0.10.1 h1:ADkXbW0CTrn7t6q3OQjY5bIFmFpovF8BF2WWPsWqZKs= +github.com/pb33f/libopenapi v0.10.1/go.mod h1:s8uj6S0DjWrwZVj20ianJBz+MMjHAbeeRYNyo9ird74= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/relvacode/iso8601 v1.4.0 h1:GsInVSEJfkYuirYFxa80nMLbH2aydgZpIf52gYZXUJs= +github.com/relvacode/iso8601 v1.4.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/serverlessworkflow/sdk-go/v2 v2.5.0 h1:bvD461rbaj0PcRjRwsBt7tqHie2M10otryB+BjzHg5Q= +github.com/serverlessworkflow/sdk-go/v2 v2.5.0/go.mod h1:kuvTQCTAb9vzDaeJ1JPY+sBGiHr9OX1g/Rxuk82c8mI= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +k8s.io/api v0.31.6 h1:ocWG/UhC9Mqp5oEfYWy9wCddbZiZyBAFTlBt0LVlhDg= +k8s.io/api v0.31.6/go.mod h1:i16xSiKMgVIVhsJMxfWq0mJbXA+Z7KhjPgYmwT41hl4= +k8s.io/apiextensions-apiserver v0.31.6 h1:v9sqyWlrgFZpAPdEb/bEiXfM98TfSppwRF0X/uWKXh0= +k8s.io/apiextensions-apiserver v0.31.6/go.mod h1:QVH3CFwqzGZtwsxPYzJlA/Qiwgb5FXmRMGls3CjzvbI= +k8s.io/apimachinery v0.31.6 h1:Pn96A0wHD0X8+l7QTdAzdLQPrpav1s8rU6A+v2/9UEY= +k8s.io/apimachinery v0.31.6/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.6 h1:51HT40qVIZ13BrHKeWxFuU52uoPnFhxTYJnv4+LTgp4= +k8s.io/client-go v0.31.6/go.mod h1:MEq7JQJelUQ0/4fMoPEUrc/OOFyGo/9LmGA38H6O6xY= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +knative.dev/pkg v0.0.0-20231023151236-29775d7c9e5c h1:xyPoEToTWeBdn6tinhLxXfnhJhTNQt5WzHiTNiFphRw= +knative.dev/pkg v0.0.0-20231023151236-29775d7c9e5c/go.mod h1:HHRXEd7ZlFpthgE+rwAZ6MUVnuJOAeolnaFSthXloUQ= +sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= +sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/cli/pkg/command/create.go b/cli/pkg/command/create.go new file mode 100644 index 00000000..2a041f9e --- /dev/null +++ b/cli/pkg/command/create.go @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package command + +import ( + "bytes" + _ "embed" + "fmt" + "os" + "path" + "text/template" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +type CreateCmdConfig struct { + ProjectName string + YAML bool + WithPersistence bool +} + +func NewCreateCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "create", + Short: "Creates a new SonataFlow project", + Long: ` + Creates a Workflow file in the specified directory (new-project is the default). + + This SonataFlow project targets use cases requiring a single Serverless + Workflow file definition. + + Additionally, you can define the configurable parameters of your application in the + "application.properties" file (inside the root project directory). + You can also store your spec files (i.e., Open API files) inside the "specs" folder, + schemas file inside "schemas" folder and also subflows inside "subflows" folder. + + A SonataFlow project, as the following structure by default: + + Workflow project root + /specs (optional) + /schemas (optional) + /subflows (optional) + workflow.sw.{json|yaml|yml} (mandatory) + + `, + Example: ` + # Create a project in the local directory + # By default the project is named "new-project" + {{.Name}} create + + # Create a project with an specific name + {{.Name}} create --name myproject + + # Creates a YAML sample workflow file (JSON is default) + {{.Name}} create --yaml-workflow + + # Add Dockerfile with persistence support to the project (default: false) + {{.Name}} create --with-persistence + + `, + SuggestFor: []string{"vreate", "creaet", "craete", "new"}, //nolint:misspell + PreRunE: common.BindEnv("name", "yaml-workflow", "with-persistence"), + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + cfg, err := runCreateCmdConfig() + if err != nil { + return fmt.Errorf("initializing create config: %w", err) + } + return runCreate(cfg) + } + + cmd.Flags().StringP("name", "n", "new-project", "Project name created in the current directory.") + cmd.Flags().Bool("yaml-workflow", false, "Create a sample YAML workflow file.") + cmd.Flags().BoolP("with-persistence", "w", false, "Add persistence support to the project (default: false)") + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +//go:embed template/SonataFlow-Builder.containerfile +var SonataFlowBuilderContainerFile string + +func runCreate(cfg CreateCmdConfig) error { + fmt.Println("🛠️ Creating SonataFlow project") + + if err := os.Mkdir(cfg.ProjectName, os.ModePerm); err != nil { + return fmt.Errorf("❌ ERROR: Error creating project directory: %w", err) + } + + var workflowFormat string + if cfg.YAML { + workflowFormat = metadata.WorkflowSwYaml + } else { + workflowFormat = metadata.WorkflowSwJson + } + + workflowPath := fmt.Sprintf("./%s/%s", cfg.ProjectName, workflowFormat) + if err := common.CreateWorkflow(workflowPath, cfg.YAML); err != nil { + return fmt.Errorf("❌ ERROR: Error creating workflow file: %w", err) + } + if cfg.WithPersistence { + err := addGitOpsDockerFile(cfg) + if err != nil { + return fmt.Errorf("❌ ERROR: Error creating Dockerfile for gitops profile: %w", err) + } + } + fmt.Println("🎉 SonataFlow project successfully created") + + return nil + +} + +func runCreateCmdConfig() (cfg CreateCmdConfig, err error) { + + cfg = CreateCmdConfig{ + ProjectName: viper.GetString("name"), + YAML: viper.GetBool("yaml-workflow"), + WithPersistence: viper.GetBool("with-persistence"), + } + return cfg, nil +} + +func addGitOpsDockerFile(cfg CreateCmdConfig) error { + data := struct { + BuildImage string + }{ + BuildImage: metadata.BuilderImage, + } + + tmpl, err := template.New("dockerfile").Parse(SonataFlowBuilderContainerFile) + if err != nil { + return fmt.Errorf("error parsing Dockerfile template: %w", err) + } + + var rendered bytes.Buffer + if err := tmpl.Execute(&rendered, data); err != nil { + return fmt.Errorf("error executing Dockerfile template: %w", err) + } + + dockerfilePath := path.Join(cfg.ProjectName, "Dockerfile.gitops") + file, err := os.Create(dockerfilePath) + if err != nil { + return fmt.Errorf("error creating Dockerfile %s: %w", dockerfilePath, err) + } + defer file.Close() + if _, err := file.WriteString(rendered.String()); err != nil { + return fmt.Errorf("error writing to Dockerfile %s: %w", dockerfilePath, err) + } + return nil +} diff --git a/cli/pkg/command/deploy.go b/cli/pkg/command/deploy.go new file mode 100644 index 00000000..30e68cc8 --- /dev/null +++ b/cli/pkg/command/deploy.go @@ -0,0 +1,327 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package command + +import ( + "errors" + "fmt" + "os" + "path" + "time" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/common/k8sclient" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + apiMetadata "github.com/kubesmarts/logic-operator/api/metadata" + "github.com/ory/viper" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func NewDeployCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "deploy", + Short: "Deploy a SonataFlow project on Kubernetes via SonataFlow Operator", + Long: ` + Deploy a SonataFlow project in Kubernetes via the SonataFlow Operator. + By default, the deploy command will generate the Operator manifests and apply them to the cluster. + You can also provide a custom manifest directory with the --custom-manifests-dir option. + `, + Example: ` + # Deploy the workflow project from the current directory's project. + # You must provide target namespace. + {{.Name}} deploy --namespace + + # Persist the generated Operator manifests on a given path and deploy the + # workflow from the current directory's project. + {{.Name}} deploy --custom-generated-manifests-dir= + + # Specify a custom manifest files directory. + # This option *will not* automatically generate the manifest files, but will use the existing ones. + {{.Name}} deploy --custom-manifests-dir= + + # Specify a custom subflows files directory. (default: ./subflows) + {{.Name}} deploy --subflows-dir= + + # Specify a custom support specs directory. (default: ./specs) + {{.Name}} deploy --specs-dir= + + # Specify a custom support schemas directory. (default: ./schemas) + {{.Name}} deploy --schemas-dir= + + # Wait for the deployment to complete and open the browser to the deployed workflow. + {{.Name}} deploy --wait + + # Specify a custom container dev image to use for the deployment. + {{.Name}} deploy --image= + + `, + + PreRunE: common.BindEnv("namespace", "custom-manifests-dir", "custom-generated-manifests-dir", "specs-dir", "schemas-dir", "subflows-dir", "wait", "image"), + SuggestFor: []string{"delpoy", "deplyo"}, + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runDeployUndeploy(cmd, args) + } + + cmd.Flags().StringP("namespace", "n", "", "Target namespace of your deployment.") + cmd.Flags().StringP("custom-generated-manifests-dir", "c", "", "Target directory of your generated Operator manifests.") + cmd.Flags().StringP("custom-manifests-dir", "m", "", "Specify a custom manifest files directory. This option will not automatically generate the manifest files, but will use the existing ones.") + cmd.Flags().StringP("specs-dir", "p", "", "Specify a custom specs files directory") + cmd.Flags().StringP("subflows-dir", "s", "", "Specify a custom subflows files directory") + cmd.Flags().StringP("schemas-dir", "t", "", "Specify a custom schemas files directory") + cmd.Flags().BoolP("minify", "f", true, "Minify the OpenAPI specs files before deploying") + cmd.Flags().BoolP("wait", "w", false, "Wait for the deployment to complete and open the browser to the deployed workflow") + cmd.Flags().StringP("image", "i", "", "Specify a custom image to use for the deployment") + + if err := viper.BindPFlag("minify", cmd.Flags().Lookup("minify")); err != nil { + fmt.Println("❌ ERROR: failed to bind minify flag") + } + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +func runDeployUndeploy(cmd *cobra.Command, args []string) error { + + cfg, err := runDeployCmdConfig(cmd) + //temp dir cleanup + defer func(cfg *DeployUndeployCmdConfig) { + if cfg.TempDir != "" { + if err := os.RemoveAll(cfg.TempDir); err != nil { + fmt.Errorf("❌ ERROR: failed to remove temp dir: %v", err) + } + } + }(&cfg) + + if err != nil { + return fmt.Errorf("❌ ERROR: initializing deploy config: %w", err) + } + + fmt.Println("🛠️️ Deploy a SonataFlow project on Kubernetes via the SonataFlow Operator...") + + if err := checkEnvironment(&cfg); err != nil { + return fmt.Errorf("❌ ERROR: checking deploy environment: %w", err) + } + + if len(cfg.CustomManifestsFileDir) == 0 { + if err := generateManifests(&cfg); err != nil { + return fmt.Errorf("❌ ERROR: generating deploy environment: %w", err) + } + } else { + fmt.Printf("🛠 Using manifests located at %s\n", cfg.CustomManifestsFileDir) + } + + if cfg.Image != "" { + metadata.DevModeImage = cfg.Image + } + + if err = deploy(&cfg); err != nil { + return fmt.Errorf("❌ ERROR: applying deploy: %w", err) + } + + fmt.Printf("\n🎉 SonataFlow project successfully deployed.\n") + + return nil +} + +type Manifest struct { + Kind string `json:"kind"` + Metadata struct { + Annotations map[string]string `json:"annotations"` + Name string `json:"name"` + } `json:"metadata"` +} + +func deploy(cfg *DeployUndeployCmdConfig) error { + fmt.Printf("🛠 Deploying your SonataFlow project in namespace %s\n", cfg.NameSpace) + + manifestExtension := []string{metadata.YAMLExtension} + + manifestPath := cfg.CustomGeneratedManifestDir + if len(cfg.CustomManifestsFileDir) != 0 { + manifestPath = cfg.CustomManifestsFileDir + } + + files, err := common.FindFilesWithExtensions(manifestPath, manifestExtension) + if err != nil { + return fmt.Errorf("❌ ERROR: failed to get manifest directory and files: %w", err) + } + + var workflowId string + + for _, file := range files { + bytes, err := os.ReadFile(file) + if err != nil { + return fmt.Errorf("❌ ERROR: failed to read SonataFlow file: %w", err) + } + + var manifest Manifest + if err = yaml.Unmarshal(bytes, &manifest); err == nil { + if err == nil { + if manifest.Kind == "SonataFlow" { + workflowId = manifest.Metadata.Name + cfg.Profile = manifest.Metadata.Annotations["sonataflow.org/profile"] + } + } + } + + if err = common.ExecuteApply(file, cfg.NameSpace); err != nil { + return fmt.Errorf("❌ ERROR: failed to deploy manifest %s, %w", file, err) + } + fmt.Printf(" - ✅ Manifest %s successfully deployed in namespace %s\n", path.Base(file), cfg.NameSpace) + + } + + if cfg.Wait && !(cfg.Profile == apiMetadata.PreviewProfile.String() || cfg.Profile == apiMetadata.GitOpsProfile.String()) { + if err := waitForDeploymentAndOpenDevUi(cfg, workflowId); err != nil { + return fmt.Errorf("❌ ERROR: failed to wait for deployment and open dev ui: %w", err) + } + } + return nil +} + +func waitForDeploymentAndOpenDevUi(cfg *DeployUndeployCmdConfig, workflowId string) error { + // run goroutine and wait for the deployment to complete using GetDeploymentStatus + deployed := make(chan bool) + errCan := make(chan error) + defer close(deployed) + defer close(errCan) + + fmt.Println("🕚 Waiting for the deployment to complete...") + + go PollGetDeploymentStatus(cfg.NameSpace, workflowId, 5 * time.Second, 5 * time.Minute, deployed, errCan) + + select { + case <-deployed: + fmt.Printf(" - ✅ Deployment of %s is completed\n", workflowId) + case err := <-errCan: + return fmt.Errorf("❌ ERROR: failed to get deployment status: %w", err) + } + + if err := common.PortForward(cfg.NameSpace, workflowId, "8080", "8080", func() { + fmt.Println(" - ✅ Port forwarding started successfully.") + fmt.Println(" - 🔎 Press Ctrl+C to stop port forwarding.") + common.OpenBrowserURL(fmt.Sprintf("http://localhost:%s/q/dev-ui", "8080")) + }); err != nil { + return fmt.Errorf("❌ ERROR: failed to port forward: %w", err) + } + + return nil +} + +func PollGetDeploymentStatus(namespace, deploymentName string, interval, timeout time.Duration, ready chan<- bool, errChan chan<- error) { + var noDeploymentFound k8sclient.NoDeploymentFoundError + timeoutCh := time.After(timeout) + + for { + select { + case <-timeoutCh: + errChan <- fmt.Errorf("❌ Timeout riched for deployment %s in namespace %s", deploymentName, namespace) + return + default: + status, err := common.GetDeploymentStatus(namespace, deploymentName) + if err != nil { + if !errors.As(err, &noDeploymentFound) { + errChan <- err + return + } + } else { + if status.ReadyReplicas == status.Replicas { + ready <- true + return + } + } + + time.Sleep(interval) + } + } +} + +func runDeployCmdConfig(cmd *cobra.Command) (cfg DeployUndeployCmdConfig, err error) { + + cfg = DeployUndeployCmdConfig{ + NameSpace: viper.GetString("namespace"), + CustomManifestsFileDir: viper.GetString("custom-manifests-dir"), + CustomGeneratedManifestDir: viper.GetString("custom-generated-manifests-dir"), + SpecsDir: viper.GetString("specs-dir"), + SchemasDir: viper.GetString("schemas-dir"), + SubflowsDir: viper.GetString("subflows-dir"), + Minify: viper.GetBool("minify"), + Wait: viper.GetBool("wait"), + Image: viper.GetString("image"), + } + + if len(cfg.SubflowsDir) == 0 { + dir, err := os.Getwd() + cfg.SubflowsDir = dir + "/subflows" + if err != nil { + return cfg, fmt.Errorf("❌ ERROR: failed to get default subflows workflow files folder: %w", err) + } + } + + if len(cfg.SpecsDir) == 0 { + dir, err := os.Getwd() + cfg.SpecsDir = dir + "/specs" + if err != nil { + return cfg, fmt.Errorf("❌ ERROR: failed to get default specs files folder: %w", err) + } + } + + if len(cfg.SchemasDir) == 0 { + dir, err := os.Getwd() + cfg.SchemasDir = dir + "/schemas" + if err != nil { + return cfg, fmt.Errorf("❌ ERROR: failed to get default schemas files folder: %w", err) + } + } + + // check if sonataflow operator and knative CRDs are installed + if err := CheckCRDs(metadata.SonataflowCRDs, "SonataFlow Operator"); err != nil { + return cfg, err + } + + //setup manifest path + if err := setupConfigManifestPath(&cfg); err != nil { + return cfg, err + } + + return cfg, nil +} + +var CheckCRDs = func(crds []string, typeName string) error { + for _, crd := range crds { + err := common.CheckCrdExists(crd) + if err != nil { + var statusErr *apierrors.StatusError + if errors.As(err, &statusErr) && statusErr.ErrStatus.Reason == metav1.StatusReasonNotFound { + return fmt.Errorf("❌ ERROR: the required CRDs are not installed.. Install the %s CRD first", typeName) + } else { + return fmt.Errorf("❌ ERROR: failed to check if CRD %s exists: %w", crd, err) + } + } + } + return nil +} diff --git a/cli/pkg/command/deploy_undeploy_common.go b/cli/pkg/command/deploy_undeploy_common.go new file mode 100644 index 00000000..bd6f0ef4 --- /dev/null +++ b/cli/pkg/command/deploy_undeploy_common.go @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package command + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/kubesmarts/logic-operator/cli/pkg/specs" + apimetadata "github.com/kubesmarts/logic-operator/api/metadata" + "github.com/kubesmarts/logic-operator/workflowproj" +) + +type DeployUndeployCmdConfig struct { + EmptyNameSpace bool + NameSpace string + KubectlContext string + SonataFlowFile string + CustomGeneratedManifestDir string + TempDir string + ApplicationPropertiesPath string + ApplicationSecretPropertiesPath string + SubflowsDir string + SpecsDir string + SchemasDir string + CustomManifestsFileDir string + Profile string + Image string + SchemasFilesPath []string + SpecsFilesPath map[string]string + SubFlowsFilesPath []string + Minify bool + Wait bool +} + +func checkEnvironment(cfg *DeployUndeployCmdConfig) error { + fmt.Println("\n🔎 Checking your environment...") + + if ctx, err := common.CheckContext(); err != nil { + return err + } else { + cfg.KubectlContext = ctx + } + + //setup namespace + if len(cfg.NameSpace) == 0 { + if defaultNamespace, err := common.GetCurrentNamespace(); err == nil { + cfg.NameSpace = defaultNamespace + } else { + return err + } + } + + // Temporarily disabled due to lack of clarity of operator 'final name' + // and also how to verify if operator is correctly installed. + // For more info, please refer to: KOGITO-9562 and KOGITO-9563 + //fmt.Println("🔎 Checking if the SonataFlow Operator is correctly installed...") + //if err := common.CheckOperatorInstalled(); err != nil { + // return err + //} + + return nil +} + +func generateManifests(cfg *DeployUndeployCmdConfig) error { + fmt.Println("\n🛠️ Generating your manifests...") + + fmt.Println("🔍 Looking for your SonataFlow files...") + if file, err := common.FindSonataFlowFile(common.WorkflowExtensionsType); err != nil { + return err + } else { + cfg.SonataFlowFile = file + } + fmt.Printf(" - ✅ SonataFlow file found: %s\n", cfg.SonataFlowFile) + + fmt.Println("🔍 Looking for your SonataFlow sub flows...") + files, err := common.FindFilesWithExtensions(cfg.SubflowsDir, common.WorkflowExtensionsType) + if err != nil { + return fmt.Errorf("❌ ERROR: failed to get subflows directory: %w", err) + } + cfg.SubFlowsFilesPath = files + for _, file := range cfg.SubFlowsFilesPath { + fmt.Printf(" - ✅ SonataFlow subflows found: %s\n", file) + } + + fmt.Println("🔍 Looking for your workflow support files...") + + dir, err := os.Getwd() + if err != nil { + return fmt.Errorf("❌ ERROR: failed to get current directory: %w", err) + } + + fmt.Println("🔍 Looking for properties files...") + + applicationPropertiesPath := findApplicationPropertiesPath(dir) + if applicationPropertiesPath != "" { + cfg.ApplicationPropertiesPath = applicationPropertiesPath + fmt.Printf(" - ✅ Properties file found: %s\n", cfg.ApplicationPropertiesPath) + } + + applicationSecretPropertiesPath := findApplicationSecretPropertiesPath(dir) + if applicationSecretPropertiesPath != "" { + cfg.ApplicationSecretPropertiesPath = applicationSecretPropertiesPath + fmt.Printf(" - ✅ Secret Properties file found: %s\n", cfg.ApplicationSecretPropertiesPath) + } + + supportFileExtensions := []string{metadata.JSONExtension, metadata.YAMLExtension, metadata.YMLExtension} + + fmt.Println("🔍 Looking for specs files...") + if cfg.Minify { + minifiedfiles, err := specs.NewMinifier(&specs.OpenApiMinifierOpts{ + SpecsDir: cfg.SpecsDir, + SubflowsDir: cfg.SubflowsDir, + }).Minify() + if err != nil { + return fmt.Errorf("❌ ERROR: failed to minify specs files: %w", err) + } + cfg.SpecsFilesPath = minifiedfiles + } else { + files, err = common.FindFilesWithExtensions(cfg.SpecsDir, supportFileExtensions) + if err != nil { + return fmt.Errorf("❌ ERROR: failed to get supportFiles directory: %w", err) + } + cfg.SpecsFilesPath = map[string]string{} + for _, file := range files { + cfg.SpecsFilesPath[file] = file + fmt.Printf(" - ✅ Specs file found: %s\n", file) + } + } + + fmt.Println("🔍 Looking for schema files...") + files, err = common.FindFilesWithExtensions(cfg.SchemasDir, supportFileExtensions) + if err != nil { + return fmt.Errorf("❌ ERROR: failed to get supportFiles directory: %w", err) + } + cfg.SchemasFilesPath = files + for _, file := range cfg.SchemasFilesPath { + fmt.Printf(" - ✅ Schemas file found: %s\n", file) + } + + fmt.Println("🚚️ Generating your Kubernetes manifest files...") + + swfFile, err := common.MustGetFile(cfg.SonataFlowFile) + if err != nil { + return err + } + + handler := workflowproj.New(cfg.NameSpace).WithWorkflow(swfFile) + if cfg.ApplicationPropertiesPath != "" { + appIO, err := common.MustGetFile(cfg.ApplicationPropertiesPath) + if err != nil { + return err + } + handler.WithAppProperties(appIO) + } + + if cfg.ApplicationSecretPropertiesPath != "" { + appIO, err := common.MustGetFile(cfg.ApplicationSecretPropertiesPath) + if err != nil { + return err + } + handler.WithSecretProperties(appIO) + } + + for _, subflow := range cfg.SubFlowsFilesPath { + specIO, err := common.MustGetFile(subflow) + if err != nil { + return err + } + handler.AddResourceAt(filepath.Base(subflow), filepath.Base(cfg.SubflowsDir), specIO) + } + + for _, supportFile := range cfg.SchemasFilesPath { + specIO, err := common.MustGetFile(supportFile) + if err != nil { + return err + } + handler.AddResourceAt(filepath.Base(supportFile), filepath.Base(cfg.SchemasDir), specIO) + } + + for supportFile, minifiedFile := range cfg.SpecsFilesPath { + specIO, err := common.MustGetFile(minifiedFile) + if err != nil { + return err + } + handler.AddResourceAt(filepath.Base(supportFile), filepath.Base(cfg.SpecsDir), specIO) + } + + if len(cfg.Profile) > 0 { + handler.Profile(apimetadata.ProfileType(cfg.Profile)) + } + + _, err = handler.AsObjects() + if err != nil { + return err + } + + if cfg.Image != "" { + handler.Image(cfg.Image) + } + + err = handler.SaveAsKubernetesManifests(cfg.CustomGeneratedManifestDir) + if err != nil { + return err + } + + return nil +} + +func findApplicationPropertiesPath(directoryPath string) string { + filePath := filepath.Join(directoryPath, metadata.ApplicationProperties) + + fileInfo, err := os.Stat(filePath) + if err != nil || fileInfo.IsDir() { + return "" + } + + return filePath +} + +func findApplicationSecretPropertiesPath(directoryPath string) string { + filePath := filepath.Join(directoryPath, metadata.ApplicationSecretProperties) + + fileInfo, err := os.Stat(filePath) + if err != nil || fileInfo.IsDir() { + return "" + } + + return filePath +} + +func setupConfigManifestPath(cfg *DeployUndeployCmdConfig) error { + + if len(cfg.CustomGeneratedManifestDir) == 0 { + tempDir, err := os.MkdirTemp("", "manifests") + if err != nil { + return fmt.Errorf("❌ ERROR: failed to create temporary directory: %w", err) + } + cfg.CustomGeneratedManifestDir = tempDir + cfg.TempDir = tempDir + } else { + _, err := os.Stat(cfg.CustomGeneratedManifestDir) + if err != nil { + return fmt.Errorf("❌ ERROR: cannot find or open directory %s : %w", cfg.CustomGeneratedManifestDir, err) + } + } + return nil +} diff --git a/cli/pkg/command/gen_manifest.go b/cli/pkg/command/gen_manifest.go new file mode 100644 index 00000000..e3a58ab4 --- /dev/null +++ b/cli/pkg/command/gen_manifest.go @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package command + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +func NewGenManifest() *cobra.Command { + cmd := &cobra.Command{ + Use: "gen-manifest", + Short: "GenerateOperator manifests", + Long: ` + Generate a list of Operator manifests for a SonataFlow project. + By default, the manifests are generated in the ./manifests directory, + but they can be configured by --custom-generated-manifest-dir flag. + `, + Example: ` + # Persist the generated Operator manifests on a default path (default ./manifests) + {{.Name}} gen-manifest + + # Specify a custom target namespace. (default: kubeclt current namespace; --namespace "" to don't set namespace on your manifest) + {{.Name}} deploy --namespace + + # Skip namespace creation + {{.Name}} gen-manifest --skip-namespace + + # Persist the generated Operator manifests on a specific custom path + {{.Name}} gen-manifest --custom-generated-manifests-dir= + + # Specify a custom subflows files directory. (default: ./subflows) + {{.Name}} gen-manifest --subflows-dir= + + # Specify a custom support specs directory. (default: ./specs) + {{.Name}} gen-manifest --specs-dir= + + # Specify a custom support schemas directory. (default: ./schemas) + {{.Name}} gen-manifest --schemas-dir= + + # Specify a profile to use for the deployment. (default: dev) + {{.Name}} gen-manifest --profile= + + # Specify a custom image to use for the deployment. + {{.Name}} gen-manifest --image= + + `, + PreRunE: common.BindEnv("namespace", "custom-generated-manifests-dir", "specs-dir", "schemas-dir", "subflows-dir"), + SuggestFor: []string{"gen-manifests", "generate-manifest"}, //nolint:misspell + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return generateManifestsCmd(cmd, args) + } + + cmd.Flags().StringP("namespace", "n", "", "Target namespace of your deployment. (default: kubeclt current namespace; \"\" to don't set namespace on your manifest)") + cmd.Flags().BoolP("skip-namespace", "k", false, "Skip namespace creation") + cmd.Flags().StringP("custom-generated-manifests-dir", "c", "", "Target directory of your generated Operator manifests.") + cmd.Flags().StringP("specs-dir", "p", "", "Specify a custom specs files directory") + cmd.Flags().StringP("subflows-dir", "s", "", "Specify a custom subflows files directory") + cmd.Flags().StringP("schemas-dir", "t", "", "Specify a custom schemas files directory") + cmd.Flags().StringP("profile", "f", "dev", "Specify a profile to use for the deployment") + cmd.Flags().StringP("image", "i", "", "Specify a custom image to use for the deployment") + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +func generateManifestsCmd(cmd *cobra.Command, args []string) error { + cfg, err := runGenManifestCmdConfig(cmd) + + if err != nil { + return fmt.Errorf("❌ ERROR: initializing deploy config: %w", err) + } + + fmt.Println("🛠️️ Generating a list of Operator manifests for a SonataFlow project...") + fmt.Printf("📂 Manifests will be generated in %s\n", cfg.CustomGeneratedManifestDir) + + if err := setupEnvironment(&cfg); err != nil { + return fmt.Errorf("❌ ERROR: setup environment: %w", err) + } + + if err := generateManifests(&cfg); err != nil { + return fmt.Errorf("❌ ERROR: generating manifests: %w", err) + } + + fmt.Printf("\n🎉 SonataFlow Operator manifests successfully generated.\n") + + return nil +} + +func runGenManifestCmdConfig(cmd *cobra.Command) (cfg DeployUndeployCmdConfig, err error) { + + cfg = DeployUndeployCmdConfig{ + NameSpace: viper.GetString("namespace"), + SpecsDir: viper.GetString("specs-dir"), + SchemasDir: viper.GetString("schemas-dir"), + SubflowsDir: viper.GetString("subflows-dir"), + CustomGeneratedManifestDir: viper.GetString("custom-generated-manifests-dir"), + Profile: viper.GetString("profile"), + Image: viper.GetString("image"), + } + + if cmd.Flags().Changed("namespace") && len(cfg.NameSpace) == 0 { + // distinguish between a user intentionally setting an empty value + // and not providing the flag at all + cfg.EmptyNameSpace = true + } + + if skipNamespace, _ := cmd.Flags().GetBool("skip-namespace"); skipNamespace { + cfg.NameSpace = "" + cfg.EmptyNameSpace = true + } + + if cmd.Flags().Changed("profile") && len(cfg.Profile) == 0 { + profile, _ := cmd.Flags().GetString("profile") + if err := common.IsValidProfile(profile); err != nil { + return cfg, err + } + cfg.Profile = profile + } + + if cmd.Flags().Changed("image") { + image, _ := cmd.Flags().GetString("image") + if image != "" { + cfg.Image = image + } + } + + if len(cfg.SubflowsDir) == 0 { + dir, err := os.Getwd() + cfg.SubflowsDir = dir + "/subflows" + if err != nil { + return cfg, fmt.Errorf("❌ ERROR: failed to get default subflows workflow files folder: %w", err) + } + } + + if len(cfg.SpecsDir) == 0 { + dir, err := os.Getwd() + cfg.SpecsDir = dir + "/specs" + if err != nil { + return cfg, fmt.Errorf("❌ ERROR: failed to get default support specs files folder: %w", err) + } + } + + if len(cfg.SchemasDir) == 0 { + dir, err := os.Getwd() + cfg.SchemasDir = dir + "/schemas" + if err != nil { + return cfg, fmt.Errorf("❌ ERROR: failed to get default support schemas files folder: %w", err) + } + } + + //setup manifest path + manifestDir, err := resolveManifestDir(cfg.CustomGeneratedManifestDir) + if err != nil { + return cfg, fmt.Errorf("❌ ERROR: failed to get manifest directory: %w", err) + } + cfg.CustomGeneratedManifestDir = manifestDir + + return cfg, nil +} + +func setupEnvironment(cfg *DeployUndeployCmdConfig) error { + fmt.Println("\n🔎 Checking your environment...") + + //setup namespace + if len(cfg.NameSpace) == 0 && !cfg.EmptyNameSpace { + cfg.NameSpace = "default" + if currentNamespace, err := common.GetCurrentNamespace(); err == nil { + cfg.NameSpace = currentNamespace + fmt.Printf(" - ✅ resolved namespace: %s\n", cfg.NameSpace) + } else { + fmt.Printf(" - ✅ resolved namespace (default): %s\n", cfg.NameSpace) + } + } else if cfg.EmptyNameSpace { + fmt.Printf(" - ❗ empty namespace manifest (you will have to setup one later) \n") + } else { + fmt.Printf(" - ✅ resolved namespace: %s\n", cfg.NameSpace) + } + + return nil +} + +func resolveManifestDir(folderName string) (string, error) { + if folderName == "" { + folderName = "manifests" + } + + if _, err := os.Stat(folderName); os.IsNotExist(err) { + err = os.Mkdir(folderName, 0755) + if err != nil { + return "", err + } + } + + absPath, err := filepath.Abs(folderName) + if err != nil { + return "", err + } + + return absPath, nil +} diff --git a/cli/pkg/command/operator/install.go b/cli/pkg/command/operator/install.go new file mode 100644 index 00000000..605add21 --- /dev/null +++ b/cli/pkg/command/operator/install.go @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package operator + +import ( + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +func NewInstallOperatorCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "install", + Short: "Install the SonataFlow Operator, which is responsible for deploying SonataFlow projects on Kubernetes.", + Long: ` + Install the SonataFlow Operator, which is responsible for deploying SonataFlow projects on Kubernetes. + `, + Example: ` + # Install the SonataFlow Operator. Usually in Openshift the namespace is "openshift-operators", in case of Minikube or Kind, with + # default OLM installation, the namespace is "operators". + {{.Name}} operator install --namespace + `, + PreRunE: common.BindEnv("namespace"), + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runInstallOperatorCommand(cmd, args) + } + cmd.Flags().StringP("namespace", "n", "", "Target namespace of your Operator deployment.") + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +func runInstallOperatorCommand(cmd *cobra.Command, args []string) error { + fmt.Println("🚀 Installing the SonataFlow Operator...") + + namespace := viper.GetString("namespace") + + operator := common.NewOperatorManager(namespace) + + err := operator.CheckOLMInstalled() + if err != nil { + return err + } + + err = operator.InstallSonataflowOperator() + if err != nil { + return err + } + fmt.Println("🎉 SonataFlow Operator successfully installed, wait for the operator to be ready.") + + return nil +} diff --git a/cli/pkg/command/operator/operator.go b/cli/pkg/command/operator/operator.go new file mode 100644 index 00000000..1776df84 --- /dev/null +++ b/cli/pkg/command/operator/operator.go @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package operator + +import ( + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/spf13/cobra" +) + +func NewOperatorCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "operator", + Short: "Manage the SonataFlow Operator, which is responsible for deploying SonataFlow projects on Kubernetes.", + Long: ` + Install or uninstall the SonataFlow Operator, which is responsible for deploying SonataFlow projects on Kubernetes. + `, + Example: ` + # Install the SonataFlow Operator. + {{.Name}} operator install + + # Uninstall the SonataFlow Operator. + {{.Name}} operator uninstall + + # Check the status of the SonataFlow Operator. + {{.Name}} operator status + `, + } + + cmd.AddCommand(NewInstallOperatorCommand()) + cmd.AddCommand(NewUnInstallOperatorCommand()) + cmd.AddCommand(NewStatusOperatorCommand()) + + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} diff --git a/cli/pkg/command/operator/status.go b/cli/pkg/command/operator/status.go new file mode 100644 index 00000000..605bbe70 --- /dev/null +++ b/cli/pkg/command/operator/status.go @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package operator + +import ( + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +func NewStatusOperatorCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "status", + Short: "Get the status of deployed SonataFlow Operator.", + Long: ` + Get the status of deployed SonataFlow Operator. + `, + Example: ` + # Check the status of the SonataFlow Operator. + {{.Name}} operator status --namespace + `, + PreRunE: common.BindEnv("namespace"), + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runStatusCommand(cmd, args) + } + cmd.Flags().StringP("namespace", "n", "", "Target namespace of your Operator deployment.") + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +func runStatusCommand(cmd *cobra.Command, args []string) error { + namespace := viper.GetString("namespace") + + operator := common.NewOperatorManager(namespace) + + err:= operator.CheckOLMInstalled() + if err != nil { + return err + } + + err = operator.GetSonataflowOperatorStats() + if err != nil { + return err + } + return nil +} diff --git a/cli/pkg/command/operator/uninstall.go b/cli/pkg/command/operator/uninstall.go new file mode 100644 index 00000000..7cb4477a --- /dev/null +++ b/cli/pkg/command/operator/uninstall.go @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package operator + +import ( + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +func NewUnInstallOperatorCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "uninstall", + Short: "Uninstall the SonataFlow Operator.", + Long: ` + Uninstall the SonataFlow Operator. + `, + Example: ` + # Uninstall the SonataFlow Operator. + {{.Name}} operator uninstall --namespace + `, + PreRunE: common.BindEnv("namespace"), + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runUnInstallOperatorCommand(cmd, args) + } + cmd.Flags().StringP("namespace", "n", "", "Target namespace of your Operator deployment.") + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +func runUnInstallOperatorCommand(cmd *cobra.Command, args []string) error { + fmt.Println("🚀 Uninstalling the SonataFlow Operator...") + + namespace := viper.GetString("namespace") + + operator := common.NewOperatorManager(namespace) + + err := operator.RemoveCR() + if err != nil { + return fmt.Errorf("failed to remove CR: %v", err) + } + + err = operator.RemoveCSV() + if err != nil { + return fmt.Errorf("failed to remove CSV: %v", err) + } + + err = operator.RemoveCRD() + if err != nil { + return fmt.Errorf("failed to remove CRD: %v", err) + } + + err = operator.RemoveSubscription() + if err != nil { + return fmt.Errorf("failed to remove subscription: %v", err) + } + + fmt.Println("🎉 SonataFlow Operator successfully uninstalled.") + + return nil +} diff --git a/cli/pkg/command/quarkus/build.go b/cli/pkg/command/quarkus/build.go new file mode 100644 index 00000000..260aacc2 --- /dev/null +++ b/cli/pkg/command/quarkus/build.go @@ -0,0 +1,400 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "fmt" + "io" + "log" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +type BuildCmdConfig struct { + Image string // full image name + Registry string // image registry (overrides image name) + Repository string // image repository (overrides image name) + ImageName string // image name (overrides image name) + Tag string // image tag (overrides image name) + + // Build strategy options + Jib bool // use Jib extension to build the image and push it to a remote registry + JibPodman bool // use Jib extension to build the image and save it in podman + Push bool // choose to push an image to a remote registry or not (Docker only) + + Test bool // choose to run the project tests +} + +func NewBuildCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "build", + Short: "Build a Quarkus SonataFlow project and generate a container image", + Long: ` + Builds a Quarkus SonataFlow project in the current directory + resulting in a container image. + By default the resultant container image will have the project name. It can be + overridden with the --image or with others image options, see help for more information. + + During the build, a knative.yml file will be generated on the ./target/kubernetes folder. + If your workflow uses eventing, an additional kogito.yml is also generated. + The deploy command uses both these files. + + Authentication is required if you want to push the resultant image to a private registry. + To authenticate to your registry, use "docker login" or any other equivalent method. +`, + Example: ` + # Build from the local directory + # The full image name will be determined automatically based on the + # project's directory name + {{.Name}} build + + # Build from the local directory, specifying the full image name + {{.Name}} build --image quay.io/myuser/myworkflow:1.0 + + # Build from the local directory, specifying the full image name and pushing + # it to the remote registry (authentication can be necessary, use docker login) + # NOTE: If no registry is specfied in the image full name, quay.io will be used. + {{.Name}} build --image quay.io/mysuer/myworkflow:1.0 --push + + # Build from the local directory, passing separately image options + {{.Name}} build --image-registry docker.io --image-repository myuser --image-name myworkflow --image-tag 1.0 + + # Build using Jib instead of Docker. (Read more: https://apache.github.io/incubator-kie-kogito-docs/serverlessworkflow/main/cloud/build-workflow-image-with-quarkus-cli.html) + # Docker is still required to save the image if the push flag is not used + {{.Name}} build --jib + + # Build using Jib and save the image in podman + # Can't use the "push" or "jib" flag for this build strategy + {{.Name}} build --jib-podman + `, + SuggestFor: []string{"biuld", "buidl", "built"}, + PreRunE: common.BindEnv("image", "image-registry", "image-repository", "image-name", "image-tag", "jib", "jib-podman", "push", "test"), + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + _, err := runBuild(cmd) + return err + } + + cmd.Flags().StringP("image", "i", "", "Full image name in the form of [registry]/[repository]/[name]:[tag]") + cmd.Flags().String("image-registry", "", "Image registry, ex: quay.io, if the --image flag is in use this option overrides image [registry]") + cmd.Flags().String("image-repository", "", "Image repository, ex: registry-user or registry-project, if the --image flag is in use, this option overrides image [repository]") + cmd.Flags().String("image-name", "", "Image name, ex: new-project, if the --image flag is in use, this option overrides the image [name]") + cmd.Flags().String("image-tag", "", "Image tag, ex: 1.0, if the --image flag is in use, this option overrides the image [tag]") + + cmd.Flags().Bool("jib", false, "Use Jib extension to generate the image (Docker is still required to save the generated image if push is not used)") + cmd.Flags().Bool("jib-podman", false, "Use Jib extension to generate the image and save it in podman (can't use --push)") + cmd.Flags().Bool("push", false, "Attempt to push the genereated image after being successfully built") + + cmd.Flags().Bool("test", false, "Run the project tests") + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + return cmd +} + +func runBuild(cmd *cobra.Command) (out string, err error) { + fmt.Println("🔨 Building your Quarkus SonataFlow project...") + + cfg, err := runBuildCmdConfig(cmd) + if err != nil { + err = fmt.Errorf("initializing build config: %w", err) + return + } + + if err = common.CheckJavaDependencies(); err != nil { + return + } + + if cfg.JibPodman { + if err = common.CheckPodman(); err != nil { + return + } + } else if (cfg.Jib && !cfg.Push) || (!cfg.Jib) { + if err = common.CheckDocker(); err != nil { + return + } + } + + if err != nil { + return + } + + if err = runAddExtension(cfg); err != nil { + return + } + + if out, err = runBuildImage(cfg); err != nil { + return + } + + fmt.Println("✅ Quarkus SonataFlow project successfully built") + + return +} + +func runBuildCmdConfig(cmd *cobra.Command) (cfg BuildCmdConfig, err error) { + cfg = BuildCmdConfig{ + Image: viper.GetString("image"), + Registry: viper.GetString("image-registry"), + Repository: viper.GetString("image-repository"), + ImageName: viper.GetString("image-name"), + Tag: viper.GetString("image-tag"), + + Jib: viper.GetBool("jib"), + JibPodman: viper.GetBool("jib-podman"), + Push: viper.GetBool("push"), + + Test: viper.GetBool("test"), + } + if len(cfg.Image) == 0 && len(cfg.ImageName) == 0 { + fmt.Println("ERROR: either --image or --image-name should be used") + err = fmt.Errorf("missing flags") + } + + if cfg.JibPodman && cfg.Push { + fmt.Println("ERROR: can't use --jib-podman with --push") + err = fmt.Errorf("invalid flags") + } + + if cfg.JibPodman && cfg.Jib { + fmt.Println("ERROR: can't use --jib-podman with --jib") + err = fmt.Errorf("invalid flags") + } + + return +} + +// This function removes the extension that is not going to be used (if present) +// and updates the chosen one. The entire operation is handled as an extension addition. +// Therefore the removal is hidden from the user +func runAddExtension(cfg BuildCmdConfig) error { + if cfg.Jib || cfg.JibPodman { + fmt.Printf(" - Adding Quarkus Jib extension\n") + if err := common.RunExtensionCommand( + "quarkus:remove-extension", + metadata.QuarkusContainerImageDocker, + ); err != nil { + return err + } + if err := common.RunExtensionCommand( + "quarkus:add-extension", + metadata.QuarkusContainerImageJib, + ); err != nil { + return err + } + } else { + fmt.Printf(" - Adding Quarkus Docker extension\n") + if err := common.RunExtensionCommand( + "quarkus:remove-extension", + metadata.QuarkusContainerImageJib, + ); err != nil { + return err + } + if err := common.RunExtensionCommand( + "quarkus:add-extension", + metadata.QuarkusContainerImageDocker, + ); err != nil { + return err + } + } + + fmt.Println("✅ Quarkus extension was successfully added to the project pom.xml") + return nil +} + +func runBuildImage(cfg BuildCmdConfig) (out string, err error) { + var flags = log.Flags() + defer log.SetFlags(flags) + log.SetFlags(0) + + dir := "target" + filePath := filepath.Join(dir, "build.log") + + if _, err := os.Stat(dir); os.IsNotExist(err) { + err = os.MkdirAll(dir, 0755) + if err != nil { + log.Fatalf("Unable to create a target folder: %v", err) + } + } + + file, err := os.Create(filePath) + if err != nil { + log.Fatalf("Unable to create a log file : %v", err) + } + log.SetOutput(file) + defer file.Close() + + multiWriter := io.MultiWriter(os.Stdout, file) + + registry, repository, name, tag := getImageConfig(cfg) + if err = checkImageName(name); err != nil { + log.Print(err) + return + } + + skipTestsConfig := getSkipTestsConfig(cfg) + builderConfig := getBuilderConfig(cfg) + executableName := getExecutableNameConfig(cfg) + + build := common.ExecCommand("mvn", "-B", "package", + skipTestsConfig, + "-Dquarkus.kubernetes.deployment-target=knative", + fmt.Sprintf("-Dquarkus.knative.name=%s", name), + "-Dquarkus.container-image.build=true", + fmt.Sprintf("-Dquarkus.container-image.registry=%s", registry), + fmt.Sprintf("-Dquarkus.container-image.group=%s", repository), + fmt.Sprintf("-Dquarkus.container-image.name=%s", name), + fmt.Sprintf("-Dquarkus.container-image.tag=%s", tag), + fmt.Sprintf("-Dquarkus.container-image.push=%s", strconv.FormatBool(cfg.Push)), + builderConfig, + executableName, + ) + + build.Stdout = multiWriter + build.Stderr = multiWriter + + if err := build.Start(); err != nil { + // write to the log as well + log.Println("ERROR: starting command \"build\" failed") + if cfg.Push { + log.Println("ERROR: Image build failed.") + log.Println("If you're using a private registry, check if you're authenticated") + } + return "", err + } + + if err := build.Wait(); err != nil { + log.Println("ERROR: something went wrong during command \"build\"") + return "", err + } + + out = getImage(registry, repository, name, tag) + if cfg.Push { + log.Printf("Created and pushed an image to registry: %s\n", out) + } else { + log.Printf("Created a local image: %s\n", out) + } + + log.Println("✅ Build success") + return +} + +func checkImageName(name string) (err error) { + matched, err := regexp.MatchString("^[a-z]([-a-z0-9]*[a-z0-9])?$", name) + if !matched { + fmt.Println(` +ERROR: Image name should match [a-z]([-a-z0-9]*[a-z0-9])? +The name needs to start with a lower case letter and then it can be composed exclusively of lower case letters, numbers or dashes ('-') +Example of valid names: "test-0-0-1", "test", "t1" +Example of invalid names: "1-test", "test.1", "test/1" + `) + err = fmt.Errorf("invalid image name: %s", name) + } + return +} + +// Use the --image-registry, --image-repository, --image-name, --image-tag to override the --image flag +func getImageConfig(cfg BuildCmdConfig) (string, string, string, string) { + imageTagArray := strings.Split(cfg.Image, ":") + imageArray := strings.SplitN(imageTagArray[0], "/", 3) + + var registry = "" + if len(cfg.Registry) > 0 { + registry = cfg.Registry + } else if len(imageArray) > 1 { + registry = imageArray[0] + } + + var repository = "" + if len(cfg.Repository) > 0 { + repository = cfg.Repository + } else if len(imageArray) == 3 { + repository = imageArray[1] + } + + var name = "" + if len(cfg.ImageName) > 0 { + name = cfg.ImageName + } else if len(imageArray) == 1 { + name = imageArray[0] + } else if len(imageArray) == 2 { + name = imageArray[1] + } else if len(imageArray) == 3 { + name = imageArray[2] + } + + var tag = metadata.DefaultTag + if len(cfg.Tag) > 0 { + tag = cfg.Tag + } else if len(imageTagArray) > 1 && len(imageTagArray[1]) > 0 { + tag = imageTagArray[1] + } + + return registry, repository, name, tag +} + +func getImage(registry string, repository string, name string, tag string) string { + if len(registry) == 0 && len(repository) == 0 { + return fmt.Sprintf("%s:%s", name, tag) + } else if len(registry) == 0 { + return fmt.Sprintf("%s/%s:%s", repository, name, tag) + } else if len(repository) == 0 { + return fmt.Sprintf("%s/%s:%s", registry, name, tag) + } + return fmt.Sprintf("%s/%s/%s:%s", registry, repository, name, tag) +} + +func getSkipTestsConfig(cfg BuildCmdConfig) string { + skipTests := "-DskipTests=" + if cfg.Test { + skipTests += "false" + } else { + skipTests += "true" + } + return skipTests +} + +func getBuilderConfig(cfg BuildCmdConfig) string { + builder := "-Dquarkus.container-image.builder=" + if cfg.Jib || cfg.JibPodman { + builder += "jib" + } else { + builder += "docker" + } + return builder +} + +func getExecutableNameConfig(cfg BuildCmdConfig) string { + executableName := "-Dquarkus.jib.docker-executable-name=" + if cfg.JibPodman { + executableName += "podman" + } else { + executableName += "docker" + } + return executableName +} diff --git a/cli/pkg/command/quarkus/build_test.go b/cli/pkg/command/quarkus/build_test.go new file mode 100644 index 00000000..e07343b1 --- /dev/null +++ b/cli/pkg/command/quarkus/build_test.go @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "fmt" + "os" + "os/exec" + "strconv" + "testing" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" +) + +type testBuildImage struct { + input BuildCmdConfig + expected string +} + +var testsRunBuildImageSuccess = []testBuildImage{ + {input: BuildCmdConfig{Image: "test", Jib: true}, expected: "test:latest"}, + {input: BuildCmdConfig{Image: "docker.io/test:latest", JibPodman: true}, expected: "docker.io/test:latest"}, + {input: BuildCmdConfig{Image: "docker.io/repo/test:latest", Push: true}, expected: "docker.io/repo/test:latest"}, + {input: BuildCmdConfig{Image: "quay.io/repo/test:0.0.0", Test: true}, expected: "quay.io/repo/test:0.0.0"}, + + {input: BuildCmdConfig{Image: "test", ImageName: "abcd"}, expected: "abcd:latest"}, + {input: BuildCmdConfig{Image: "docker.io/test", Registry: "quay.io"}, expected: "quay.io/test:latest"}, + {input: BuildCmdConfig{Image: "test", Repository: "myuser"}, expected: "myuser/test:latest"}, + {input: BuildCmdConfig{Image: "quay.io/test", Repository: "myuser"}, expected: "quay.io/myuser/test:latest"}, + {input: BuildCmdConfig{Image: "test:0.0.0", Tag: "0.0.1"}, expected: "test:0.0.1"}, +} + +var testsRunBuildImageFail = []testBuildImage{ + {input: BuildCmdConfig{Image: ""}, expected: ""}, + {input: BuildCmdConfig{Image: "1"}, expected: ""}, + {input: BuildCmdConfig{Image: "-test"}, expected: ""}, + {input: BuildCmdConfig{Image: "test.abc"}, expected: ""}, + {input: BuildCmdConfig{Image: "myuser/1"}, expected: ""}, + {input: BuildCmdConfig{Image: "test", ImageName: "1"}, expected: ""}, +} + +func fakeRunBuildImage(testIndex int) func(command string, args ...string) *exec.Cmd { + return func(command string, args ...string) *exec.Cmd { + cs := []string{"-test.run=TestHelperRunBuildImage", "--", command} + cs = append(cs, args...) + cmd := exec.Command(os.Args[0], cs...) + cmd.Env = []string{fmt.Sprintf("GO_TEST_HELPER_RUN_BUILDER_IMAGE=%d", testIndex)} + return cmd + } +} + +func TestHelperRunBuildImage(t *testing.T) { + testIndex, err := strconv.Atoi(os.Getenv("GO_TEST_HELPER_RUN_BUILDER_IMAGE")) + if err != nil { + return + } + fmt.Fprintf(os.Stdout, "%s", testsRunBuildImageSuccess[testIndex].expected) + os.Exit(0) +} + +func TestRunBuildImage_Success(t *testing.T) { + for testIndex, test := range testsRunBuildImageSuccess { + common.ExecCommand = fakeRunBuildImage(testIndex) + defer func() { common.ExecCommand = exec.Command }() + + out, err := runBuildImage(test.input) + if err != nil { + t.Errorf("Expected nil error, got %#v", err) + } + + if string(out) != test.expected { + t.Errorf("Expected %q, got %q", test.expected, out) + } + } +} + +func TestRunBuildImage_Fail(t *testing.T) { + for testIndex, test := range testsRunBuildImageFail { + common.ExecCommand = fakeRunBuildImage(testIndex) + defer func() { common.ExecCommand = exec.Command }() + + out, err := runBuildImage(test.input) + if err == nil { + t.Errorf("Expected error, got %#v", out) + + } + } +} diff --git a/cli/pkg/command/quarkus/convert.go b/cli/pkg/command/quarkus/convert.go new file mode 100644 index 00000000..8458650d --- /dev/null +++ b/cli/pkg/command/quarkus/convert.go @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "fmt" + fsutils "github.com/kubesmarts/logic-operator/cli/pkg/common/fs" + "io" + "os" + "path/filepath" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +func NewConvertCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "convert", + Short: "Convert a SonataFlow project to a Quarkus SonataFlow project", + Long: ` + Convert a SonataFlow project to a Quarkus SonataFlow Project. + `, + Example: ` + # Run the local directory + {{.Name}} quarkus convert + `, + SuggestFor: []string{"convert-to-quarkus"}, + PreRunE: common.BindEnv("extension", "quarkus-platform-group-id", "quarkus-version"), + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runConvert() + } + + quarkusDependencies := metadata.ResolveQuarkusDependencies() + + cmd.Flags().StringP("extension", "e", "", "On Quarkus projects, setup project custom Maven extensions, separated with a comma.") + cmd.Flags().StringP("quarkus-platform-group-id", "G", quarkusDependencies.QuarkusPlatformGroupId, "On Quarkus projects, setup project group id.") + cmd.Flags().StringP("quarkus-version", "V", quarkusDependencies.QuarkusVersion, "On Quarkus projects, setup the project version.") + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +func runConvert() error { + if common.IsSonataFlowProject() { + return convert() + } else if common.IsQuarkusSonataFlowProject() { + return fmt.Errorf("looks like you are already inside a Quarkus project, so no need to convert it") + } else { + return fmt.Errorf("cannot find SonataFlow project") + } +} + +func loadConvertCmdConfig() (cfg CreateQuarkusProjectConfig, err error) { + quarkusPlatformGroupId := viper.GetString("quarkus-platform-group-id") + quarkusVersion := viper.GetString("quarkus-version") + + cfg = CreateQuarkusProjectConfig{ + Extensions: fmt.Sprintf("%s,%s,%s,%s", + metadata.QuarkusKubernetesExtension, + metadata.QuarkusResteasyJacksonExtension, + metadata.SmallryeHealth, + viper.GetString("extension"), + ), + DependenciesVersion: metadata.DependenciesVersion{ + QuarkusPlatformGroupId: quarkusPlatformGroupId, + QuarkusVersion: quarkusVersion, + }, + } + if cfg.ProjectName == "" { + dir, err := os.Getwd() + if err != nil { + dir = "project-name" + } + cfg.ProjectName = filepath.Base(dir) + } + return +} +func convert() error { + + cfg, err := loadConvertCmdConfig() + + if err != nil { + return fmt.Errorf("initializing create config: %w", err) + } + + if err := common.CheckJavaDependencies(); err != nil { + return err + } + + if err = runConvertProject(cfg); err != nil { + return err + } + + return nil +} + +func runConvertProject(cfg CreateQuarkusProjectConfig) (err error) { + + fmt.Println("🔨 Creating a Quarkus SonataFlow project...") + if err = CreateQuarkusProject(cfg); err != nil { + fmt.Println("❌ Error creating Quarkus project", err) + return err + } + + fmt.Println("🔨 Moving SonataFlow files to Quarkus SonataFlow project...") + rootFolder, err := os.Getwd() + if err != nil { + return err + } + + if err := moveSWFFilesToQuarkusProject(cfg, rootFolder); err != nil { + return err + } + + generatedQuarkusProjectPath := rootFolder + "/" + cfg.ProjectName + + if err := copyDir(generatedQuarkusProjectPath, rootFolder); err != nil { + fmt.Println("❌ Error migrating Quarkus SonataFlow project files", err) + return err + } + if err := os.RemoveAll(generatedQuarkusProjectPath); err != nil { + fmt.Println("❌ Error migrating Quarkus SonataFlow project", err) + return err + } + + fmt.Println("✅ Quarkus SonataFlow project successfully created") + + return nil +} + +func moveSWFFilesToQuarkusProject(cfg CreateQuarkusProjectConfig, rootFolder string) error { + targetFolder := filepath.Join(rootFolder, cfg.ProjectName+"/src/main/resources") + + err := os.MkdirAll(targetFolder, os.ModePerm) + if err != nil { + return err + } + + items, err := os.ReadDir(rootFolder) + if err != nil { + return err + } + + for _, item := range items { + if item.IsDir() && item.Name() == cfg.ProjectName { + continue + } + + info, err := item.Info() + if err != nil { + return err + } + if fsutils.IsHidden(info, item.Name()) && info.IsDir() { + continue + } + + srcPath := filepath.Join(rootFolder, item.Name()) + dstPath := filepath.Join(targetFolder, item.Name()) + + if err := os.Rename(srcPath, dstPath); err != nil { + return fmt.Errorf("error moving %s: %w", srcPath, err) + } + } + + return nil +} + +func copyFile(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err + } + + return nil +} + +func copyDir(src, dst string) error { + err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if fsutils.IsHidden(info, path) && info.IsDir() { + return nil + } + + dstPath := filepath.Join(dst, path[len(src):]) + if info.IsDir() { + err = os.MkdirAll(dstPath, info.Mode()) + if err != nil { + return err + } + } else { + err = copyFile(path, dstPath) + if err != nil { + return err + } + } + + return nil + }) + + return err +} diff --git a/cli/pkg/command/quarkus/create.go b/cli/pkg/command/quarkus/create.go new file mode 100644 index 00000000..fe2a4910 --- /dev/null +++ b/cli/pkg/command/quarkus/create.go @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "fmt" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +func NewCreateCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "create", + Short: "Create a Quarkus SonataFlow project", + Long: ` + Creates a Quarkus SonataFlow project in the current directory. + It sets up a Quarkus project with minimal extensions to build a workflow + project. + The generated project has a "hello world" workflow.sw.json located on the + .//src/main/resources directory. + `, + Example: ` + # Create a project in the local directory + # By default the project is named "new-project" + {{.Name}} create + + # Create a project with an specific name + {{.Name}} create --name myproject + + # Create a project with additional extensions + # You can add multiple extensions by separating them with a comma + {{.Name}} create --extensions kogito-addons-quarkus-persistence-postgresql,quarkus-core + `, + SuggestFor: []string{"vreate", "creaet", "craete", "new"}, + PreRunE: common.BindEnv("name", "extension", "quarkus-platform-group-id", "quarkus-version"), + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runCreate() + } + + quarkusDependencies := metadata.ResolveQuarkusDependencies() + + cmd.Flags().StringP("name", "n", "new-project", "Project name created in the current directory.") + cmd.Flags().StringP("extension", "e", "", "On Quarkus projects, setup project custom Maven extensions, separated with a comma.") + cmd.Flags().StringP("quarkus-platform-group-id", "G", quarkusDependencies.QuarkusPlatformGroupId, "On Quarkus projects, setup project group id.") + cmd.Flags().StringP("quarkus-version", "V", quarkusDependencies.QuarkusVersion, "On Quarkus projects, setup the project version.") + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +func runCreate() error { + cfg, err := runCreateCmdConfig() + if err != nil { + return fmt.Errorf("initializing create config: %w", err) + } + + exists, err := common.CheckIfDirExists(cfg.ProjectName) + if exists { + return fmt.Errorf("directory with name \"%s\" already exists", cfg.ProjectName) + } + if err != nil { + return fmt.Errorf("directory with name \"%s\" already exists: %w", cfg.ProjectName, err) + } + + if err := common.CheckJavaDependencies(); err != nil { + return err + } + + fmt.Println("🛠️ Creating a Quarkus SonataFlow project...") + if err = CreateQuarkusProject(cfg); err != nil { + fmt.Println("❌ ERROR: creating Quarkus SonataFlow project", err) + return err + } + + workflowFilePath := fmt.Sprintf("./%s/src/main/resources/%s", cfg.ProjectName, metadata.WorkflowSwJson) + common.CreateWorkflow(workflowFilePath, false) + + fmt.Println("🎉 Quarkus SonataFlow project successfully created") + return nil +} + +func runCreateProject(cfg CreateQuarkusProjectConfig) (err error) { + if err = common.CheckProjectName(cfg.ProjectName); err != nil { + return err + } + exists, err := common.CheckIfDirExists(cfg.ProjectName) + if err != nil || exists { + return fmt.Errorf("directory with name \"%s\" already exists: %w", cfg.ProjectName, err) + } + + create := common.ExecCommand( + "mvn", + fmt.Sprintf("%s:%s:%s:create", cfg.DependenciesVersion.QuarkusPlatformGroupId, metadata.QuarkusMavenPlugin, cfg.DependenciesVersion.QuarkusVersion), + "-DprojectGroupId=org.acme", + fmt.Sprintf("-DplatformVersion=%s", cfg.DependenciesVersion.QuarkusVersion), + fmt.Sprintf("-DprojectArtifactId=%s", cfg.ProjectName), + fmt.Sprintf("-Dextensions=%s", cfg.Extensions)) + + fmt.Println("Creating a Quarkus SonataFlow project...") + + if err := common.RunCommand( + create, + "create", + ); err != nil { + return err + } + + if err := PostMavenCleanup(cfg); err != nil { + return err + } + + return +} + +func runCreateCmdConfig() (cfg CreateQuarkusProjectConfig, err error) { + quarkusPlatformGroupId := viper.GetString("quarkus-platform-group-id") + quarkusVersion := viper.GetString("quarkus-version") + + cfg = CreateQuarkusProjectConfig{ + ProjectName: viper.GetString("name"), + Extensions: fmt.Sprintf("%s,%s,%s,%s", + metadata.QuarkusKubernetesExtension, + metadata.QuarkusResteasyJacksonExtension, + metadata.SmallryeHealth, + viper.GetString("extension"), + ), + DependenciesVersion: metadata.DependenciesVersion{ + QuarkusPlatformGroupId: quarkusPlatformGroupId, + QuarkusVersion: quarkusVersion, + }, + } + return +} diff --git a/cli/pkg/command/quarkus/create_test.go b/cli/pkg/command/quarkus/create_test.go new file mode 100644 index 00000000..f9b3867f --- /dev/null +++ b/cli/pkg/command/quarkus/create_test.go @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "fmt" + "os" + "os/exec" + "strconv" + "testing" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/spf13/afero" +) + +type testCreate struct { + input CreateQuarkusProjectConfig + existingProject bool +} + +var testRunCreateSuccess = []testCreate{ + {input: CreateQuarkusProjectConfig{ProjectName: "new-project", Extensions: ""}}, + {input: CreateQuarkusProjectConfig{ + ProjectName: "second-project", + Extensions: "extension-name", + DependenciesVersion: metadata.DependenciesVersion{ + QuarkusPlatformGroupId: "io.quarkus.platform", + QuarkusVersion: "2.16.0.Final", + }, + }}, +} +var testRunCreateFail = []testCreate{ + {input: CreateQuarkusProjectConfig{ProjectName: "test-data"}, existingProject: true}, + {input: CreateQuarkusProjectConfig{ProjectName: "wrong*project/name"}}, +} + +func fakeRunCreate(testIndex int) func(command string, args ...string) *exec.Cmd { + return func(command string, args ...string) *exec.Cmd { + cs := []string{"-test.run=TestHelperRunCreate", "--", command} + cs = append(cs, args...) + cmd := exec.Command(os.Args[0], cs...) + cmd.Env = []string{fmt.Sprintf("GO_TEST_HELPER_RUN_CREATE_IMAGE=%d", testIndex)} + return cmd + } +} + +func TestHelperRunCreate(t *testing.T) { + testIndex, err := strconv.Atoi(os.Getenv("GO_TEST_HELPER_RUN_CREATE_IMAGE")) + if err != nil { + return + } + fmt.Fprintf(os.Stdout, "%v", testRunCreateSuccess[testIndex].input.ProjectName) + os.Exit(0) +} + +func TestRunCreate_Success(t *testing.T) { + for testIndex, test := range testRunCreateSuccess { + common.ExecCommand = fakeRunCreate(testIndex) + defer func() { common.ExecCommand = exec.Command }() + + err := runCreateProject(test.input) + if err != nil { + t.Errorf("Expected nil error, got %#v", err) + } + } +} + +func TestRunCreate_Fail(t *testing.T) { + common.FS = afero.NewMemMapFs() + for testIndex, test := range testRunCreateFail { + if test.existingProject == true { + common.CreateFolderStructure(t, test.input.ProjectName) + } + common.ExecCommand = fakeRunCreate(testIndex) + defer func() { common.ExecCommand = exec.Command }() + + err := runCreateProject(test.input) + if err == nil { + t.Errorf("Expected error, got pass") + } + if test.existingProject == true { + common.DeleteFolderStructure(t, test.input.ProjectName) + } + } +} diff --git a/cli/pkg/command/quarkus/deploy.go b/cli/pkg/command/quarkus/deploy.go new file mode 100644 index 00000000..89e8c4f8 --- /dev/null +++ b/cli/pkg/command/quarkus/deploy.go @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/command" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/ory/viper" + "github.com/spf13/cobra" + "path/filepath" +) + +type DeployCmdConfig struct { + Path string // service name + Namespace string +} + +func NewDeployCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "deploy", + Short: "Deploy a Quarkus SonataFlow project", + Long: ` + Deploys a Quarkus SonataFlow project in the current directory. + By default, this command uses the ./target/kubernetes folder to find + the deployment files generated in the build process. The build step + is required before using the deploy command. + + Before you use the deploy command, ensure that your cluster have + access to the build output image. + `, + Example: ` + # Deploy the workflow from the current directory's project. + # Deploy as Knative service. + {{.Name}} deploy + + # You can provide target namespace or use default + {{.Name}} deploy --namespace + + # Specify the path of the directory containing the "knative.yml" + {{.Name}} deploy --path ./kubernetes + `, + SuggestFor: []string{"delpoy", "deplyo"}, + PreRunE: common.BindEnv("namespace", "path"), + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runDeploy(cmd, args) + } + + cmd.Flags().StringP("path", "p", "./target/kubernetes", fmt.Sprintf("%s path to knative deployment files", cmd.Name())) + cmd.Flags().StringP("namespace", "n", "", "Target namespace of your deployment.") + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +var crds = map[string][]string{ + "SonataFlow Operator": metadata.SonataflowCRDs, + "Knative Serving and Knative Eventing": metadata.KnativeCoreServingCRDs, +} + +func runDeploy(cmd *cobra.Command, args []string) error { + fmt.Println("🛠️ Deploying your Quarkus SonataFlow project...") + + cfg, err := runDeployCmdConfig(cmd) + if err != nil { + return fmt.Errorf("initializing deploy config: %w", err) + } + + // check necessary CRDs are installed + for name, crds := range crds { + if err := command.CheckCRDs(crds, name); err != nil { + return err + } + } + + if _, err = deployKnativeServiceAndEventingBindings(cfg); err != nil { + fmt.Println("❌ ERROR:Deploy failed, Knative Eventing binding was not created.") + return err + } + + fmt.Println("🎉 Quarkus SonataFlow project successfully deployed") + + return nil +} + +func deployKnativeServiceAndEventingBindings(cfg DeployCmdConfig) (bool, error) { + isKnativeEventingBindingsCreated := false + + err := common.ExecuteApply(filepath.Join(cfg.Path, "knative.yml"), cfg.Namespace) + if err != nil { + return isKnativeEventingBindingsCreated, err + } + fmt.Println("🎉 Knative service successfully created") + + if exists, err := checkIfKogitoFileExists(cfg); exists && err == nil { + if err := common.ExecuteApply(filepath.Join(cfg.Path, "kogito.yml"), cfg.Namespace); err != nil { + return isKnativeEventingBindingsCreated, err + } + isKnativeEventingBindingsCreated = true + fmt.Println("🎉 Knative Eventing bindings successfully created") + } + return isKnativeEventingBindingsCreated, nil +} + +func runDeployCmdConfig(cmd *cobra.Command) (cfg DeployCmdConfig, err error) { + cfg = DeployCmdConfig{ + Path: viper.GetString("path"), + Namespace: viper.GetString("namespace"), + } + return +} + +func checkIfKogitoFileExists(cfg DeployCmdConfig) (bool, error) { + if _, err := common.FS.Stat(filepath.Join(cfg.Path, "kogito.yml")); err == nil { + return true, nil + } else { + return false, err + } +} diff --git a/cli/pkg/command/quarkus/deploy_test.go b/cli/pkg/command/quarkus/deploy_test.go new file mode 100644 index 00000000..229e0935 --- /dev/null +++ b/cli/pkg/command/quarkus/deploy_test.go @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "context" + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/common/k8sclient" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "testing" +) + +type testDeploy struct { + input DeployCmdConfig + expected bool + knative string + kogito string + resourcesCount int +} + +const defaultPath = "./target/kubernetes" + +var quarkusDependencies = metadata.ResolveQuarkusDependencies() + +var testRunDeploy = []testDeploy{ + {input: DeployCmdConfig{Path: defaultPath}, expected: true, knative: "knative.yml", kogito: "kogito-default.yml"}, + {input: DeployCmdConfig{Path: "./different/folders"}, expected: true, knative: "knative.yml", kogito: "kogito-default.yml"}, + {input: DeployCmdConfig{Path: "different/folders"}, expected: true, knative: "knative.yml", kogito: "kogito-complex.yml"}, + {input: DeployCmdConfig{Path: "different/folders"}, expected: true, knative: "knative.yml", kogito: "kogito-complex2.yml"}, + {input: DeployCmdConfig{Path: "./different/folders", Namespace: "mycustom"}, expected: true, knative: "knative.yml", kogito: "kogito-default-custom-namespace.yml"}, + {input: DeployCmdConfig{Path: "different/folders", Namespace: "mycustom"}, expected: true, knative: "knative.yml", kogito: "kogito-complex-custom-namespace.yml"}, + {input: DeployCmdConfig{Path: "different/folders", Namespace: "mycustom"}, expected: true, knative: "knative.yml", kogito: "kogito-complex2-custom-namespace.yml"}, + {input: DeployCmdConfig{Path: "different/folders", Namespace: "mycustom"}, expected: true, knative: "knative.yml", kogito: "kogito-complex3-custom-namespace.yml"}, + {input: DeployCmdConfig{}, expected: false, kogito: ""}, + {input: DeployCmdConfig{}, expected: false}, +} + +func fakeRunDeploy(testIndex int) func(command string, args ...string) *exec.Cmd { + return func(command string, args ...string) *exec.Cmd { + cs := []string{"-test.run=TestHelperRunDeploy", "--", command} + cs = append(cs, args...) + cmd := exec.Command(os.Args[0], cs...) + cmd.Env = []string{fmt.Sprintf("GO_TEST_HELPER_RUN_DEPLOY_IMAGE=%d", testIndex)} + return cmd + } +} + +func TestHelperRunDeploy(t *testing.T) { + testIndex, err := strconv.Atoi(os.Getenv("GO_TEST_HELPER_RUN_DEPLOY_IMAGE")) + if err != nil { + return + } + out := []string{"Test", strconv.Itoa(testIndex)} + if testRunDeploy[testIndex].kogito != "" { + out = append(out, "with creating", testRunDeploy[testIndex].kogito, "file") + } + fmt.Fprintf(os.Stdout, "%v", out) + os.Exit(0) +} + +func TestRunDeploy(t *testing.T) { + common.FS = afero.NewMemMapFs() + originalParseYamlFile := k8sclient.ParseYamlFile + originalDynamicClient := k8sclient.DynamicClient + originalGetNamespace := k8sclient.GetCurrentNamespace + + fakeClient := k8sclient.Fake{FS: common.FS} + + defer func() { + k8sclient.ParseYamlFile = originalParseYamlFile + k8sclient.DynamicClient = originalDynamicClient + k8sclient.GetCurrentNamespace = originalGetNamespace + }() + + k8sclient.ParseYamlFile = fakeClient.FakeParseYamlFile + k8sclient.DynamicClient = fakeClient.FakeDynamicClient + k8sclient.GetCurrentNamespace = fakeClient.GetCurrentNamespace + + for _, test := range testRunDeploy { + checkDeploy(t, test) + } +} + +func checkDeploy(t *testing.T, test testDeploy) { + + expectedResources := []unstructured.Unstructured{} + + prepareFolderAndFiles(t, test) + populateExpectedResources(t, &expectedResources, test) + + out, err := deployKnativeServiceAndEventingBindings(test.input) + if err != nil && test.expected { + assert.True(t, false, "Expected no error, got %v", err) + } + + assert.Equal(t, out, test.expected, "Expected %v, got %v", test.expected, out) + + checkResourcesCreated(t, &expectedResources, test) + + if test.kogito != "" || test.knative != "" { + undeploy(t, test, test.input.Namespace) + checkResourcesDeleted(t, &expectedResources, test) + common.DeleteFolderStructure(t, test.input.Path) + } +} + +func checkResourcesCreated(t *testing.T, expectedResources *[]unstructured.Unstructured, test testDeploy) { + for _, resource := range *expectedResources { + if result, err := checkObjectCreated(resource, test.input.Namespace); err != nil { + t.Errorf("Error checking if resource was deleted: %v", err) + } else { + assert.True(t, result, "Expected resource to be created: %s", resource.GetName()) + } + } +} + +func checkResourcesDeleted(t *testing.T, expectedResources *[]unstructured.Unstructured, test testDeploy) { + for _, r := range *expectedResources { + if result, err := checkObjectCreated(r, test.input.Namespace); err != nil { + t.Errorf("Error checking if resource was deleted: %v", err) + } else { + assert.False(t, result, "Expected resource to be deleted: %s", r.GetName()) + } + } +} + +func populateExpectedResources(t *testing.T, resources *[]unstructured.Unstructured, test testDeploy) { + if test.knative != "" { + if knativeResources, err := k8sclient.ParseYamlFile(filepath.Join(test.input.Path, "knative.yml")); err == nil { + *resources = append(*resources, knativeResources...) + } else { + t.Errorf("❌ ERROR: Failed to parse Knative resources: %v", err) + } + } else { + fmt.Printf("❌ ERROR: Failed to parse Knative resources: %v", test.knative) + } + if test.kogito != "" { + if kogitoResources, err := k8sclient.ParseYamlFile(filepath.Join(test.input.Path, "kogito.yml")); err == nil { + *resources = append(*resources, kogitoResources...) + } else { + t.Errorf("❌ ERROR: Failed to parse Kogito resources: %v", err) + } + } else { + fmt.Printf("❌ ERROR: Failed to parse Kogito resources: %v", test.kogito) + } +} + +func prepareFolderAndFiles(t *testing.T, test testDeploy) { + if test.input.Path == "" { + test.input.Path = defaultPath + } + common.CreateFolderStructure(t, test.input.Path) + knativeFixQuarkusVersionAndWriteToTestFolder(t, test) + common.CopyFileInFolderStructure(t, test.input.Path, test.kogito, "kogito.yml") +} + +func knativeFixQuarkusVersionAndWriteToTestFolder(t *testing.T, test testDeploy) { + knativeBytes, err := os.ReadFile(filepath.Join("testdata", "knative.yml")) + if err != nil { + t.Errorf("❌ ERROR: Failed to read Knative file: %v", err) + } + knativeWithQuarkusVersion := strings.ReplaceAll(string(knativeBytes), "QUARKUS_VERSION", quarkusDependencies.QuarkusVersion) + + if err := afero.WriteFile(common.FS, filepath.Join(test.input.Path, "knative.yml"), []byte(knativeWithQuarkusVersion), 0644); err != nil { + t.Errorf("Error writing to file: %s", filepath.Join(test.input.Path, "knative.yml")) + } +} + +func checkObjectCreated(obj unstructured.Unstructured, namespace string) (bool, error) { + if namespace == "" { + currentNamespace, err := common.GetCurrentNamespace() + if err != nil { + return false, fmt.Errorf("❌ ERROR: Failed to get current namespace: %v", err) + } + namespace = currentNamespace + } + + client, err := k8sclient.DynamicClient() + if err != nil { + return false, fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes client: %v", err) + } + + gvk := obj.GroupVersionKind() + gvr, _ := meta.UnsafeGuessKindToResource(gvk) + + applyNamespace := namespace + if obj.GetNamespace() != "" { + applyNamespace = obj.GetNamespace() + } + + _, err = client.Resource(gvr).Namespace(applyNamespace).Get(context.Background(), obj.GetName(), metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, fmt.Errorf("❌ ERROR: Failed to get resource: %v", err) + } + return true, nil +} + +func undeploy(t *testing.T, test testDeploy, namespace string) { + if _, err := common.FS.Stat(filepath.Join(test.input.Path, "knative.yml")); err == nil { + if err := common.ExecuteDelete(filepath.Join(test.input.Path, "knative.yml"), namespace); err != nil { + t.Errorf("❌ ERROR: Undeploy failed, Knative service was not created. %v", err) + } + } + + if _, err := common.FS.Stat(filepath.Join(test.input.Path, "kogito.yml")); err == nil { + if err := common.ExecuteDelete(filepath.Join(test.input.Path, "kogito.yml"), namespace); err != nil { + t.Errorf("❌ ERROR: Undeploy failed, Kogito service was not created. %v", err) + } + } +} diff --git a/cli/pkg/command/quarkus/quarkus.go b/cli/pkg/command/quarkus/quarkus.go new file mode 100644 index 00000000..e46b5059 --- /dev/null +++ b/cli/pkg/command/quarkus/quarkus.go @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/spf13/cobra" +) + +func NewQuarkusCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "quarkus", + Short: "Manage SonataFlow projects built in Quarkus", + Long: `Manage SonataFlow projects built in Quarkus`, + SuggestFor: []string{"quaks", "qarkus"}, + } + + cmd.AddCommand(NewCreateCommand()) + cmd.AddCommand(NewBuildCommand()) + cmd.AddCommand(NewRunCommand()) + cmd.AddCommand(NewDeployCommand()) + cmd.AddCommand(NewConvertCommand()) + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + return cmd +} diff --git a/cli/pkg/command/quarkus/quarkus_project.go b/cli/pkg/command/quarkus/quarkus_project.go new file mode 100644 index 00000000..54cf2e00 --- /dev/null +++ b/cli/pkg/command/quarkus/quarkus_project.go @@ -0,0 +1,316 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "bufio" + "fmt" + "os" + "path" + "strconv" + "strings" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/beevik/etree" +) + +type CreateQuarkusProjectConfig struct { + ProjectName string + Extensions string // List of extensions separated by "," to be added to the Quarkus project + DependenciesVersion metadata.DependenciesVersion +} + +type Repository struct { + Id string + Name string + Url string +} + +type Releases struct { + Enabled bool +} + +type Snapshots struct { + Enabled bool +} + +type PluginRepository struct { + Id string + Name string + Url string + Layout string + Releases Releases + Snapshots Snapshots +} + +var filesToRemove = []string{"mvnw", "mvnw.cmd", ".mvn", + "src/test/java/org/acme/GreetingResourceTest.java", + "src/test/java/org/acme/GreetingResourceIT.java", + "src/main/java/org/acme/GreetingResource.java", +} + +func CreateQuarkusProject(cfg CreateQuarkusProjectConfig) error { + if err := common.CheckProjectName(cfg.ProjectName); err != nil { + return err + } + exists, err := common.CheckIfDirExists(cfg.ProjectName) + if err != nil || exists { + return fmt.Errorf("directory with name \"%s\" already exists: %w", cfg.ProjectName, err) + } + create := common.ExecCommand( + "mvn", + fmt.Sprintf("%s:%s:%s:create", cfg.DependenciesVersion.QuarkusPlatformGroupId, metadata.QuarkusMavenPlugin, cfg.DependenciesVersion.QuarkusVersion), + "-DprojectGroupId=org.acme", + fmt.Sprintf("-DprojectArtifactId=%s", cfg.ProjectName), + fmt.Sprintf("-Dextensions=%s", cfg.Extensions)) + + if err := common.RunCommand( + create, + "create", + ); err != nil { + return err + } + + if err := PostMavenCleanup(cfg); err != nil { + return err + } + + //Until we are part of Quarkus 3.x bom we need to manipulate the pom.xml to use the right kogito dependencies + pomPath := path.Join(cfg.ProjectName, "pom.xml") + if err := manipulatePomToKogito(pomPath, cfg); err != nil { + return err + } + + dockerIgnorePath := path.Join(cfg.ProjectName, ".dockerignore") + if err := manipulateDockerIgnore(dockerIgnorePath); err != nil { + return err + } + + extensions := []string{"jvm", "legacy-jar", "native", "native-micro"} + + for _, extension := range extensions { + dockerfilePath := path.Join(cfg.ProjectName, "src/main/docker", "Dockerfile."+extension) + if err := manipulateDockerfile(dockerfilePath); err != nil { + return err + } + } + + return nil +} + +func PostMavenCleanup(cfg CreateQuarkusProjectConfig) error { + for _, file := range filesToRemove { + var fqdn = path.Join(cfg.ProjectName, file) + if err := os.RemoveAll(fqdn); err != nil { + return fmt.Errorf("error removing %s: %w", fqdn, err) + } + } + return nil +} + +func manipulatePomToKogito(filename string, cfg CreateQuarkusProjectConfig) error { + + if cfg.DependenciesVersion.QuarkusPlatformGroupId == "" || cfg.DependenciesVersion.QuarkusVersion == "" { + return fmt.Errorf("configuration for Quarkus versions is not complete") + } + + doc := etree.NewDocument() + err := doc.ReadFromFile(filename) + if err != nil { + return fmt.Errorf("error reading %s: %w", filename, err) + } + + // Update quarkus.platform.group-id + properties := doc.FindElement("//properties") + if properties == nil { + return fmt.Errorf("error parsing %s: %w", filename, err) + } + groupIDElement := properties.FindElement("quarkus.platform.group-id") + if groupIDElement == nil { + return fmt.Errorf("error parsing %s: %w", filename, err) + } + groupIDElement.SetText(cfg.DependenciesVersion.QuarkusPlatformGroupId) + + // Update quarkus.platform.version + versionElement := properties.FindElement("quarkus.platform.version") + if versionElement == nil { + return fmt.Errorf("error parsing %s: %w", filename, err) + } + versionElement.SetText(cfg.DependenciesVersion.QuarkusVersion) + + properties.CreateElement("kie.version").SetText(metadata.KogitoBomDependency.Version) + properties.CreateElement("kie.tooling.version").SetText(metadata.PluginVersion) + + //Add kogito bom dependency + depManagement := doc.FindElement("//dependencyManagement") + if depManagement == nil { + return fmt.Errorf("error parsing %s: %w", filename, err) + } + + dependenciesManagendChild := depManagement.FindElement("dependencies") + if dependenciesManagendChild == nil { + return fmt.Errorf("error parsing %s: %w", filename, err) + } + + dependencyElement := dependenciesManagendChild.CreateElement("dependency") + dependencyElement.CreateElement("groupId").SetText(metadata.KogitoBomDependency.GroupId) + dependencyElement.CreateElement("artifactId").SetText(metadata.KogitoBomDependency.ArtifactId) + dependencyElement.CreateElement("version").SetText("${kie.version}") + dependencyElement.CreateElement("type").SetText(metadata.KogitoBomDependency.Type) + dependencyElement.CreateElement("scope").SetText(metadata.KogitoBomDependency.Scope) + + // Update kogito pom dependencies + dependencies := doc.FindElement("//dependencies") + if dependencies == nil { + return fmt.Errorf("error parsing %s: %w", filename, err) + } + + for _, dep := range metadata.KogitoDependencies { + dependencyElement := dependencies.CreateElement("dependency") + dependencyElement.CreateElement("groupId").SetText(dep.GroupId) + dependencyElement.CreateElement("artifactId").SetText(dep.ArtifactId) + if dep.Version != "" { + dependencyElement.CreateElement("version").SetText(dep.Version) + } + } + + //add apache repository after profiles declaration + var repositories = []Repository{ + {Id: "redhat-ga-public-repository", Name: "RedHat GA Public Repository", Url: "https://maven.repository.redhat.com/ga/"}, + } + + var pluginRepositories = []PluginRepository{ + {Id: "redhat-ga-public-repository", Name: "RedHat GA Public Repository", Url: "https://maven.repository.redhat.com/ga/", + Layout: "default", + Releases: Releases{Enabled: true}, + Snapshots: Snapshots{Enabled: true}, + }, + } + + var project = doc.FindElement("//project") + repositoriesElement := project.FindElement("//repositories") + if repositoriesElement == nil { + repositoriesElement = project.CreateElement("repositories") + } + + for _, repo := range repositories { + var repository = repositoriesElement.CreateElement("repository") + repository.CreateElement("id").SetText(repo.Id) + repository.CreateElement("name").SetText(repo.Name) + repository.CreateElement("url").SetText(repo.Url) + } + + doc.Indent(4) + + pluginRepositoriesElement := project.FindElement("//pluginRepositories") + if pluginRepositoriesElement == nil { + pluginRepositoriesElement = project.CreateElement("pluginRepositories") + } + + for _, plugin := range pluginRepositories { + var repository = pluginRepositoriesElement.CreateElement("pluginRepository") + repository.CreateElement("id").SetText(plugin.Id) + repository.CreateElement("name").SetText(plugin.Name) + repository.CreateElement("url").SetText(plugin.Url) + repository.CreateElement("layout").SetText(plugin.Layout) + repository.CreateElement("releases").CreateElement("enabled").SetText(strconv.FormatBool(plugin.Releases.Enabled)) + repository.CreateElement("snapshots").CreateElement("enabled").SetText(strconv.FormatBool(plugin.Releases.Enabled)) + } + + doc.Indent(4) + + if err := doc.WriteToFile(filename); err != nil { + return fmt.Errorf("error writing modified content to %s: %w", filename, err) + } + + return nil + +} + +func manipulateDockerIgnore(filename string) error { + pattern := "\n!target/classes/*.sw.%s" + extensions := []string{"json", "yaml", "yml"} + f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("error opening %s: %w", filename, err) + } + defer f.Close() + + for _, extension := range extensions { + if _, err := f.WriteString(fmt.Sprintf(pattern, extension)); err != nil { + return fmt.Errorf("error writing to %s: %w", filename, err) + } + } + + return nil +} + +func manipulateDockerfile(filename string) error { + extensions := []string{"json", "yaml", "yml"} + pattern := "COPY --chown=185 target/classes/*.sw.%s /deployments/app/" + + file, err := os.Open(filename) + defer file.Close() + if err != nil { + return fmt.Errorf("error opening %s: %w", filename, err) + } + + appended := false + scanner := bufio.NewScanner(file) + + var lines []string + + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "COPY") && !appended { + for _, extension := range extensions { + lines = append(lines, fmt.Sprintf(pattern, extension)) + } + appended = true + } + lines = append(lines, line) + } + + if err := scanner.Err(); err != nil { + return fmt.Errorf("error reading from %s: %w", filename, err) + } + + file, err = os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return fmt.Errorf("error opening %s for writing: %w", filename, err) + } + defer file.Close() + + writer := bufio.NewWriter(file) + for _, line := range lines { + _, err := writer.WriteString(line + "\n") + if err != nil { + return fmt.Errorf("error writing to %s: %w", filename, err) + } + } + + err = writer.Flush() + if err != nil { + return fmt.Errorf("error flushing to %s: %w", filename, err) + } + + return nil +} diff --git a/cli/pkg/command/quarkus/quarkus_project_test.go b/cli/pkg/command/quarkus/quarkus_project_test.go new file mode 100644 index 00000000..b2777d06 --- /dev/null +++ b/cli/pkg/command/quarkus/quarkus_project_test.go @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "bufio" + "os" + "path" + "strings" + "testing" + + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" +) + +func TestManipulatePom(t *testing.T) { + + //setup + metadata.KogitoVersion = "1.0.0.Final" + metadata.PluginVersion = "0.0.0" + metadata.KogitoBomDependency.Version = "0.0.0" + + inputPath := "testdata/pom1-input.xml_no_auto_formatting" + expectedPath := "testdata/pom1-expected.xml_no_auto_formatting" + + var deps = metadata.DependenciesVersion{ + QuarkusPlatformGroupId: "org.quarkus.fake", + QuarkusVersion: "0.0.1", + } + + var cfg = CreateQuarkusProjectConfig{ + DependenciesVersion: deps, + } + + tempFile := "testdata/temp.xml" + err := copyFile(inputPath, tempFile) + if err != nil { + t.Fatalf("Error copying test XML: %v", err) + } + defer os.Remove(tempFile) + + err = manipulatePomToKogito(tempFile, cfg) + if err != nil { + t.Fatalf("Error manipulating XML: %v", err) + } + + modifiedData, err := os.ReadFile(tempFile) + if err != nil { + t.Fatalf("Error reading modified XML: %v", err) + } + + expectedData, err := os.ReadFile(expectedPath) + + if err != nil { + t.Fatalf("Error reading expected XML: %v", err) + } + + // Normalize line endings for cross-platform compatibility + modifiedStr := normalizeLineEndings(string(modifiedData)) + expectedStr := normalizeLineEndings(string(expectedData)) + + if modifiedStr != expectedStr { + t.Errorf("Manipulated XML does not match expected XML") + } +} + +func TestManipulateDockerFiles(t *testing.T) { + text := []string{"COPY --chown=185 target/classes/*.sw.json /deployments/app/", "COPY --chown=185 target/classes/*.sw.yaml /deployments/app/" } + tempDir, err := os.MkdirTemp("", "project") + if err != nil { + t.Fatalf("❌ ERROR: failed to create temporary directory: %v", err) + } + defer os.RemoveAll(tempDir) + + dockerDir := path.Join(tempDir, "/src/main/docker") + err = os.MkdirAll(dockerDir, 0755) + if err != nil { + t.Fatalf("Error creating docker directory: %v", err) + } + err = copyDir("testdata/docker", dockerDir) + if err != nil { + t.Fatalf("Error copying Dockerfiles: %v", err) + } + + extensions := []string{"jvm", "legacy-jar", "native", "native-micro"} + + for _, extension := range extensions { + dockerFilePath := path.Join(dockerDir, "Dockerfile."+extension) + _, err := os.Stat(dockerFilePath) + if err != nil { + t.Fatalf("Error reading Dockerfile: %v", err) + } + + if err := manipulateDockerfile(dockerFilePath); err != nil { + t.Fatalf("Error manipulating Dockerfile: %v", err) + } + + for _, fragment := range text { + contains, err := checkFileContainsText(dockerFilePath, fragment) + if err != nil { + t.Fatalf("Failed to stat Dockerfile for extension %s: %v", extension, err) + } + if !contains { + t.Errorf("Dockerfile does not contain expected text") + } + } + } +} +func TestManipulateDockerIgnoreFile(t *testing.T) { + text := []string{"!target/classes/*.sw.json", "!target/classes/*.sw.yaml"} + tempDir, err := os.MkdirTemp("", "project") + if err != nil { + t.Fatalf("❌ ERROR: failed to create temporary directory: %v", err) + } + defer os.RemoveAll(tempDir) + + dockerIgnorePath := path.Join(tempDir, ".dockerignore") + err = copyFile("testdata/dockerignore", dockerIgnorePath) + if err != nil { + t.Fatalf("Error copying .dockerignore: %v", err) + } + if err := manipulateDockerIgnore(dockerIgnorePath); err != nil { + t.Fatalf("Error manipulating .dockerignore: %v", err) + } + for _, fragment := range text { + contains, err := checkFileContainsText(dockerIgnorePath, fragment) + if err != nil { + t.Fatalf("Error reading .dockerignore: %v", err) + } + if !contains { + t.Errorf(".dockerignore does not contain expected text") + } + } + +} + +func checkFileContainsText(filePath, text string) (bool, error) { + file, err := os.Open(filePath) + if err != nil { + return false, err + } + defer file.Close() + + var contains = false + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if line == text { + contains = true + break + } + } + return contains, nil +} + +// normalizeLineEndings normalizes line endings to \n for cross-platform comparison +func normalizeLineEndings(s string) string { + return strings.ReplaceAll(strings.ReplaceAll(s, "\r\n", "\n"), "\r", "\n") +} diff --git a/cli/pkg/command/quarkus/run.go b/cli/pkg/command/quarkus/run.go new file mode 100644 index 00000000..af082162 --- /dev/null +++ b/cli/pkg/command/quarkus/run.go @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package quarkus + +import ( + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/ory/viper" + "github.com/spf13/cobra" + "sync" + "time" +) + +type RunCmdConfig struct { + PortMapping string + OpenDevUI bool +} + +func NewRunCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "run", + Short: "Run a Quarkus SonataFlow project in development mode", + Long: ` + Run a Quarkus SonataFlow project based on Quarkus in development mode. + `, + Example: ` + # Run the local directory + {{.Name}} quarkus run + # Run the local directory mapping a different host port to the running container port. + {{.Name}} run --port 8081 + `, + SuggestFor: []string{"rnu", "start"}, //nolint:misspell + PreRunE: common.BindEnv("port", "open-dev-ui"), + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return run(cmd, args) + } + cmd.Flags().StringP("port", "p", "8080", "Maps a different port to Quarkus dev mode.") + cmd.Flags().Bool("open-dev-ui", true, "If false, disables automatic browser launch of SonataFlow Dev UI") + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +func run(cmd *cobra.Command, args []string) error { + cfg, err := runDevCmdConfig(cmd) + if err != nil { + return fmt.Errorf("initializing create config: %w", err) + } + + if common.IsQuarkusSonataFlowProject() { + return runQuarkusSWFProject(cfg) + } + + return fmt.Errorf("cannot find Quarkus SonataFlow project") +} + +func runDevCmdConfig(cmd *cobra.Command) (cfg RunCmdConfig, err error) { + cfg = RunCmdConfig{ + PortMapping: viper.GetString("port"), + OpenDevUI: viper.GetBool("open-dev-ui"), + } + return cfg, nil +} + +func runQuarkusSWFProject(cfg RunCmdConfig) error { + + if err := common.CheckJavaDependencies(); err != nil { + return fmt.Errorf("error checking Java dependencies: %w", err) + } + + return runQuarkusProjectDevMode(cfg) +} + +func runQuarkusProjectDevMode(cfg RunCmdConfig) (err error) { + fmt.Println("🛠️ Starting your Quarkus SonataFlow in dev mode...") + create := common.ExecCommand( + "mvn", + "quarkus:dev", + "-Dquarkus.http.port="+fmt.Sprintf("%s", cfg.PortMapping), + ) + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + if err := common.RunCommand(create, "mvn quarkus:dev"); err != nil { + fmt.Printf("❌ ERROR: running Quarkus project: %v", err) + err = fmt.Errorf("Error running Quarkus project: %w", err) + } + }() + + readyCheckURL := fmt.Sprintf("http://localhost:%s/q/health/ready", cfg.PortMapping) + pollInterval := 5 * time.Second + common.ReadyCheck(readyCheckURL, pollInterval, cfg.PortMapping, cfg.OpenDevUI) + + wg.Wait() + return err +} diff --git a/cli/pkg/command/quarkus/testdata/docker/Dockerfile.jvm b/cli/pkg/command/quarkus/testdata/docker/Dockerfile.jvm new file mode 100644 index 00000000..9ba13b67 --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/docker/Dockerfile.jvm @@ -0,0 +1,16 @@ +FROM registry.access.redhat.com/ubi9/openjdk-17:1.24 + +ENV LANGUAGE='en_US:en' + + +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] diff --git a/cli/pkg/command/quarkus/testdata/docker/Dockerfile.legacy-jar b/cli/pkg/command/quarkus/testdata/docker/Dockerfile.legacy-jar new file mode 100644 index 00000000..c0bb06ac --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/docker/Dockerfile.legacy-jar @@ -0,0 +1,14 @@ +FROM registry.access.redhat.com/ubi9/openjdk-17:1.24 + +ENV LANGUAGE='en_US:en' + + +COPY target/lib/* /deployments/lib/ +COPY target/*-runner.jar /deployments/quarkus-run.jar + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] diff --git a/cli/pkg/command/quarkus/testdata/docker/Dockerfile.native b/cli/pkg/command/quarkus/testdata/docker/Dockerfile.native new file mode 100644 index 00000000..b640808e --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/docker/Dockerfile.native @@ -0,0 +1,11 @@ +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/cli/pkg/command/quarkus/testdata/docker/Dockerfile.native-micro b/cli/pkg/command/quarkus/testdata/docker/Dockerfile.native-micro new file mode 100644 index 00000000..fb244ab5 --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/docker/Dockerfile.native-micro @@ -0,0 +1,11 @@ +FROM quay.io/quarkus/quarkus-micro-image:2.0 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/cli/pkg/command/quarkus/testdata/dockerignore b/cli/pkg/command/quarkus/testdata/dockerignore new file mode 100644 index 00000000..94810d00 --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/dockerignore @@ -0,0 +1,5 @@ +* +!target/*-runner +!target/*-runner.jar +!target/lib/* +!target/quarkus-app/* \ No newline at end of file diff --git a/cli/pkg/command/quarkus/testdata/knative.yml b/cli/pkg/command/quarkus/testdata/knative.yml new file mode 100644 index 00000000..408cd879 --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/knative.yml @@ -0,0 +1,54 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + app.quarkus.io/quarkus-version: QUARKUS_VERSION + app.quarkus.io/build-timestamp: 2024-08-22 - 21:01:20 +0000 + labels: + app.kubernetes.io/version: 1.0.0-SNAPSHOT + app.kubernetes.io/name: incubator-kie-sonataflow-builder + name: incubator-kie-sonataflow-builder +--- +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + annotations: + app.quarkus.io/quarkus-version: QUARKUS_VERSION + app.quarkus.io/build-timestamp: 2024-08-22 - 21:01:20 +0000 + labels: + app.kubernetes.io/version: 1.0.0-SNAPSHOT + app.kubernetes.io/name: incubator-kie-sonataflow-builder + name: incubator-kie-sonataflow-builder +spec: + template: + spec: + containers: + - image: apache/incubator-kie-sonataflow-builder:latest + imagePullPolicy: Always + livenessProbe: + failureThreshold: 3 + httpGet: + path: /q/health/live + port: null + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 10 + name: incubator-kie-sonataflow-builder + ports: + - containerPort: 8080 + name: http1 + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /q/health/ready + port: null + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 10 + serviceAccountName: incubator-kie-sonataflow-builder diff --git a/cli/pkg/command/quarkus/testdata/kogito-complex-custom-namespace.yml b/cli/pkg/command/quarkus/testdata/kogito-complex-custom-namespace.yml new file mode 100644 index 00000000..481cf4fe --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/kogito-complex-custom-namespace.yml @@ -0,0 +1,52 @@ +--- +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + annotations: + sonataflow.org/description: Description + sonataflow.org/expressionLang: jq + sonataflow.org/profile: dev + sonataflow.org/version: "1.0" + creationTimestamp: null + labels: + app: hello + sonataflow.org/workflow-app: hello + name: hello + namespace: mycustom +spec: + flow: + start: + stateName: HelloWorld + states: + - data: + message: Hello World + end: + terminate: true + name: HelloWorld + type: inject + podTemplate: + container: + resources: {} + resources: {} +status: + address: {} + lastTimeRecoverAttempt: null +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: example-configmap + namespace: mycustom +data: + config_key1: value1 + config_key2: value2 +--- +apiVersion: v1 +kind: Secret +metadata: + name: example-secret + namespace: mycustom +type: Opaque +data: + username: YWRtaW4= + password: cGFzc3dvcmQ= diff --git a/cli/pkg/command/quarkus/testdata/kogito-complex.yml b/cli/pkg/command/quarkus/testdata/kogito-complex.yml new file mode 100644 index 00000000..fe2fd959 --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/kogito-complex.yml @@ -0,0 +1,52 @@ +--- +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + annotations: + sonataflow.org/description: Description + sonataflow.org/expressionLang: jq + sonataflow.org/profile: dev + sonataflow.org/version: "1.0" + creationTimestamp: null + labels: + app: hello + sonataflow.org/workflow-app: hello + name: hello + namespace: default +spec: + flow: + start: + stateName: HelloWorld + states: + - data: + message: Hello World + end: + terminate: true + name: HelloWorld + type: inject + podTemplate: + container: + resources: {} + resources: {} +status: + address: {} + lastTimeRecoverAttempt: null +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: example-configmap + namespace: default +data: + config_key1: value1 + config_key2: value2 +--- +apiVersion: v1 +kind: Secret +metadata: + name: example-secret + namespace: default +type: Opaque +data: + username: YWRtaW4= + password: cGFzc3dvcmQ= diff --git a/cli/pkg/command/quarkus/testdata/kogito-complex2-custom-namespace.yml b/cli/pkg/command/quarkus/testdata/kogito-complex2-custom-namespace.yml new file mode 100644 index 00000000..348884bf --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/kogito-complex2-custom-namespace.yml @@ -0,0 +1,66 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: example-job + namespace: mycustom +spec: + template: + spec: + containers: + - name: example-job-container + image: busybox + command: ["echo", "Hello, Kubernetes!"] + restartPolicy: Never + backoffLimit: 4 +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: example-cronjob + namespace: mycustom +spec: + schedule: "*/5 * * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: example-cronjob-container + image: busybox + command: ["echo", "Hello from CronJob!"] + restartPolicy: Never + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 1 +--- +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + annotations: + sonataflow.org/description: Description + sonataflow.org/expressionLang: jq + sonataflow.org/profile: dev + sonataflow.org/version: "1.0" + creationTimestamp: null + labels: + app: hello + sonataflow.org/workflow-app: hello + name: hello + namespace: mycustom +spec: + flow: + start: + stateName: HelloWorld + states: + - data: + message: Hello World + end: + terminate: true + name: HelloWorld + type: inject + podTemplate: + container: + resources: {} + resources: {} +status: + address: {} + lastTimeRecoverAttempt: null diff --git a/cli/pkg/command/quarkus/testdata/kogito-complex2.yml b/cli/pkg/command/quarkus/testdata/kogito-complex2.yml new file mode 100644 index 00000000..abc4efab --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/kogito-complex2.yml @@ -0,0 +1,66 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: example-job + namespace: default +spec: + template: + spec: + containers: + - name: example-job-container + image: busybox + command: ["echo", "Hello, Kubernetes!"] + restartPolicy: Never + backoffLimit: 4 +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: example-cronjob + namespace: default +spec: + schedule: "*/5 * * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: example-cronjob-container + image: busybox + command: ["echo", "Hello from CronJob!"] + restartPolicy: Never + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 1 +--- +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + annotations: + sonataflow.org/description: Description + sonataflow.org/expressionLang: jq + sonataflow.org/profile: dev + sonataflow.org/version: "1.0" + creationTimestamp: null + labels: + app: hello + sonataflow.org/workflow-app: hello + name: hello + namespace: default +spec: + flow: + start: + stateName: HelloWorld + states: + - data: + message: Hello World + end: + terminate: true + name: HelloWorld + type: inject + podTemplate: + container: + resources: {} + resources: {} +status: + address: {} + lastTimeRecoverAttempt: null diff --git a/cli/pkg/command/quarkus/testdata/kogito-complex3-custom-namespace.yml b/cli/pkg/command/quarkus/testdata/kogito-complex3-custom-namespace.yml new file mode 100644 index 00000000..348884bf --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/kogito-complex3-custom-namespace.yml @@ -0,0 +1,66 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: example-job + namespace: mycustom +spec: + template: + spec: + containers: + - name: example-job-container + image: busybox + command: ["echo", "Hello, Kubernetes!"] + restartPolicy: Never + backoffLimit: 4 +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: example-cronjob + namespace: mycustom +spec: + schedule: "*/5 * * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: example-cronjob-container + image: busybox + command: ["echo", "Hello from CronJob!"] + restartPolicy: Never + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 1 +--- +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + annotations: + sonataflow.org/description: Description + sonataflow.org/expressionLang: jq + sonataflow.org/profile: dev + sonataflow.org/version: "1.0" + creationTimestamp: null + labels: + app: hello + sonataflow.org/workflow-app: hello + name: hello + namespace: mycustom +spec: + flow: + start: + stateName: HelloWorld + states: + - data: + message: Hello World + end: + terminate: true + name: HelloWorld + type: inject + podTemplate: + container: + resources: {} + resources: {} +status: + address: {} + lastTimeRecoverAttempt: null diff --git a/cli/pkg/command/quarkus/testdata/kogito-complex3.yml b/cli/pkg/command/quarkus/testdata/kogito-complex3.yml new file mode 100644 index 00000000..abc4efab --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/kogito-complex3.yml @@ -0,0 +1,66 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: example-job + namespace: default +spec: + template: + spec: + containers: + - name: example-job-container + image: busybox + command: ["echo", "Hello, Kubernetes!"] + restartPolicy: Never + backoffLimit: 4 +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: example-cronjob + namespace: default +spec: + schedule: "*/5 * * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: example-cronjob-container + image: busybox + command: ["echo", "Hello from CronJob!"] + restartPolicy: Never + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 1 +--- +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + annotations: + sonataflow.org/description: Description + sonataflow.org/expressionLang: jq + sonataflow.org/profile: dev + sonataflow.org/version: "1.0" + creationTimestamp: null + labels: + app: hello + sonataflow.org/workflow-app: hello + name: hello + namespace: default +spec: + flow: + start: + stateName: HelloWorld + states: + - data: + message: Hello World + end: + terminate: true + name: HelloWorld + type: inject + podTemplate: + container: + resources: {} + resources: {} +status: + address: {} + lastTimeRecoverAttempt: null diff --git a/cli/pkg/command/quarkus/testdata/kogito-default-custom-namespace.yml b/cli/pkg/command/quarkus/testdata/kogito-default-custom-namespace.yml new file mode 100755 index 00000000..5a8c46f6 --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/kogito-default-custom-namespace.yml @@ -0,0 +1,32 @@ +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + annotations: + sonataflow.org/description: Description + sonataflow.org/expressionLang: jq + sonataflow.org/profile: dev + sonataflow.org/version: "1.0" + creationTimestamp: null + labels: + app: hello + sonataflow.org/workflow-app: hello + name: hello + namespace: mycustom +spec: + flow: + start: + stateName: HelloWorld + states: + - data: + message: Hello World + end: + terminate: true + name: HelloWorld + type: inject + podTemplate: + container: + resources: {} + resources: {} +status: + address: {} + lastTimeRecoverAttempt: null diff --git a/cli/pkg/command/quarkus/testdata/kogito-default.yml b/cli/pkg/command/quarkus/testdata/kogito-default.yml new file mode 100755 index 00000000..d2841325 --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/kogito-default.yml @@ -0,0 +1,32 @@ +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + annotations: + sonataflow.org/description: Description + sonataflow.org/expressionLang: jq + sonataflow.org/profile: dev + sonataflow.org/version: "1.0" + creationTimestamp: null + labels: + app: hello + sonataflow.org/workflow-app: hello + name: hello + namespace: default +spec: + flow: + start: + stateName: HelloWorld + states: + - data: + message: Hello World + end: + terminate: true + name: HelloWorld + type: inject + podTemplate: + container: + resources: {} + resources: {} +status: + address: {} + lastTimeRecoverAttempt: null diff --git a/cli/pkg/command/quarkus/testdata/pom1-expected.xml_no_auto_formatting b/cli/pkg/command/quarkus/testdata/pom1-expected.xml_no_auto_formatting new file mode 100644 index 00000000..507434fb --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/pom1-expected.xml_no_auto_formatting @@ -0,0 +1,212 @@ + + + 4.0.0 + org.acme + getting-started + 1.0.0-SNAPSHOT + + 3.13.0 + 17 + UTF-8 + UTF-8 + quarkus-bom + org.quarkus.fake + 0.0.1 + true + 3.5.0 + 0.0.0 + 0.0.0 + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + org.kie.kogito + kogito-bom + 1.0.0.Final + pom + import + + + org.kie.kogito + kogito-bom + ${kie.version} + pom + import + + + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.kie + kie-addons-quarkus-knative-eventing + + + org.kie + kie-addons-quarkus-source-files + + + org.apache.kie.sonataflow + sonataflow-quarkus-devui + + + org.kie + kogito-addons-quarkus-data-index-inmemory + + + org.apache.kie.sonataflow + sonataflow-quarkus + + + org.kie + kie-addons-quarkus-knative-eventing + + + org.kie + kie-addons-quarkus-process-management + + + org.kie + kie-addons-quarkus-source-files + + + org.kie + kogito-addons-quarkus-data-index-inmemory + + + org.kie + kogito-addons-quarkus-jobs-service-embedded + + + org.apache.kie.sonataflow + sonataflow-quarkus + + + org.apache.kie.sonataflow + sonataflow-quarkus-devui + ${kie.tooling.version} + + + org.testcontainers + testcontainers + 1.21.4 + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + native + + + native + + + + false + native + + + + + + redhat-ga-public-repository + RedHat GA Public Repository + https://maven.repository.redhat.com/ga/ + + + + + redhat-ga-public-repository + RedHat GA Public Repository + https://maven.repository.redhat.com/ga/ + default + + true + + + true + + + + diff --git a/cli/pkg/command/quarkus/testdata/pom1-input.xml_no_auto_formatting b/cli/pkg/command/quarkus/testdata/pom1-input.xml_no_auto_formatting new file mode 100644 index 00000000..b7cdcd01 --- /dev/null +++ b/cli/pkg/command/quarkus/testdata/pom1-input.xml_no_auto_formatting @@ -0,0 +1,150 @@ + + + 4.0.0 + org.acme + getting-started + 1.0.0-SNAPSHOT + + 3.13.0 + 17 + UTF-8 + UTF-8 + quarkus-bom + org.quarkus.fake + 0.0.1 + true + 3.5.0 + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + org.kie.kogito + kogito-bom + 1.0.0.Final + pom + import + + + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.kie + kie-addons-quarkus-knative-eventing + + + org.kie + kie-addons-quarkus-source-files + + + org.apache.kie.sonataflow + sonataflow-quarkus-devui + + + org.kie + kogito-addons-quarkus-data-index-inmemory + + + org.apache.kie.sonataflow + sonataflow-quarkus + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + native + + + native + + + + false + native + + + + diff --git a/cli/pkg/command/run.go b/cli/pkg/command/run.go new file mode 100644 index 00000000..dc6ee2b6 --- /dev/null +++ b/cli/pkg/command/run.go @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package command + +import ( + "bufio" + "fmt" + "os" + "sync" + "time" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +type RunCmdConfig struct { + PortMapping string + OpenDevUI bool + StopContainerOnUserInput bool + Image string +} + +const StopContainerMsg = "Press any key to stop the container" + + +func NewRunCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "run", + Short: "Run a SonataFlow project in development mode", + Long: ` + Run a SonataFlow project in development mode. + + By default, it runs over ` + metadata.DevModeImage + ` on Docker. + Alternatively, you can run the same image with Podman. + + `, + Example: ` + # Run the workflow inside the current local directory + {{.Name}} run + + # Run the current local directory mapping a different host port to the running container port. + {{.Name}} run --port 8081 + + # Disable automatic browser launch of SonataFlow Dev UI + {{.Name}} run --open-dev-ui=false + + # Stop the container when the user presses any key + {{.Name}} run --stop-container-on-user-input=false + + # Specify a custom container image to use for the deployment. + # By default, the ` + metadata.DevModeImage + ` image is used + {{.Name}} run --image= + + `, + SuggestFor: []string{"rnu", "start"}, //nolint:misspell + PreRunE: common.BindEnv("port", "open-dev-ui", "stop-container-on-user-input", "image"), + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return run() + } + + cmd.Flags().StringP("port", "p", "8080", "Maps a different host port to the running container port.") + cmd.Flags().Bool("open-dev-ui", true, "Disable automatic browser launch of SonataFlow Dev UI") + cmd.Flags().Bool("stop-container-on-user-input", true, "Stop the container when the user presses any key") + cmd.Flags().StringP("image", "i", "", "Specify a custom image to use for the deployment. By default, the `" + metadata.DevModeImage + "` image is used") + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +func run() error { + cfg, err := runDevCmdConfig() + if err != nil { + return fmt.Errorf("initializing create config: %w", err) + } + + if cfg.Image != "" { + metadata.DevModeImage = cfg.Image + } + + if common.IsSonataFlowProject() { + if err := runSWFProject(cfg); err != nil { + return err + } + return nil + } else if common.IsQuarkusSonataFlowProject() { + return fmt.Errorf("looks like you are inside a Quarkus project. If that is the case, you should run it with \"quarkus run\" command") + } else { + return fmt.Errorf("cannot find SonataFlow project") + } +} + +func runDevCmdConfig() (cfg RunCmdConfig, err error) { + cfg = RunCmdConfig{ + PortMapping: viper.GetString("port"), + OpenDevUI: viper.GetBool("open-dev-ui"), + StopContainerOnUserInput: viper.GetBool("stop-container-on-user-input"), + Image: viper.GetString("image"), + } + return +} + +func runSWFProject(cfg RunCmdConfig) error { + + if errDocker := common.CheckDocker(); errDocker == nil { + if err := runSWFProjectDevMode(common.Docker, cfg); err != nil { + return err + } + } else if errPodman := common.CheckPodman(); errPodman == nil { + if err := runSWFProjectDevMode(common.Podman, cfg); err != nil { + return err + } + } else { + return fmt.Errorf("there is no docker or podman available") + } + return nil +} + +func runSWFProjectDevMode(containerTool string, cfg RunCmdConfig) (err error) { + fmt.Println("⏳ Starting your SonataFlow project in dev mode...") + path, err := os.Getwd() + if err != nil { + fmt.Errorf("❌ Error running SonataFlow project: %w", err) + } + + common.GracefullyStopTheContainerWhenInterrupted(containerTool) + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + if err := common.RunContainerCommand(containerTool, cfg.PortMapping, path); err != nil { + fmt.Errorf("❌ Error running SonataFlow project: %w", err) + } + }() + + readyCheckURL := fmt.Sprintf("http://localhost:%s/q/health/ready", cfg.PortMapping) + pollInterval := 5 * time.Second + common.ReadyCheck(readyCheckURL, pollInterval, cfg.PortMapping, cfg.OpenDevUI) + + if cfg.StopContainerOnUserInput { + if err := stopContainer(containerTool); err != nil { + return err + } + } + + wg.Wait() + return err +} + +func stopContainer(containerTool string) error { + fmt.Println(StopContainerMsg) + + reader := bufio.NewReader(os.Stdin) + + _, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("error reading from stdin: %w", err) + } + + fmt.Println("⏳ Stopping the container...") + + containerID, err := common.GetContainerID(containerTool) + if err != nil { + return err + } + if err := common.StopContainer(containerTool, containerID); err != nil { + return err + } + return nil +} + + diff --git a/cli/pkg/command/specs/minify.go b/cli/pkg/command/specs/minify.go new file mode 100644 index 00000000..a1ece3c6 --- /dev/null +++ b/cli/pkg/command/specs/minify.go @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/spf13/cobra" +) + +func minifyCommand() *cobra.Command { + // add command minify here + + var cmd = &cobra.Command{ + Use: "minify", + Short: "Minification of OpenAPI specs", + Long: ` + Minification of OpenAPI specs: + Minification allows us to reduce the size of an OpenAPI spec file, which is essential given the maximum YAML + size supported by Kubernetes is limited to 3,145,728 bytes. + + Note: right now only OpenAPI specs are supported for minification, see examples below. + `, + Example: ` + #Minify the workflow project's OpenAPI spec file located in the current project. + {{.Name}} specs minify openapi + `, + } + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + cmd.AddCommand(minifyOpenApi()) + + return cmd +} diff --git a/cli/pkg/command/specs/minify_test.go b/cli/pkg/command/specs/minify_test.go new file mode 100644 index 00000000..b2c0fd7d --- /dev/null +++ b/cli/pkg/command/specs/minify_test.go @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMinifyCommand(t *testing.T) { + cmd := minifyCommand() + + assert.NotNil(t, cmd) + + assert.Equal(t, "minify", cmd.Use) + + subcommands := cmd.Commands() + assert.NotEmpty(t, subcommands) + assert.Equal(t, 1, len(subcommands)) + + found := false + for _, c := range subcommands { + if c.Name() == "openapi" { + found = true + break + } + } + assert.True(t, found, "minify subcommand not found") +} diff --git a/cli/pkg/command/specs/openapi.go b/cli/pkg/command/specs/openapi.go new file mode 100644 index 00000000..07364ff0 --- /dev/null +++ b/cli/pkg/command/specs/openapi.go @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/specs" + "github.com/ory/viper" + "github.com/spf13/cobra" + "os" +) + +func minifyOpenApi() *cobra.Command { + + var cmd = &cobra.Command{ + Use: "openapi", + Short: "Minify the openAPI spec files to trim operations only used by the workflows", + Long: ` + Minification of OpenAPI specs: + Minification allows us to reduce the size of an OpenAPI spec file, which is essential given the maximum YAML + size supported by Kubernetes is limited to 3,145,728 bytes.`, + Example: ` + #Minify the workflow project's OpenAPI spec file located in the current project. + {{.Name}} specs minify openapi + + # Specify a custom subflows files directory. (default: ./subflows) + {{.Name}} specs minify openapi --subflows-dir= + + # Specify a custom support specs directory. (default: ./specs) + {{.Name}} specs minify openapi --specs-dir= + `, + PreRunE: common.BindEnv("specs-dir", "subflows-dir"), + } + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + cmd.Flags().StringP("specs-dir", "p", "", "Specify a custom specs files directory") + cmd.Flags().StringP("subflows-dir", "s", "", "Specify a custom subflows files directory") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runMinifyOpenApi() + } + + return cmd + +} + +func runMinifyOpenApi() error { + + var cfg = &specs.OpenApiMinifierOpts{ + SpecsDir: viper.GetString("specs-dir"), + SubflowsDir: viper.GetString("subflows-dir"), + } + + if len(cfg.SubflowsDir) == 0 { + dir, err := os.Getwd() + cfg.SubflowsDir = dir + "/subflows" + if err != nil { + return fmt.Errorf("❌ ERROR: failed to get default subflows workflow files folder: %w", err) + } + } + + if len(cfg.SpecsDir) == 0 { + dir, err := os.Getwd() + cfg.SpecsDir = dir + "/specs" + if err != nil { + return fmt.Errorf("❌ ERROR: failed to get default specs files folder: %w", err) + } + } + + minifier := specs.NewMinifier(cfg) + _, err := minifier.Minify() + return err +} diff --git a/cli/pkg/command/specs/openapi_test.go b/cli/pkg/command/specs/openapi_test.go new file mode 100644 index 00000000..1c588735 --- /dev/null +++ b/cli/pkg/command/specs/openapi_test.go @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestOpenApiCommand(t *testing.T) { + cmd := minifyOpenApi() + + assert.NotNil(t, cmd) + + assert.Equal(t, "openapi", cmd.Use) + + subcommands := cmd.Commands() + assert.Empty(t, subcommands) + + specsDirFlag := cmd.Flags().Lookup("specs-dir") + assert.NotNil(t, specsDirFlag) + assert.Equal(t, "p", specsDirFlag.Shorthand) + + subflowsDirFlag := cmd.Flags().Lookup("subflows-dir") + assert.NotNil(t, subflowsDirFlag) + assert.Equal(t, "s", subflowsDirFlag.Shorthand) +} diff --git a/cli/pkg/command/specs/specs.go b/cli/pkg/command/specs/specs.go new file mode 100644 index 00000000..9efdcf4a --- /dev/null +++ b/cli/pkg/command/specs/specs.go @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/spf13/cobra" +) + +func SpecsCommand() *cobra.Command { + // add command specs here + + var cmd = &cobra.Command{ + Use: "specs", + Short: "Set of utilities to work with specs", + Long: ` + Minification of OpenAPI specs: + Minification allows us to reduce the size of an OpenAPI spec file, which is essential given the maximum YAML + size supported by Kubernetes is limited to 3,145,728 bytes.`, + Example: ` + #Minify the workflow project's OpenAPI spec file located in the current project. + {{.Name}} specs minify openapi + `, + } + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + cmd.AddCommand(minifyCommand()) + + return cmd +} diff --git a/cli/pkg/command/specs/specs_test.go b/cli/pkg/command/specs/specs_test.go new file mode 100644 index 00000000..58f0df1c --- /dev/null +++ b/cli/pkg/command/specs/specs_test.go @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSpecsCommand(t *testing.T) { + cmd := SpecsCommand() + + assert.NotNil(t, cmd) + + assert.Equal(t, "specs", cmd.Use) + + subcommands := cmd.Commands() + assert.NotEmpty(t, subcommands) + assert.Equal(t, 1, len(subcommands)) + + found := false + for _, c := range subcommands { + if c.Name() == "minify" { + found = true + break + } + } + assert.True(t, found, "minify subcommand not found") +} diff --git a/cli/pkg/command/template/SonataFlow-Builder.containerfile b/cli/pkg/command/template/SonataFlow-Builder.containerfile new file mode 100644 index 00000000..faeb0e80 --- /dev/null +++ b/cli/pkg/command/template/SonataFlow-Builder.containerfile @@ -0,0 +1,57 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +FROM {{ .BuildImage }} AS builder + +# variables that can be overridden by the builder +# To add a Quarkus extension to your application +ARG QUARKUS_EXTENSIONS +ENV QUARKUS_EXTENSIONS="${QUARKUS_EXTENSIONS:-org.kie:kie-addons-quarkus-persistence-jdbc,io.quarkus:quarkus-jdbc-postgresql,io.quarkus:quarkus-agroal}" +# Args to pass to the Quarkus CLI add extension command +ARG QUARKUS_ADD_EXTENSION_ARGS +# Additional java/mvn arguments to pass to the builder +ARG MAVEN_ARGS_APPEND +ENV MAVEN_ARGS_APPEND="${MAVEN_ARGS_APPEND:-} \ + -DmaxYamlCodePoints=35000000 \ + -Dkogito.persistence.type=jdbc \ + -Dquarkus.datasource.db-kind=postgresql \ + -Dkogito.persistence.proto.marshaller=false" + +# Copy from build context to skeleton resources project +COPY --chown=1001 . ./resources + +RUN /home/kogito/launch/build-app.sh ./resources + +#============================= +# Runtime Run +#============================= +FROM registry.access.redhat.com/ubi9/openjdk-17-runtime:1.24 + +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/lib/ /deployments/lib/ +COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/*.jar /deployments/ +COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/app/ /deployments/app/ +COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 + +ENV AB_JOLOKIA_OFF="" +ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" diff --git a/cli/pkg/command/undeploy.go b/cli/pkg/command/undeploy.go new file mode 100644 index 00000000..6825bb73 --- /dev/null +++ b/cli/pkg/command/undeploy.go @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package command + +import ( + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/ory/viper" + "github.com/spf13/cobra" + "os" + "path" +) + +func NewUndeployCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "undeploy", + Short: "Undeploy a SonataFlow project on Kubernetes via SonataFlow Operator", + Long: ` + Undeploy a SonataFlow project in Kubernetes via the SonataFlow Operator. + `, + Example: ` + # Undeploy the workflow project from the current directory's project. + # You must provide target namespace. + {{.Name}} undeploy --namespace + + # Persist the generated Kubernetes manifests on a given path and deploy the + # workflow from the current directory's project. + {{.Name}} undeploy --custom-generated-manifests-dir= + + # Specify a custom manifest files directory. + # This option *will not* automatically generate the manifest files, but will use the existing ones. + {{.Name}} deploy --custom-manifests-dir= + + # Specify a custom subflows files directory. (default: ./subflows) + {{.Name}} deploy --subflows-dir= + + # Specify a custom support specs directory. (default: ./specs) + {{.Name}} deploy --specs-dir= + + # Specify a custom support schemas directory. (default: ./schemas) + {{.Name}} deploy --schemas-dir= + + `, + + PreRunE: common.BindEnv("namespace", "custom-manifests-dir", "custom-generated-manifests-dir", "specs-dir", "schemas-dir", "subflows-dir"), + SuggestFor: []string{"undelpoy", "undeplyo"}, + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runUndeploy(cmd, args) + } + + cmd.Flags().StringP("namespace", "n", "", "Target namespace of your deployment.") + cmd.Flags().StringP("custom-manifests-dir", "m", "", "Specify a custom manifest files directory. This option will not automatically generate the manifest files, but will use the existing ones.") + cmd.Flags().StringP("custom-generated-manifests-dir", "c", "", "Target directory of your generated Kubernetes manifests.") + cmd.Flags().StringP("specs-dir", "p", "", "Specify a custom specs files directory") + cmd.Flags().StringP("subflows-dir", "s", "", "Specify a custom subflows files directory") + cmd.Flags().StringP("schemas-dir", "t", "", "Specify a custom schemas files directory") + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + return cmd +} + +func runUndeploy(cmd *cobra.Command, args []string) error { + + cfg, err := runUndeployCmdConfig(cmd) + //temp dir cleanup + defer func(cfg *DeployUndeployCmdConfig) { + if cfg.TempDir != "" { + if err := os.RemoveAll(cfg.TempDir); err != nil { + fmt.Errorf("❌ ERROR: failed to remove temp dir: %v", err) + } + } + }(&cfg) + + if err != nil { + return fmt.Errorf("❌ ERROR: initializing undeploy config: %w", err) + } + + fmt.Println("🛠️️ Undeploy a SonataFlow project on Kubernetes via the SonataFlow Operator...") + + if err := checkEnvironment(&cfg); err != nil { + return fmt.Errorf("❌ ERROR: checking undeploy environment: %w", err) + } + + if len(cfg.CustomManifestsFileDir) == 0 { + if err := generateManifests(&cfg); err != nil { + return fmt.Errorf("❌ ERROR: generating deploy environment: %w", err) + } + } else { + fmt.Printf("🛠 Using manifests located at %s\n", cfg.CustomManifestsFileDir) + } + + if err = undeploy(&cfg); err != nil { + return fmt.Errorf("❌ ERROR: undeploying: %w", err) + } + + fmt.Println("\n🎉 SonataFlow project successfully undeployed.") + + return nil +} + +func undeploy(cfg *DeployUndeployCmdConfig) error { + fmt.Printf("🔨 Undeploying your SonataFlow project in namespace %s\n", cfg.NameSpace) + + manifestExtension := []string{metadata.YAMLExtension} + + manifestPath := cfg.CustomGeneratedManifestDir + if len(cfg.CustomManifestsFileDir) != 0 { + manifestPath = cfg.CustomManifestsFileDir + } + + files, err := common.FindFilesWithExtensions(manifestPath, manifestExtension) + if err != nil { + return fmt.Errorf("❌ ERROR: failed to get manifest directory and files: %w", err) + } + + for _, file := range files { + if err = common.ExecuteDelete(file, cfg.NameSpace); err != nil { + return fmt.Errorf("❌ ERROR: failed to undeploy manifest %s, %w", file, err) + } + fmt.Printf(" - ✅ Manifest %s successfully undeployed in namespace %s\n", path.Base(file), cfg.NameSpace) + + } + return nil +} + +func runUndeployCmdConfig(cmd *cobra.Command) (cfg DeployUndeployCmdConfig, err error) { + + cfg = DeployUndeployCmdConfig{ + NameSpace: viper.GetString("namespace"), + CustomManifestsFileDir: viper.GetString("custom-manifests-dir"), + CustomGeneratedManifestDir: viper.GetString("custom-generated-manifests-dir"), + SpecsDir: viper.GetString("specs-dir"), + SchemasDir: viper.GetString("schemas-dir"), + SubflowsDir: viper.GetString("subflows-dir"), + } + + if len(cfg.SubflowsDir) == 0 { + dir, err := os.Getwd() + cfg.SubflowsDir = dir + "/subflows" + if err != nil { + return cfg, fmt.Errorf("❌ ERROR: failed to get default subflows workflow files folder: %w", err) + } + } + + if len(cfg.SpecsDir) == 0 { + dir, err := os.Getwd() + cfg.SpecsDir = dir + "/specs" + if err != nil { + return cfg, fmt.Errorf("❌ ERROR: failed to get default support specs files folder: %w", err) + } + } + + if len(cfg.SchemasDir) == 0 { + dir, err := os.Getwd() + cfg.SchemasDir = dir + "/schemas" + if err != nil { + return cfg, fmt.Errorf("❌ ERROR: failed to get default support schemas files folder: %w", err) + } + } + + //setup manifest path + if err := setupConfigManifestPath(&cfg); err != nil { + return cfg, err + } + + return cfg, nil +} diff --git a/cli/pkg/command/version.go b/cli/pkg/command/version.go new file mode 100644 index 00000000..a245a5c6 --- /dev/null +++ b/cli/pkg/command/version.go @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package command + +import ( + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/spf13/cobra" +) + +func NewVersionCommand(version string) *cobra.Command { + cmd := &cobra.Command{ + Use: "version", + Short: "Show the version", + Long: ` + Shows the plugin version. + `, + Example: ` + # Shows the plugin version + {{.Name}} version + `, + SuggestFor: []string{"vers", "verison"}, //nolint:misspell + } + + cmd.SetHelpFunc(common.DefaultTemplatedHelp) + + cmd.Run = func(cmd *cobra.Command, args []string) { + runVersion(version) + } + + return cmd +} + +func runVersion(version string) { + fmt.Printf("Version: %s\n\n", version) + + fmt.Printf("Default Quarkus version: %s\n", metadata.QuarkusVersion) + fmt.Printf("Default Quarkus platform group Id: %s\n", metadata.QuarkusPlatformGroupId) + fmt.Printf("Kogito Version: %s\n", metadata.KogitoVersion) + fmt.Printf("DevMode Image: %s\n", metadata.DevModeImage) +} diff --git a/cli/pkg/common/afero.go b/cli/pkg/common/afero.go new file mode 100644 index 00000000..6b6777d2 --- /dev/null +++ b/cli/pkg/common/afero.go @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import "github.com/spf13/afero" + +// FS /* Global variable for Afero filesystem. +var FS afero.Fs = afero.NewOsFs() diff --git a/cli/pkg/common/bind.go b/cli/pkg/common/bind.go new file mode 100644 index 00000000..507dad97 --- /dev/null +++ b/cli/pkg/common/bind.go @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +type bindWorkflow func(*cobra.Command, []string) error + +func BindEnv(flags ...string) bindWorkflow { + return func(cmd *cobra.Command, args []string) (err error) { + for _, flag := range flags { + if err = viper.BindPFlag(flag, cmd.Flags().Lookup(flag)); err != nil { + return + } + } + return + } +} diff --git a/cli/pkg/common/browser.go b/cli/pkg/common/browser.go new file mode 100644 index 00000000..61edccf0 --- /dev/null +++ b/cli/pkg/common/browser.go @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "log" + "os/exec" + "runtime" +) + +func OpenBrowserURL(url string) { + var cmd *exec.Cmd + switch runtime.GOOS { + case "windows": + cmd = exec.Command("cmd", "/c", "start", url) + case "darwin": + cmd = exec.Command("open", url) + default: + cmd = exec.Command("xdg-open", url) + } + + err := cmd.Start() + if err != nil { + log.Printf("Error opening browser: %v", err) + } +} diff --git a/cli/pkg/common/checks.go b/cli/pkg/common/checks.go new file mode 100644 index 00000000..6bbfc770 --- /dev/null +++ b/cli/pkg/common/checks.go @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "context" + "fmt" + "github.com/docker/docker/api/types/container" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + apiMetadata "github.com/kubesmarts/logic-operator/api/metadata" + "github.com/docker/docker/client" +) + +func CheckJavaDependencies() error { + fmt.Println("✅ Checking dependencies...") + if err := checkJava(); err != nil { + return fmt.Errorf("%w", err) + } + if err := checkMaven(); err != nil { + return fmt.Errorf("%w", err) + } + return nil +} + +func checkJava() error { + javaCheck := ExecCommand("java", "-version") + version, err := javaCheck.CombinedOutput() + if err != nil { + fmt.Println("ERROR: Java not found") + fmt.Printf("At least Java %.2d is required to use this command\n", metadata.JavaVersion) + return err + } + userJavaVersion, err := parseJavaVersion(string(version)) + if err != nil { + return fmt.Errorf("error while parsing Java version: %w", err) + } + + if userJavaVersion < metadata.JavaVersion { + fmt.Printf("ERROR: Please make sure you are using Java version %.2d or later", metadata.JavaVersion) + fmt.Println("Installation stopped. Please upgrade Java and run again") + os.Exit(1) + } else { + fmt.Println(" - Java version check.") + } + return nil +} + +func checkMaven() error { + mavenCheck := ExecCommand("mvn", "--version") + version, err := mavenCheck.CombinedOutput() + if err != nil { + fmt.Println("ERROR: Maven not found") + fmt.Printf("At least Maven %.2d.%.2d.1 is required to use this command\n", metadata.MavenMajorVersion, metadata.MavenMinorVersion) + return err + } + major, minor, err := parseMavenVersion(string(version)) + if err != nil { + return fmt.Errorf("error while parsing Maven version: %w", err) + } + + if major < metadata.MavenMajorVersion && minor < metadata.MavenMinorVersion { + fmt.Printf("ERROR: Please make sure you are using Maven version %d.%d.1 or later", major, minor) + fmt.Println("Installation stopped. Please upgrade Maven and run again") + os.Exit(1) + } else { + fmt.Println(" - Maven version check.") + } + + return nil +} + +func CheckDocker() error { + fmt.Println("✅ Checking if Docker is available...") + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + fmt.Println("Error creating docker client") + return err + } + _, err = cli.ContainerList(context.Background(), container.ListOptions{}) + if err != nil { + fmt.Println("ERROR: Docker not found.") + fmt.Println("Download from https://docs.docker.com/get-docker/") + fmt.Println("If it's already installed, check if the docker daemon is running") + return err + } + fmt.Println(" - Docker is running") + return nil +} + +func CheckPodman() error { + fmt.Println("✅ Checking if Podman is available...") + dockerCheck := ExecCommand("podman", "stats", "--no-stream") + if err := dockerCheck.Run(); err != nil { + fmt.Println("ERROR: Podman not found.") + fmt.Println("Download from https://docs.podman.io/en/latest/") + fmt.Println("If it's already installed, check if the podman daemon is running") + return err + } + + fmt.Println(" - Podman is running") + return nil +} + +func parseJavaVersion(version string) (int64, error) { + dotVersion := strings.Split(strings.Split(version, "\"")[1], ".") + intVersion, err := strconv.ParseInt(dotVersion[0], 10, 8) + if err != nil { + return 0, err + } + return intVersion, nil +} + +func parseMavenVersion(version string) (int64, int64, error) { + stringVersion := strings.Split(version, " ")[2] + dotVersion := strings.Split(stringVersion, ".") + majorVersion, err := strconv.ParseInt(dotVersion[0], 10, 8) + if err != nil { + return 0, 0, err + } + minorVersion, err := strconv.ParseInt(dotVersion[1], 10, 8) + if err != nil { + return 0, 0, err + } + return majorVersion, minorVersion, nil +} + +func CheckIfDirExists(dirName string) (bool, error) { + _, err := FS.Stat(fmt.Sprintf("./%s", dirName)) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +func IsQuarkusSonataFlowProject() bool { + if fileExists("pom.xml") { + return true + } + return false +} + +func IsSonataFlowProject() bool { + if anyFileExists("*.sw.*") { + return true + } + return false +} + +func fileExists(filename string) bool { + _, err := os.Stat(filename) + return !os.IsNotExist(err) +} + +func anyFileExists(extension string) bool { + matches, err := filepath.Glob(extension) + if err != nil { + return false + } + + if len(matches) > 0 { + return true + } + return false +} + +func CheckProjectName(name string) (err error) { + matched, err := regexp.MatchString(`^([_\-\.a-zA-Z0-9]+)$`, name) + if !matched { + fmt.Printf("The project name (\"%s\") contains invalid characters. Valid characters are alphanumeric (A-Za-z), underscore, dash and dot.", name) + err = fmt.Errorf("invalid project name") + + } + return +} + +func IsValidProfile(profile string) error { + var allProfiles = []apiMetadata.ProfileType{ + apiMetadata.DevProfile, + apiMetadata.PreviewProfile, + apiMetadata.GitOpsProfile, + } + + for _, t := range allProfiles { + if t.String() == profile { + return nil + } + } + keys := make([]string, 0, len(allProfiles)) + for k := range allProfiles { + keys = append(keys, allProfiles[k].String()) + } + return fmt.Errorf("❌ ERROR: invalid profile: %s, valid profiles are: %s", profile, strings.Join(keys, ",")) +} diff --git a/cli/pkg/common/containers.go b/cli/pkg/common/containers.go new file mode 100644 index 00000000..651b59ab --- /dev/null +++ b/cli/pkg/common/containers.go @@ -0,0 +1,434 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "github.com/distribution/reference" + "github.com/docker/docker/api/types/image" + "io" + "os" + "os/exec" + "os/signal" + "runtime" + "strings" + "syscall" + "time" + + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "github.com/docker/docker/pkg/stdcopy" + "github.com/docker/go-connections/nat" +) + +const ( + Docker = "docker" + Podman = "podman" +) + +type DockerLogMessage struct { + Status string `json:"status,omitempty"` + ID string `json:"id,omitempty"` +} + +type DockerClient interface { + ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) +} + +func getDockerClient() (*client.Client, error) { + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return nil, fmt.Errorf("failed to create Docker client: %s", err) + } + return cli, nil +} + +func GetContainerID(containerTool string) (string, error) { + + switch containerTool { + case Podman: + return getPodmanContainerID() + case Docker: + return getDockerContainerID() + default: + return "", fmt.Errorf("no matching container type found") + } +} + +func getPodmanContainerID() (string, error) { + cmd := exec.Command("podman", + "ps", + "-a", + "--filter", + fmt.Sprintf("ancestor=%s", metadata.DevModeImage), + "--filter", + "status=running", + "--format", "{{.ID}}") + fmt.Println(cmd) + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("error getting podman container id: %w", err) + } + containerID := strings.TrimSpace(string(output)) + return containerID, nil +} + +func getDockerContainerID() (string, error) { + cli, err := getDockerClient() + if err != nil { + return "", err + } + + containers, err := cli.ContainerList(context.Background(), container.ListOptions{}) + if err != nil { + return "", err + } + + for _, c := range containers { + // Check if the container has the expected image name or other identifying information + if strings.Contains(c.Image, metadata.DevModeImage) { + return c.ID, nil + } + } + + return "", fmt.Errorf("no matching container found") +} + +func StopContainer(containerTool string, containerID string) error { + if containerTool == Podman { + stopCmd := exec.Command(containerTool, "stop", containerID) + if err := stopCmd.Run(); err != nil { + fmt.Printf("Unable to stop container %s: %s", containerID, err) + return err + } + } else if containerTool == Docker { + cli, err := getDockerClient() + if err != nil { + fmt.Printf("unable to create client for docker") + return err + } + if err := cli.ContainerStop(context.Background(), containerID, container.StopOptions{}); err != nil { + fmt.Printf("Unable to stop container %s: %s", containerID, err) + return err + } + } else { + return errors.New(fmt.Sprintf("The specified containerTool:%s does not exist", containerTool)) + } + fmt.Printf("\n🛑 Container %s stopped successfully.\n", containerID) + return nil +} + +func resolveVolumeBindPath(containerTool string) string { + if containerTool == "podman" && runtime.GOOS == "linux" { + return metadata.VolumeBindPathSELinux + } + return metadata.VolumeBindPath +} + +func RunContainerCommand(containerTool string, portMapping string, path string) error { + volumeBindPath := resolveVolumeBindPath(containerTool) + fmt.Printf("🔎 Warming up SonataFlow containers (%s), this could take some time...\n", metadata.DevModeImage) + if containerTool == Podman { + c := exec.Command( + containerTool, + "run", + "--rm", + "-p", + fmt.Sprintf("%s:8080", portMapping), + "-v", + fmt.Sprintf("%s:%s", path, volumeBindPath), + fmt.Sprintf("%s", metadata.DevModeImage), + ) + if err := RunCommand( + c, + "container run", + ); err != nil { + return err + } + } else if containerTool == Docker { + if err := runDockerContainer(portMapping, path); err != nil { + return err + } + } else { + return errors.New(fmt.Sprintf("The specified containerTool:%s does not exist", containerTool)) + } + return nil +} + +func GracefullyStopTheContainerWhenInterrupted(containerTool string) { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + + go func() { + <-c // Wait for the interrupt signal + containerID, err := GetContainerID(containerTool) + if err != nil { + fmt.Printf("\nerror getting container id: %v\n", err) + os.Exit(1) // Exit the program with error + } + + fmt.Println("\n🔨 Stopping the container id: " + containerID) + if containerID != "" { + err := StopContainer(containerTool, containerID) + if err != nil { + fmt.Println("❌ ERROR: Error stopping container id: " + containerID) + os.Exit(1) + } else { + fmt.Println("🎉 Successfully stopped container id: " + containerID) + } + } + + os.Exit(0) // Exit the program gracefully + }() +} + +func pullDockerImage(cli *client.Client, ctx context.Context) error { + // Check if the image exists locally. + exists, err := CheckImageExists(cli, ctx, metadata.DevModeImage) + if err != nil { + return fmt.Errorf("error listing images: %s", err) + } + + // If the image is not found locally, pull it from the remote registry + if !exists { + fmt.Printf("\n⏳ Retrieving (%s), this could take some time...\n", metadata.DevModeImage) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) + defer cancel() + + reader, writer := io.Pipe() + defer writer.Close() + + var stderr bytes.Buffer + + go func() { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + fmt.Print(".") + } + }() + + // we use local docker client to pull the image + cmd := exec.CommandContext(ctx, "docker", "pull", metadata.DevModeImage) + cmd.Stdout = writer + cmd.Stderr = &stderr + + if err := cmd.Start(); err != nil { + return fmt.Errorf("\nError pulling image: %s. Error is: %s", metadata.DevModeImage, err) + } + + if err := cmd.Wait(); err != nil { + return fmt.Errorf("\nError pulling image: %s. Error is: %s", metadata.DevModeImage, stderr.String()) + } + fmt.Println("\n🎉 Successfully pulled the image") + } + + return nil +} + +func CheckImageExists(cli DockerClient, ctx context.Context, imageName string) (bool, error) { + named, err := reference.ParseNormalizedNamed(imageName) + + if tagged, ok := named.(reference.Tagged); ok { + imageName = fmt.Sprintf("%s:%s", reference.Path(named), tagged.Tag()) + } else { + imageName = fmt.Sprintf("%s:%s", reference.Path(named), "latest") + } + images, err := cli.ImageList(ctx, image.ListOptions{All: true}) + if err != nil { + return false, fmt.Errorf("error listing images: %s", err) + } + + for _, i := range images { + for _, tag := range i.RepoTags { + if strings.HasSuffix(tag, imageName) { + return true, nil + } + } + } + return false, nil +} + +func processDockerImagePullLogs(reader io.ReadCloser) error { + for { + err := waitToImageBeReady(reader) + if err == io.EOF { + break + } else if err != nil { + return fmt.Errorf("error decoding ImagePull JSON: %s", err) + } + } + return nil +} + +func waitToImageBeReady(reader io.ReadCloser) error { + var message DockerLogMessage + decoder := json.NewDecoder(reader) + if err := decoder.Decode(&message); err != nil { + return err + } + if message.Status != "" { + fmt.Print(".") + } + + return nil +} + +func createDockerContainer(cli *client.Client, ctx context.Context, portMapping string, path string) (container.CreateResponse, error) { + containerConfig := &container.Config{ + Image: metadata.DevModeImage, + } + hostConfig := &container.HostConfig{ + AutoRemove: true, + PortBindings: nat.PortMap{ + metadata.DockerInternalPort: []nat.PortBinding{ + { + HostIP: "0.0.0.0", + HostPort: portMapping, + }, + }, + }, + Binds: []string{ + fmt.Sprintf("%s:%s", path, metadata.VolumeBindPath), + }, + } + + resp, err := cli.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, "") + if err != nil { + return resp, fmt.Errorf("\nUnable to create container %s: %s", metadata.DevModeImage, err) + } + return resp, nil +} + +func startDockerContainer(cli *client.Client, ctx context.Context, resp container.CreateResponse) error { + fmt.Printf("\nCreated container with ID %s", resp.ID) + fmt.Println("\n⏳ Starting your container and SonataFlow project...") + + if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { + return fmt.Errorf("\nUnable to start container %s", resp.ID) + } + + return nil +} + +func runDockerContainer(portMapping string, path string) error { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + + cli, err := getDockerClient() + if err != nil { + return err + } + err = pullDockerImage(cli, ctx) + if err != nil { + return err + } + + resp, err := createDockerContainer(cli, ctx, portMapping, path) + if err != nil { + return err + } + + if err := startDockerContainer(cli, ctx, resp); err != nil { + return err + } + + if err := processOutputDuringContainerExecution(cli, ctx, resp); err != nil { + return err + } + + return nil +} +func processOutputDuringContainerExecution(cli *client.Client, ctx context.Context, resp container.CreateResponse) error { + statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) + + //Print all container logs + out, err := cli.ContainerLogs(ctx, resp.ID, container.LogsOptions{ShowStdout: false, ShowStderr: true, Follow: true}) + if err != nil { + return fmt.Errorf("\nError getting container logs: %s", err) + } + + go func() { + _, err := stdcopy.StdCopy(os.Stdout, os.Stderr, out) + if err != nil { + fmt.Errorf("\nError copying container logs to stdout: %s", err) + } + }() + + select { + case err := <-errCh: + if err != nil { + return fmt.Errorf("\nError starting the container %s: %s", resp.ID, err) + } + case <-statusCh: + //state of the container matches the condition, in our case WaitConditionNotRunning + } + + return nil +} + +func PollContainerStoppedCheck(containerID string, interval time.Duration, ready chan<- bool) { + for { + running, err := IsContainerRunning(containerID) + if err != nil { + fmt.Printf("Error checking if container %s is running: %s", containerID, err) + ready <- false + return + } + if !running { + ready <- true + return + } + time.Sleep(interval) + } +} + +func IsContainerRunning(containerID string) (bool, error) { + if errDocker := CheckDocker(); errDocker == nil { + cli, err := getDockerClient() + if err != nil { + return false, fmt.Errorf("unable to create docker client: %w", err) + } + containerJSON, err := cli.ContainerInspect(context.Background(), containerID) + if err != nil { + if client.IsErrNotFound(err) { + return false, nil + } + return false, fmt.Errorf("unable to inspect container %s with docker: %w", containerID, err) + } + return containerJSON.State.Running, nil + + } else if errPodman := CheckPodman(); errPodman == nil { + cmd := exec.Command("podman", "inspect", containerID, "--format", "{{.State.Running}}") + output, err := cmd.Output() + if err != nil { + return false, fmt.Errorf("unable to inspect container %s with podman: %w", containerID, err) + } + return strings.TrimSpace(string(output)) == "true", nil + } + + return false, fmt.Errorf("there is no docker or podman available") +} diff --git a/cli/pkg/common/containers_test.go b/cli/pkg/common/containers_test.go new file mode 100644 index 00000000..9474e43f --- /dev/null +++ b/cli/pkg/common/containers_test.go @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "context" + "github.com/docker/docker/api/types/image" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "testing" +) + +type MockDockerClient struct { + mock.Mock +} + +func (m *MockDockerClient) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) { + args := m.Called(ctx, options) + return args.Get(0).([]image.Summary), args.Error(1) +} + +func TestCheckImageExists(t *testing.T) { + + tests := []struct { + lookup string + images []string + expected bool + }{ + {"docker.io/example/app-image:latest", []string{"docker.io/example/app-image:latest"}, true}, + {"docker.io/demo/service-image:1.0", []string{"demo/service-image:1.0"}, true}, + + {"docker.io/testuser/sample-app", []string{"docker.io/testuser/sample-app:latest"}, true}, + {"docker.io/testuser/sample-app", []string{"testuser/sample-app:latest"}, true}, + + {"testuser/sample-app:dev", []string{"docker.io/testuser/sample-app:dev"}, true}, + {"testuser/sample-app:dev", []string{"testuser/sample-app:dev"}, true}, + + {"docker.io/example/app-image:latest", []string{"app-image:latest"}, false}, + {"docker.io/testuser/sample-app", []string{"sample-app:latest"}, false}, + {"testuser/sample-app:dev", []string{"sample-app:dev"}, false}, + } + + for _, test := range tests { + ctx := context.Background() + mockClient := new(MockDockerClient) + + mockClient.On("ImageList", ctx, mock.Anything).Return([]image.Summary{ + { + RepoTags: test.images, + }, + }, nil) + + exists, err := CheckImageExists(mockClient, ctx, test.lookup) + assert.NoError(t, err, "Error should be nil") + assert.True(t, exists == test.expected, "Expected %t, got %t", test.expected, exists) + } +} diff --git a/cli/pkg/common/create_workflow.go b/cli/pkg/common/create_workflow.go new file mode 100644 index 00000000..0dddd967 --- /dev/null +++ b/cli/pkg/common/create_workflow.go @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "encoding/json" + "fmt" + "github.com/spf13/afero" + "gopkg.in/yaml.v2" +) + +type WorkflowStates struct { + Name string `json:"name"` + Type string `json:"type"` + Data map[string]string `json:"data"` + End bool `json:"end"` +} + +type Workflow struct { + Id string `json:"id"` + Version string `json:"version"` + SpecVersion string `json:"specVersion" yaml:"specVersion"` + Name string `json:"name"` + Description string `json:"description"` + Start string `json:"start"` + States []WorkflowStates `json:"states"` +} + +func GetWorkflowTemplate(yamlWorkflow bool) (workflowByte []byte, err error) { + workflowStates := WorkflowStates{ + Name: "HelloWorld", + Type: "inject", + Data: map[string]string{ + "message": "Hello World", + }, + End: true, + } + + workflow := Workflow{ + Id: "hello", + Version: "1.0", + SpecVersion: "0.8.0", + Name: "Hello World", + Description: "Description", + Start: "HelloWorld", + States: []WorkflowStates{workflowStates}, + } + + if yamlWorkflow { + workflowByte, err = yaml.Marshal(workflow) + if err != nil { + return nil, fmt.Errorf("error marshaling the workflow file. %w", err) + } + } else { + workflowByte, err = json.MarshalIndent(workflow, "", " ") + if err != nil { + return nil, fmt.Errorf("error marshaling the workflow file. %w", err) + } + } + + return workflowByte, nil +} + +func CreateWorkflow(workflowFilePath string, yamlWorkflow bool) (err error) { + + workflowByte, err := GetWorkflowTemplate(yamlWorkflow) + err = afero.WriteFile(FS, workflowFilePath, workflowByte, 0644) + if err != nil { + return fmt.Errorf("error writing the workflow file: %w", err) + } + + fmt.Printf("Workflow file created at %s \n", workflowFilePath) + return nil +} diff --git a/cli/pkg/common/create_workflow_test.go b/cli/pkg/common/create_workflow_test.go new file mode 100644 index 00000000..1984dfb2 --- /dev/null +++ b/cli/pkg/common/create_workflow_test.go @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "testing" + + "github.com/spf13/afero" +) + +func TestCreateWrokflow(t *testing.T) { + var err error + filePath := "new-workflow.sw.json" + FS = afero.NewMemMapFs() + + err = CreateWorkflow(filePath, false) + defer FS.Remove(filePath) + if err != nil { + t.Errorf("Error when creating workflow: %#v", err) + } + + _, err = FS.Stat(filePath) + if err != nil { + t.Errorf("Error when opening workflow file: %#v", err) + } +} diff --git a/cli/pkg/common/exec.go b/cli/pkg/common/exec.go new file mode 100644 index 00000000..5b0773db --- /dev/null +++ b/cli/pkg/common/exec.go @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import "os/exec" + +var ExecCommand = exec.Command // Make it a global var, so it can be override in tests diff --git a/cli/pkg/common/fs/is_hidden_unix.go b/cli/pkg/common/fs/is_hidden_unix.go new file mode 100644 index 00000000..7d8b9903 --- /dev/null +++ b/cli/pkg/common/fs/is_hidden_unix.go @@ -0,0 +1,32 @@ +//go:build !windows +// +build !windows + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package fsutils + +import ( + "os" + "strings" +) + +func IsHidden(info os.FileInfo, path string) bool { + return strings.HasPrefix(info.Name(), ".") +} diff --git a/cli/pkg/common/fs/is_hidden_windows.go b/cli/pkg/common/fs/is_hidden_windows.go new file mode 100644 index 00000000..40474387 --- /dev/null +++ b/cli/pkg/common/fs/is_hidden_windows.go @@ -0,0 +1,40 @@ +//go:build windows +// +build windows + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package fsutils + +import ( + "golang.org/x/sys/windows" + "os" +) + +func IsHidden(info os.FileInfo, path string) bool { + p, err := windows.UTF16PtrFromString(path) + if err != nil { + return false + } + attrs, err := windows.GetFileAttributes(p) + if err != nil { + return false + } + return attrs&windows.FILE_ATTRIBUTE_HIDDEN != 0 +} diff --git a/cli/pkg/common/helper.go b/cli/pkg/common/helper.go new file mode 100644 index 00000000..753fa985 --- /dev/null +++ b/cli/pkg/common/helper.go @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "bytes" + "fmt" + "github.com/spf13/cobra" + "html/template" + "io" + "os" + "os/exec" +) + +func RunCommand(command *exec.Cmd, commandName string) error { + var stdoutBuf, stderrBuf bytes.Buffer + + command.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) + command.Stderr = io.MultiWriter(os.Stderr, &stderrBuf) + + if err := command.Start(); err != nil { + fmt.Printf("ERROR: starting command \"%s\" failed\n", commandName) + return err + } + + if err := command.Wait(); err != nil { + fmt.Printf("ERROR: something went wrong during command \"%s\"\n", commandName) + return err + } + + return nil +} + +func RunExtensionCommand(extensionCommand string, extensions string) error { + command := ExecCommand("mvn", extensionCommand, fmt.Sprintf("-Dextensions=%s", extensions)) + if err := RunCommand(command, extensionCommand); err != nil { + fmt.Println("ERROR: It wasn't possible to add Quarkus extension in your pom.xml.") + return err + } + return nil +} + +func GetTemplate(cmd *cobra.Command, name string) *template.Template { + var ( + body = cmd.Long + "\n\n" + cmd.UsageString() + t = template.New(name) + tpl = template.Must(t.Parse(body)) + ) + return tpl +} + +func DefaultTemplatedHelp(cmd *cobra.Command, args []string) { + tpl := GetTemplate(cmd, "help") + var data = struct{ Name string }{Name: cmd.Root().Use} + + if err := tpl.Execute(cmd.OutOrStdout(), data); err != nil { + fmt.Fprintf(cmd.ErrOrStderr(), "unable to display help text: %v", err) + } +} diff --git a/cli/pkg/common/io.go b/cli/pkg/common/io.go new file mode 100644 index 00000000..cb4a1b18 --- /dev/null +++ b/cli/pkg/common/io.go @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "io" + "os" + "path/filepath" + "strings" +) + +var WorkflowExtensionsType = []string{metadata.YAMLSWExtension, metadata.YMLSWExtension, metadata.JSONSWExtension} + + +func FindFilesWithExtensions(directoryPath string, extensions []string) ([]string, error) { + filePaths := []string{} + + _, err := os.Stat(directoryPath) + if os.IsNotExist(err) { + return filePaths, nil + } else if err != nil { + return nil, fmt.Errorf("❌ ERROR: failed to access directory: %s", err) + } + + files, err := os.ReadDir(directoryPath) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: failed to read directory: %s", err) + } + + for _, file := range files { + if file.IsDir() { + continue + } + + filename := file.Name() + for _, ext := range extensions { + if strings.HasSuffix(strings.ToLower(filename), strings.ToLower(ext)) { + filePath := filepath.Join(directoryPath, filename) + filePaths = append(filePaths, filePath) + break + } + } + } + + return filePaths, nil +} + +func FindSonataFlowFileByDefaultExtensions() (string, error) { + return FindSonataFlowFile(WorkflowExtensionsType) +} + +func FindSonataFlowFile(extensions []string) (string, error) { + dir, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("❌ ERROR: failed to get current directory: %w", err) + } + + matchingFiles := FindSonataFlowFiles(dir, extensions) + + switch len(matchingFiles) { + case 0: + return "", fmt.Errorf("❌ ERROR: no matching files found") + case 1: + return matchingFiles[0], nil + default: + return "", fmt.Errorf("❌ ERROR: multiple SonataFlow definition files found") + } +} + +func FindSonataFlowFiles(dir string, extensions []string) []string { + var matchingFiles []string + for _, ext := range extensions { + files, _ := filepath.Glob(filepath.Join(dir, "*."+ext)) + matchingFiles = append(matchingFiles, files...) + } + return matchingFiles +} + +func MustGetFile(filepath string) (io.Reader, error) { + file, err := os.OpenFile(filepath, os.O_RDONLY, os.ModePerm) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: failed to read file: %s", err) + } + return file, nil +} diff --git a/cli/pkg/common/k8sclient/errors.go b/cli/pkg/common/k8sclient/errors.go new file mode 100644 index 00000000..c8ebf141 --- /dev/null +++ b/cli/pkg/common/k8sclient/errors.go @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package k8sclient + +type NoDeploymentFoundError string + +func (e NoDeploymentFoundError) Error() string { + return string(e) +} + +const ( + NoDeploymentFound = NoDeploymentFoundError("No deployment found") +) \ No newline at end of file diff --git a/cli/pkg/common/k8sclient/fake.go b/cli/pkg/common/k8sclient/fake.go new file mode 100644 index 00000000..7e3903ce --- /dev/null +++ b/cli/pkg/common/k8sclient/fake.go @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package k8sclient + +import ( + "fmt" + "github.com/spf13/afero" + v1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/fake" + "strings" +) + +type Fake struct { + FS afero.Fs +} + +var currentDynamicClient = initDynamicClient() + +func initDynamicClient() dynamic.Interface { + scheme := runtime.NewScheme() + fakeDynamicClient := fake.NewSimpleDynamicClient(scheme) + return fakeDynamicClient +} + +func (m Fake) FakeDynamicClient() (dynamic.Interface, error) { + return currentDynamicClient, nil +} + +func (m Fake) GetCurrentNamespace() (string, error) { + return "default", nil +} + +func (m Fake) FakeParseYamlFile(path string) ([]unstructured.Unstructured, error) { + data, err := afero.ReadFile(m.FS, path) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to read YAML file: %w", err) + } + decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(string(data)), 4096) + result := []unstructured.Unstructured{} + for { + rawObj := &unstructured.Unstructured{} + err := decoder.Decode(rawObj) + if err != nil { + break + } + result = append(result, *rawObj) + } + return result, nil +} + +func (m Fake) GetDeploymentStatus(namespace, deploymentName string) (v1.DeploymentStatus, error) { + return v1.DeploymentStatus{}, nil +} + +func (m Fake) PortForward(namespace, serviceName, portFrom, portTo string, onReady func()) error { + return nil +} diff --git a/cli/pkg/common/k8sclient/goapi.go b/cli/pkg/common/k8sclient/goapi.go new file mode 100644 index 00000000..ca989d6a --- /dev/null +++ b/cli/pkg/common/k8sclient/goapi.go @@ -0,0 +1,525 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package k8sclient + +import ( + "context" + "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + "strings" + + v1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/yaml" + + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/client-go/tools/portforward" + "k8s.io/client-go/transport/spdy" +) + +type GoAPI struct{} + +var selfSubjectAccessGVR = schema.GroupVersionResource{ + Group: "authorization.k8s.io", + Version: "v1", + Resource: "selfsubjectaccessreviews", +} + +var kubeconfigInfoDisplayed = false + +func (m GoAPI) IsCreateAllowed(resourcePath string, namespace string) (bool, error) { + dynamicClient, err := DynamicClient() + if err != nil { + return false, fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes client: %v", err) + } + + if resources, err := ParseYamlFile(resourcePath); err != nil { + return false, fmt.Errorf("❌ ERROR: Failed to parse YAML file: %v", err) + } else { + for _, resource := range resources { + sar := parseResource(resource, namespace) + + result, err := dynamicClient.Resource(selfSubjectAccessGVR).Create(context.TODO(), sar, metav1.CreateOptions{}) + if err != nil { + return false, fmt.Errorf("failed to perform access review: %v", err) + } + + allowed, _, _ := unstructured.NestedBool(result.Object, "status", "allowed") + return allowed, nil + } + } + return false, nil +} + +func (m GoAPI) IsDeleteAllowed(name string, namespace string) error { + dynamicClient, err := DynamicClient() + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes client: %v", err) + } + + err = dynamicClient.Resource(selfSubjectAccessGVR).Namespace(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) + if err != nil { + return fmt.Errorf("failed to perform access review: %v", err) + } + return nil +} + +func (m GoAPI) GetCurrentNamespace() (string, error) { + return GetCurrentNamespace() +} + +func (m GoAPI) GetNamespace(namespace string) (*corev1.Namespace, error) { + config, err := KubeRestConfig() + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to create rest config for Kubernetes client: %v", err) + } + + clientSet, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to create k8s client: %v", err) + } + ns, err := clientSet.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to get namespace: %v", err) + } + return ns, nil +} + +func (m GoAPI) CheckContext() (string, error) { + config, err := KubeApiConfig() + if err != nil { + return "", fmt.Errorf("❌ ERROR: No current k8s context found %w", err) + } + context := config.CurrentContext + if context == "" { + return "", fmt.Errorf("❌ ERROR: No current k8s context found") + } + fmt.Printf(" - ✅ k8s current context: %s\n", context) + return context, nil +} + +func (m GoAPI) ExecuteApply(path, namespace string) error { + client, err := DynamicClient() + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes client: %v", err) + } + fmt.Printf("🔨 Applying YAML file %s\n", path) + + if namespace == "" { + currentNamespace, err := m.GetCurrentNamespace() + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to get current namespace: %w", err) + } + namespace = currentNamespace + } + + if resources, err := ParseYamlFile(path); err != nil { + return fmt.Errorf("❌ ERROR: Failed to parse YAML file: %v", err) + } else { + created := make([]unstructured.Unstructured, 0, len(resources)) + for _, resource := range resources { + gvk := resource.GroupVersionKind() + gvr, _ := meta.UnsafeGuessKindToResource(gvk) + + if resource.GetNamespace() != "" && namespace != resource.GetNamespace() { + return fmt.Errorf("❌ ERROR: the namespace from the provided object \"%s\" does not match"+ + " the namespace \"%s\". You must pass '--namespace=%s' to perform this operation.:", + resource.GetNamespace(), namespace, resource.GetNamespace()) + } + + _, err := client.Resource(gvr).Namespace(namespace).Create(context.Background(), &resource, metav1.CreateOptions{}) + if err != nil { + if errors.IsAlreadyExists(err) { + existingResource, err := client.Resource(gvr).Namespace(namespace).Get(context.Background(), resource.GetName(), metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to get existing resource: %v", err) + } + resource.SetResourceVersion(existingResource.GetResourceVersion()) + _, err = client.Resource(gvr).Namespace(namespace).Update(context.Background(), &resource, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to update resource: %v", err) + } + } else { + // rollback + if err := doRollback(created, namespace, client); err != nil { + return fmt.Errorf("❌ ERROR: Failed to rollback resource: %v", err) + } + return fmt.Errorf("❌ ERROR: Failed to create resource: %v", err) + } + } + created = append(created, resource) + } + } + return nil +} + +func (m GoAPI) ExecuteCreate(gvr schema.GroupVersionResource, object *unstructured.Unstructured, namespace string) (*unstructured.Unstructured, error) { + dynamicClient, err := DynamicClient() + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes client: %v", err) + } + resulted, err := dynamicClient.Resource(gvr).Namespace(namespace).Create(context.Background(), object, metav1.CreateOptions{}) + if err != nil { + if errors.IsAlreadyExists(err) { + fmt.Printf("✅ Resource %q already exists\n", object.GetName()) + } + return nil, fmt.Errorf("❌ Failed to create resource: %v", err) + } + + return resulted, nil +} + +func (m GoAPI) ExecuteGet(gvr schema.GroupVersionResource, name string, namespace string) (*unstructured.Unstructured, error) { + dynamicClient, err := DynamicClient() + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes client: %v", err) + } + + return dynamicClient.Resource(gvr).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{}) +} + +func (m GoAPI) ExecuteDelete(path, namespace string) error { + fmt.Println("🔨 Deleting resources...", path) + + if namespace == "" { + currentNamespace, err := m.GetCurrentNamespace() + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to get current namespace: %w", err) + } + namespace = currentNamespace + } + + if resources, err := ParseYamlFile(path); err != nil { + return fmt.Errorf("❌ ERROR: Failed to parse YAML file: %v", err) + } else { + for _, resource := range resources { + gvk := resource.GroupVersionKind() + gvr, _ := meta.UnsafeGuessKindToResource(gvk) + + err := m.ExecuteDeleteGVR(gvr, resource.GetName(), namespace) + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to delete resource: %v", err) + } + } + } + return nil +} + +func (m GoAPI) ExecuteDeleteGVR(gvr schema.GroupVersionResource, name string, namespace string) error { + client, err := DynamicClient() + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes client: %v", err) + } + + deletePolicy := metav1.DeletePropagationForeground + err = client.Resource(gvr).Namespace(namespace).Delete(context.Background(), name, metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }) + + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to delete Resource: %w", err) + } + return nil +} + +func (m GoAPI) CheckCrdExists(crd string) error { + config, err := KubeRestConfig() + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to create rest config for Kubernetes client: %v", err) + } + + crdClientSet, err := clientset.NewForConfig(config) + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to create k8s client: %v", err) + } + + _, err = crdClientSet.ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), crd, metav1.GetOptions{}) + if err != nil { + return err + } + + return nil +} + +func (m GoAPI) GetDeploymentStatus(namespace, deploymentName string) (v1.DeploymentStatus, error) { + if namespace == "" { + currentNamespace, err := m.GetCurrentNamespace() + if err != nil { + return v1.DeploymentStatus{}, fmt.Errorf("❌ ERROR: Failed to get current namespace: %w", err) + } + namespace = currentNamespace + } + + config, err := KubeRestConfig() + if err != nil { + return v1.DeploymentStatus{}, fmt.Errorf("❌ ERROR: Failed to create rest config for Kubernetes client: %v", err) + } + + newConfig, err := kubernetes.NewForConfig(config) + if err != nil { + return v1.DeploymentStatus{}, fmt.Errorf("❌ ERROR: Failed to create k8s client: %v", err) + } + deployments, err := newConfig.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: fmt.Sprintf("sonataflow.org/workflow-app=%s", deploymentName), + }) + + if err != nil { + return v1.DeploymentStatus{}, fmt.Errorf("❌ ERROR: Failed to get deployments: %v", err) + } + + if len(deployments.Items) == 0 { + return v1.DeploymentStatus{}, NoDeploymentFound + } + + if len(deployments.Items) > 1 { + return v1.DeploymentStatus{}, fmt.Errorf("❌ ERROR: More than one deployment named %s in namespace %s found", deploymentName, namespace) + } + + return deployments.Items[0].Status, nil +} + +func (m GoAPI) PortForward(namespace, serviceName, portFrom, portTo string, onReady func()) error { + if namespace == "" { + currentNamespace, err := m.GetCurrentNamespace() + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to get current namespace: %w", err) + } + namespace = currentNamespace + } + + config, err := KubeRestConfig() + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to create rest config for Kubernetes client: %v", err) + } + + clientSet, err := kubernetes.NewForConfig(config) + + service, err := clientSet.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to get service: %v", err) + } + + var labelSelector string + for key, value := range service.Spec.Selector { + if labelSelector != "" { + labelSelector += "," + } + labelSelector += fmt.Sprintf("%s=%s", key, value) + } + + pods, err := clientSet.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: labelSelector, + }) + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to get pods: %v", err) + } + + if len(pods.Items) == 0 { + return fmt.Errorf("❌ ERROR: No pods found for service %s in namespace %s", serviceName, namespace) + } + + req := clientSet.CoreV1().RESTClient().Post().Resource("pods").Namespace(pods.Items[0].Namespace). + Name(pods.Items[0].Name).SubResource("portforward") + + transport, upgrader, err := spdy.RoundTripperFor(config) + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to create round tripper: %v", err) + } + + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL()) + + errCh := make(chan error) + stopCh := make(chan struct{}) + readyCh := make(chan struct{}) + + ports := []string{fmt.Sprintf("%s:%s", portFrom, portTo)} + go func() { + forwardPorts, err := portforward.New(dialer, ports, stopCh, readyCh, io.Discard, os.Stderr) + if err != nil { + errCh <- err + } + err = forwardPorts.ForwardPorts() + if err != nil { + errCh <- err + } + }() + + select { + case <-readyCh: + onReady() + case err := <-errCh: + return fmt.Errorf("❌ Error starting port forwarding: %v\n", err) + } + <-stopCh + + return nil +} + +func (m GoAPI) ExecuteList(gvr schema.GroupVersionResource, namespace string) (*unstructured.UnstructuredList, error) { + client, err := DynamicClient() + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes client: %v", err) + } + + list, err := client.Resource(gvr).Namespace(namespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to list resources: %v", err) + } + + return list, nil +} + +func KubeApiConfig() (*api.Config, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("error getting user home dir: %w", err) + } + kubeConfigPath := filepath.Join(homeDir, ".kube", "config") + if !kubeconfigInfoDisplayed { + fmt.Printf("🔎 Using kubeconfig: %s\n", kubeConfigPath) + kubeconfigInfoDisplayed = true + } + config, err := clientcmd.LoadFromFile(kubeConfigPath) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to load kubeconfig: %w", err) + } + return config, nil +} + +func KubeRestConfig() (*rest.Config, error) { + config, err := rest.InClusterConfig() + if err != nil { + kubeConfig, err := KubeApiConfig() + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to load kubeconfig: %w", err) + } + clientConfig := clientcmd.NewDefaultClientConfig(*kubeConfig, &clientcmd.ConfigOverrides{}) + restConfig, err := clientConfig.ClientConfig() + if err != nil { + log.Fatalf("Error converting to rest.Config: %v", err) + } + return restConfig, nil + } + return config, nil +} + +var DynamicClient = func() (dynamic.Interface, error) { + config, err := KubeRestConfig() + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to create rest config for Kubernetes client: %v", err) + } + + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes client: %v", err) + } + + return dynamicClient, nil +} + +var ParseYamlFile = func(path string) ([]unstructured.Unstructured, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to read YAML file: %w", err) + } + decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(string(data)), 4096) + result := []unstructured.Unstructured{} + for { + rawObj := &unstructured.Unstructured{} + err := decoder.Decode(rawObj) + if err != nil { + break + } + result = append(result, *rawObj) + } + return result, nil +} + +var GetCurrentNamespace = func() (string, error) { + fmt.Println("🔎 Checking current namespace in k8s...") + + config, err := KubeApiConfig() + if err != nil { + return "", fmt.Errorf("❌ ERROR: Failed to get current k8s namespace: %w", err) + } + ctx := config.Contexts[config.CurrentContext] + + if ctx == nil { + return "", fmt.Errorf("❌ ERROR: No current k8s context found") + } + + namespace := ctx.Namespace + + if len(namespace) == 0 { + namespace = "default" + } + fmt.Printf(" - ✅ k8s current namespace: %s\n", namespace) + return namespace, nil +} + +func parseResource(resource unstructured.Unstructured, namespace string) *unstructured.Unstructured { + gvk := resource.GroupVersionKind() + gvr, _ := meta.UnsafeGuessKindToResource(gvk) + + sar := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "authorization.k8s.io/v1", + "kind": "SelfSubjectAccessReview", + "spec": map[string]interface{}{ + "resourceAttributes": map[string]interface{}{ + "namespace": namespace, + "verb": gvr.Version, + "group": gvk.Group, + "resource": gvr.Resource, + }, + }, + }, + } + return sar +} + +func doRollback(created []unstructured.Unstructured, applyNamespace string, client dynamic.Interface) error { + for _, r := range created { + gvk := r.GroupVersionKind() + gvr, _ := meta.UnsafeGuessKindToResource(gvk) + if r.GetNamespace() != "" { + applyNamespace = r.GetNamespace() + } + + if err := client.Resource(gvr).Namespace(applyNamespace).Delete(context.Background(), r.GetName(), metav1.DeleteOptions{}); err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("❌ ERROR: Failed to rollback resource: %v", err) + } + } + return nil +} diff --git a/cli/pkg/common/kubectl.go b/cli/pkg/common/kubectl.go new file mode 100644 index 00000000..8eaaebc4 --- /dev/null +++ b/cli/pkg/common/kubectl.go @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "github.com/kubesmarts/logic-operator/cli/pkg/common/k8sclient" + v1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type K8sApi interface { + + IsCreateAllowed(resourcePath string, namespace string) (bool, error) + IsDeleteAllowed(resourcePath string, namespace string) error + GetCurrentNamespace() (string, error) + GetNamespace(namespace string) (*corev1.Namespace, error) + CheckContext() (string, error) + ExecuteApply(path, namespace string) error + ExecuteCreate(gvr schema.GroupVersionResource, object *unstructured.Unstructured, namespace string) (*unstructured.Unstructured, error) + ExecuteDelete(path, namespace string) error + ExecuteDeleteGVR(gvr schema.GroupVersionResource, name string, namespace string) error + ExecuteGet(gvr schema.GroupVersionResource, name string, namespace string) (*unstructured.Unstructured, error) + ExecuteList(gvr schema.GroupVersionResource, namespace string) (*unstructured.UnstructuredList, error) + CheckCrdExists(path string) error + GetDeploymentStatus(namespace, deploymentName string) (v1.DeploymentStatus, error) + PortForward(namespace, serviceName, portFrom, portTo string, onReady func()) error +} + +var Current K8sApi = k8sclient.GoAPI{} + +func IsCreateAllowed(resourcePath string, namespace string) (bool, error) { + return Current.IsCreateAllowed(resourcePath, namespace) +} + +func IsDeleteAllowed(name string, namespace string) error { + return Current.IsDeleteAllowed(name, namespace) +} + +func CheckContext() (string, error) { + return Current.GetCurrentNamespace() +} + +func GetCurrentNamespace() (string, error) { + return Current.GetCurrentNamespace() +} + +func GetNamespace(namespace string) (*corev1.Namespace, error) { + return Current.GetNamespace(namespace) +} + +var ExecuteApply = func(path, namespace string) error { + return Current.ExecuteApply(path, namespace) +} + +func ExecuteCreate(gvr schema.GroupVersionResource, object *unstructured.Unstructured, namespace string) (*unstructured.Unstructured, error) { + return Current.ExecuteCreate(gvr, object, namespace) +} + +func ExecuteDelete(path, namespace string) error { + return Current.ExecuteDelete(path, namespace) +} + +func ExecuteDeleteGVR(gvr schema.GroupVersionResource, name string, namespace string) error { + return Current.ExecuteDeleteGVR(gvr, name, namespace) +} + +func ExecuteGet(gvr schema.GroupVersionResource, name string, namespace string) (*unstructured.Unstructured, error) { + return Current.ExecuteGet(gvr, name, namespace) +} + +func ExecuteList(gvr schema.GroupVersionResource, namespace string)(*unstructured.UnstructuredList, error) { + return Current.ExecuteList(gvr, namespace) +} + +func CheckCrdExists(crd string) error { + return Current.CheckCrdExists(crd) +} + +func GetDeploymentStatus(namespace, deploymentName string) (v1.DeploymentStatus, error) { + return Current.GetDeploymentStatus(namespace, deploymentName) +} + +func PortForward(namespace, deploymentName, portFrom, portTo string, onReady func()) error { + return Current.PortForward(namespace, deploymentName, portFrom, portTo, onReady) +} diff --git a/cli/pkg/common/operator.go b/cli/pkg/common/operator.go new file mode 100644 index 00000000..ed47035b --- /dev/null +++ b/cli/pkg/common/operator.go @@ -0,0 +1,454 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "context" + "fmt" + "io" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" + "os" + "path/filepath" + "strings" + + "github.com/kubesmarts/logic-operator/cli/pkg/common/k8sclient" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type Document struct { + Kind string `yaml:"kind"` + Metadata struct { + Name string `yaml:"name"` + } `yaml:"metadata"` +} + +var namespacesGVR = schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "namespaces", +} + +var sonataflowGVR = schema.GroupVersionResource{ + Group: "sonataflow.org", + Version: "v1alpha08", + Resource: "sonataflows", +} + +var subscriptionsGVR = schema.GroupVersionResource{ + Group: "operators.coreos.com", + Version: "v1alpha1", + Resource: "subscriptions", +} + +var clusterServiceVersionsGVR = schema.GroupVersionResource{ + Group: "operators.coreos.com", + Version: "v1alpha1", + Resource: "clusterserviceversions", +} + +var customResourceDefinitionsGVR = schema.GroupVersionResource{ + Group: "apiextensions.k8s.io", + Version: "v1", + Resource: "customresourcedefinitions", +} + +var catalogSourcesGVR = schema.GroupVersionResource{ + Group: "operators.coreos.com", + Version: "v1alpha1", + Resource: "catalogsources", +} + +type OperatorManager struct { + namespace string + isOpenshift bool + dynamicClient dynamic.Interface +} + +var openshiftOperatorNamespaces = []string{"openshift-operators", "community-operators"} + +func NewOperatorManager(namespace string) *OperatorManager { + isOpenshift, err := isOpenshift() + if err != nil { + fmt.Printf("❌ ERROR: %v\n", err) + os.Exit(1) + } + + if namespace == "" { + namespace, err = guessOperatorNamespace(isOpenshift) + if err != nil { + fmt.Printf("❌ ERROR: %v\n", err) + os.Exit(1) + } + } + + dynamicClient, err := k8sclient.DynamicClient() + if err != nil { + fmt.Printf("❌ ERROR: %v\n", err) + os.Exit(1) + } + + return &OperatorManager{ + namespace: namespace, + isOpenshift: isOpenshift, + dynamicClient: dynamicClient, + } +} + +func checkOperatorRunning(getPodsOutPut string) bool { + pods := strings.Split(getPodsOutPut, "\n") + for _, pod := range pods { + // Split each line into fields (NAME, READY, STATUS, RESTARTS, AGE) + fields := strings.Fields(pod) + + // Check if this line contains information about the desired operator manager pod + if len(fields) > 2 && strings.HasPrefix(fields[0], metadata.OperatorManagerPod) && fields[2] == "Running" { + return true + } + } + return false +} + +func FindServiceFiles(directory string) ([]string, error) { + var serviceFiles []string + + err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("❌ ERROR: failure accessing a path %q: %v\n", path, err) + } + + if info.IsDir() || filepath.Ext(path) != ".yaml" { + return nil + } + + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("❌ ERROR: failure opening file %q: %v\n", path, err) + } + defer file.Close() + + byteValue, err := io.ReadAll(file) + if err != nil { + return fmt.Errorf("❌ ERROR: failure reading file %q: %v\n", path, err) + } + + var doc Document + if err := yaml.Unmarshal(byteValue, &doc); err != nil { + return fmt.Errorf("❌ ERROR: failure unmarshalling YAML from file %q: %v\n", path, err) + } + + if doc.Kind == metadata.ManifestServiceFilesKind { + serviceFiles = append(serviceFiles, path) + } + + return nil + }) + + if err != nil { + return nil, err + } + + return serviceFiles, nil +} + +func (m OperatorManager) CheckOLMInstalled() error { + resources, err := m.dynamicClient.Resource(catalogSourcesGVR).List(context.Background(), v1.ListOptions{}) + if err != nil { + return fmt.Errorf("❌ ERROR: OLM (Operator Lifecycle Manager) isn't installed, install OLM first : %v\n", err) + } + var operatorSources []string + + if m.isOpenshift { + operatorSources = append(operatorSources, openshiftOperatorNamespaces...) + } else { + operatorSources = append(operatorSources, "operatorhubio-catalog") + } + + for _, resource := range resources.Items { + name, found, err := unstructured.NestedString(resource.Object, "metadata", "name") + namespace, found, err := unstructured.NestedString(resource.Object, "metadata", "namespace") + if err != nil || !found { + continue + } + for _, operatorSource := range operatorSources { + if name == operatorSource && metadata.OLMCatalogSourcesMap[name] == namespace { + return nil + } + } + } + + return fmt.Errorf("❌ ERROR: OLM (Operator Lifecycle Manager) is not installed. Please install OLM before installing the SonataFlow Operator.") +} + +func (m OperatorManager) InstallSonataflowOperator() error { + var source string + var sourceNamespace string + + if m.isOpenshift { + source = "community-operators" + sourceNamespace = "openshift-marketplace" + } else { + source = "operatorhubio-catalog" + sourceNamespace = "olm" + } + + subscriptionUnstructured := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "operators.coreos.com/v1alpha1", + "kind": "Subscription", + "metadata": map[string]interface{}{ + "name": metadata.SonataFlowOperatorName, + "namespace": m.namespace, + }, + "spec": map[string]interface{}{ + "channel": "alpha", + "name": "sonataflow-operator", + "source": source, + "sourceNamespace": sourceNamespace, + }, + }, + } + + _, err := ExecuteCreate(subscriptionsGVR, subscriptionUnstructured, m.namespace) + if err != nil { + return fmt.Errorf("❌ Failed to create subscription: %v", err) + } + fmt.Println("✅ Subscription created successfully") + + return nil +} + +func (m OperatorManager) GetSonataflowOperatorStats() error { + subscription, err := ExecuteGet(subscriptionsGVR, metadata.SonataFlowOperatorName, m.namespace) + if err != nil { + return fmt.Errorf("❌ SonataFlow Operator is not installed.") + } + + status, found, err := unstructured.NestedMap(subscription.Object, "status") + if err != nil { + return fmt.Errorf("error getting status: %v", err) + } + if !found { + return fmt.Errorf("status not found in subscription") + } + + currentCSV, _, _ := unstructured.NestedString(status, "currentCSV") + installedCSV, _, _ := unstructured.NestedString(status, "installedCSV") + state, _, _ := unstructured.NestedString(status, "state") + + fmt.Println() + fmt.Println("📊 Subscription Status:") + fmt.Println() + + fmt.Printf("Current CSV: %s\n", currentCSV) + fmt.Printf("Installed CSV: %s\n", installedCSV) + fmt.Printf("State: %s\n", state) + + conditions, exists, _ := unstructured.NestedSlice(status, "conditions") + if exists { + fmt.Println() + fmt.Printf("Conditions:\n") + for _, c := range conditions { + condition := c.(map[string]interface{}) + fmt.Printf("Type: %s\n", condition["type"]) + fmt.Printf("Status: %s\n", condition["status"]) + fmt.Printf("Message: %s\n", condition["message"]) + fmt.Printf("Last Transition Time: %s\n\n", condition["lastTransitionTime"]) + } + } + + return nil +} + +func (m OperatorManager) RemoveSubscription() error { + fmt.Println("🔧 Deleting the SonataFlow Operator subscription...") + + err := ExecuteDeleteGVR(subscriptionsGVR, metadata.SonataFlowOperatorName, m.namespace) + if err != nil { + return fmt.Errorf("❌ Failed to delete subscription `sonataflow-operator` in namespace %s: %v\n", m.namespace, err) + } + fmt.Printf("✅ Subscription `sonataflow-operator` deleted successfully in namespace %s\n", m.namespace) + + return nil +} + +func (m OperatorManager) RemoveCSV() error { + resources, err := ExecuteList(clusterServiceVersionsGVR, m.namespace) + if err != nil { + return fmt.Errorf("❌ ERROR: failed to get CSV resources: %v", err) + } + for _, resource := range resources.Items { + name, found, err := unstructured.NestedString(resource.Object, "metadata", "name") + if err != nil || !found { + continue + } + if strings.HasPrefix(name, metadata.SonataFlowOperatorName) { + err := ExecuteDeleteGVR(clusterServiceVersionsGVR, name, m.namespace) + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to delete CSV `sonataflow-operator` in namespace %s: %v\n", m.namespace, err) + } + fmt.Printf("✅ CSV `sonataflow-operator` deleted successfully in namespace %s\n", m.namespace) + return nil + } + } + return fmt.Errorf("❌ ERROR: CSV `sonataflow-operator` not found in namespace %s\n", m.namespace) +} + +func (m OperatorManager) ListOperatorResources() ([]unstructured.Unstructured, error) { + resources, err := m.dynamicClient.Resource(clusterServiceVersionsGVR).List(context.Background(), v1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: Failed to list resources: %v", err) + } + + if len(resources.Items) == 0 { + return nil, fmt.Errorf("❌ No resources found") + } + var result []unstructured.Unstructured + for _, csv := range resources.Items { + if strings.HasPrefix(csv.GetName(), metadata.SonataFlowOperatorName) { + result = append(result, csv) + } + } + return result, nil +} + +func (m OperatorManager) RemoveCRD() error { + subscription, err := ExecuteGet(subscriptionsGVR, metadata.SonataFlowOperatorName, m.namespace) + if err != nil { + return fmt.Errorf("failed to get subscription %s: %v", metadata.SonataFlowOperatorName, err) + } + + installedCSV, found, err := unstructured.NestedString(subscription.Object, "status", "installedCSV") + if err != nil || !found { + return fmt.Errorf("failed to extract installedCSV from subscription: %v", err) + } + + csvObj, err := ExecuteGet(clusterServiceVersionsGVR, installedCSV, m.namespace) + if err != nil { + return fmt.Errorf("❌ Failed to get CSV %q in namespace %q: %v", installedCSV, m.namespace, err) + } + + ownedCRDs, found, err := unstructured.NestedSlice(csvObj.Object, + "spec", "customresourcedefinitions", "owned") + if err != nil { + return fmt.Errorf("❌ Failed to extract owned CRDs: %v", err) + } + if !found { + fmt.Println("✅ No owned CRDs found in CSV") + return nil + } + + for _, crdRaw := range ownedCRDs { + crdMap, ok := crdRaw.(map[string]interface{}) + if !ok { + continue + } + crdName, _, _ := unstructured.NestedString(crdMap, "name") + crdKind, _, _ := unstructured.NestedString(crdMap, "kind") + crdVersion, _, _ := unstructured.NestedString(crdMap, "version") + + fmt.Printf("✅ CRD Name: %s, Kind: %s, Version: %s\n", crdName, crdKind, crdVersion) + + err := ExecuteDeleteGVR(customResourceDefinitionsGVR, crdName, "") + if err != nil { + return fmt.Errorf("❌ ERROR: Failed to delete CRD %q: %v\n", crdName, err) + } + fmt.Printf("✅ CRD %q deleted\n", crdName) + } + return nil +} + +func (m OperatorManager) RemoveCR() error { + namespaces, err := ExecuteList(namespacesGVR, "") + if err != nil { + return fmt.Errorf("❌ Failed to list resources in namespace: %v", err) + } + + for _, ns := range namespaces.Items { + namespace := ns.GetName() + + sonataflows, err := ExecuteList(sonataflowGVR, namespace) + if err != nil { + if !errors.IsNotFound(err) && !errors.IsForbidden(err) { + return fmt.Errorf("❌ Error listing sonataflows in namespace %s: %v\n", namespace, err) + } + continue + } + + if len(sonataflows.Items) > 0 { + for _, deployment := range sonataflows.Items { + name := deployment.GetName() + err := ExecuteDeleteGVR(sonataflowGVR, name, namespace) + if err != nil { + return fmt.Errorf("❌ Failed to delete %s: %v\n", name, err) + } else { + fmt.Printf("✅ Deleted %s successfully\n", name) + } + } + } + } + + return nil +} + +func isOpenshift() (bool, error) { + dynamicConfig, err := k8sclient.KubeRestConfig() + if err != nil { + return false, fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes client: %v", err) + } + + discoveryClient, err := discovery.NewDiscoveryClientForConfig(dynamicConfig) + if err != nil { + return false, err + } + + apiGroups, err := discoveryClient.ServerGroups() + if err != nil { + return false, fmt.Errorf("❌ ERROR: Failed to discover server groups: %v", err) + } + + for _, group := range apiGroups.Groups { + if group.Name == "route.openshift.io" { + return true, nil + } + } + return false, nil +} + +var possibleOpenshiftNamespaces = []string{"openshift-operators", "community-operators"} + +func guessOperatorNamespace(isOpenshift bool) (string, error) { + if isOpenshift { + for _, ns := range possibleOpenshiftNamespaces { + if _, err := GetNamespace(ns); err == nil { + return ns, nil + } + } + } else { + // In case of Minikube or Kind, with default OLM installation, the namespace is "operators" + return "operators", nil + } + return "", fmt.Errorf("❌ ERROR: No valid namespace found for the Operator, please provide a namespace with the --namespace flag") +} diff --git a/cli/pkg/common/operator_test.go b/cli/pkg/common/operator_test.go new file mode 100644 index 00000000..a70f0946 --- /dev/null +++ b/cli/pkg/common/operator_test.go @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "testing" +) + +var testCasesCheckOperatorRunning = []struct { + name string + input string + expected bool +}{ + { + name: "pod running", + input: `NAME READY STATUS RESTARTS AGE +sonataflow-operator-controller-manager-78cb446b89-gj9jz 2/2 Running 0 95m`, + expected: true, + }, + { + name: "no pods", + input: "No resources found in sonataflow-operator-system namespace.", + expected: false, + }, + { + name: "no pods - empty string", + input: "", + expected: false, + }, + { + name: "no pods - - empty string 2", + input: " ", + expected: false, + }, + { + name: "no pods - some return", + input: " some weird return ", + expected: false, + }, +} + +func TestCheckOperatorRunning(t *testing.T) { + for _, tc := range testCasesCheckOperatorRunning { + t.Run(tc.name, func(t *testing.T) { + result := checkOperatorRunning(tc.input) + if result != tc.expected { + t.Errorf("Expected %v, but got %v", tc.expected, result) + } + }) + } +} diff --git a/cli/pkg/common/readyCheck.go b/cli/pkg/common/readyCheck.go new file mode 100644 index 00000000..1161eae8 --- /dev/null +++ b/cli/pkg/common/readyCheck.go @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "fmt" + "net/http" + "time" +) + +func ReadyCheck(healthCheckURL string, pollInterval time.Duration, portMapping string, openDevUI bool) { + ready := make(chan bool) + + go PollReadyCheckURL(healthCheckURL, pollInterval, ready) + select { + case <-ready: + fmt.Println("\n✅ SonataFlow project is up and running") + if openDevUI { + OpenBrowserURL(fmt.Sprintf("http://localhost:%s/q/dev", portMapping)) + } + case <-time.After(10 * time.Minute): + fmt.Printf("Timeout reached. Server at %s is not ready.", healthCheckURL) + } +} + +func PollReadyCheckURL(healthCheckURL string, interval time.Duration, ready chan<- bool) { + client := http.Client{ + Timeout: 5 * time.Second, + } + + for { + resp, err := client.Get(healthCheckURL) + if err == nil && resp.StatusCode == http.StatusOK { + if resp.StatusCode == http.StatusOK { + resp.Body.Close() // close the response body right after checking status + ready <- true + return + } + resp.Body.Close() + } + time.Sleep(interval) + } +} diff --git a/cli/pkg/common/test_helper.go b/cli/pkg/common/test_helper.go new file mode 100644 index 00000000..2c58bf47 --- /dev/null +++ b/cli/pkg/common/test_helper.go @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package common + +import ( + "github.com/spf13/afero" + "os" + "path/filepath" + "testing" +) + +func DeleteFolderStructure(t *testing.T, path string) { + err := FS.RemoveAll(path) + if err != nil { + t.Error("Unable to delete folder structure" + path) + } +} + +func CreateFolderStructure(t *testing.T, path string) { + err := FS.MkdirAll(path, 0750) + if err != nil { + t.Error("Unable to create folder structure" + path) + } +} + +func CreateFileInFolderStructure(t *testing.T, path string, fileName string) { + _, err := FS.Create(filepath.Join(path, fileName)) + //defer file.Close() + if err != nil { + t.Error("Unable to create" + fileName + "file in" + path) + } +} + +func CopyFileInFolderStructure(t *testing.T, path, src, dist string) { + if src == "" || dist == "" { + return + } + + r, err := os.Open(filepath.Join("testdata", src)) + if err != nil { + t.Errorf("Unable to open %s", filepath.Join("testdata", src)) + + } + defer r.Close() + + if err := afero.WriteReader(FS, filepath.Join(path, dist), r); err != nil { + t.Errorf("Error writing to file: %s", filepath.Join(path, dist)) + } +} diff --git a/cli/pkg/metadata/builderImage.go b/cli/pkg/metadata/builderImage.go new file mode 100644 index 00000000..725b1a6e --- /dev/null +++ b/cli/pkg/metadata/builderImage.go @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package metadata + +var BuilderImage string diff --git a/cli/pkg/metadata/constants.go b/cli/pkg/metadata/constants.go new file mode 100644 index 00000000..0ea6a998 --- /dev/null +++ b/cli/pkg/metadata/constants.go @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package metadata + +// Dependency represents a Maven dependency. +type Dependency struct { + GroupId string + ArtifactId string + Version string + Type string + Scope string +} + +var KogitoBomDependency = Dependency{ + GroupId: "org.kie.kogito", + ArtifactId: "kogito-bom", + Version: KogitoVersion, + Type: "pom", + Scope: "import", +} + +// KogitoDependencies defines the set of dependencies to be added to the pom.xml +// of created and converted Quarkus projects. +var KogitoDependencies = []Dependency{ + {GroupId: "org.kie", ArtifactId: "kie-addons-quarkus-knative-eventing"}, + {GroupId: "org.kie", ArtifactId: "kie-addons-quarkus-process-management"}, + {GroupId: "org.kie", ArtifactId: "kie-addons-quarkus-source-files"}, + {GroupId: "org.kie", ArtifactId: "kogito-addons-quarkus-data-index-inmemory"}, + {GroupId: "org.kie", ArtifactId: "kogito-addons-quarkus-jobs-service-embedded"}, + {GroupId: "org.apache.kie.sonataflow", ArtifactId: "sonataflow-quarkus"}, + {GroupId: "org.apache.kie.sonataflow", ArtifactId: "sonataflow-quarkus-devui", Version: "${kie.tooling.version}"}, + {GroupId: "org.testcontainers", ArtifactId: "testcontainers", Version: "1.21.4"}, +} + +// requared crds for sonataflow +var SonataflowCRDs = []string{"sonataflows.sonataflow.org", "sonataflowbuilds.sonataflow.org", "sonataflowplatforms.sonataflow.org"} +var KnativeCoreServingCRDs = []string{"images.caching.internal.knative.dev", "certificates.networking.internal.knative.dev", "configurations.serving.knative.dev", "clusterdomainclaims.networking.internal.knative.dev", "domainmappings.serving.knative.dev", "ingresses.networking.internal.knative.dev", "metrics.autoscaling.internal.knative.dev", "podautoscalers.autoscaling.internal.knative.dev", "revisions.serving.knative.dev", "routes.serving.knative.dev", "services.serving.knative.dev", "serverlessservices.networking.internal.knative.dev"} + +// OLM CatalogSources +var OLMCatalogSourcesMap = map[string]string{"operatorhubio-catalog": "olm", "community-operators": "openshift-marketplace"} + +var SonataFlowOperatorName = "sonataflow-operator" + + +const ( + QuarkusMavenPlugin = "quarkus-maven-plugin" + QuarkusKubernetesExtension = "quarkus-kubernetes" + QuarkusResteasyJacksonExtension = "quarkus-resteasy-jackson" + QuarkusContainerImageJib = "quarkus-container-image-jib" + SmallryeHealth = "smallrye-health" + QuarkusContainerImageDocker = "quarkus-container-image-docker" + KogitoQuarkusServerlessWorkflowExtension = "sonataflow-quarkus" + KogitoAddonsQuarkusKnativeEventingExtension = "kie-addons-quarkus-knative-eventing" + KogitoQuarkusServerlessWorkflowDevUi = "sonataflow-quarkus-devui" + KogitoAddonsQuarkusSourceFiles = "kie-addons-quarkus-source-files" + KogitoDataIndexInMemory = "kogito-addons-quarkus-data-index-inmemory" + + JavaVersion = 11 + MavenMajorVersion = 3 + MavenMinorVersion = 8 + + DefaultTag = "latest" + WorkflowSwJson = "workflow.sw.json" + WorkflowSwYaml = "workflow.sw.yaml" + + OperatorName = "sonataflow-operator-system" + OperatorManagerPod = "sonataflow-operator-controller-manager" + + YAMLExtension = ".yaml" + YMLExtension = ".yml" + JSONExtension = ".json" + YAMLSWExtension = "sw.yaml" + YMLSWExtension = "sw.yml" + JSONSWExtension = "sw.json" + ApplicationProperties = "application.properties" + ApplicationSecretProperties = "secret.properties" + ManifestServiceFilesKind = "SonataFlow" + + DockerInternalPort = "8080/tcp" + // VolumeBindPath The :z is to let docker know that the volume content can be shared between containers(SELinux) + VolumeBindPathSELinux = "/home/kogito/serverless-workflow-project/src/main/resources:z" + VolumeBindPath = "/home/kogito/serverless-workflow-project/src/main/resources" +) diff --git a/cli/pkg/metadata/devModeImage.go b/cli/pkg/metadata/devModeImage.go new file mode 100644 index 00000000..3beb686f --- /dev/null +++ b/cli/pkg/metadata/devModeImage.go @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package metadata + +var DevModeImage string diff --git a/cli/pkg/metadata/kogitoVersion.go b/cli/pkg/metadata/kogitoVersion.go new file mode 100644 index 00000000..6f8a6cb0 --- /dev/null +++ b/cli/pkg/metadata/kogitoVersion.go @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package metadata + +var KogitoVersion string diff --git a/cli/pkg/metadata/quarkus.go b/cli/pkg/metadata/quarkus.go new file mode 100644 index 00000000..e84f151f --- /dev/null +++ b/cli/pkg/metadata/quarkus.go @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package metadata + +var QuarkusPlatformGroupId string +var QuarkusVersion string + +type DependenciesVersion struct { + QuarkusPlatformGroupId string + QuarkusVersion string +} + +func ResolveQuarkusDependencies() DependenciesVersion { + return DependenciesVersion{ + QuarkusPlatformGroupId: QuarkusPlatformGroupId, + QuarkusVersion: QuarkusVersion, + } +} diff --git a/cli/pkg/metadata/version.go b/cli/pkg/metadata/version.go new file mode 100644 index 00000000..92320e6c --- /dev/null +++ b/cli/pkg/metadata/version.go @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package metadata + +var PluginVersion string diff --git a/cli/pkg/openshift/operation.go b/cli/pkg/openshift/operation.go new file mode 100644 index 00000000..d0d387f2 --- /dev/null +++ b/cli/pkg/openshift/operation.go @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package openshift + +import ( + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/common" +) + +func CheckOCInstalled() error { + fmt.Println("✅ Checking if Openshift CLI installed...") + ocCheck := common.ExecCommand("oc", "version") + err := ocCheck.Run() + if err != nil { + fmt.Println("ERROR: Openshift CLI not found.") + return fmt.Errorf("rror while checking if Openshift CLI is installed: %w", err) + } + return nil +} + +func CheckPermissions() error { + fmt.Println("✅ Checking permissions...") + namespace := "sonataflow-operator-system" + checkPermissions := common.ExecCommand("oc", "auth", "can-i", "create", "subscription", "-n", namespace) + result, err := checkPermissions.CombinedOutput() + if err != nil { + return fmt.Errorf("error while checking permissions: %w", err) + } + if string(result) != "yes\n" { + return fmt.Errorf("you don't have the required permissions to create a subscription in the namespace %s", namespace) + } + + return nil +} \ No newline at end of file diff --git a/cli/pkg/root/root.go b/cli/pkg/root/root.go new file mode 100644 index 00000000..4bf0021c --- /dev/null +++ b/cli/pkg/root/root.go @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package root + +import ( + "fmt" + "github.com/kubesmarts/logic-operator/cli/pkg/command/operator" + "github.com/kubesmarts/logic-operator/cli/pkg/command/specs" + + "github.com/kubesmarts/logic-operator/cli/pkg/command/quarkus" + + "github.com/kubesmarts/logic-operator/cli/pkg/command" + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/ory/viper" + "github.com/spf13/cobra" +) + +type RootCmdConfig struct { + Name string + Version string +} + +func NewRootCommand(cfg RootCmdConfig) *cobra.Command { + var cmd = &cobra.Command{ + Use: cfg.Name, + Short: "SonataFlow", + Long: ` + Manage SonataFlow projects + ========================== + + Currently, SonataFlow targets use cases with a single Serverless Workflow main + file definition (i.e. workflow.sw.{json|yaml|yml}). + + Additionally, you can define the configurable parameters of your application in the + "application.properties" file (inside the root project directory). + You can also store your spec files (i.e., Open API files) inside the "specs" folder, + schemas file inside "schemas" folder and also subflows inside "subflows" folder. + + A SonataFlow project, as the following structure by default: + + Workflow project root + /specs (optional) + /schemas (optional) + /subflows (optional) + workflow.sw.{json|yaml|yml} (mandatory) + + `, + Aliases: []string{"kn-workflow"}, + } + + viper.AutomaticEnv() // read in environment variables for WORKFLOW_ + viper.SetEnvPrefix("workflow") // ensure that all have the prefix + + cmd.Version = cfg.Version + cmd.SetVersionTemplate(`{{printf "%s\n" .Version}}`) + + cmd.AddCommand(command.NewCreateCommand()) + cmd.AddCommand(command.NewRunCommand()) + cmd.AddCommand(command.NewDeployCommand()) + cmd.AddCommand(command.NewUndeployCommand()) + cmd.AddCommand(command.NewGenManifest()) + cmd.AddCommand(quarkus.NewQuarkusCommand()) + cmd.AddCommand(command.NewVersionCommand(cfg.Version)) + cmd.AddCommand(specs.SpecsCommand()) + cmd.AddCommand(operator.NewOperatorCommand()) + + cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { + runRootHelp(cmd, args) + }) + + return cmd +} + +func runRootHelp(cmd *cobra.Command, args []string) { + tpl := common.GetTemplate(cmd, "root") + var data = struct { + Name string + }{ + Name: cmd.Root().Use, + } + + if err := tpl.Execute(cmd.OutOrStdout(), data); err != nil { + fmt.Fprintf(cmd.ErrOrStderr(), "unable to display help text: %v", err) + } +} diff --git a/cli/pkg/root/root_test.go b/cli/pkg/root/root_test.go new file mode 100644 index 00000000..e1833a89 --- /dev/null +++ b/cli/pkg/root/root_test.go @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package root + +import ( + "bytes" + "testing" + + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" +) + +var cfgTestInputRoot = RootCmdConfig{Name: "kn\u00A0workflow", Version: metadata.PluginVersion} + +func TestNewRootCommand(t *testing.T) { + //https://issues.redhat.com/browse/KOGITO-9847 + //t.Run("Test root command name and help", func(t *testing.T) { + // cmd := NewRootCommand(cfgTestInputRoot) + // output, err := ExecuteCommandSoft(cmd) + // require.NoError(t, err, "Error: %v", err) + // require.Contains(t, output, "Usage:\n kn\u00a0workflow [command]\n\nAliases:\n kn\u00a0workflow, kn-workflow") + // require.Contains(t, output, "Use \"kn\u00a0workflow [command] --help\" for more information about a command.") + //}) + + t.Run("Check subcommands except Cobra generated (help, completion)", func(t *testing.T) { + expectedSubCommands := []string{ + "create", + "deploy", + "quarkus", + "run", + "specs", + "undeploy", + "gen-manifest", + "version", + "operator", + } + + cmd := NewRootCommand(cfgTestInputRoot) + require.True(t, cmd.HasSubCommands()) + require.Equal(t, len(expectedSubCommands), len(cmd.Commands())) + + for _, e := range expectedSubCommands { + _, _, err := cmd.Find([]string{e}) + require.NoError(t, err, "Root command should have subcommand `%q`", e) + } + }) +} + +// ExecuteCommandC execute cobra.command and catch the output +func ExecuteCommandSoftC(root *cobra.Command, args ...string) (c *cobra.Command, output string, err error) { + buf := new(bytes.Buffer) + root.SetOut(buf) + root.SetErr(buf) + root.SetArgs(args) + c, err = root.ExecuteC() + return c, buf.String(), err +} + +// ExecuteCommand similar to ExecuteCommandC but does not return *cobra.Command +func ExecuteCommandSoft(root *cobra.Command, args ...string) (output string, err error) { + _, o, err := ExecuteCommandSoftC(root, args...) + return o, err +} diff --git a/cli/pkg/specs/openapi_minifier.go b/cli/pkg/specs/openapi_minifier.go new file mode 100644 index 00000000..205251bb --- /dev/null +++ b/cli/pkg/specs/openapi_minifier.go @@ -0,0 +1,336 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "path" + "path/filepath" + "reflect" + "strings" + + "github.com/kubesmarts/logic-operator/cli/pkg/common" + "github.com/kubesmarts/logic-operator/cli/pkg/metadata" + "github.com/kubesmarts/logic-operator/api/v1alpha08" + "github.com/getkin/kin-openapi/openapi3" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/util/sets" + yamlk8s "k8s.io/apimachinery/pkg/util/yaml" +) + +type OpenApiMinifier struct { + workflows []string + params *OpenApiMinifierOpts + operations map[string]sets.Set[string] +} + +type OpenApiMinifierOpts struct { + RootPath string + SpecsDir string + SubflowsDir string +} + +var workflowExtensionsType = []string{metadata.YAMLSWExtension, metadata.YMLSWExtension, metadata.JSONSWExtension} + +var minifiedExtensionsType = []string{metadata.YAMLExtension, metadata.YMLExtension, metadata.JSONExtension} + +// k8sFileSizeLimit defines the maximum file size allowed (e.g., Kubernetes ConfigMap size limit is 1MB) +const k8sFileSizeLimit = 3145728 // 3MB + +func NewMinifier(params *OpenApiMinifierOpts) *OpenApiMinifier { + return &OpenApiMinifier{params: params, operations: make(map[string]sets.Set[string]), workflows: []string{}} +} + +// Minify removes unused operations from OpenAPI specs based on the functions used in workflows. +func (m *OpenApiMinifier) Minify() (map[string]string, error) { + if err := m.findWorkflowFile(); err != nil { + return nil, err + } + + m.findSubflowsFiles(m.params) + + if err := m.fetchSpecFromFunctions(); err != nil { + return nil, err + } + + if err := m.validateSpecsFiles(); err != nil { + return nil, err + } + + minifySpecsFiles, err := m.minifySpecsFiles() + if err != nil { + return nil, err + } + + return minifySpecsFiles, nil +} + +func (m *OpenApiMinifier) fetchSpecFromFunctions() error { + for _, workflowFile := range m.workflows { + err := m.fetchSpecFromFunction(workflowFile) + if err != nil { + return err + } + } + return nil +} + +func (m *OpenApiMinifier) fetchSpecFromFunction(workflowFile string) error { + workflow, err := m.GetWorkflow(workflowFile) + if err != nil { + return err + } + + relativePath := filepath.Base(m.params.SpecsDir) + + if workflow.Functions == nil { + return nil + } + + for _, function := range workflow.Functions { + if strings.HasPrefix(function.Operation, relativePath) { + trimmedPrefix := strings.TrimPrefix(function.Operation, relativePath+"/") + if !strings.Contains(trimmedPrefix, "#") { + return fmt.Errorf("Invalid operation format in function: %s", function.Operation) + } + parts := strings.SplitN(trimmedPrefix, "#", 2) + if len(parts) != 2 { + return fmt.Errorf("❌ ERROR: Invalid operation format: %s", function.Operation) + } + apiFileName := path.Base(parts[0]) + operation := parts[1] + + if _, ok := m.operations[apiFileName]; !ok { + m.operations[apiFileName] = sets.Set[string]{} + } + m.operations[apiFileName].Insert(operation) + } + } + return nil +} + +func (m *OpenApiMinifier) validateSpecsFiles() error { + for specFile := range m.operations { + specFileName := filepath.Join(m.params.SpecsDir, specFile) + if _, err := os.Stat(specFileName); err != nil { + return fmt.Errorf("❌ ERROR: file %s not found or can't be open", specFileName) + } + } + return nil +} + +func (m *OpenApiMinifier) minifySpecsFiles() (map[string]string, error) { + minifySpecsFiles := map[string]string{} + for key, value := range m.operations { + minifiedSpecName, err := m.minifySpecsFile(key, value) + if err != nil { + return nil, err + } + minifySpecsFiles[key] = minifiedSpecName + } + return minifySpecsFiles, nil +} + +func (m *OpenApiMinifier) minifySpecsFile(specFileName string, operations sets.Set[string]) (string, error) { + specFile := filepath.Join(m.params.SpecsDir, specFileName) + data, err := os.ReadFile(specFile) + if err != nil { + return "", fmt.Errorf("❌ ERROR: failed to read OpenAPI document: %w", err) + } + + doc, err := m.removeUnusedNodes(data, specFile, operations) + if err != nil { + return "", err + } + + minifiedFile, err := m.writeMinifiedFileToDisk(specFile, doc) + if err != nil { + return "", fmt.Errorf("❌ ERROR: failed to write minified file of %s : %w", specFile, err) + } + finalSize, err := validateSpecsFileSize(minifiedFile) + if err != nil { + return "", fmt.Errorf("❌ ERROR: Minification of %s failed: %w", specFile, err) + } + + initialSize, err := os.Stat(specFile) + if err != nil { + return "", fmt.Errorf("❌ ERROR: failed to get file %s info: %w", specFile, err) + } + + fmt.Printf("✅ Minified file %s created with %d bytes (original size: %d bytes)\n", minifiedFile, finalSize, initialSize.Size()) + return minifiedFile, nil +} + +func (m *OpenApiMinifier) removeUnusedNodes(data []byte, specFileName string, operations sets.Set[string]) (*openapi3.T, error) { + doc, err := openapi3.NewLoader().LoadFromData(data) + + collector, err := newCollector(specFileName) + if err != nil { + return nil, err + } + + keep, err := collector.collect(operations) + if err != nil { + return nil, err + } + + if err != nil { + return nil, fmt.Errorf("❌ ERROR: failed to load OpenAPI document: %w", err) + } + if doc.Paths == nil { + return nil, fmt.Errorf("OpenAPI document %s has no paths", specFileName) + } + for key, value := range doc.Paths.Map() { + for method, operation := range value.Operations() { + if !operations.Has(operation.OperationID) { + value.SetOperation(method, nil) + } + } + if isPathItemEmpty(value) { + doc.Paths.Delete(key) + } + } + + if doc.Components != nil { + // note we have to skip securitySchemes, because it aren't referenced by operation directly via $ref + components := map[string]interface{}{ + "schemas": doc.Components.Schemas, + "headers": doc.Components.Headers, + "parameters": doc.Components.Parameters, + "responses": doc.Components.Responses, + "requestBodies": doc.Components.RequestBodies, + "examples": doc.Components.Examples, + "links": doc.Components.Links, + "callbacks": doc.Components.Callbacks, + } + + for key, componentMap := range components { + if componentMap == nil { + continue + } + + componentValue := reflect.ValueOf(componentMap) + for _, name := range componentValue.MapKeys() { + nameStr := name.String() + if !keep["components"][key].Has(nameStr) { + componentValue.SetMapIndex(name, reflect.Value{}) + } + } + } + } + return doc, nil +} + +func (m *OpenApiMinifier) findWorkflowFile() error { + file, err := common.FindSonataFlowFile(workflowExtensionsType) + if err != nil { + return err + } + m.workflows = append(m.workflows, file) + return nil +} + +func (m *OpenApiMinifier) findSubflowsFiles(cfg *OpenApiMinifierOpts) { + files := common.FindSonataFlowFiles(cfg.SubflowsDir, workflowExtensionsType) + m.workflows = append(m.workflows, files...) +} + +func (m *OpenApiMinifier) GetWorkflow(workflowFile string) (*v1alpha08.Flow, error) { + workflow := &v1alpha08.Flow{} + file, err := os.Open(workflowFile) + if err != nil { + return workflow, err + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return workflow, fmt.Errorf("failed to read workflow file %s: %w", workflowFile, err) + } + + if err = yamlk8s.Unmarshal(data, workflow); err != nil { + return workflow, fmt.Errorf("failed to unmarshal workflow file %s: %w", workflowFile, err) + } + return workflow, nil +} + +func (m *OpenApiMinifier) writeMinifiedFileToDisk(specFile string, doc *openapi3.T) (string, error) { + var output []byte + var err error + if strings.HasSuffix(specFile, metadata.YAMLExtension) || strings.HasSuffix(specFile, metadata.YMLExtension) { + var buf bytes.Buffer + encoder := yaml.NewEncoder(&buf) + encoder.SetIndent(2) + err = encoder.Encode(doc) + output = buf.Bytes() + } else { + output, err = json.Marshal(doc) + } + + if err != nil { + return "", fmt.Errorf("❌ ERROR: failed to marshal OpenAPI document: %w", err) + } + + minifiedSpecFile, err := MinifiedName(specFile) + if err != nil { + return "", fmt.Errorf("❌ ERROR: failed to get minified file name: %w", err) + } + + err = os.WriteFile(minifiedSpecFile, output, 0644) + if err != nil { + return "", fmt.Errorf("❌ ERROR: failed to write OpenAPI document: %w", err) + } + return minifiedSpecFile, nil +} + +func validateSpecsFileSize(specFile string) (int64, error) { + file, err := os.Stat(specFile) + if err != nil { + return -1, fmt.Errorf("❌ ERROR: failed to get file info: %w", err) + } + if file.Size() >= k8sFileSizeLimit { + return -1, fmt.Errorf("❌ ERROR: Minified file %s exceeds the size limit of %d bytes", specFile, k8sFileSizeLimit) + } + return file.Size(), nil +} + +func isPathItemEmpty(pathItem *openapi3.PathItem) bool { + return pathItem.Get == nil && + pathItem.Put == nil && + pathItem.Post == nil && + pathItem.Delete == nil && + pathItem.Options == nil && + pathItem.Head == nil && + pathItem.Patch == nil && + pathItem.Trace == nil +} + +func MinifiedName(specFile string) (string, error) { + for _, ext := range minifiedExtensionsType { + if strings.HasSuffix(specFile, ext) { + return strings.TrimSuffix(specFile, ext) + ".min" + ext, nil + } + } + return "", fmt.Errorf("❌ ERROR: unknown file extension: %s", specFile) +} diff --git a/cli/pkg/specs/openapi_minifier_test.go b/cli/pkg/specs/openapi_minifier_test.go new file mode 100644 index 00000000..9b715e58 --- /dev/null +++ b/cli/pkg/specs/openapi_minifier_test.go @@ -0,0 +1,449 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "fmt" + "io" + "os" + "path" + "reflect" + "strings" + "testing" + + "github.com/kubesmarts/logic-operator/api/v1alpha08" + "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/yaml" +) + +type spec struct { + file string + expected string + initial int + minified int +} + +type minifyTest struct { + workflowFile string + openapiSpecFiles []spec + specsDir string + subflowsDir string + subflows []string +} + +func TestOpenAPIMinify(t *testing.T) { + tests := []minifyTest{ + { + workflowFile: "testdata/workflow.sw.yaml", // 4 functions, 2 of them are ref to the same openapi spec + openapiSpecFiles: []spec{{file: "testdata/flink-openapi.yaml", initial: 5, minified: 3}}, // 5 operations, 3 must left + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow.sw.json", // 4 functions, 2 of them are ref to the same openapi spec + openapiSpecFiles: []spec{{file: "testdata/flink-openapi.yaml", initial: 5, minified: 3}}, // 5 operations, 3 must left + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-json-openapi.sw.json", // 4 functions, 2 of them are ref to the same openapi spec + openapiSpecFiles: []spec{{file: "testdata/flink-openapi-json.json", initial: 5, minified: 3}}, // 5 operations, 3 must left + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow2.sw.yaml", // 4 functions, 1 per openapi spec file + openapiSpecFiles: []spec{ + {file: "testdata/flink1-openapi.yaml", initial: 3, minified: 1}, + {file: "testdata/flink2-openapi.yaml", initial: 3, minified: 1}, + {file: "testdata/flink3-openapi.yaml", initial: 3, minified: 1}, + {file: "testdata/flink4-openapi.yaml", initial: 3, minified: 1}}, + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-empty.sw.yaml", // check don't fail with empty workflow + openapiSpecFiles: []spec{}, + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-empty.sw.yaml", // check all operations are removed + openapiSpecFiles: []spec{{file: "testdata/flink-openapi.yaml", initial: 5, minified: 0}}, + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-mySpecsDir.sw.yaml", // check all operations are removed, with different specs dir + openapiSpecFiles: []spec{{file: "testdata/flink-openapi.yaml", initial: 5, minified: 3}}, + specsDir: "mySpecsDir", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-mySpecsDir-one-finction.sw.yaml", // check all operations are removed, with different specs dir + openapiSpecFiles: []spec{{file: "testdata/flink-openapi.yaml", initial: 5, minified: 2}}, + specsDir: "mySpecsDir", + subflowsDir: "subflows", + subflows: []string{"testdata/subflow-mySpecsDir.sw.yaml"}, + }, + { + workflowFile: "testdata/workflow-empty.sw.yaml", // check all operations are removed, with different subflow dir + openapiSpecFiles: []spec{{file: "testdata/flink-openapi.yaml", initial: 5, minified: 0}}, + specsDir: "mySpecsDir", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-empty2.sw.yaml", // check don't fail with workflow with non openapi functions + openapiSpecFiles: []spec{}, + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/workflow-empty2.sw.yaml", // check functions is on subflow + openapiSpecFiles: []spec{{file: "testdata/flink-openapi.yaml", initial: 5, minified: 2}}, + specsDir: "specs", + subflowsDir: "subflows", + subflows: []string{"testdata/subflow.sw.yaml"}, + }, + { + workflowFile: "testdata/workflow-empty2.sw.yaml", // check functions is on subflow, with different subflow and specs dirs + openapiSpecFiles: []spec{{file: "testdata/flink-openapi.yaml", initial: 5, minified: 2}}, + specsDir: "mySpecsDir", + subflowsDir: "mySubFlowDir", + subflows: []string{"testdata/subflow-mySpecsDir.sw.yaml"}, + }, + { + workflowFile: "testdata/workflow-greeting.sw.yaml", // check we can process subflows with the same file name but different extensions + openapiSpecFiles: []spec{{file: "testdata/greetingAPI.yaml", initial: 3, minified: 1}}, + specsDir: "specs", + subflowsDir: "custom_subflows", + subflows: []string{"testdata/hello.sw.json", "testdata/hello.sw.yaml"}, // 2 subflows, 1 of them has a function that uses the greetingAPI.yaml + }, + { + workflowFile: "testdata/workflow-greeting.sw.yaml", // check we can process subflows with the same file name but different extensions + openapiSpecFiles: []spec{{file: "testdata/flink-openapi.yaml", initial: 5, minified: 2}}, + specsDir: "custom_specs", + subflowsDir: "custom_subflows", + subflows: []string{"testdata/subflow-custom.sw.json", "testdata/subflow-custom.sw.yaml"}, // 2 subflows, each one has a function that uses the flink-openapi.yaml + }, + { + workflowFile: "testdata/workflow-subflow-custom.sw.yaml", // workflow with a function that uses a subflow with a function that uses the flink-openapi.yaml + openapiSpecFiles: []spec{{file: "testdata/flink-openapi.yaml", initial: 5, minified: 3}}, + specsDir: "custom_specs", + subflowsDir: "custom_subflows", + subflows: []string{"testdata/subflow-custom.sw.json", "testdata/subflow-custom.sw.yaml"}, // 2 subflows, each one has a function that uses the flink-openapi.yaml + }, + } + + current, err := os.Getwd() + if err != nil { + t.Fatalf("Error getting current directory: %v", err) + } + + for _, test := range tests { + t.Run(test.workflowFile, func(t *testing.T) { + prepareStructure(t, test) + defer cleanUp(t, test) + + minifiedfiles, err := NewMinifier(&OpenApiMinifierOpts{ + SpecsDir: path.Join(current, test.specsDir), + SubflowsDir: path.Join(current, test.subflowsDir), + }).Minify() + if err != nil { + t.Fatalf("Error minifying openapi specs: %v", err) + } + checkInitial(t, test) + checkResult(t, test, minifiedfiles) + }) + } +} + +// These tests contain openapi specs with $ref to other specs +func TestOpenAPIMinifyRefs(t *testing.T) { + tests := []minifyTest{ + { + workflowFile: "testdata/refs/workflow.sw.yaml", + openapiSpecFiles: []spec{{file: "testdata/refs/openapi.yaml", expected: "testdata/refs/openapi.expected.yaml", initial: 5, minified: 3}}, + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/refs/workflow.sw.yaml", + openapiSpecFiles: []spec{{file: "testdata/refs/openapi.yaml", expected: "testdata/refs/openapi.expected.yaml", initial: 5, minified: 3}}, + specsDir: "my_specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/refs/emptyworkflow.sw.yaml", + openapiSpecFiles: []spec{{file: "testdata/refs/openapi1.yaml", expected: "testdata/refs/openapi1.expected.yaml", initial: 1, minified: 1}, + {file: "testdata/refs/openapi2.yaml", expected: "testdata/refs/openapi2.expected.yaml", initial: 1, minified: 1}}, + specsDir: "specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/refs/emptyworkflow.sw.yaml", + openapiSpecFiles: []spec{{file: "testdata/refs/openapi1.yaml", expected: "testdata/refs/openapi1.expected.yaml", initial: 1, minified: 1}, + {file: "testdata/refs/openapi2.yaml", expected: "testdata/refs/openapi2.expected.yaml", initial: 1, minified: 1}}, + specsDir: "my_specs", + subflowsDir: "subflows", + }, + { + workflowFile: "testdata/refs/emptyworkflow.sw.yaml", + openapiSpecFiles: []spec{{file: "testdata/refs/openapi1.yaml", expected: "testdata/refs/openapi1.expected.yaml", initial: 1, minified: 1}, + {file: "testdata/refs/openapi2.yaml", expected: "testdata/refs/openapi2.expected.yaml", initial: 1, minified: 1}}, + specsDir: "my_specs", + subflowsDir: "custom_specs", + subflows: []string{"testdata/refs//subflow2.sw.yaml", "testdata/refs/subflow2.sw.yaml"}, // 2 subflows, each one has a function that uses the flink-openapi.yaml + }, + { + workflowFile: "testdata/refs/emptyworkflow.sw.yaml", + openapiSpecFiles: []spec{{file: "testdata/refs/openapi.yaml", expected: "testdata/refs/openapi-subflow34.expected.yaml", initial: 5, minified: 2}}, + specsDir: "specs", + subflowsDir: "custom_specs", + subflows: []string{"testdata/refs//subflow3.sw.yaml", "testdata/refs/subflow4.sw.yaml"}, // 2 subflows, each one has a function that uses the flink-openapi.yaml + }, + } + + current, err := os.Getwd() + if err != nil { + t.Fatalf("Error getting current directory: %v", err) + } + + for _, test := range tests { + t.Run(test.workflowFile, func(t *testing.T) { + prepareStructure(t, test) + defer cleanUp(t, test) + + minifiedfiles, err := NewMinifier(&OpenApiMinifierOpts{ + SpecsDir: path.Join(current, test.specsDir), + SubflowsDir: path.Join(current, test.subflowsDir), + }).Minify() + + if err != nil { + t.Fatalf("Error minifying openapi specs: %v", err) + } + testFiles := map[string]spec{} + + for _, spec := range test.openapiSpecFiles { + testFiles[path.Base(spec.file)] = spec + } + + for k, v := range minifiedfiles { + expected := testFiles[k].expected + assert.Nil(t, validateOpenAPISpec(v), "Minified file %s is not a valid OpenAPI spec", v) + assert.True(t, compareYAMLFiles(t, v, expected), "Minified file %s is not equal to the expected file %s", v, expected) + } + }) + } +} + +func validateOpenAPISpec(filePath string) error { + loader := openapi3.NewLoader() + doc, err := loader.LoadFromFile(filePath) + if err != nil { + return fmt.Errorf("failed to load OpenAPI spec from file: %v", err) + } + + if err := doc.Validate(loader.Context); err != nil { + return fmt.Errorf("OpenAPI spec is invalid: %v", err) + } + return nil +} + +func compareYAMLFiles(t *testing.T, file1Path, file2Path string) bool { + data1, err := os.ReadFile(file1Path) + if err != nil { + t.Fatalf("failed to read file %s: %v", file1Path, err) + } + + data2, err := os.ReadFile(file2Path) + if err != nil { + t.Fatalf("failed to read file %s: %v", file2Path, err) + } + + var obj1, obj2 interface{} + if err := yaml.Unmarshal(data1, &obj1); err != nil { + t.Fatalf("failed to unmarshal file %s: %v", file1Path, err) + } + if err := yaml.Unmarshal(data2, &obj2); err != nil { + t.Fatalf("failed to unmarshal file %s: %v", file2Path, err) + } + + return reflect.DeepEqual(obj1, obj2) +} + +func prepareStructure(t *testing.T, test minifyTest) { + if err := os.Mkdir(test.specsDir, 0755); err != nil { + t.Fatalf("Error creating specs directory: %v", err) + } + if err := copyFile(test.workflowFile, path.Base(test.workflowFile)); err != nil { + t.Fatalf("Error copying workflow file: %v", err) + } + if len(test.subflows) > 0 { + if err := os.Mkdir(test.subflowsDir, 0755); err != nil { + t.Fatalf("Error creating subflows directory: %v", err) + } + for _, subflow := range test.subflows { + if err := copyFile(subflow, path.Join(test.subflowsDir, path.Base(subflow))); err != nil { + t.Fatalf("Error copying subflow file: %v", err) + } + } + } + for _, openapiSpecFile := range test.openapiSpecFiles { + if err := copyFile(openapiSpecFile.file, path.Join(test.specsDir, path.Base(openapiSpecFile.file))); err != nil { + t.Fatalf("Error copying openapi spec file: %v", err) + } + } +} + +func cleanUp(t *testing.T, test minifyTest) { + err := os.Remove(path.Base(test.workflowFile)) + if err != nil { + t.Fatalf("Error removing workflow file: %v", err) + } + err = os.RemoveAll(test.specsDir) + if err != nil { + t.Fatalf("Error removing specs directory: %v", err) + } + err = os.RemoveAll(test.subflowsDir) + if err != nil { + t.Fatalf("Error removing subflows directory: %v", err) + } + +} + +// checkInitial checks the initial number of operations in the openapi specs +func checkInitial(t *testing.T, test minifyTest) { + for _, spec := range test.openapiSpecFiles { + data, err := os.ReadFile(spec.file) + if err != nil { + t.Fatalf("Error reading openapi spec file: %v", err) + } + doc, err := openapi3.NewLoader().LoadFromData(data) + if err != nil { + t.Fatalf("Error loading openapi spec file: %v", err) + } + assert.Equalf(t, spec.initial, len(doc.Paths.Map()), "Initial number of operations in %s is not correct", spec.file) + } +} + +// checkResult checks the number of operations in the minified openapi specs +func checkResult(t *testing.T, test minifyTest, minifiedFiles map[string]string) { + workflow, err := parseWorkflow(path.Base(test.workflowFile)) + if err != nil { + t.Fatalf("Error parsing workflow file: %v", err) + } + + functions := map[string]sets.Set[string]{} + parseFunctions(t, functions, workflow, test) + for _, subflow := range test.subflows { + subflowWorkflow, err := parseWorkflow(subflow) + if err != nil { + t.Fatalf("Error parsing subflow file: %v", err) + } + parseFunctions(t, functions, subflowWorkflow, test) + } + + countOfOperationInSpecs := map[string]int{} + + for file, operationSet := range functions { + minified := minifiedFiles[file] + data, err := os.ReadFile(minified) + if err != nil { + t.Fatalf("Error reading minified file %s: %v", minified, err) + } + doc, err := openapi3.NewLoader().LoadFromData(data) + for _, value := range doc.Paths.Map() { + for _, operation := range value.Operations() { + assert.True(t, operationSet.Has(operation.OperationID), "Operation %s not found in workflow", operation.OperationID) + } + } + countOfOperationInSpecs[file] = len(doc.Paths.Map()) + assert.Equal(t, len(operationSet), len(doc.Paths.Map())) + } + for _, spec := range test.openapiSpecFiles { + assert.Equalf(t, spec.minified, countOfOperationInSpecs[path.Base(spec.file)], "Minified number of operations in %s is not correct", spec.file) + } + +} + +func parseFunctions(t *testing.T, functions map[string]sets.Set[string], workflow *v1alpha08.Flow, test minifyTest) { + for _, function := range workflow.Functions { + if strings.HasPrefix(function.Operation, test.specsDir) { + trimmedPrefix := strings.TrimPrefix(function.Operation, test.specsDir+"/") + if !strings.Contains(trimmedPrefix, "#") { + t.Fatalf("Invalid operation format in function: %s", function.Operation) + } + parts := strings.SplitN(trimmedPrefix, "#", 2) + if len(parts) != 2 { + t.Fatalf("Invalid operation format: %s", function.Operation) + } + apiFileName := path.Base(parts[0]) + operation := parts[1] + + if _, ok := functions[apiFileName]; !ok { + functions[apiFileName] = sets.Set[string]{} + } + functions[apiFileName].Insert(operation) + } + } +} + +func parseWorkflow(workflowFile string) (*v1alpha08.Flow, error) { + workflow := &v1alpha08.Flow{} + file, err := os.Open(workflowFile) + if err != nil { + return workflow, err + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return workflow, fmt.Errorf("failed to read workflow file %s: %w", workflowFile, err) + } + + if err = yaml.Unmarshal(data, workflow); err != nil { + return workflow, fmt.Errorf("failed to unmarshal workflow file %s: %w", workflowFile, err) + } + return workflow, nil +} + +func copyFile(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err + } + + return nil +} diff --git a/cli/pkg/specs/ref_collector.go b/cli/pkg/specs/ref_collector.go new file mode 100644 index 00000000..90011103 --- /dev/null +++ b/cli/pkg/specs/ref_collector.go @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package specs + +import ( + "fmt" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/util/sets" + "os" + "strings" +) + +type collector struct { + filename string + refs sets.Set[string] + doc map[string]any +} + +type node struct { + section string + subsection string + object string +} + +func newCollector(file string) (*collector, error) { + data, err := os.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: failed to read OpenAPI spec file %s: %w", file, err) + } + + m := make(map[string]any) + err = yaml.Unmarshal(data, &m) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: failed to unmarshal OpenAPI spec file %s: %w", file, err) + } + + return &collector{filename: file, doc: m, refs: sets.Set[string]{}}, nil +} + +func (c *collector) collect(operations sets.Set[string]) (map[string]map[string]sets.Set[string], error) { + for operation := range operations { + operationNode, err := c.findByOperationId(operation, c.doc) + if err != nil { + return nil, err + } + mapEntry(operationNode.(map[string]interface{}), c.refs) + } + visited, err := c.collectDependentRefs() + if err != nil { + return nil, fmt.Errorf("❌ ERROR: failed to collect dependent refs in OpenAPI spec file %s: %w", c.filename, err) + } + + preserve := map[string]map[string]sets.Set[string]{} + for ref := range visited { + node, err := c.parseRef(ref) + if err != nil { + return nil, fmt.Errorf("❌ ERROR: failed to parse ref at OpenAPI spec file %s: %w", c.filename, err) + } + if preserve[node.section] == nil { + preserve[node.section] = map[string]sets.Set[string]{} + } + if preserve[node.section][node.subsection] == nil { + preserve[node.section][node.subsection] = sets.Set[string]{} + } + preserve[node.section][node.subsection].Insert(node.object) + } + return preserve, nil +} + +func (c *collector) collectDependentRefs() (sets.Set[string], error) { + var visited = sets.Set[string]{} + for c.refs.Len() > 0 { + operation, _ := c.refs.PopAny() + if !visited.Has(operation) { + visited.Insert(operation) + var current = sets.Set[string]{} + node, err := c.findByRefObject(operation, c.doc) + if err != nil { + return nil, err + } + mapEntry(node, current) + for current.Len() > 0 { + operation, _ := current.PopAny() + if !visited.Has(operation) { + c.refs.Insert(operation) + } + } + } + } + return visited, nil +} + +func (c *collector) parseRef(ref string) (node, error) { + if !strings.HasPrefix(ref, "#/") { + return node{}, fmt.Errorf("invalid $ref: %s, must start with #/ at OpenAPI spec file %s", ref, c.filename) + } + parts := strings.Split(ref, "/") + if len(parts) < 4 { + return node{}, fmt.Errorf("invalid $ref %s at OpenAPI spec file %s", ref, c.filename) + } + return node{section: parts[1], subsection: parts[2], object: parts[3]}, nil +} + +func (c *collector) findByRefObject(ref string, m map[string]interface{}) (map[string]interface{}, error) { + parsedRef, err := c.parseRef(ref) + if err != nil { + return nil, err + } + section, ok := m[parsedRef.section].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("OpenAPI spec file %s has no such section: %s", c.filename, ref) + } + subsection, ok := section[parsedRef.subsection].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("OpenAPI spec file %s has no such subsection: %s", c.filename, ref) + } + object, ok := subsection[parsedRef.object].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("OpenAPI spec file %s has no such object: %s", c.filename, ref) + } + + return object, nil +} + +func (c *collector) findByOperationId(operationId string, m map[string]interface{}) (any, error) { + paths, ok := m["paths"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("OpenAPI spec file %s has no paths", c.filename) + } + for _, pathItem := range paths { + operations, ok := pathItem.(map[string]interface{}) + if !ok { + continue + } + for _, operationDetails := range operations { + operation, ok := operationDetails.(map[string]interface{}) + if !ok { + continue + } + if operation["operationId"] == operationId { + return operation, nil + } + } + } + return nil, fmt.Errorf("operationId %s not found at OpenAPI spec file %s", operationId, c.filename) +} + +func entry(e any, refs sets.Set[string]) { + switch v := e.(type) { + case map[string]interface{}: + mapEntry(v, refs) + case []interface{}: + sliceEntry(v, refs) + default: + return + } +} + +func sliceEntry(s []interface{}, refs sets.Set[string]) { + for _, v := range s { + entry(v, refs) + } +} + +func mapEntry(m map[string]interface{}, refs sets.Set[string]) { + for k, v := range m { + if k == "$ref" { + refs.Insert(v.(string)) + continue + } + entry(v, refs) + } +} diff --git a/cli/pkg/specs/testdata/flink-openapi-json.json b/cli/pkg/specs/testdata/flink-openapi-json.json new file mode 100644 index 00000000..7049ac6b --- /dev/null +++ b/cli/pkg/specs/testdata/flink-openapi-json.json @@ -0,0 +1,123 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Flink JobManager REST API", + "contact": { + "email": "user@flink.apache.org" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "v1/1.20-SNAPSHOT" + }, + "paths": { + "/jars": { + "get": { + "description": "Returns a list of all jars previously uploaded via '/jars/upload'.", + "operationId": "getJarList", + "responses": { + "200": { + "description": "The request was successful." + } + } + } + }, + "/jars/{jarid}/run": { + "post": { + "description": "Submits a job for execution.", + "operationId": "submitJobFromJar", + "parameters": [ + { + "name": "jarid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The request was successful." + } + } + } + }, + "/jobs/{jobid}": { + "get": { + "description": "Returns details of a job.", + "operationId": "getJobDetails", + "parameters": [ + { + "name": "jobid", + "in": "path", + "description": "32-character hexadecimal string value that identifies a job.", + "required": true + } + ], + "responses": { + "200": { + "description": "The request was successful." + } + } + }, + "patch": { + "description": "Terminates a job.", + "operationId": "cancelJob", + "parameters": [ + { + "name": "jobid", + "in": "path", + "description": "32-character hexadecimal string value that identifies a job.", + "required": true + }, + { + "name": "mode", + "in": "query", + "description": "String value that specifies the termination mode. The only supported value is: \"cancel\".", + "required": false, + "style": "form" + } + ], + "responses": { + "202": { + "description": "The request was successful." + } + } + } + }, + "/jars/upload": { + "post": { + "description": "Uploads a jar.", + "operationId": "uploadJar", + "requestBody": { + "content": { + "application/x-java-archive": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "The request was successful." + } + } + } + }, + "/test": { + "get": { + "description": "Test endpoint", + "operationId": "test", + "responses": { + "200": { + "description": "The request was successful." + } + } + } + } + } +} diff --git a/cli/pkg/specs/testdata/flink-openapi.yaml b/cli/pkg/specs/testdata/flink-openapi.yaml new file mode 100644 index 00000000..14874d7a --- /dev/null +++ b/cli/pkg/specs/testdata/flink-openapi.yaml @@ -0,0 +1,79 @@ +openapi: 3.0.1 +info: + title: Flink JobManager REST API + contact: + email: user@flink.apache.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: v1/1.20-SNAPSHOT +paths: + /jars: + get: + description: Returns a list of all jars previously uploaded via '/jars/upload'. + operationId: getJarList + responses: + "200": + description: The request was successful. + /jars/{jarid}/run: + post: + description: Submits a job for execution. + operationId: submitJobFromJar + parameters: + - name: jarid + in: path + required: true + schema: + type: string + responses: + "200": + description: The request was successful. + /jobs/{jobid}: + get: + description: Returns details of a job. + operationId: getJobDetails + parameters: + - name: jobid + in: path + description: 32-character hexadecimal string value that identifies a job. + required: true + responses: + "200": + description: The request was successful. + patch: + description: Terminates a job. + operationId: cancelJob + parameters: + - name: jobid + in: path + description: 32-character hexadecimal string value that identifies a job. + required: true + - name: mode + in: query + description: "String value that specifies the termination mode. The only supported\ + \ value is: \"cancel\"." + required: false + style: form + responses: + "202": + description: The request was successful. + /jars/upload: + post: + description: Uploads a jar. + operationId: uploadJar + requestBody: + content: + application/x-java-archive: + schema: + type: string + format: binary + responses: + "200": + description: The request was successful. + /test: + get: + description: Test endpoint + operationId: test + responses: + "200": + description: The request was successful. diff --git a/cli/pkg/specs/testdata/flink1-openapi.yaml b/cli/pkg/specs/testdata/flink1-openapi.yaml new file mode 100644 index 00000000..585a9cc2 --- /dev/null +++ b/cli/pkg/specs/testdata/flink1-openapi.yaml @@ -0,0 +1,37 @@ +openapi: 3.0.1 +info: + title: Flink JobManager REST API + contact: + email: user@flink.apache.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: v1/1.20-SNAPSHOT +paths: + /jars: + get: + description: Returns a list of all jars previously uploaded via '/jars/upload'. + operationId: getJarList + responses: + "200": + description: The request was successful. + /jars/upload: + post: + description: Uploads a jar. + operationId: uploadJar + requestBody: + content: + application/x-java-archive: + schema: + type: string + format: binary + responses: + "200": + description: The request was successful. + /test: + get: + description: Test endpoint + operationId: test + responses: + "200": + description: The request was successful. diff --git a/cli/pkg/specs/testdata/flink2-openapi.yaml b/cli/pkg/specs/testdata/flink2-openapi.yaml new file mode 100644 index 00000000..9827c587 --- /dev/null +++ b/cli/pkg/specs/testdata/flink2-openapi.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.1 +info: + title: Flink JobManager REST API + contact: + email: user@flink.apache.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: v1/1.20-SNAPSHOT +paths: + /jars/{jarid}/run: + post: + description: Submits a job for execution. + operationId: submitJobFromJar + parameters: + - name: jarid + in: path + required: true + schema: + type: string + responses: + "200": + description: The request was successful. + /jars/upload: + post: + description: Uploads a jar. + operationId: uploadJar + requestBody: + content: + application/x-java-archive: + schema: + type: string + format: binary + responses: + "200": + description: The request was successful. + /test: + get: + description: Test endpoint + operationId: test + responses: + "200": + description: The request was successful. diff --git a/cli/pkg/specs/testdata/flink3-openapi.yaml b/cli/pkg/specs/testdata/flink3-openapi.yaml new file mode 100644 index 00000000..321baa6b --- /dev/null +++ b/cli/pkg/specs/testdata/flink3-openapi.yaml @@ -0,0 +1,59 @@ +openapi: 3.0.1 +info: + title: Flink JobManager REST API + contact: + email: user@flink.apache.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: v1/1.20-SNAPSHOT +paths: + /jobs/{jobid}: + get: + description: Returns details of a job. + operationId: getJobDetails + parameters: + - name: jobid + in: path + description: 32-character hexadecimal string value that identifies a job. + required: true + responses: + "200": + description: The request was successful. + patch: + description: Terminates a job. + operationId: cancelJob + parameters: + - name: jobid + in: path + description: 32-character hexadecimal string value that identifies a job. + required: true + - name: mode + in: query + description: "String value that specifies the termination mode. The only supported\ + \ value is: \"cancel\"." + required: false + style: form + responses: + "202": + description: The request was successful. + /jars/upload: + post: + description: Uploads a jar. + operationId: uploadJar + requestBody: + content: + application/x-java-archive: + schema: + type: string + format: binary + responses: + "200": + description: The request was successful. + /test: + get: + description: Test endpoint + operationId: test + responses: + "200": + description: The request was successful. diff --git a/cli/pkg/specs/testdata/flink4-openapi.yaml b/cli/pkg/specs/testdata/flink4-openapi.yaml new file mode 100644 index 00000000..585a9cc2 --- /dev/null +++ b/cli/pkg/specs/testdata/flink4-openapi.yaml @@ -0,0 +1,37 @@ +openapi: 3.0.1 +info: + title: Flink JobManager REST API + contact: + email: user@flink.apache.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: v1/1.20-SNAPSHOT +paths: + /jars: + get: + description: Returns a list of all jars previously uploaded via '/jars/upload'. + operationId: getJarList + responses: + "200": + description: The request was successful. + /jars/upload: + post: + description: Uploads a jar. + operationId: uploadJar + requestBody: + content: + application/x-java-archive: + schema: + type: string + format: binary + responses: + "200": + description: The request was successful. + /test: + get: + description: Test endpoint + operationId: test + responses: + "200": + description: The request was successful. diff --git a/cli/pkg/specs/testdata/greetingAPI.yaml b/cli/pkg/specs/testdata/greetingAPI.yaml new file mode 100644 index 00000000..6402f4d0 --- /dev/null +++ b/cli/pkg/specs/testdata/greetingAPI.yaml @@ -0,0 +1,282 @@ +--- +openapi: 3.0.0 +info: + title: greeting-flow API + version: "1.0" +paths: + /: + post: + requestBody: + content: + "*/*": + schema: + $ref: "#/components/schemas/CloudEvent" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Response" + /hello: + get: + tags: + - Reactive Greeting Resource + operationId: hello + responses: + "200": + description: OK + content: + text/plain: + schema: + type: string + /messaging/topics: + get: + tags: + - Quarkus Topics Information Resource + responses: + "200": + description: OK +components: + schemas: + Type: + type: object + properties: + tags: + uniqueItems: true + type: array + items: + type: string + CloudEvent: + type: object + properties: + specVersion: + $ref: "#/components/schemas/SpecVersion" + id: + type: string + type: + type: string + source: + format: uri + type: string + dataContentType: + type: string + dataSchema: + format: uri + type: string + subject: + type: string + time: + format: date-time + type: string + attributeNames: + uniqueItems: true + type: array + items: + type: string + extensionNames: + uniqueItems: true + type: array + items: + type: string + data: + $ref: "#/components/schemas/CloudEventData" + CloudEventData: + type: object + EntityTag: + type: object + properties: + value: + type: string + weak: + type: boolean + Family: + enum: + - INFORMATIONAL + - SUCCESSFUL + - REDIRECTION + - CLIENT_ERROR + - SERVER_ERROR + - OTHER + type: string + Link: + type: object + properties: + uri: + format: uri + type: string + uriBuilder: + $ref: "#/components/schemas/UriBuilder" + rel: + type: string + rels: + type: array + items: + type: string + title: + type: string + type: + type: string + params: + type: object + additionalProperties: + type: string + Locale: + type: object + properties: + language: + type: string + script: + type: string + country: + type: string + variant: + type: string + extensionKeys: + uniqueItems: true + type: array + items: + format: byte + type: string + unicodeLocaleAttributes: + uniqueItems: true + type: array + items: + type: string + unicodeLocaleKeys: + uniqueItems: true + type: array + items: + type: string + iSO3Language: + type: string + iSO3Country: + type: string + displayLanguage: + type: string + displayScript: + type: string + displayCountry: + type: string + displayVariant: + type: string + displayName: + type: string + MediaType: + type: object + properties: + type: + type: string + subtype: + type: string + parameters: + type: object + additionalProperties: + type: string + wildcardType: + type: boolean + wildcardSubtype: + type: boolean + MultivaluedMapStringObject: + type: object + additionalProperties: + type: array + items: + type: object + MultivaluedMapStringString: + type: object + additionalProperties: + type: array + items: + type: string + NewCookie: + type: object + properties: + name: + type: string + value: + type: string + version: + format: int32 + type: integer + path: + type: string + domain: + type: string + comment: + type: string + maxAge: + format: int32 + type: integer + expiry: + format: date + type: string + secure: + type: boolean + httpOnly: + type: boolean + Response: + type: object + properties: + status: + format: int32 + type: integer + statusInfo: + $ref: "#/components/schemas/StatusType" + entity: + type: object + mediaType: + $ref: "#/components/schemas/MediaType" + language: + $ref: "#/components/schemas/Locale" + length: + format: int32 + type: integer + allowedMethods: + uniqueItems: true + type: array + items: + type: string + cookies: + type: object + additionalProperties: + $ref: "#/components/schemas/NewCookie" + entityTag: + $ref: "#/components/schemas/EntityTag" + date: + format: date + type: string + lastModified: + format: date + type: string + location: + format: uri + type: string + links: + uniqueItems: true + type: array + items: + $ref: "#/components/schemas/Link" + metadata: + $ref: "#/components/schemas/MultivaluedMapStringObject" + headers: + $ref: "#/components/schemas/MultivaluedMapStringObject" + stringHeaders: + $ref: "#/components/schemas/MultivaluedMapStringString" + SpecVersion: + enum: + - V03 + - V1 + type: string + StatusType: + type: object + properties: + statusCode: + format: int32 + type: integer + family: + $ref: "#/components/schemas/Family" + reasonPhrase: + type: string + UriBuilder: + type: object diff --git a/cli/pkg/specs/testdata/hello.sw.json b/cli/pkg/specs/testdata/hello.sw.json new file mode 100644 index 00000000..bb79e3ff --- /dev/null +++ b/cli/pkg/specs/testdata/hello.sw.json @@ -0,0 +1,33 @@ +{ + "id": "helloworldjson", + "version": "1.0", + "specVersion": "0.8", + "name": "Hello World Workflow", + "description": "JSON based hello world workflow", + "start": "Inject Hello World SubFlow", + "functions": [ + { + "name": "HelloFunction", + "operation": "specs/greetingAPI.yaml#hello", + "type": "rest" + } + ], + "states": [ + { + "name": "Inject Hello World SubFlow", + "type": "inject", + "data": { + "greeting-subflow": "Hello World SubFlow" + }, + "transition": "Inject Mantra SubFlow" + }, + { + "name": "Inject Mantra SubFlow", + "type": "inject", + "data": { + "mantra-subflow": "SubFlow Serverless Workflow is awesome!" + }, + "end": true + } + ] +} diff --git a/cli/pkg/specs/testdata/hello.sw.yaml b/cli/pkg/specs/testdata/hello.sw.yaml new file mode 100644 index 00000000..89577531 --- /dev/null +++ b/cli/pkg/specs/testdata/hello.sw.yaml @@ -0,0 +1,12 @@ +id: helloworldyaml +version: "1.0.0" +specVersion: "0.8" +name: Hello World Workflow +description: Inject Hello World +start: Hello State +states: + - name: Hello State + type: inject + data: + result: Hello World YAML SubFlow! Eder + end: true diff --git a/cli/pkg/specs/testdata/refs/emptyworkflow.sw.yaml b/cli/pkg/specs/testdata/refs/emptyworkflow.sw.yaml new file mode 100644 index 00000000..bd4c84d6 --- /dev/null +++ b/cli/pkg/specs/testdata/refs/emptyworkflow.sw.yaml @@ -0,0 +1,29 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/cli/pkg/specs/testdata/refs/openapi-subflow34.expected.yaml b/cli/pkg/specs/testdata/refs/openapi-subflow34.expected.yaml new file mode 100644 index 00000000..2e9cf85c --- /dev/null +++ b/cli/pkg/specs/testdata/refs/openapi-subflow34.expected.yaml @@ -0,0 +1,115 @@ +info: + title: Example API + version: 1.0.0 +openapi: 3.0.3 +paths: + /items: + get: + operationId: getItems + parameters: + - in: query + name: filter + schema: + $ref: "#/components/schemas/FilterSchema" + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Item" + type: array + description: Successful response + "201": + description: Created + links: + GetItemById: + $ref: "#/components/links/GetItemById" + summary: Get a list of items + tags: + - items + /orders: + get: + operationId: getOrders + parameters: + - $ref: "#/components/parameters/OrderId" + - $ref: "#/components/parameters/Limit" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/OrdersList" + description: List of orders + headers: + X-Rate-Limit-Remaining: + $ref: "#/components/headers/RateLimitHeader" + summary: Get a list of orders +components: + parameters: + Limit: + description: Record limit + in: query + name: limit + schema: + type: integer + OrderId: + description: Order identifier + in: query + name: orderId + required: true + schema: + type: string + schemas: + FilterSchema: + properties: + category: + type: string + status: + type: string + type: object + Item: + properties: + id: + type: string + name: + type: string + type: object + Order: + type: object + properties: + id: + type: string + status: + type: string + OrdersList: + type: array + items: + $ref: "#/components/schemas/Order" + links: + GetItemById: + operationId: getItem + parameters: + itemId: $response.body#/id + headers: + RateLimitHeader: + description: Remaining request limit + schema: + type: integer + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2Security: + type: oauth2 + flows: + authorizationCode: + authorizationUrl: https://example.com/oauth/authorize + tokenUrl: https://example.com/oauth/token + scopes: + read: Read access + write: Write access +tags: + - description: Item operations + name: items diff --git a/cli/pkg/specs/testdata/refs/openapi.expected.yaml b/cli/pkg/specs/testdata/refs/openapi.expected.yaml new file mode 100644 index 00000000..83ff5bd3 --- /dev/null +++ b/cli/pkg/specs/testdata/refs/openapi.expected.yaml @@ -0,0 +1,186 @@ +info: + title: Example API + version: 1.0.0 +openapi: 3.0.3 +paths: + /items: + get: + operationId: getItems + parameters: + - in: query + name: filter + schema: + $ref: "#/components/schemas/FilterSchema" + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Item" + type: array + description: Successful response + "201": + description: Created + links: + GetItemById: + $ref: "#/components/links/GetItemById" + summary: Get a list of items + tags: + - items + /orders: + get: + operationId: getOrders + parameters: + - $ref: "#/components/parameters/OrderId" + - $ref: "#/components/parameters/Limit" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/OrdersList" + description: List of orders + headers: + X-Rate-Limit-Remaining: + $ref: "#/components/headers/RateLimitHeader" + summary: Get a list of orders + post: + operationId: createOrder + requestBody: + $ref: "#/components/requestBodies/CreateOrderRequest" + responses: + "201": + $ref: "#/components/responses/OrderCreatedResponse" + summary: Create a new order + /orders/{orderId}: + get: + operationId: getOrder + parameters: + - in: path + name: orderId + required: true + schema: + type: string + responses: + "200": + $ref: "#/components/responses/OrderResponse" + "404": + $ref: "#/components/responses/NotFoundResponse" + summary: Get order information +components: + examples: + OrderExample: + summary: Order example + value: + id: "12345" + status: processing + headers: + RateLimitHeader: + description: Remaining request limit + schema: + type: integer + links: + GetItemById: + operationId: getItem + parameters: + itemId: $response.body#/id + parameters: + Limit: + description: Record limit + in: query + name: limit + schema: + type: integer + OrderId: + description: Order identifier + in: query + name: orderId + required: true + schema: + type: string + requestBodies: + CreateOrderRequest: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateOrderSchema" + description: Data to create a new order + responses: + NotFoundResponse: + content: + application/json: + schema: + properties: + error: + type: string + type: object + description: Resource not found + OrderCreatedResponse: + content: + application/json: + schema: + properties: + id: + type: string + type: object + description: Order created + OrderResponse: + content: + application/json: + examples: + orderExample: + $ref: "#/components/examples/OrderExample" + schema: + $ref: "#/components/schemas/Order" + description: Order information + schemas: + CreateOrderSchema: + properties: + productId: + type: string + quantity: + type: integer + type: object + FilterSchema: + properties: + category: + type: string + status: + type: string + type: object + Item: + properties: + id: + type: string + name: + type: string + type: object + Order: + properties: + id: + type: string + status: + type: string + type: object + OrdersList: + items: + $ref: "#/components/schemas/Order" + type: array + securitySchemes: + ApiKeyAuth: + in: header + name: X-API-Key + type: apiKey + OAuth2Security: + flows: + authorizationCode: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + write: Write access + tokenUrl: https://example.com/oauth/token + type: oauth2 +tags: + - description: Item operations + name: items diff --git a/cli/pkg/specs/testdata/refs/openapi.yaml b/cli/pkg/specs/testdata/refs/openapi.yaml new file mode 100644 index 00000000..a8370ccf --- /dev/null +++ b/cli/pkg/specs/testdata/refs/openapi.yaml @@ -0,0 +1,320 @@ +openapi: 3.0.3 +info: + title: Example API + version: 1.0.0 + +paths: + # 1. $ref within a parameter using a schema + /items: + get: + summary: Get a list of items + operationId: getItems + tags: + - items + parameters: + - name: filter + in: query + schema: + $ref: "#/components/schemas/FilterSchema" + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Item" + "201": + description: Created + links: + GetItemById: + $ref: "#/components/links/GetItemById" + + # 2. $ref within response headers + /orders: + get: + summary: Get a list of orders + operationId: getOrders + parameters: + - $ref: "#/components/parameters/OrderId" + - $ref: "#/components/parameters/Limit" + responses: + "200": + description: List of orders + headers: + X-Rate-Limit-Remaining: + $ref: "#/components/headers/RateLimitHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/OrdersList" + post: + summary: Create a new order + operationId: createOrder + requestBody: + $ref: "#/components/requestBodies/CreateOrderRequest" + responses: + "201": + $ref: "#/components/responses/OrderCreatedResponse" + + # 3. $ref within the request body schema + /orders/{orderId}: + get: + summary: Get order information + operationId: getOrder + parameters: + - name: orderId + in: path + required: true + schema: + type: string + responses: + "200": + $ref: "#/components/responses/OrderResponse" + "404": + $ref: "#/components/responses/NotFoundResponse" + + # 4. $ref for examples in responses + put: + summary: Update an order + operationId: updateOrder + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateOrder" + examples: + updateExample: + $ref: "#/components/examples/UpdateOrderExample" + responses: + "200": + $ref: "#/components/responses/OrderResponse" + + # 5. $ref within callbacks + /webhooks: + post: + summary: Set a webhook + operationId: setWebhook + requestBody: + $ref: "#/components/requestBodies/WebhookRequest" + responses: + "201": + description: Webhook set + callbacks: + onEvent: + $ref: "#/components/callbacks/EventCallback" + + # 7. Using security schemes + /secure-data: + get: + summary: Get secure data + operationId: getSecureData + security: + - ApiKeyAuth: [] + - OAuth2Security: ["read", "write"] + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: object + properties: + data: + type: string + +components: + # 1. Schemas used in parameters and request bodies + schemas: + FilterSchema: + type: object + properties: + status: + type: string + category: + type: string + Item: + type: object + properties: + id: + type: string + name: + type: string + CreateOrderSchema: + type: object + properties: + productId: + type: string + quantity: + type: integer + UpdateOrder: + type: object + properties: + status: + type: string + Order: + type: object + properties: + id: + type: string + status: + type: string + OrdersList: + type: array + items: + $ref: "#/components/schemas/Order" + Event: + type: object + properties: + event: + type: string + + # 2. Parameters + parameters: + OrderId: + name: orderId + in: query + description: Order identifier + required: true + schema: + type: string + Limit: + name: limit + in: query + description: Record limit + required: false + schema: + type: integer + + # 3. Headers + headers: + RateLimitHeader: + description: Remaining request limit + schema: + type: integer + + # 4. Request bodies + requestBodies: + CreateOrderRequest: + description: Data to create a new order + content: + application/json: + schema: + $ref: "#/components/schemas/CreateOrderSchema" + WebhookRequest: + description: Data to set a webhook + content: + application/json: + schema: + type: object + properties: + url: + type: string + + # 6. Responses + responses: + OrderCreatedResponse: + description: Order created + content: + application/json: + schema: + type: object + properties: + id: + type: string + OrderCreatedResponseWithLink: + description: Order created + content: + application/json: + schema: + type: object + properties: + id: + type: string + links: + GetOrderById: + $ref: "#/components/links/GetOrderById" + OrdersResponse: + description: List of orders + headers: + X-Rate-Limit-Remaining: + $ref: "#/components/headers/RateLimitHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/OrdersList" + OrderResponse: + description: Order information + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + examples: + orderExample: + $ref: "#/components/examples/OrderExample" + NotFoundResponse: + description: Resource not found + content: + application/json: + schema: + type: object + properties: + error: + type: string + + # 7. Examples for responses + examples: + OrderExample: + summary: Order example + value: + id: "12345" + status: "processing" + UpdateOrderExample: + summary: Order update example + value: + status: "shipped" + + # 8. Callbacks + callbacks: + EventCallback: + "{$request.body#/callbackUrl}": + post: + summary: Event received + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Event" + responses: + "200": + description: Event processed + + # 9. Links + links: + GetOrderById: + operationId: getOrder + parameters: + orderId: "$response.body#/id" + GetItemById: + operationId: getItem + parameters: + itemId: "$response.body#/id" + # 10. Security schemes + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2Security: + type: oauth2 + flows: + authorizationCode: + authorizationUrl: https://example.com/oauth/authorize + tokenUrl: https://example.com/oauth/token + scopes: + read: Read access + write: Write access +tags: + - name: items + description: Item operations diff --git a/cli/pkg/specs/testdata/refs/openapi1.expected.yaml b/cli/pkg/specs/testdata/refs/openapi1.expected.yaml new file mode 100644 index 00000000..7d5905f2 --- /dev/null +++ b/cli/pkg/specs/testdata/refs/openapi1.expected.yaml @@ -0,0 +1,42 @@ +openapi: 3.0.3 +info: + title: Example API + version: 1.0.0 + +paths: + /items: + get: + summary: Get a list of items + operationId: getItems + tags: + - items + parameters: + - name: filter + in: query + schema: + $ref: "#/components/schemas/FilterSchema" + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Item" +components: + schemas: + Item: + type: object + properties: + id: + type: integer + name: + type: string + FilterSchema: + type: object + properties: + type: + type: string + description: + type: string diff --git a/cli/pkg/specs/testdata/refs/openapi1.yaml b/cli/pkg/specs/testdata/refs/openapi1.yaml new file mode 100644 index 00000000..3f87553a --- /dev/null +++ b/cli/pkg/specs/testdata/refs/openapi1.yaml @@ -0,0 +1,75 @@ +openapi: 3.0.3 +info: + title: Example API + version: 1.0.0 + +paths: + /items: + get: + summary: Get a list of items + operationId: getItems + tags: + - items + parameters: + - name: filter + in: query + schema: + $ref: "#/components/schemas/FilterSchema" + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Item" +components: + schemas: + Item: + type: object + properties: + id: + type: integer + name: + type: string + FilterSchema: + type: object + properties: + type: + type: string + description: + type: string + OrdersList: + type: object + properties: + orders: + type: array + items: + $ref: "#/components/schemas/Order" + Order: + type: object + properties: + id: + type: integer + + Jars: + type: object + properties: + jarid: + type: string + jarname: + type: string + jarversion: + type: string + Message: + type: object + properties: + message: + type: string + requestBodies: + HelloWorld: + content: + application/json: + schema: + $ref: "#/components/schemas/Message" diff --git a/cli/pkg/specs/testdata/refs/openapi2.expected.yaml b/cli/pkg/specs/testdata/refs/openapi2.expected.yaml new file mode 100644 index 00000000..2d2ba094 --- /dev/null +++ b/cli/pkg/specs/testdata/refs/openapi2.expected.yaml @@ -0,0 +1,36 @@ +openapi: 3.0.3 +info: + title: Example API + version: 1.0.0 + +paths: + /orders: + get: + summary: Get a list of items + operationId: getOrders + responses: + "200": + description: List of orders + content: + application/json: + schema: + $ref: "#/components/schemas/OrdersList" +components: + schemas: + OrdersList: + type: object + properties: + orders: + type: array + items: + $ref: "#/components/schemas/Order" + Order: + type: object + properties: + id: + type: integer + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key diff --git a/cli/pkg/specs/testdata/refs/openapi2.yaml b/cli/pkg/specs/testdata/refs/openapi2.yaml new file mode 100644 index 00000000..9365ac5a --- /dev/null +++ b/cli/pkg/specs/testdata/refs/openapi2.yaml @@ -0,0 +1,64 @@ +openapi: 3.0.3 +info: + title: Example API + version: 1.0.0 + +paths: + /orders: + get: + summary: Get a list of items + operationId: getOrders + responses: + "200": + description: List of orders + content: + application/json: + schema: + $ref: "#/components/schemas/OrdersList" + +components: + schemas: + OrdersList: + type: object + properties: + orders: + type: array + items: + $ref: "#/components/schemas/Order" + Order: + type: object + properties: + id: + type: integer + parameters: + OrderId: + name: orderId + in: path + required: true + schema: + type: integer + Limit: + name: limit + in: query + required: false + schema: + type: integer + links: + GetItemById: + operationId: getItemById + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + requestBodies: + CreateOrderRequest: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + headers: + RateLimitHeader: + description: Rate limit remaining + schema: + type: integer diff --git a/cli/pkg/specs/testdata/refs/subflow1.sw.yaml b/cli/pkg/specs/testdata/refs/subflow1.sw.yaml new file mode 100644 index 00000000..3a034500 --- /dev/null +++ b/cli/pkg/specs/testdata/refs/subflow1.sw.yaml @@ -0,0 +1,21 @@ +--- +id: "helloworldyaml1" +version: "1.0" +specVersion: "0.8" +name: "Hello World Workflow" +description: "JSON based hello world workflow" +start: "Inject Hello World SubFlow" +functions: + - name: getItems + operation: custom_specs/openapi1.yaml#getItems +states: + - name: "Inject Hello World SubFlow" + type: "inject" + data: + greeting-subflow: "Hello World SubFlow" + transition: "Inject Mantra SubFlow" + - name: "Inject Mantra SubFlow" + type: "inject" + data: + mantra-subflow: "SubFlow Serverless Workflow is awesome!" + end: true diff --git a/cli/pkg/specs/testdata/refs/subflow2.sw.yaml b/cli/pkg/specs/testdata/refs/subflow2.sw.yaml new file mode 100644 index 00000000..4131ae2e --- /dev/null +++ b/cli/pkg/specs/testdata/refs/subflow2.sw.yaml @@ -0,0 +1,21 @@ +--- +id: "helloworldyaml1" +version: "1.0" +specVersion: "0.8" +name: "Hello World Workflow" +description: "JSON based hello world workflow" +start: "Inject Hello World SubFlow" +functions: + - name: getOrders + operation: custom_specs/openapi2.yaml#getOrders +states: + - name: "Inject Hello World SubFlow" + type: "inject" + data: + greeting-subflow: "Hello World SubFlow" + transition: "Inject Mantra SubFlow" + - name: "Inject Mantra SubFlow" + type: "inject" + data: + mantra-subflow: "SubFlow Serverless Workflow is awesome!" + end: true diff --git a/cli/pkg/specs/testdata/refs/subflow3.sw.yaml b/cli/pkg/specs/testdata/refs/subflow3.sw.yaml new file mode 100644 index 00000000..107c96a3 --- /dev/null +++ b/cli/pkg/specs/testdata/refs/subflow3.sw.yaml @@ -0,0 +1,21 @@ +--- +id: "helloworldyaml1" +version: "1.0" +specVersion: "0.8" +name: "Hello World Workflow" +description: "JSON based hello world workflow" +start: "Inject Hello World SubFlow" +functions: + - name: getItems + operation: specs/openapi.yaml#getItems +states: + - name: "Inject Hello World SubFlow" + type: "inject" + data: + greeting-subflow: "Hello World SubFlow" + transition: "Inject Mantra SubFlow" + - name: "Inject Mantra SubFlow" + type: "inject" + data: + mantra-subflow: "SubFlow Serverless Workflow is awesome!" + end: true diff --git a/cli/pkg/specs/testdata/refs/subflow4.sw.yaml b/cli/pkg/specs/testdata/refs/subflow4.sw.yaml new file mode 100644 index 00000000..7c854e30 --- /dev/null +++ b/cli/pkg/specs/testdata/refs/subflow4.sw.yaml @@ -0,0 +1,21 @@ +--- +id: "helloworldyaml1" +version: "1.0" +specVersion: "0.8" +name: "Hello World Workflow" +description: "JSON based hello world workflow" +start: "Inject Hello World SubFlow" +functions: + - name: getOrders + operation: specs/openapi.yaml#getOrders +states: + - name: "Inject Hello World SubFlow" + type: "inject" + data: + greeting-subflow: "Hello World SubFlow" + transition: "Inject Mantra SubFlow" + - name: "Inject Mantra SubFlow" + type: "inject" + data: + mantra-subflow: "SubFlow Serverless Workflow is awesome!" + end: true diff --git a/cli/pkg/specs/testdata/refs/workflow.sw.yaml b/cli/pkg/specs/testdata/refs/workflow.sw.yaml new file mode 100644 index 00000000..566d18e4 --- /dev/null +++ b/cli/pkg/specs/testdata/refs/workflow.sw.yaml @@ -0,0 +1,38 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: getItems + operation: specs/openapi.yaml#getItems + - name: getOrders + operation: specs/openapi.yaml#getOrders + - name: createOrder + operation: specs/openapi.yaml#createOrder + - name: getOrder + operation: specs/openapi.yaml#getOrder +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/cli/pkg/specs/testdata/refs/workflow1.sw.yaml b/cli/pkg/specs/testdata/refs/workflow1.sw.yaml new file mode 100644 index 00000000..6e7f3233 --- /dev/null +++ b/cli/pkg/specs/testdata/refs/workflow1.sw.yaml @@ -0,0 +1,34 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: getItems + operation: specs/openapi1.yaml#getItems + - name: getOrders + operation: specs/openapi2.yaml#getOrders +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/cli/pkg/specs/testdata/subflow-custom.sw.json b/cli/pkg/specs/testdata/subflow-custom.sw.json new file mode 100644 index 00000000..aff6964b --- /dev/null +++ b/cli/pkg/specs/testdata/subflow-custom.sw.json @@ -0,0 +1,33 @@ +{ + "id": "helloworldjson", + "version": "1.0", + "specVersion": "0.8", + "name": "Hello World Workflow", + "description": "JSON based hello world workflow", + "start": "Inject Hello World SubFlow", + "functions": [ + { + "name": "HelloFunction", + "operation": "custom_specs/flink-openapi.yaml#getJarList", + "type": "rest" + } + ], + "states": [ + { + "name": "Inject Hello World SubFlow", + "type": "inject", + "data": { + "greeting-subflow": "Hello World SubFlow" + }, + "transition": "Inject Mantra SubFlow" + }, + { + "name": "Inject Mantra SubFlow", + "type": "inject", + "data": { + "mantra-subflow": "SubFlow Serverless Workflow is awesome!" + }, + "end": true + } + ] +} diff --git a/cli/pkg/specs/testdata/subflow-custom.sw.yaml b/cli/pkg/specs/testdata/subflow-custom.sw.yaml new file mode 100644 index 00000000..2dce01fa --- /dev/null +++ b/cli/pkg/specs/testdata/subflow-custom.sw.yaml @@ -0,0 +1,22 @@ +--- +id: "helloworldyaml" +version: "1.0" +specVersion: "0.8" +name: "Hello World Workflow" +description: "JSON based hello world workflow" +start: "Inject Hello World SubFlow" +functions: + - name: "HelloFunction" + operation: "custom_specs/flink-openapi.yaml#submitJobFromJar" + type: "rest" +states: + - name: "Inject Hello World SubFlow" + type: "inject" + data: + greeting-subflow: "Hello World SubFlow" + transition: "Inject Mantra SubFlow" + - name: "Inject Mantra SubFlow" + type: "inject" + data: + mantra-subflow: "SubFlow Serverless Workflow is awesome!" + end: true diff --git a/cli/pkg/specs/testdata/subflow-mySpecsDir.sw.yaml b/cli/pkg/specs/testdata/subflow-mySpecsDir.sw.yaml new file mode 100644 index 00000000..d56c3e79 --- /dev/null +++ b/cli/pkg/specs/testdata/subflow-mySpecsDir.sw.yaml @@ -0,0 +1,30 @@ +id: fraudhandling +name: Fraud Handling +expressionLang: jsonpath +start: FraudHandling +version: "1.0" +events: + - kind: produced + name: FraudEvaluation + type: fraudEvaluation + source: fraudEvaluation +functions: + - name: getFlinkJobs + operation: mySpecsDir/flink-openapi.yaml#getJarList + - name: runFlinkJob + operation: mySpecsDir/flink-openapi.yaml#submitJobFromJar +states: + - name: FraudHandling + type: switch + dataConditions: + - condition: "{{ $.[?(@.total > 1000)] }}" + transition: FraudVerificationNeeded + - condition: "{{ $.[?(@.total <= 1000)] }}" + end: true + - name: FraudVerificationNeeded + type: inject + data: + fraudEvaluation: true + end: + produceEvents: + - eventRef: FraudEvaluation diff --git a/cli/pkg/specs/testdata/subflow.sw.yaml b/cli/pkg/specs/testdata/subflow.sw.yaml new file mode 100644 index 00000000..776924c8 --- /dev/null +++ b/cli/pkg/specs/testdata/subflow.sw.yaml @@ -0,0 +1,30 @@ +id: fraudhandling +name: Fraud Handling +expressionLang: jsonpath +start: FraudHandling +version: "1.0" +events: + - kind: produced + name: FraudEvaluation + type: fraudEvaluation + source: fraudEvaluation +functions: + - name: getFlinkJobs + operation: specs/flink-openapi.yaml#getJarList + - name: runFlinkJob + operation: specs/flink-openapi.yaml#submitJobFromJar +states: + - name: FraudHandling + type: switch + dataConditions: + - condition: "{{ $.[?(@.total > 1000)] }}" + transition: FraudVerificationNeeded + - condition: "{{ $.[?(@.total <= 1000)] }}" + end: true + - name: FraudVerificationNeeded + type: inject + data: + fraudEvaluation: true + end: + produceEvents: + - eventRef: FraudEvaluation diff --git a/cli/pkg/specs/testdata/workflow-empty.sw.yaml b/cli/pkg/specs/testdata/workflow-empty.sw.yaml new file mode 100644 index 00000000..111322b3 --- /dev/null +++ b/cli/pkg/specs/testdata/workflow-empty.sw.yaml @@ -0,0 +1,33 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/cli/pkg/specs/testdata/workflow-empty2.sw.yaml b/cli/pkg/specs/testdata/workflow-empty2.sw.yaml new file mode 100644 index 00000000..bd4c84d6 --- /dev/null +++ b/cli/pkg/specs/testdata/workflow-empty2.sw.yaml @@ -0,0 +1,29 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/cli/pkg/specs/testdata/workflow-greeting.sw.yaml b/cli/pkg/specs/testdata/workflow-greeting.sw.yaml new file mode 100644 index 00000000..76feeb20 --- /dev/null +++ b/cli/pkg/specs/testdata/workflow-greeting.sw.yaml @@ -0,0 +1,18 @@ +id: subworkflows +version: "1.0.0" +specVersion: "0.8" +name: Parallel Execution Workflow +description: Executes two branches in parallel +start: ParallelExec +states: + - name: ParallelExec + type: parallel + completionType: allOf + branches: + - name: Sub1 + actions: + - subFlowRef: helloworldyaml + - name: Sub2 + actions: + - subFlowRef: helloworldjson + end: true diff --git a/cli/pkg/specs/testdata/workflow-json-openapi.sw.json b/cli/pkg/specs/testdata/workflow-json-openapi.sw.json new file mode 100644 index 00000000..600c518e --- /dev/null +++ b/cli/pkg/specs/testdata/workflow-json-openapi.sw.json @@ -0,0 +1,70 @@ +{ + "id": "flink-workflow", + "version": "1.0", + "specVersion": "0.8", + "name": "flink workflow", + "description": "Create a starter flink job management", + "functions": [ + { + "name": "getFlinkJobs", + "operation": "specs/flink-openapi-json.json#getJarList" + }, + { + "name": "runFlinkJob", + "operation": "specs/flink-openapi-json.json#submitJobFromJar" + }, + { + "name": "stopFlinkJob", + "operation": "specs/flink-openapi-json.json#cancelJob" + }, + { + "name": "getJars", + "operation": "specs/flink-openapi-json.json#getJarList" + }, + { + "name": "sysout", + "type": "custom", + "operation": "sysout" + } + ], + "start": "Get Flink Jars", + "states": [ + { + "name": "Get Flink Jars", + "type": "operation", + "actionMode": "sequential", + "actions": [ + { + "name": "Get Flink Jars", + "functionRef": { + "refName": "getJars" + } + } + ], + "transition": "Run Flink Job" + }, + { + "name": "Run Flink Job", + "type": "operation", + "actionMode": "sequential", + "actions": [ + { + "actionDataFilter": { + "useResults": true + }, + "name": "Run Flink Job", + "functionRef": { + "refName": "runFlinkJob", + "arguments": { + "jarid": "72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar", + "entry-class": "com.demo.flink.streaming.StreamingJob" + } + } + } + ], + "end": { + "terminate": true + } + } + ] +} diff --git a/cli/pkg/specs/testdata/workflow-mySpecsDir-one-finction.sw.yaml b/cli/pkg/specs/testdata/workflow-mySpecsDir-one-finction.sw.yaml new file mode 100644 index 00000000..5fdb58a6 --- /dev/null +++ b/cli/pkg/specs/testdata/workflow-mySpecsDir-one-finction.sw.yaml @@ -0,0 +1,35 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: getFlinkJobs + operation: mySpecsDir/flink-openapi.yaml#getJarList + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/cli/pkg/specs/testdata/workflow-mySpecsDir.sw.yaml b/cli/pkg/specs/testdata/workflow-mySpecsDir.sw.yaml new file mode 100644 index 00000000..4aa6d2ca --- /dev/null +++ b/cli/pkg/specs/testdata/workflow-mySpecsDir.sw.yaml @@ -0,0 +1,41 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: getFlinkJobs + operation: mySpecsDir/flink-openapi.yaml#getJarList + - name: runFlinkJob + operation: mySpecsDir/flink-openapi.yaml#submitJobFromJar + - name: stopFlinkJob + operation: mySpecsDir/flink-openapi.yaml#cancelJob + - name: getJars + operation: mySpecsDir/flink-openapi.yaml#getJarList + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/cli/pkg/specs/testdata/workflow-subflow-custom.sw.yaml b/cli/pkg/specs/testdata/workflow-subflow-custom.sw.yaml new file mode 100644 index 00000000..ac153498 --- /dev/null +++ b/cli/pkg/specs/testdata/workflow-subflow-custom.sw.yaml @@ -0,0 +1,35 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: stopFlinkJob + operation: custom_specs/flink-openapi.yaml#cancelJob + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/cli/pkg/specs/testdata/workflow.sw.json b/cli/pkg/specs/testdata/workflow.sw.json new file mode 100644 index 00000000..54a0ed68 --- /dev/null +++ b/cli/pkg/specs/testdata/workflow.sw.json @@ -0,0 +1,70 @@ +{ + "id": "flink-workflow", + "version": "1.0", + "specVersion": "0.8", + "name": "flink workflow", + "description": "Create a starter flink job management", + "functions": [ + { + "name": "getFlinkJobs", + "operation": "specs/flink-openapi.yaml#getJarList" + }, + { + "name": "runFlinkJob", + "operation": "specs/flink-openapi.yaml#submitJobFromJar" + }, + { + "name": "stopFlinkJob", + "operation": "specs/flink-openapi.yaml#cancelJob" + }, + { + "name": "getJars", + "operation": "specs/flink-openapi.yaml#getJarList" + }, + { + "name": "sysout", + "type": "custom", + "operation": "sysout" + } + ], + "start": "Get Flink Jars", + "states": [ + { + "name": "Get Flink Jars", + "type": "operation", + "actionMode": "sequential", + "actions": [ + { + "name": "Get Flink Jars", + "functionRef": { + "refName": "getJars" + } + } + ], + "transition": "Run Flink Job" + }, + { + "name": "Run Flink Job", + "type": "operation", + "actionMode": "sequential", + "actions": [ + { + "actionDataFilter": { + "useResults": true + }, + "name": "Run Flink Job", + "functionRef": { + "refName": "runFlinkJob", + "arguments": { + "jarid": "72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar", + "entry-class": "com.demo.flink.streaming.StreamingJob" + } + } + } + ], + "end": { + "terminate": true + } + } + ] +} diff --git a/cli/pkg/specs/testdata/workflow.sw.yaml b/cli/pkg/specs/testdata/workflow.sw.yaml new file mode 100644 index 00000000..45b28a07 --- /dev/null +++ b/cli/pkg/specs/testdata/workflow.sw.yaml @@ -0,0 +1,41 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: getFlinkJobs + operation: specs/flink-openapi.yaml#getJarList + - name: runFlinkJob + operation: specs/flink-openapi.yaml#submitJobFromJar + - name: stopFlinkJob + operation: specs/flink-openapi.yaml#cancelJob + - name: getJars + operation: specs/flink-openapi.yaml#getJarList + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/cli/pkg/specs/testdata/workflow2.sw.yaml b/cli/pkg/specs/testdata/workflow2.sw.yaml new file mode 100644 index 00000000..3232c6bc --- /dev/null +++ b/cli/pkg/specs/testdata/workflow2.sw.yaml @@ -0,0 +1,41 @@ +id: flink-workflow +version: "1.0" +specVersion: "0.8" +name: flink workflow +description: Create a starter flink job management +functions: + - name: getFlinkJobs + operation: specs/flink1-openapi.yaml#getJarList + - name: runFlinkJob + operation: specs/flink2-openapi.yaml#submitJobFromJar + - name: stopFlinkJob + operation: specs/flink3-openapi.yaml#cancelJob + - name: getJars + operation: specs/flink4-openapi.yaml#getJarList + - name: sysout + type: custom + operation: sysout +start: Get Flink Jars +states: + - name: Get Flink Jars + type: operation + actionMode: sequential + actions: + - name: Get Flink Jars + functionRef: + refName: getJars + transition: Run Flink Job + - name: Run Flink Job + type: operation + actionMode: sequential + actions: + - actionDataFilter: + useResults: true + name: Run Flink Job + functionRef: + refName: runFlinkJob + arguments: + jarid: 72ecfc25-43ca-4f53-a4ee-1aaf93ac709a_flink-streaming-1.0.jar + entry-class: com.demo.flink.streaming.StreamingJob + end: + terminate: true diff --git a/cli/tools/tools.go b/cli/tools/tools.go new file mode 100644 index 00000000..9b151fb4 --- /dev/null +++ b/cli/tools/tools.go @@ -0,0 +1,26 @@ +//go:build tools + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package tools + +import ( + _ "github.com/jstemmer/go-junit-report/v2" +) diff --git a/docs/CLI_E2E_TODO.md b/docs/CLI_E2E_TODO.md new file mode 100644 index 00000000..c48f7a75 --- /dev/null +++ b/docs/CLI_E2E_TODO.md @@ -0,0 +1,136 @@ +# CLI E2E Tests - Integration TODO + +## Current Status + +The CLI has been successfully migrated to the logic-operator repository with all phases complete. However, the CLI e2e test suite currently expects the operator to be installed via OLM (Operator Lifecycle Manager), while our CI workflows deploy the operator directly from source. + +## The Issue + +**CLI e2e test setup (`cli/e2e-tests/main_test.go`):** +- Calls `InstallOperator()` which uses `operator install` command +- This command creates an OLM Subscription to install the operator from operatorhub.io +- Expects operator to be available as a published OLM package + +**Current CI workflow (`cli-e2e.yaml`):** +- Builds operator from source with `make docker-build` +- Loads image into KIND cluster +- Deploys operator directly with `make deploy` (not via OLM) + +## Solutions + +### Option 1: Create Local OLM Catalog (Recommended for CI) + +Build a local OLM catalog with our operator bundle: + +1. Build operator bundle image +2. Create catalog image with our bundle +3. Deploy catalog to cluster +4. Modify CLI's operator install to use local catalog +5. Run existing e2e tests unchanged + +**Pros:** +- Tests operator installation as users would experience it +- No changes to existing test code +- Tests the full OLM integration + +**Cons:** +- More complex CI setup +- Longer build times + +### Option 2: Adapt Tests for Direct Deployment + +Modify CLI e2e tests to work with directly deployed operator: + +1. Add environment variable to skip OLM-based installation +2. Assume operator is already deployed +3. Modify `InstallOperator()` to check for existing deployment instead +4. Update tests to work with pre-deployed operator + +**Pros:** +- Simpler CI setup +- Faster test execution +- Works with development workflows + +**Cons:** +- Doesn't test OLM installation path +- Requires test code changes + +### Option 3: Hybrid Approach + +Run two separate test suites: + +1. **Unit + Integration tests**: Deploy operator directly, run non-OLM tests +2. **Full E2E with OLM**: Separate workflow with OLM catalog setup + +**Pros:** +- Best of both worlds +- Fast feedback from unit/integration tests +- Full OLM coverage in dedicated workflow + +**Cons:** +- More complex workflow setup +- Higher maintenance burden + +## Recommendation + +Start with **Option 2** (adapt tests) for fast iteration, then add **Option 1** (OLM catalog) as a separate nightly/release workflow. + +## Test Categories + +Current CLI e2e tests: + +- `TestCreateProjectSuccess` - ✅ Works without operator +- `TestCreateProjectFail` - ✅ Works without operator +- `TestQuarkusCreateProjectSuccess` - ✅ Works without operator +- `TestQuarkusCreateProjectFail` - ✅ Works without operator +- `TestQuarkusConvertProjectSuccess` - ✅ Works without operator +- `TestQuarkusConvertProjectFailed` - ✅ Works without operator +- `TestQuarkusBuildCommand` - ⚠️ Requires Docker +- `TestRunCommand` - ⚠️ Requires Docker +- `TestQuarkusRunCommand` - ⚠️ Requires Docker +- `TestGenManifestProjectSuccess` - ✅ Works without operator +- `TestDeployProjectSuccess` - ❌ **Requires operator** +- `TestDeployProjectSuccessWithImageDefined` - ❌ **Requires operator** +- `TestDeployProjectSuccessWithoutResultEventRef` - ❌ **Requires operator** + +**Strategy:** +1. Short term: Run tests that don't need operator (create, gen-manifest, convert) +2. Medium term: Add Docker-in-Docker for build/run tests +3. Long term: Set up OLM catalog for deploy tests + +## Implementation Steps + +### Phase 1: Non-Operator Tests (Immediate) + +```yaml +- name: Run CLI e2e tests (non-operator) + working-directory: cli + run: | + go test -v ./e2e-tests/... -tags e2e_tests \ + -run "TestCreate|TestQuarkusCreate|TestQuarkusConvert|TestGenManifest" \ + -timeout 10m +``` + +### Phase 2: Build/Run Tests with Docker (Next) + +- Set up Docker-in-Docker in GitHub Actions +- Enable TestQuarkusBuildCommand, TestRunCommand, TestQuarkusRunCommand + +### Phase 3: Deploy Tests with Operator (Future) + +- Either adapt tests to use pre-deployed operator +- Or set up local OLM catalog with built operator + +## Files to Modify + +- `.github/workflows/cli-e2e.yaml` - Update test execution +- `cli/e2e-tests/main_test.go` - Add conditional operator installation +- `cli/e2e-tests/operator_helper.go` - Support pre-deployed operator mode + +## Success Criteria + +- [ ] All non-operator tests pass in CI +- [ ] Build/run tests pass with Docker setup +- [ ] Deploy tests pass with either approach +- [ ] Tests run in under 30 minutes +- [ ] Clear documentation of test categories and requirements diff --git a/docs/CLI_MIGRATION_METADATA.md b/docs/CLI_MIGRATION_METADATA.md new file mode 100644 index 00000000..b90907fc --- /dev/null +++ b/docs/CLI_MIGRATION_METADATA.md @@ -0,0 +1,90 @@ +# CLI Migration: Current Metadata Values + +**Source:** `apache/incubator-kie-tools/packages/kn-plugin-workflow` +**Date:** 2026-06-22 +**Purpose:** Document current values for migration to logic-operator + +## Build Metadata (Injected via ldflags) + +### Version Information +```go +// pkg/metadata/version.go +var PluginVersion string // Injected from package.json (currently "0.0.0") +``` + +### Image References +```go +// From env/index.js composition +var DevModeImage string // From @kie-tools/sonataflow-devmode-image/env +var BuilderImage string // From @kie-tools/sonataflow-builder-image/env +``` + +**Current image paths (from kie-tools):** +- DevMode: `quay.io/apache/incubator-kie-sonataflow-devmode:main` +- Builder: `quay.io/apache/incubator-kie-sonataflow-builder:main` + +### Quarkus Configuration +```go +// From env/index.js +var QuarkusPlatformGroupId string // Default: io.quarkus.platform +var QuarkusPlatformArtifactId string // Default: quarkus-bom +var QuarkusVersion string // From root-env +``` + +## Migration Strategy + +### New Metadata Structure +```go +// cli/pkg/metadata/metadata.go (NEW) +package metadata + +// Injected at build time via -ldflags +var ( + PluginVersion string // Git tag (e.g., "v2.0.0") + QuarkusVersion string // From root .env + BuilderImage string // From root .env + DevModeImage string // From root .env +) +``` + +### Build Command (NEW) +```makefile +# cli/Makefile +include ../.env + +VERSION ?= $(shell git describe --tags --always) + +LDFLAGS := -X github.com/kubesmarts/logic-operator/cli/pkg/metadata.PluginVersion=$(VERSION) \ + -X github.com/kubesmarts/logic-operator/cli/pkg/metadata.QuarkusVersion=$(QUARKUS_VERSION) \ + -X github.com/kubesmarts/logic-operator/cli/pkg/metadata.BuilderImage=$(BUILDER_IMAGE) \ + -X github.com/kubesmarts/logic-operator/cli/pkg/metadata.DevModeImage=$(DEVMODE_IMAGE) + +build: + go build -ldflags="$(LDFLAGS)" -o bin/kn-workflow cmd/main.go +``` + +### Root .env Values (inherit from parent) +```bash +# From /Users/ricferna/dev/github/kubesmarts/logic-operator/.env + +# Quarkus +QUARKUS_VERSION=3.8.1 + +# Images (updated to kubesmarts registry) +BUILDER_IMAGE=quay.io/kubesmarts/logic-operator-builder:main +DEVMODE_IMAGE=quay.io/kubesmarts/logic-operator-devmode:main + +# Or keep apache images initially: +# BUILDER_IMAGE=quay.io/apache/incubator-kie-sonataflow-builder:main +# DEVMODE_IMAGE=quay.io/apache/incubator-kie-sonataflow-devmode:main +``` + +## Files to Remove +- ❌ `env/index.js` - Replaced by Makefile + .env +- ❌ `package.json` - No longer using pnpm +- ❌ All Node.js build wrapper code + +## Files to Create/Update +- ✅ `cli/pkg/metadata/metadata.go` - Add QuarkusVersion, BuilderImage, DevModeImage vars +- ✅ `cli/Makefile` - Simplified build with ldflags injection +- ✅ `cli/README.md` - Update build instructions diff --git a/go.work b/go.work index eb0eecee..fcbeee40 100644 --- a/go.work +++ b/go.work @@ -3,6 +3,7 @@ go 1.26.0 use ( . ./api + ./cli ./container-builder ./workflowproj ) diff --git a/go.work.sum b/go.work.sum index 4271db21..72b39049 100644 --- a/go.work.sum +++ b/go.work.sum @@ -6,6 +6,8 @@ cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= cel.dev/expr v0.16.0 h1:yloc84fytn4zmJX2GU3TkXGsaieaV7dQ057Qs4sIG2Y= cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= +cel.dev/expr v0.16.2 h1:RwRhoH17VhAu9U5CMvMhH1PDVgf0tuz9FT+24AfMLfU= +cel.dev/expr v0.16.2/go.mod h1:gXngZQMkWJoSbE8mOzehJlXQyubn/Vg0vR9/F3W7iw8= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= @@ -40,6 +42,8 @@ cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0 h1:x0cEHro/JFPd7eS4BlEWNTMecIj2HdXjOVB5BtvwER0= @@ -49,6 +53,8 @@ cloud.google.com/go/accessapproval v1.7.1/go.mod h1:JYczztsHRMK7NTXb6Xw+dwbs/WnO cloud.google.com/go/accessapproval v1.7.4 h1:ZvLvJ952zK8pFHINjpMBY5k7LTAp/6pBf50RDMRgBUI= cloud.google.com/go/accessapproval v1.7.5 h1:uzmAMSgYcnlHa9X9YSQZ4Q1wlfl4NNkZyQgho1Z6p04= cloud.google.com/go/accessapproval v1.7.5/go.mod h1:g88i1ok5dvQ9XJsxpUInWWvUBrIZhyPDPbk4T01OoJ0= +cloud.google.com/go/accessapproval v1.8.2 h1:h4u1MypgeYXTGvnNc1luCBLDN4Kb9Re/gw0Atvoi8HE= +cloud.google.com/go/accessapproval v1.8.2/go.mod h1:aEJvHZtpjqstffVwF/2mCXXSQmpskyzvw6zKLvLutZM= cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= @@ -59,6 +65,8 @@ cloud.google.com/go/accesscontextmanager v1.8.1/go.mod h1:JFJHfvuaTC+++1iL1coPiG cloud.google.com/go/accesscontextmanager v1.8.4 h1:Yo4g2XrBETBCqyWIibN3NHNPQKUfQqti0lI+70rubeE= cloud.google.com/go/accesscontextmanager v1.8.5 h1:2GLNaNu9KRJhJBFTIVRoPwk6xE5mUDgD47abBq4Zp/I= cloud.google.com/go/accesscontextmanager v1.8.5/go.mod h1:TInEhcZ7V9jptGNqN3EzZ5XMhT6ijWxTGjzyETwmL0Q= +cloud.google.com/go/accesscontextmanager v1.9.2 h1:P0uVixQft8aacbZ7VDZStNZdrftF24Hk8JkA3kfvfqI= +cloud.google.com/go/accesscontextmanager v1.9.2/go.mod h1:T0Sw/PQPyzctnkw1pdmGAKb7XBA84BqQzH0fSU7wzJU= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= @@ -74,6 +82,8 @@ cloud.google.com/go/aiplatform v1.54.0 h1:wH7OYl9Vq/5tupok0BPTFY9xaTLb0GxkReHtB5 cloud.google.com/go/aiplatform v1.58.2/go.mod h1:c3kCiVmb6UC1dHAjZjcpDj6ZS0bHQ2slL88ZjC2LtlA= cloud.google.com/go/aiplatform v1.60.0 h1:0cSrii1ZeLr16MbBoocyy5KVnrSdiQ3KN/vtrTe7RqE= cloud.google.com/go/aiplatform v1.60.0/go.mod h1:eTlGuHOahHprZw3Hio5VKmtThIOak5/qy6pzdsqcQnM= +cloud.google.com/go/aiplatform v1.69.0 h1:XvBzK8e6/6ufbi/i129Vmn/gVqFwbNPmRQ89K+MGlgc= +cloud.google.com/go/aiplatform v1.69.0/go.mod h1:nUsIqzS3khlnWvpjfJbP+2+h+VrFyYsTm7RNCAViiY8= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= @@ -85,6 +95,8 @@ cloud.google.com/go/analytics v0.21.3/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N cloud.google.com/go/analytics v0.21.6 h1:fnV7B8lqyEYxCU0LKk+vUL7mTlqRAq4uFlIthIdr/iA= cloud.google.com/go/analytics v0.23.0 h1:Q+y94XH84jM8SK8O7qiY/PJRexb6n7dRbQ6PiUa4YGM= cloud.google.com/go/analytics v0.23.0/go.mod h1:YPd7Bvik3WS95KBok2gPXDqQPHy08TsCQG6CdUCb+u0= +cloud.google.com/go/analytics v0.25.2 h1:KgJ5Taxtsnro/co7WIhmAHi5pzYAtvxu8LMqenPAlSo= +cloud.google.com/go/analytics v0.25.2/go.mod h1:th0DIunqrhI1ZWVlT3PH2Uw/9ANX8YHfFDEPqf/+7xM= cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= cloud.google.com/go/apigateway v1.5.0 h1:ZI9mVO7x3E9RK/BURm2p1aw9YTBSCQe3klmyP1WxWEg= @@ -94,6 +106,8 @@ cloud.google.com/go/apigateway v1.6.1/go.mod h1:ufAS3wpbRjqfZrzpvLC2oh0MFlpRJm2E cloud.google.com/go/apigateway v1.6.4 h1:VVIxCtVerchHienSlaGzV6XJGtEM9828Erzyr3miUGs= cloud.google.com/go/apigateway v1.6.5 h1:sPXnpk+6TneKIrjCjcpX5YGsAKy3PTdpIchoj8/74OE= cloud.google.com/go/apigateway v1.6.5/go.mod h1:6wCwvYRckRQogyDDltpANi3zsCDl6kWi0b4Je+w2UiI= +cloud.google.com/go/apigateway v1.7.2 h1:TRB5q0vvbT5Yx4bNSCWlqLJFJnhc7tDlCR9ccpo1vzg= +cloud.google.com/go/apigateway v1.7.2/go.mod h1:+weId+9aR9J6GRwDka7jIUSrKEX60XGcikX7dGU8O7M= cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= cloud.google.com/go/apigeeconnect v1.5.0 h1:sWOmgDyAsi1AZ48XRHcATC0tsi9SkPT7DA/+VCfkaeA= @@ -103,6 +117,8 @@ cloud.google.com/go/apigeeconnect v1.6.1/go.mod h1:C4awq7x0JpLtrlQCr8AzVIzAaYgng cloud.google.com/go/apigeeconnect v1.6.4 h1:jSoGITWKgAj/ssVogNE9SdsTqcXnryPzsulENSRlusI= cloud.google.com/go/apigeeconnect v1.6.5 h1:CrfIKv9Go3fh/QfQgisU3MeP90Ww7l/sVGmr3TpECo8= cloud.google.com/go/apigeeconnect v1.6.5/go.mod h1:MEKm3AiT7s11PqTfKE3KZluZA9O91FNysvd3E6SJ6Ow= +cloud.google.com/go/apigeeconnect v1.7.2 h1:GHg0ddEQUZ08C1qC780P5wwY/jaIW8UtxuRQXLLuRXs= +cloud.google.com/go/apigeeconnect v1.7.2/go.mod h1:he/SWi3A63fbyxrxD6jb67ak17QTbWjva1TFbT5w8Kw= cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= cloud.google.com/go/apigeeregistry v0.6.0 h1:E43RdhhCxdlV+I161gUY2rI4eOaMzHTA5kNkvRsFXvc= @@ -112,6 +128,8 @@ cloud.google.com/go/apigeeregistry v0.7.1/go.mod h1:1XgyjZye4Mqtw7T9TsY4NW10U7Bo cloud.google.com/go/apigeeregistry v0.8.2 h1:DSaD1iiqvELag+lV4VnnqUUFd8GXELu01tKVdWZrviE= cloud.google.com/go/apigeeregistry v0.8.3 h1:C+QU2K+DzDjk4g074ouwHQGkoff1h5OMQp6sblCVreQ= cloud.google.com/go/apigeeregistry v0.8.3/go.mod h1:aInOWnqF4yMQx8kTjDqHNXjZGh/mxeNlAf52YqtASUs= +cloud.google.com/go/apigeeregistry v0.9.2 h1:fC3ZXEk2QsBxUlZZDZpbBGXC/ZQglCBmHDGgY5aNipg= +cloud.google.com/go/apigeeregistry v0.9.2/go.mod h1:A5n/DwpG5NaP2fcLYGiFA9QfzpQhPRFNATO1gie8KM8= cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= cloud.google.com/go/apikeys v0.6.0 h1:B9CdHFZTFjVti89tmyXXrO+7vSNo2jvZuHG8zD5trdQ= @@ -127,6 +145,8 @@ cloud.google.com/go/appengine v1.8.1/go.mod h1:6NJXGLVhZCN9aQ/AEDvmfzKEfoYBlfB80 cloud.google.com/go/appengine v1.8.4 h1:Qub3fqR7iA1daJWdzjp/Q0Jz0fUG0JbMc7Ui4E9IX/E= cloud.google.com/go/appengine v1.8.5 h1:l2SviT44zWQiOv8bPoMBzW0vOcMO22iO0s+nVtVhdts= cloud.google.com/go/appengine v1.8.5/go.mod h1:uHBgNoGLTS5di7BvU25NFDuKa82v0qQLjyMJLuPQrVo= +cloud.google.com/go/appengine v1.9.2 h1:pxAQ//FsyEQsaF9HJduPCOEvj9GV4fvnLARGz1+KDzM= +cloud.google.com/go/appengine v1.9.2/go.mod h1:bK4dvmMG6b5Tem2JFZcjvHdxco9g6t1pwd3y/1qr+3s= cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= @@ -137,6 +157,8 @@ cloud.google.com/go/area120 v0.8.1/go.mod h1:BVfZpGpB7KFVNxPiQBuHkX6Ed0rS51xIgmG cloud.google.com/go/area120 v0.8.4 h1:YnSO8m02pOIo6AEOgiOoUDVbw4pf+bg2KLHi4rky320= cloud.google.com/go/area120 v0.8.5 h1:vTs08KPLN/iMzTbxpu5ciL06KcsrVPMjz4IwcQyZ4uY= cloud.google.com/go/area120 v0.8.5/go.mod h1:BcoFCbDLZjsfe4EkCnEq1LKvHSK0Ew/zk5UFu6GMyA0= +cloud.google.com/go/area120 v0.9.2 h1:LODm6TjW27/LJ4z4fBNJHRb+tlvy0gSu6Vb8j2lfluY= +cloud.google.com/go/area120 v0.9.2/go.mod h1:Ar/KPx51UbrTWGVGgGzFnT7hFYQuk/0VOXkvHdTbQMI= cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= @@ -151,6 +173,8 @@ cloud.google.com/go/artifactregistry v1.14.1/go.mod h1:nxVdG19jTaSTu7yA7+VbWL346 cloud.google.com/go/artifactregistry v1.14.6 h1:/hQaadYytMdA5zBh+RciIrXZQBWK4vN7EUsrQHG+/t8= cloud.google.com/go/artifactregistry v1.14.7 h1:W9sVlyb1VRcUf83w7aM3yMsnp4HS4PoyGqYQNG0O5lI= cloud.google.com/go/artifactregistry v1.14.7/go.mod h1:0AUKhzWQzfmeTvT4SjfI4zjot72EMfrkvL9g9aRjnnM= +cloud.google.com/go/artifactregistry v1.16.0 h1:BZpz0x8HCG7hwTkD+GlUwPQVFGOo9w84t8kxQwwc0DA= +cloud.google.com/go/artifactregistry v1.16.0/go.mod h1:LunXo4u2rFtvJjrGjO0JS+Gs9Eco2xbZU6JVJ4+T8Sk= cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= @@ -166,6 +190,8 @@ cloud.google.com/go/asset v1.15.3 h1:uI8Bdm81s0esVWbWrTHcjFDFKNOa9aB7rI1vud1hO84 cloud.google.com/go/asset v1.17.1/go.mod h1:byvDw36UME5AzGNK7o4JnOnINkwOZ1yRrGrKIahHrng= cloud.google.com/go/asset v1.17.2 h1:xgFnBP3luSbUcC9RWJvb3Zkt+y/wW6PKwPHr3ssnIP8= cloud.google.com/go/asset v1.17.2/go.mod h1:SVbzde67ehddSoKf5uebOD1sYw8Ab/jD/9EIeWg99q4= +cloud.google.com/go/asset v1.20.3 h1:/jQBAkZVUbsIczRepDkwaf/K5NcRYvQ6MBiWg5i20fU= +cloud.google.com/go/asset v1.20.3/go.mod h1:797WxTDwdnFAJzbjZ5zc+P5iwqXc13yO9DHhmS6wl+o= cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= @@ -178,6 +204,12 @@ cloud.google.com/go/assuredworkloads v1.11.1/go.mod h1:+F04I52Pgn5nmPG36CWFtxmav cloud.google.com/go/assuredworkloads v1.11.4 h1:FsLSkmYYeNuzDm8L4YPfLWV+lQaUrJmH5OuD37t1k20= cloud.google.com/go/assuredworkloads v1.11.5 h1:gCrN3IyvqY3cP0wh2h43d99CgH3G+WYs9CeuFVKChR8= cloud.google.com/go/assuredworkloads v1.11.5/go.mod h1:FKJ3g3ZvkL2D7qtqIGnDufFkHxwIpNM9vtmhvt+6wqk= +cloud.google.com/go/assuredworkloads v1.12.2 h1:6Y6a4V7CD50qtjvayhu7f5o35UFJP8ade7IbHNfdQEc= +cloud.google.com/go/assuredworkloads v1.12.2/go.mod h1:/WeRr/q+6EQYgnoYrqCVgw7boMoDfjXZZev3iJxs2Iw= +cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= +cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= @@ -189,6 +221,8 @@ cloud.google.com/go/automl v1.13.1/go.mod h1:1aowgAHWYZU27MybSCFiukPO7xnyawv7pt3 cloud.google.com/go/automl v1.13.4 h1:i9tOKXX+1gE7+rHpWKjiuPfGBVIYoWvLNIGpWgPtF58= cloud.google.com/go/automl v1.13.5 h1:ijiJy9sYWh75WrqImXsfWc1e3HR3iO+ef9fvW03Ig/4= cloud.google.com/go/automl v1.13.5/go.mod h1:MDw3vLem3yh+SvmSgeYUmUKqyls6NzSumDm9OJ3xJ1Y= +cloud.google.com/go/automl v1.14.2 h1:RzR5Nx78iaF2FNAfaaQ/7o2b4VuQ17YbOaeK/DLYSW4= +cloud.google.com/go/automl v1.14.2/go.mod h1:mIat+Mf77W30eWQ/vrhjXsXaRh8Qfu4WiymR0hR6Uxk= cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= cloud.google.com/go/baremetalsolution v0.5.0 h1:2AipdYXL0VxMboelTTw8c1UJ7gYu35LZYUbuRv9Q28s= @@ -198,6 +232,8 @@ cloud.google.com/go/baremetalsolution v1.2.0/go.mod h1:68wi9AwPYkEWIUT4SvSGS9UJw cloud.google.com/go/baremetalsolution v1.2.3 h1:oQiFYYCe0vwp7J8ZmF6siVKEumWtiPFJMJcGuyDVRUk= cloud.google.com/go/baremetalsolution v1.2.4 h1:LFydisRmS7hQk9P/YhekwuZGqb45TW4QavcrMToWo5A= cloud.google.com/go/baremetalsolution v1.2.4/go.mod h1:BHCmxgpevw9IEryE99HbYEfxXkAEA3hkMJbYYsHtIuY= +cloud.google.com/go/baremetalsolution v1.3.2 h1:rhawlI+9gy/i1ZQbN/qL6FXHGXusWbfr6UoQdcCpybw= +cloud.google.com/go/baremetalsolution v1.3.2/go.mod h1:3+wqVRstRREJV/puwaKAH3Pnn7ByreZG2aFRsavnoBQ= cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= cloud.google.com/go/batch v0.7.0 h1:YbMt0E6BtqeD5FvSv1d56jbVsWEzlGm55lYte+M6Mzs= @@ -207,6 +243,8 @@ cloud.google.com/go/batch v1.4.1/go.mod h1:KdBmDD61K0ovcxoRHGrN6GmOBWeAOyCgKD0Mu cloud.google.com/go/batch v1.6.3 h1:mPiIH20a5NU02rucbAmLeO4sLPO9hrTK0BLjdHyW8xw= cloud.google.com/go/batch v1.8.0 h1:2HK4JerwVaIcCh/lJiHwh6+uswPthiMMWhiSWLELayk= cloud.google.com/go/batch v1.8.0/go.mod h1:k8V7f6VE2Suc0zUM4WtoibNrA6D3dqBpB+++e3vSGYc= +cloud.google.com/go/batch v1.11.2 h1:OVhgpMMJc+mrFw51R3C06JKC0D6u125RlEBULpg78No= +cloud.google.com/go/batch v1.11.2/go.mod h1:ehsVs8Y86Q4K+qhEStxICqQnNqH8cqgpCxx89cmU5h4= cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= @@ -217,6 +255,8 @@ cloud.google.com/go/beyondcorp v1.0.0/go.mod h1:YhxDWw946SCbmcWo3fAhw3V4XZMSpQ/V cloud.google.com/go/beyondcorp v1.0.3 h1:VXf9SnrnSmj2BF2cHkoTHvOUp8gjsz1KJFOMW7czdsY= cloud.google.com/go/beyondcorp v1.0.4 h1:qs0J0O9Ol2h1yA0AU+r7l3hOCPzs2MjE1d6d/kaHIKo= cloud.google.com/go/beyondcorp v1.0.4/go.mod h1:Gx8/Rk2MxrvWfn4WIhHIG1NV7IBfg14pTKv1+EArVcc= +cloud.google.com/go/beyondcorp v1.1.2 h1:hzKZf9ScvqTWqR8xGKVvD35ScQuxbMySELvJ0OW1usI= +cloud.google.com/go/beyondcorp v1.1.2/go.mod h1:q6YWSkEsSZTU2WDt1qtz6P5yfv79wgktGtNbd0FJTLI= cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= @@ -232,6 +272,10 @@ cloud.google.com/go/bigquery v1.57.1 h1:FiULdbbzUxWD0Y4ZGPSVCDLvqRSyCIO6zKV7E2nf cloud.google.com/go/bigquery v1.58.0/go.mod h1:0eh4mWNY0KrBTjUzLjoYImapGORq9gEPT7MWjCy9lik= cloud.google.com/go/bigquery v1.59.1 h1:CpT+/njKuKT3CEmswm6IbhNu9u35zt5dO4yPDLW+nG4= cloud.google.com/go/bigquery v1.59.1/go.mod h1:VP1UJYgevyTwsV7desjzNzDND5p6hZB+Z8gZJN1GQUc= +cloud.google.com/go/bigquery v1.64.0 h1:vSSZisNyhr2ioJE1OuYBQrnrpB7pIhRQm4jfjc7E/js= +cloud.google.com/go/bigquery v1.64.0/go.mod h1:gy8Ooz6HF7QmA+TRtX8tZmXBKH5mCFBwUApGAb3zI7Y= +cloud.google.com/go/bigtable v1.33.0 h1:2BDaWLRAwXO14DJL/u8crbV2oUbMZkIa2eGq8Yao1bk= +cloud.google.com/go/bigtable v1.33.0/go.mod h1:HtpnH4g25VT1pejHRtInlFPnN5sjTxbQlsYBjh9t5l0= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= @@ -244,6 +288,8 @@ cloud.google.com/go/billing v1.17.0/go.mod h1:Z9+vZXEq+HwH7bhJkyI4OQcR6TSbeMrjlp cloud.google.com/go/billing v1.17.4 h1:77/4kCqzH6Ou5CCDzNmqmboE+WvbwFBJmw1QZQz19AI= cloud.google.com/go/billing v1.18.2 h1:oWUEQvuC4JvtnqLZ35zgzdbuHt4Itbftvzbe6aEyFdE= cloud.google.com/go/billing v1.18.2/go.mod h1:PPIwVsOOQ7xzbADCwNe8nvK776QpfrOAUkvKjCUcpSE= +cloud.google.com/go/billing v1.19.2 h1:shcyz1UkrUxbPsqHL6L84ZdtBZ7yocaFFCxMInTsrNo= +cloud.google.com/go/billing v1.19.2/go.mod h1:AAtih/X2nka5mug6jTAq8jfh1nPye0OjkHbZEZgU59c= cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= @@ -255,6 +301,8 @@ cloud.google.com/go/binaryauthorization v1.7.0/go.mod h1:Zn+S6QqTMn6odcMU1zDZCJx cloud.google.com/go/binaryauthorization v1.7.3 h1:3R6WYn1JKIaVicBmo18jXubu7xh4mMkmbIgsTXk0cBA= cloud.google.com/go/binaryauthorization v1.8.1 h1:1jcyh2uIUwSZkJ/JmL8kd5SUkL/Krbv8zmYLEbAz6kY= cloud.google.com/go/binaryauthorization v1.8.1/go.mod h1:1HVRyBerREA/nhI7yLang4Zn7vfNVA3okoAR9qYQJAQ= +cloud.google.com/go/binaryauthorization v1.9.2 h1:zZX4cvtYSXc5ogOar1w5KA1BLz3j464RPSaR/HhroJ8= +cloud.google.com/go/binaryauthorization v1.9.2/go.mod h1:T4nOcRWi2WX4bjfSRXJkUnpliVIqjP38V88Z10OvEv4= cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= cloud.google.com/go/certificatemanager v1.6.0 h1:5C5UWeSt8Jkgp7OWn2rCkLmYurar/vIWIoSQ2+LaTOc= @@ -264,6 +312,8 @@ cloud.google.com/go/certificatemanager v1.7.1/go.mod h1:iW8J3nG6SaRYImIa+wXQ0g8I cloud.google.com/go/certificatemanager v1.7.4 h1:5YMQ3Q+dqGpwUZ9X5sipsOQ1fLPsxod9HNq0+nrqc6I= cloud.google.com/go/certificatemanager v1.7.5 h1:UMBr/twXvH3jcT5J5/YjRxf2tvwTYIfrpemTebe0txc= cloud.google.com/go/certificatemanager v1.7.5/go.mod h1:uX+v7kWqy0Y3NG/ZhNvffh0kuqkKZIXdvlZRO7z0VtM= +cloud.google.com/go/certificatemanager v1.9.2 h1:/lO1ejN415kRaiO6DNNCHj0UvQujKP714q3l8gp4lsY= +cloud.google.com/go/certificatemanager v1.9.2/go.mod h1:PqW+fNSav5Xz8bvUnJpATIRo1aaABP4mUg/7XIeAn6c= cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= @@ -274,6 +324,8 @@ cloud.google.com/go/channel v1.17.0/go.mod h1:RpbhJsGi/lXWAUM1eF4IbQGbsfVlg2o8Ii cloud.google.com/go/channel v1.17.3 h1:Rd4+fBrjiN6tZ4TR8R/38elkyEkz6oogGDr7jDyjmMY= cloud.google.com/go/channel v1.17.5 h1:/omiBnyFjm4S1ETHoOmJbL7LH7Ljcei4rYG6Sj3hc80= cloud.google.com/go/channel v1.17.5/go.mod h1:FlpaOSINDAXgEext0KMaBq/vwpLMkkPAw9b2mApQeHc= +cloud.google.com/go/channel v1.19.1 h1:l4XcnfzJ5UGmqZQls0atcpD6ERDps4PLd5hXSyTWFv0= +cloud.google.com/go/channel v1.19.1/go.mod h1:ungpP46l6XUeuefbA/XWpWWnAY3897CSRPXUbDstwUo= cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= @@ -287,6 +339,8 @@ cloud.google.com/go/cloudbuild v1.14.3/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhV cloud.google.com/go/cloudbuild v1.15.0 h1:9IHfEMWdCklJ1cwouoiQrnxmP0q3pH7JUt8Hqx4Qbck= cloud.google.com/go/cloudbuild v1.15.1 h1:ZB6oOmJo+MTov9n629fiCrO9YZPOg25FZvQ7gIHu5ng= cloud.google.com/go/cloudbuild v1.15.1/go.mod h1:gIofXZSu+XD2Uy+qkOrGKEx45zd7s28u/k8f99qKals= +cloud.google.com/go/cloudbuild v1.19.0 h1:Uo0bL251yvyWsNtO3Og9m5Z4S48cgGf3IUX7xzOcl8s= +cloud.google.com/go/cloudbuild v1.19.0/go.mod h1:ZGRqbNMrVGhknIIjwASa6MqoRTOpXIVMSI+Ew5DMPuY= cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= cloud.google.com/go/clouddms v1.5.0 h1:E7v4TpDGUyEm1C/4KIrpVSOCTm0P6vWdHT0I4mostRA= @@ -296,6 +350,8 @@ cloud.google.com/go/clouddms v1.7.0/go.mod h1:MW1dC6SOtI/tPNCciTsXtsGNEM0i0Occyk cloud.google.com/go/clouddms v1.7.3 h1:xe/wJKz55VO1+L891a1EG9lVUgfHr9Ju/I3xh1nwF84= cloud.google.com/go/clouddms v1.7.4 h1:Sr0Zo5EAcPQiCBgHWICg3VGkcdS/LLP1d9SR7qQBM/s= cloud.google.com/go/clouddms v1.7.4/go.mod h1:RdrVqoFG9RWI5AvZ81SxJ/xvxPdtcRhFotwdE79DieY= +cloud.google.com/go/clouddms v1.8.2 h1:U53ztLRgTkclaxgmBBles+tv+nNcZ5fhbRbw3b2axFw= +cloud.google.com/go/clouddms v1.8.2/go.mod h1:pe+JSp12u4mYOkwXpSMouyCCuQHL3a6xvWH2FgOcAt4= cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= @@ -308,6 +364,8 @@ cloud.google.com/go/cloudtasks v1.12.1/go.mod h1:a9udmnou9KO2iulGscKR0qBYjreuX8o cloud.google.com/go/cloudtasks v1.12.4 h1:5xXuFfAjg0Z5Wb81j2GAbB3e0bwroCeSF+5jBn/L650= cloud.google.com/go/cloudtasks v1.12.6 h1:EUt1hIZ9bLv8Iz9yWaCrqgMnIU+Tdh0yXM1MMVGhjfE= cloud.google.com/go/cloudtasks v1.12.6/go.mod h1:b7c7fe4+TJsFZfDyzO51F7cjq7HLUlRi/KZQLQjDsaY= +cloud.google.com/go/cloudtasks v1.13.2 h1:x6Qw5JyNbH3reL0arUtlYf77kK6OVjZZ//8JCvUkLro= +cloud.google.com/go/cloudtasks v1.13.2/go.mod h1:2pyE4Lhm7xY8GqbZKLnYk7eeuh8L0JwAvXx1ecKxYu8= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.1.0/go.mod h1:2NIffxgWfORSI7EOYMFatGTfjMLnqrOKBEyYb6NoRgA= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= @@ -338,6 +396,8 @@ cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOk cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= +cloud.google.com/go/compute v1.29.0 h1:Lph6d8oPi38NHkOr6S55Nus/Pbbcp37m/J0ohgKAefs= +cloud.google.com/go/compute v1.29.0/go.mod h1:HFlsDurE5DpQZClAGf/cYh+gxssMhBxBovZDYkEn/Og= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0 h1:nBbNSZyDpkNlo3DepaaLKVuO7ClyifSAmNloSCZrHnQ= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= @@ -348,6 +408,9 @@ cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2Qx cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0 h1:jXIpfcH/VYSE1SYcPzO0n1VVb+sAamiLOgCw45JbOQk= @@ -359,6 +422,8 @@ cloud.google.com/go/contactcenterinsights v1.11.3/go.mod h1:HHX5wrz5LHVAwfI2smIo cloud.google.com/go/contactcenterinsights v1.12.0 h1:wP41IUA4ucMVooj/TP53jd7vbNjWrDkAPOeulVJGT5U= cloud.google.com/go/contactcenterinsights v1.13.0 h1:6Vs/YnDG5STGjlWMEjN/xtmft7MrOTOnOZYUZtGTx0w= cloud.google.com/go/contactcenterinsights v1.13.0/go.mod h1:ieq5d5EtHsu8vhe2y3amtZ+BE+AQwX5qAy7cpo0POsI= +cloud.google.com/go/contactcenterinsights v1.15.1 h1:cR/gQMweaG8RIWAlS5Jo1ARi8LUVQJ51t84EUefHeZ8= +cloud.google.com/go/contactcenterinsights v1.15.1/go.mod h1:cFGxDVm/OwEVAHbU9UO4xQCtQFn0RZSrSUcF/oJ0Bbs= cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= @@ -373,6 +438,8 @@ cloud.google.com/go/container v1.28.0 h1:/o82CFWXIYnT9p/07SnRgybqL3Pmmu86jYIlzlJ cloud.google.com/go/container v1.30.1/go.mod h1:vkbfX0EnAKL/vgVECs5BZn24e1cJROzgszJirRKQ4Bg= cloud.google.com/go/container v1.31.0 h1:MAaNH7VRNPWEhvqOypq2j+7ONJKrKzon4v9nS3nLZe0= cloud.google.com/go/container v1.31.0/go.mod h1:7yABn5s3Iv3lmw7oMmyGbeV6tQj86njcTijkkGuvdZA= +cloud.google.com/go/container v1.42.0 h1:sH9Hj9SoLeP+uKvLXc/04nWyWDiMo4Q85xfb1Nl5sAg= +cloud.google.com/go/container v1.42.0/go.mod h1:YL6lDgCUi3frIWNIFU9qrmF7/6K1EYrtspmFTyyqJ+k= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= @@ -383,6 +450,8 @@ cloud.google.com/go/containeranalysis v0.11.0/go.mod h1:4n2e99ZwpGxpNcz+YsFT1dfO cloud.google.com/go/containeranalysis v0.11.3 h1:5rhYLX+3a01drpREqBZVXR9YmWH45RnML++8NsCtuD8= cloud.google.com/go/containeranalysis v0.11.4 h1:doJ0M1ljS4hS0D2UbHywlHGwB7sQLNrt9vFk9Zyi7vY= cloud.google.com/go/containeranalysis v0.11.4/go.mod h1:cVZT7rXYBS9NG1rhQbWL9pWbXCKHWJPYraE8/FTSYPE= +cloud.google.com/go/containeranalysis v0.13.2 h1:AG2gOcfZJFRiz+3SZCPnxU+gwbzKe++QSX/ej71Lom8= +cloud.google.com/go/containeranalysis v0.13.2/go.mod h1:AiKvXJkc3HiqkHzVIt6s5M81wk+q7SNffc6ZlkTDgiE= cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= @@ -399,6 +468,8 @@ cloud.google.com/go/datacatalog v1.18.3/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+ cloud.google.com/go/datacatalog v1.19.0 h1:rbYNmHwvAOOwnW2FPXYkaK3Mf1MmGqRzK0mMiIEyLdo= cloud.google.com/go/datacatalog v1.19.3 h1:A0vKYCQdxQuV4Pi0LL9p39Vwvg4jH5yYveMv50gU5Tw= cloud.google.com/go/datacatalog v1.19.3/go.mod h1:ra8V3UAsciBpJKQ+z9Whkxzxv7jmQg1hfODr3N3YPJ4= +cloud.google.com/go/datacatalog v1.23.0 h1:9F2zIbWNNmtrSkPIyGRQNsIugG5VgVVFip6+tXSdWLg= +cloud.google.com/go/datacatalog v1.23.0/go.mod h1:9Wamq8TDfL2680Sav7q3zEhBJSPBrDxJU8WtPJ25dBM= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= cloud.google.com/go/dataflow v0.8.0 h1:eYyD9o/8Nm6EttsKZaEGD84xC17bNgSKCu0ZxwqUbpg= @@ -408,6 +479,8 @@ cloud.google.com/go/dataflow v0.9.1/go.mod h1:Wp7s32QjYuQDWqJPFFlnBKhkAtiFpMTdg0 cloud.google.com/go/dataflow v0.9.4 h1:7VmCNWcPJBS/srN2QnStTB6nu4Eb5TMcpkmtaPVhRt4= cloud.google.com/go/dataflow v0.9.5 h1:RYHtcPhmE664+F0Je46p+NvFbG8z//KCXp+uEqB4jZU= cloud.google.com/go/dataflow v0.9.5/go.mod h1:udl6oi8pfUHnL0z6UN9Lf9chGqzDMVqcYTcZ1aPnCZQ= +cloud.google.com/go/dataflow v0.10.2 h1:o9P5/zR2mOYJmCnfp9/7RprKFZCwmSu3TvemQSmCaFM= +cloud.google.com/go/dataflow v0.10.2/go.mod h1:+HIb4HJxDCZYuCqDGnBHZEglh5I0edi/mLgVbxDf0Ag= cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= @@ -419,6 +492,8 @@ cloud.google.com/go/dataform v0.8.1/go.mod h1:3BhPSiw8xmppbgzeBbmDvmSWlwouuJkXsX cloud.google.com/go/dataform v0.9.1 h1:jV+EsDamGX6cE127+QAcCR/lergVeeZdEQ6DdrxW3sQ= cloud.google.com/go/dataform v0.9.2 h1:5e4eqGrd0iDTCg4Q+VlAao5j2naKAA7xRurNtwmUknU= cloud.google.com/go/dataform v0.9.2/go.mod h1:S8cQUwPNWXo7m/g3DhWHsLBoufRNn9EgFrMgne2j7cI= +cloud.google.com/go/dataform v0.10.2 h1:t16DoejuOHoxJR88qrpdmFFlCXA9+x5PKrqI9qiDYz0= +cloud.google.com/go/dataform v0.10.2/go.mod h1:oZHwMBxG6jGZCVZqqMx+XWXK+dA/ooyYiyeRbUxI15M= cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= cloud.google.com/go/datafusion v1.6.0 h1:sZjRnS3TWkGsu1LjYPFD/fHeMLZNXDK6PDHi2s2s/bk= @@ -428,6 +503,8 @@ cloud.google.com/go/datafusion v1.7.1/go.mod h1:KpoTBbFmoToDExJUso/fcCiguGDk7MEz cloud.google.com/go/datafusion v1.7.4 h1:Q90alBEYlMi66zL5gMSGQHfbZLB55mOAg03DhwTTfsk= cloud.google.com/go/datafusion v1.7.5 h1:HQ/BUOP8OIGJxuztpYvNvlb+/U+/Bfs9SO8tQbh61fk= cloud.google.com/go/datafusion v1.7.5/go.mod h1:bYH53Oa5UiqahfbNK9YuYKteeD4RbQSNMx7JF7peGHc= +cloud.google.com/go/datafusion v1.8.2 h1:RPoHvIeXexXwlWhEU6DNgrYCh+C+FR2EXbrnMs2ptpI= +cloud.google.com/go/datafusion v1.8.2/go.mod h1:XernijudKtVG/VEvxtLv08COyVuiYPraSxm+8hd4zXA= cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= cloud.google.com/go/datalabeling v0.7.0 h1:ch4qA2yvddGRUrlfwrNJCr79qLqhS9QBwofPHfFlDIk= @@ -437,6 +514,8 @@ cloud.google.com/go/datalabeling v0.8.1/go.mod h1:XS62LBSVPbYR54GfYQsPXZjTW8UxCK cloud.google.com/go/datalabeling v0.8.4 h1:zrq4uMmunf2KFDl/7dS6iCDBBAxBnKVDyw6+ajz3yu0= cloud.google.com/go/datalabeling v0.8.5 h1:GpIFRdm0qIZNsxqURFJwHt0ZBJZ0nF/mUVEigR7PH/8= cloud.google.com/go/datalabeling v0.8.5/go.mod h1:IABB2lxQnkdUbMnQaOl2prCOfms20mcPxDBm36lps+s= +cloud.google.com/go/datalabeling v0.9.2 h1:UesbU2kYIUWhHUcnFS86ANPbugEq98X9k1whTNcenlc= +cloud.google.com/go/datalabeling v0.9.2/go.mod h1:8me7cCxwV/mZgYWtRAd3oRVGFD6UyT7hjMi+4GRyPpg= cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= @@ -450,6 +529,8 @@ cloud.google.com/go/dataplex v1.11.2 h1:AfFFR15Ifh4U+Me1IBztrSd5CrasTODzy3x8KtDy cloud.google.com/go/dataplex v1.14.1/go.mod h1:bWxQAbg6Smg+sca2+Ex7s8D9a5qU6xfXtwmq4BVReps= cloud.google.com/go/dataplex v1.14.2 h1:fxIfdU8fxzR3clhOoNI7XFppvAmndxDu1AMH+qX9WKQ= cloud.google.com/go/dataplex v1.14.2/go.mod h1:0oGOSFlEKef1cQeAHXy4GZPB/Ife0fz/PxBf+ZymA2U= +cloud.google.com/go/dataplex v1.19.2 h1:R2xnsZnuWpHi2NmBR0e43GZk2IZcQ1AFEAo1fUI0xsw= +cloud.google.com/go/dataplex v1.19.2/go.mod h1:vsxxdF5dgk3hX8Ens9m2/pMNhQZklUhSgqTghZtF1v4= cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= cloud.google.com/go/dataproc v1.12.0 h1:W47qHL3W4BPkAIbk4SWmIERwsWBaNnWm0P2sdx3YgGU= @@ -461,6 +542,8 @@ cloud.google.com/go/dataproc/v2 v2.2.3/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn cloud.google.com/go/dataproc/v2 v2.3.0 h1:tTVP9tTxmc8fixxOd/8s6Q6Pz/+yzn7r7XdZHretQH0= cloud.google.com/go/dataproc/v2 v2.4.0 h1:/u81Fd+BvCLp+xjctI1DiWVJn6cn9/s3Akc8xPH02yk= cloud.google.com/go/dataproc/v2 v2.4.0/go.mod h1:3B1Ht2aRB8VZIteGxQS/iNSJGzt9+CA0WGnDVMEm7Z4= +cloud.google.com/go/dataproc/v2 v2.10.0 h1:B0b7eLRXzFTzb4UaxkGGidIF23l/Xpyce28m1Q0cHmU= +cloud.google.com/go/dataproc/v2 v2.10.0/go.mod h1:HD16lk4rv2zHFhbm8gGOtrRaFohMDr9f0lAUMLmg1PM= cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= cloud.google.com/go/dataqna v0.7.0 h1:yFzi/YU4YAdjyo7pXkBE2FeHbgz5OQQBVDdbErEHmVQ= @@ -470,6 +553,8 @@ cloud.google.com/go/dataqna v0.8.1/go.mod h1:zxZM0Bl6liMePWsHA8RMGAfmTG34vJMapbH cloud.google.com/go/dataqna v0.8.4 h1:NJnu1kAPamZDs/if3bJ3+Wb6tjADHKL83NUWsaIp2zg= cloud.google.com/go/dataqna v0.8.5 h1:9ybXs3nr9BzxSGC04SsvtuXaHY0qmJSLIpIAbZo9GqQ= cloud.google.com/go/dataqna v0.8.5/go.mod h1:vgihg1mz6n7pb5q2YJF7KlXve6tCglInd6XO0JGOlWM= +cloud.google.com/go/dataqna v0.9.2 h1:hrEcid5jK5fEdlYZ0eS8HJoq+ZCTRWSV7Av42V/G994= +cloud.google.com/go/dataqna v0.9.2/go.mod h1:WCJ7pwD0Mi+4pIzFQ+b2Zqy5DcExycNKHuB+VURPPgs= cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= cloud.google.com/go/datastore v1.11.0 h1:iF6I/HaLs3Ado8uRKMvZRvF/ZLkWaWE9i8AiHzbC774= @@ -478,6 +563,8 @@ cloud.google.com/go/datastore v1.14.0 h1:Mq0ApTRdLW3/dyiw+DkjTk0+iGIUvkbzaC8sfPw cloud.google.com/go/datastore v1.14.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8= cloud.google.com/go/datastore v1.15.0 h1:0P9WcsQeTWjuD1H14JIY7XQscIPQ4Laje8ti96IC5vg= cloud.google.com/go/datastore v1.15.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8= +cloud.google.com/go/datastore v1.20.0 h1:NNpXoyEqIJmZFc0ACcwBEaXnmscUpcG4NkKnbCePmiM= +cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew= cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= @@ -490,6 +577,8 @@ cloud.google.com/go/datastream v1.10.0/go.mod h1:hqnmr8kdUBmrnk65k5wNRoHSCYksvpd cloud.google.com/go/datastream v1.10.3 h1:Z2sKPIB7bT2kMW5Uhxy44ZgdJzxzE5uKjavoW+EuHEE= cloud.google.com/go/datastream v1.10.4 h1:o1QDKMo/hk0FN7vhoUQURREuA0rgKmnYapB+1M+7Qz4= cloud.google.com/go/datastream v1.10.4/go.mod h1:7kRxPdxZxhPg3MFeCSulmAJnil8NJGGvSNdn4p1sRZo= +cloud.google.com/go/datastream v1.11.2 h1:vgtrwwPfY7JFEDD0VARJK4qyiApnFnPkFRQVuczYb/w= +cloud.google.com/go/datastream v1.11.2/go.mod h1:RnFWa5zwR5SzHxeZGJOlQ4HKBQPcjGfD219Qy0qfh2k= cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= @@ -502,6 +591,8 @@ cloud.google.com/go/deploy v1.14.2/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88 cloud.google.com/go/deploy v1.15.0 h1:ZdmYzRMTGkVyP1nXEUat9FpbJGJemDcNcx82RSSOElc= cloud.google.com/go/deploy v1.17.1 h1:m27Ojwj03gvpJqCbodLYiVmE9x4/LrHGGMjzc0LBfM4= cloud.google.com/go/deploy v1.17.1/go.mod h1:SXQyfsXrk0fBmgBHRzBjQbZhMfKZ3hMQBw5ym7MN/50= +cloud.google.com/go/deploy v1.25.0 h1:nYLFG2TSsYMJuengVru5P8iWnA5mNA4rKFV5YoOWQ3M= +cloud.google.com/go/deploy v1.25.0/go.mod h1:h9uVCWxSDanXUereI5WR+vlZdbPJ6XGy+gcfC25v5rM= cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= @@ -517,6 +608,8 @@ cloud.google.com/go/dialogflow v1.44.3 h1:cK/f88KX+YVR4tLH4clMQlvrLWD2qmKJQziusj cloud.google.com/go/dialogflow v1.48.2/go.mod h1:7A2oDf6JJ1/+hdpnFRfb/RjJUOh2X3rhIa5P8wQSEX4= cloud.google.com/go/dialogflow v1.49.0 h1:KqG0oxGE71qo0lRVyAoeBozefCvsMfcDzDjoLYSY0F4= cloud.google.com/go/dialogflow v1.49.0/go.mod h1:dhVrXKETtdPlpPhE7+2/k4Z8FRNUp6kMV3EW3oz/fe0= +cloud.google.com/go/dialogflow v1.60.0 h1:H+Q1SUeVU2La0Y0ZGEaKkhEXg3bj9Ceg5YKcMbyNOEc= +cloud.google.com/go/dialogflow v1.60.0/go.mod h1:PjsrI+d2FI4BlGThxL0+Rua/g9vLI+2A1KL7s/Vo3pY= cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= cloud.google.com/go/dlp v1.9.0 h1:1JoJqezlgu6NWCroBxr4rOZnwNFILXr4cB9dMaSKO4A= @@ -526,6 +619,8 @@ cloud.google.com/go/dlp v1.10.1/go.mod h1:IM8BWz1iJd8njcNcG0+Kyd9OPnqnRNkDV8j42V cloud.google.com/go/dlp v1.11.1 h1:OFlXedmPP/5//X1hBEeq3D9kUVm9fb6ywYANlpv/EsQ= cloud.google.com/go/dlp v1.11.2 h1:lTipOuJaSjlYnnotPMbEhKURLC6GzCMDDzVbJAEbmYM= cloud.google.com/go/dlp v1.11.2/go.mod h1:9Czi+8Y/FegpWzgSfkRlyz+jwW6Te9Rv26P3UfU/h/w= +cloud.google.com/go/dlp v1.20.0 h1:Wwz1FoZp3pyrTNkS5fncaAccP/AbqzLQuN5WMi3aVYQ= +cloud.google.com/go/dlp v1.20.0/go.mod h1:nrGsA3r8s7wh2Ct9FWu69UjBObiLldNyQda2RCHgdaY= cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= @@ -539,6 +634,8 @@ cloud.google.com/go/documentai v1.23.5 h1:KAlzT+q8qvRxAmhsJUvLtfFHH0PNvz3M79H6Cg cloud.google.com/go/documentai v1.23.8/go.mod h1:Vd/y5PosxCpUHmwC+v9arZyeMfTqBR9VIwOwIqQYYfA= cloud.google.com/go/documentai v1.25.0 h1:lI62GMEEPO6vXJI9hj+G9WjOvnR0hEjvjokrnex4cxA= cloud.google.com/go/documentai v1.25.0/go.mod h1:ftLnzw5VcXkLItp6pw1mFic91tMRyfv6hHEY5br4KzY= +cloud.google.com/go/documentai v1.35.0 h1:DO4ut86a+Xa0gBq7j3FZJPavnKBNoznrg44csnobqIY= +cloud.google.com/go/documentai v1.35.0/go.mod h1:ZotiWUlDE8qXSUqkJsGMQqVmfTMYATwJEYqbPXTR9kk= cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= cloud.google.com/go/domains v0.8.0 h1:2ti/o9tlWL4N+wIuWUNH+LbfgpwxPr8J1sv9RHA4bYQ= @@ -548,6 +645,8 @@ cloud.google.com/go/domains v0.9.1/go.mod h1:aOp1c0MbejQQ2Pjf1iJvnVyT+z6R6s8pX66 cloud.google.com/go/domains v0.9.4 h1:ua4GvsDztZ5F3xqjeLKVRDeOvJshf5QFgWGg1CKti3A= cloud.google.com/go/domains v0.9.5 h1:Mml/R6s3vQQvFPpi/9oX3O5dRirgjyJ8cksK8N19Y7g= cloud.google.com/go/domains v0.9.5/go.mod h1:dBzlxgepazdFhvG7u23XMhmMKBjrkoUNaw0A8AQB55Y= +cloud.google.com/go/domains v0.10.2 h1:ekJCkuzbciXyPKkwPwvI+2Ov1GcGJtMXj/fbgilPFqg= +cloud.google.com/go/domains v0.10.2/go.mod h1:oL0Wsda9KdJvvGNsykdalHxQv4Ri0yfdDkIi3bzTUwk= cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= @@ -558,8 +657,12 @@ cloud.google.com/go/edgecontainer v1.1.1/go.mod h1:O5bYcS//7MELQZs3+7mabRqoWQhXC cloud.google.com/go/edgecontainer v1.1.4 h1:Szy3Q/N6bqgQGyxqjI+6xJZbmvPvnFHp3UZr95DKcQ0= cloud.google.com/go/edgecontainer v1.1.5 h1:tBY32km78ScpK2aOP84JoW/+wtpx5WluyPUSEE3270U= cloud.google.com/go/edgecontainer v1.1.5/go.mod h1:rgcjrba3DEDEQAidT4yuzaKWTbkTI5zAMu3yy6ZWS0M= +cloud.google.com/go/edgecontainer v1.4.0 h1:vpKTEkQPpkl55d6aUU2rzDFvTkMUATvBXfZSlI2KMR0= +cloud.google.com/go/edgecontainer v1.4.0/go.mod h1:Hxj5saJT8LMREmAI9tbNTaBpW5loYiWFyisCjDhzu88= cloud.google.com/go/errorreporting v0.3.0 h1:kj1XEWMu8P0qlLhm3FwcaFsUvXChV/OraZwA70trRR0= cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/errorreporting v0.3.1 h1:E/gLk+rL7u5JZB9oq72iL1bnhVlLrnfslrgcptjJEUE= +cloud.google.com/go/errorreporting v0.3.1/go.mod h1:6xVQXU1UuntfAf+bVkFk6nld41+CPyF2NSPCyXE3Ztk= cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= cloud.google.com/go/essentialcontacts v1.5.0 h1:gIzEhCoOT7bi+6QZqZIzX1Erj4SswMPIteNvYVlu+pM= @@ -569,6 +672,8 @@ cloud.google.com/go/essentialcontacts v1.6.2/go.mod h1:T2tB6tX+TRak7i88Fb2N9Ok3P cloud.google.com/go/essentialcontacts v1.6.5 h1:S2if6wkjR4JCEAfDtIiYtD+sTz/oXjh2NUG4cgT1y/Q= cloud.google.com/go/essentialcontacts v1.6.6 h1:13eHn5qBnsawxI7mIrv4jRIEmQ1xg0Ztqw5ZGqtUNfA= cloud.google.com/go/essentialcontacts v1.6.6/go.mod h1:XbqHJGaiH0v2UvtuucfOzFXN+rpL/aU5BCZLn4DYl1Q= +cloud.google.com/go/essentialcontacts v1.7.2 h1:a/reGTn7WblM5DgieiLbX6CswHgTneWrA4ZNS5E+1Bg= +cloud.google.com/go/essentialcontacts v1.7.2/go.mod h1:NoCBlOIVteJFJU+HG9dIG/Cc9kt1K9ys9mbOaGPUmPc= cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= @@ -579,6 +684,8 @@ cloud.google.com/go/eventarc v1.13.0/go.mod h1:mAFCW6lukH5+IZjkvrEss+jmt2kOdYlN8 cloud.google.com/go/eventarc v1.13.3 h1:+pFmO4eu4dOVipSaFBLkmqrRYG94Xl/TQZFOeohkuqU= cloud.google.com/go/eventarc v1.13.4 h1:ORkd6/UV5FIdA8KZQDLNZYKS7BBOrj0p01DXPmT4tE4= cloud.google.com/go/eventarc v1.13.4/go.mod h1:zV5sFVoAa9orc/52Q+OuYUG9xL2IIZTbbuTHC6JSY8s= +cloud.google.com/go/eventarc v1.15.0 h1:IVU2EOR8P2f6N8eneuwspN122LR87v9G54B+7ihd1TY= +cloud.google.com/go/eventarc v1.15.0/go.mod h1:PAd/pPIZdJtJQFJI1yDEUms1mqohdNuM1BFEVHHlVFg= cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= @@ -591,6 +698,8 @@ cloud.google.com/go/filestore v1.7.4/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6Bqjwpkk cloud.google.com/go/filestore v1.8.0 h1:/+wUEGwk3x3Kxomi2cP5dsR8+SIXxo7M0THDjreFSYo= cloud.google.com/go/filestore v1.8.1 h1:X5G4y/vrUo1B8Nsz93qSWTMAcM8LXbGUldq33OdcdCw= cloud.google.com/go/filestore v1.8.1/go.mod h1:MbN9KcaM47DRTIuLfQhJEsjaocVebNtNQhSLhKCF5GM= +cloud.google.com/go/filestore v1.9.2 h1:DYwMNAcF5bELHHMxRdkIWWZ3XicKp+ZpEBy+c6Gt4uY= +cloud.google.com/go/filestore v1.9.2/go.mod h1:I9pM7Hoetq9a7djC1xtmtOeHSUYocna09ZP6x+PG1Xw= cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY= cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= @@ -600,6 +709,8 @@ cloud.google.com/go/firestore v1.13.0 h1:/3S4RssUV4GO/kvgJZB+tayjhOfyAHs+KcpJgRV cloud.google.com/go/firestore v1.13.0/go.mod h1:QojqqOh8IntInDUSTAh0c8ZsPYAr68Ma8c5DWOy8xb8= cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw= cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ= +cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ= +cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= @@ -613,6 +724,8 @@ cloud.google.com/go/functions v1.15.1/go.mod h1:P5yNWUTkyU+LvW/S9O6V+V423VZooALQ cloud.google.com/go/functions v1.15.4 h1:ZjdiV3MyumRM6++1Ixu6N0VV9LAGlCX4AhW6Yjr1t+U= cloud.google.com/go/functions v1.16.0 h1:IWVylmK5F6hJ3R5zaRW7jI5PrWhCvtBVU4axQLmXSo4= cloud.google.com/go/functions v1.16.0/go.mod h1:nbNpfAG7SG7Duw/o1iZ6ohvL7mc6MapWQVpqtM29n8k= +cloud.google.com/go/functions v1.19.2 h1:Cu2Gj1JBBJv9gi89r8LrZNsJhGwePnhttn4Blqw/EYI= +cloud.google.com/go/functions v1.19.2/go.mod h1:SBzWwWuaFDLnUyStDAMEysVN1oA5ECLbP3/PfJ9Uk7Y= cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= @@ -628,6 +741,8 @@ cloud.google.com/go/gkebackup v1.3.1/go.mod h1:vUDOu++N0U5qs4IhG1pcOnD1Mac79xWy6 cloud.google.com/go/gkebackup v1.3.4 h1:KhnOrr9A1tXYIYeXKqCKbCI8TL2ZNGiD3dm+d7BDUBg= cloud.google.com/go/gkebackup v1.3.5 h1:iuE8KNtTsPOc79qeWoNS8zOWoXPD9SAdOmwgxtlCmh8= cloud.google.com/go/gkebackup v1.3.5/go.mod h1:KJ77KkNN7Wm1LdMopOelV6OodM01pMuK2/5Zt1t4Tvc= +cloud.google.com/go/gkebackup v1.6.2 h1:lWaSgjSonOXe41UhwQjts6lhDZdr5e882LNUTtnjZS0= +cloud.google.com/go/gkebackup v1.6.2/go.mod h1:WsTSWqKJkGan1pkp5dS30oxb+Eaa6cLvxEUxKTUALwk= cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= cloud.google.com/go/gkeconnect v0.7.0 h1:gXYKciHS/Lgq0GJ5Kc9SzPA35NGc3yqu6SkjonpEr2Q= @@ -637,6 +752,8 @@ cloud.google.com/go/gkeconnect v0.8.1/go.mod h1:KWiK1g9sDLZqhxB2xEuPV8V9NYzrqTUm cloud.google.com/go/gkeconnect v0.8.4 h1:1JLpZl31YhQDQeJ98tK6QiwTpgHFYRJwpntggpQQWis= cloud.google.com/go/gkeconnect v0.8.5 h1:17d+ZSSXKqG/RwZCq3oFMIWLPI8Zw3b8+a9/BEVlwH0= cloud.google.com/go/gkeconnect v0.8.5/go.mod h1:LC/rS7+CuJ5fgIbXv8tCD/mdfnlAadTaUufgOkmijuk= +cloud.google.com/go/gkeconnect v0.12.0 h1:MuA3/aIuncXkXuUDGdbT7OLnIp7xpFhciuHAnQaoQz4= +cloud.google.com/go/gkeconnect v0.12.0/go.mod h1:zn37LsFiNZxPN4iO7YbUk8l/E14pAJ7KxpoXoxt7Ly0= cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= @@ -647,6 +764,8 @@ cloud.google.com/go/gkehub v0.14.1/go.mod h1:VEXKIJZ2avzrbd7u+zeMtW00Y8ddk/4V951 cloud.google.com/go/gkehub v0.14.4 h1:J5tYUtb3r0cl2mM7+YHvV32eL+uZQ7lONyUZnPikCEo= cloud.google.com/go/gkehub v0.14.5 h1:RboLNFzf9wEMSo7DrKVBlf+YhK/A/jrLN454L5Tz99Q= cloud.google.com/go/gkehub v0.14.5/go.mod h1:6bzqxM+a+vEH/h8W8ec4OJl4r36laxTs3A/fMNHJ0wA= +cloud.google.com/go/gkehub v0.15.2 h1:CR5MPEP/Ogk5IahCq3O2fKS6TJZQi8mrnrysGHCs0g8= +cloud.google.com/go/gkehub v0.15.2/go.mod h1:8YziTOpwbM8LM3r9cHaOMy2rNgJHXZCrrmGgcau9zbQ= cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= cloud.google.com/go/gkemulticloud v0.5.0 h1:8I84Q4vl02rJRsFiinBxl7WCozfdLlUVBQuSrqr9Wtk= @@ -656,12 +775,16 @@ cloud.google.com/go/gkemulticloud v1.0.0/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZVi cloud.google.com/go/gkemulticloud v1.0.3 h1:NmJsNX9uQ2CT78957xnjXZb26TDIMvv+d5W2vVUt0Pg= cloud.google.com/go/gkemulticloud v1.1.1 h1:rsSZAGLhyjyE/bE2ToT5fqo1qSW7S+Ubsc9jFOcbhSI= cloud.google.com/go/gkemulticloud v1.1.1/go.mod h1:C+a4vcHlWeEIf45IB5FFR5XGjTeYhF83+AYIpTy4i2Q= +cloud.google.com/go/gkemulticloud v1.4.1 h1:SvVD2nJTGScEDYygIQ5dI14oFYhgtJx8HazkT3aufEI= +cloud.google.com/go/gkemulticloud v1.4.1/go.mod h1:KRvPYcx53bztNwNInrezdfNF+wwUom8Y3FuJBwhvFpQ= cloud.google.com/go/grafeas v0.2.0 h1:CYjC+xzdPvbV65gi6Dr4YowKcmLo045pm18L0DhdELM= cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/grafeas v0.3.0 h1:oyTL/KjiUeBs9eYLw/40cpSZglUC+0F7X4iu/8t7NWs= cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6GUgyFSOU8= cloud.google.com/go/grafeas v0.3.4 h1:D4x32R/cHX3MTofKwirz015uEdVk4uAxvZkZCZkOrF4= cloud.google.com/go/grafeas v0.3.4/go.mod h1:A5m316hcG+AulafjAbPKXBO/+I5itU4LOdKO2R/uDIc= +cloud.google.com/go/grafeas v0.3.11 h1:CobnwnyeY1j1Defi5vbEircI+jfrk3ci5m004ZjiFP4= +cloud.google.com/go/grafeas v0.3.11/go.mod h1:dcQyG2+T4tBgG0MvJAh7g2wl/xHV2w+RZIqivwuLjNg= cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= cloud.google.com/go/gsuiteaddons v1.5.0 h1:1mvhXqJzV0Vg5Fa95QwckljODJJfDFXV4pn+iL50zzA= @@ -671,6 +794,8 @@ cloud.google.com/go/gsuiteaddons v1.6.1/go.mod h1:CodrdOqRZcLp5WOwejHWYBjZvfY0kO cloud.google.com/go/gsuiteaddons v1.6.4 h1:uuw2Xd37yHftViSI8J2hUcCS8S7SH3ZWH09sUDLW30Q= cloud.google.com/go/gsuiteaddons v1.6.5 h1:CZEbaBwmbYdhFw21Fwbo+C35HMe36fTE0FBSR4KSfWg= cloud.google.com/go/gsuiteaddons v1.6.5/go.mod h1:Lo4P2IvO8uZ9W+RaC6s1JVxo42vgy+TX5a6hfBZ0ubs= +cloud.google.com/go/gsuiteaddons v1.7.2 h1:Rma+a2tCB2PV0Rm87Ywr4P96dCwGIm8vw8gF23ZlYoY= +cloud.google.com/go/gsuiteaddons v1.7.2/go.mod h1:GD32J2rN/4APilqZw4JKmwV84+jowYYMkEVwQEYuAWc= cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= @@ -688,6 +813,8 @@ cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= @@ -699,6 +826,8 @@ cloud.google.com/go/iap v1.9.0/go.mod h1:01OFxd1R+NFrg78S+hoPV5PxEzv22HXaNqUUlmN cloud.google.com/go/iap v1.9.3 h1:M4vDbQ4TLXdaljXVZSwW7XtxpwXUUarY2lIs66m0aCM= cloud.google.com/go/iap v1.9.4 h1:94zirc2r4t6KzhAMW0R6Dme005eTP6yf7g6vN4IhRrA= cloud.google.com/go/iap v1.9.4/go.mod h1:vO4mSq0xNf/Pu6E5paORLASBwEmphXEjgCFg7aeNu1w= +cloud.google.com/go/iap v1.10.2 h1:rvM+FNIF2wIbwUU8299FhhVGak2f7oOvbW8J/I5oflE= +cloud.google.com/go/iap v1.10.2/go.mod h1:cClgtI09VIfazEK6VMJr6bX8KQfuQ/D3xqX+d0wrUlI= cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= cloud.google.com/go/ids v1.3.0 h1:fodnCDtOXuMmS8LTC2y3h8t24U8F3eKWfhi+3LY6Qf0= @@ -708,6 +837,8 @@ cloud.google.com/go/ids v1.4.1/go.mod h1:np41ed8YMU8zOgv53MMMoCntLTn2lF+SUzlM+O3 cloud.google.com/go/ids v1.4.4 h1:VuFqv2ctf/A7AyKlNxVvlHTzjrEvumWaZflUzBPz/M4= cloud.google.com/go/ids v1.4.5 h1:xd4U7pgl3GHV+MABnv1BF4/Vy/zBF7CYC8XngkOLzag= cloud.google.com/go/ids v1.4.5/go.mod h1:p0ZnyzjMWxww6d2DvMGnFwCsSxDJM666Iir1bK1UuBo= +cloud.google.com/go/ids v1.5.2 h1:EDYZQraE+Eq6BewUQxVRY8b3VUUo/MnjMfzSh1NGjx8= +cloud.google.com/go/ids v1.5.2/go.mod h1:P+ccDD96joXlomfonEdCnyrHvE68uLonc7sJBPVM5T0= cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= @@ -718,6 +849,8 @@ cloud.google.com/go/iot v1.7.1/go.mod h1:46Mgw7ev1k9KqK1ao0ayW9h0lI+3hxeanz+L1zm cloud.google.com/go/iot v1.7.4 h1:m1WljtkZnvLTIRYW1YTOv5A6H1yKgLHR6nU7O8yf27w= cloud.google.com/go/iot v1.7.5 h1:munTeBlbqI33iuTYgXy7S8lW2TCgi5l1hA4roSIY+EE= cloud.google.com/go/iot v1.7.5/go.mod h1:nq3/sqTz3HGaWJi1xNiX7F41ThOzpud67vwk0YsSsqs= +cloud.google.com/go/iot v1.8.2 h1:KMN0wujrPV7q0yfs4rt5CUl9Di8sQhJ0uohJn1h6yaI= +cloud.google.com/go/iot v1.8.2/go.mod h1:UDwVXvRD44JIcMZr8pzpF3o4iPsmOO6fmbaIYCAg1ww= cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= @@ -732,6 +865,8 @@ cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM= cloud.google.com/go/kms v1.15.6/go.mod h1:yF75jttnIdHfGBoE51AKsD/Yqf+/jICzB9v1s1acsms= cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM= cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI= +cloud.google.com/go/kms v1.20.1 h1:og29Wv59uf2FVaZlesaiDAqHFzHaoUyHI3HYp9VUHVg= +cloud.google.com/go/kms v1.20.1/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= @@ -743,6 +878,8 @@ cloud.google.com/go/language v1.11.0/go.mod h1:uDx+pFDdAKTY8ehpWbiXyQdz8tDSYLJbQ cloud.google.com/go/language v1.12.2 h1:zg9uq2yS9PGIOdc0Kz/l+zMtOlxKWonZjjo5w5YPG2A= cloud.google.com/go/language v1.12.3 h1:iaJZg6K4j/2PvZZVcjeO/btcWWIllVRBhuTFjGO4LXs= cloud.google.com/go/language v1.12.3/go.mod h1:evFX9wECX6mksEva8RbRnr/4wi/vKGYnAJrTRXU8+f8= +cloud.google.com/go/language v1.14.2 h1:rwrIOwcAgPTYbigOaiMSjKCvBy0xHZJbRc7HB/xMECA= +cloud.google.com/go/language v1.14.2/go.mod h1:dviAbkxT9art+2ioL9AM05t+3Ql6UPfMpwq1cDsF+rg= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= cloud.google.com/go/lifesciences v0.8.0 h1:uWrMjWTsGjLZpCTWEAzYvyXj+7fhiZST45u9AgasasI= @@ -752,6 +889,8 @@ cloud.google.com/go/lifesciences v0.9.1/go.mod h1:hACAOd1fFbCGLr/+weUKRAJas82Y4v cloud.google.com/go/lifesciences v0.9.4 h1:rZEI/UxcxVKEzyoRS/kdJ1VoolNItRWjNN0Uk9tfexg= cloud.google.com/go/lifesciences v0.9.5 h1:gXvN70m2p+4zgJFzaz6gMKaxTuF9WJ0USYoMLWAOm8g= cloud.google.com/go/lifesciences v0.9.5/go.mod h1:OdBm0n7C0Osh5yZB7j9BXyrMnTRGBJIZonUMxo5CzPw= +cloud.google.com/go/lifesciences v0.10.2 h1:eZSaRgBwbnb/oXwCj1SGE0Kp534DuXpg55iYBWgN024= +cloud.google.com/go/lifesciences v0.10.2/go.mod h1:vXDa34nz0T/ibUNoeHnhqI+Pn0OazUTdxemd0OLkyoY= cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= cloud.google.com/go/logging v1.7.0 h1:CJYxlNNNNAMkHp9em/YEXcfJg+rPDg7YfwoRpMU+t5I= cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= @@ -759,6 +898,8 @@ cloud.google.com/go/logging v1.8.1 h1:26skQWPeYhvIasWKm48+Eq7oUqdcdbwsCVwz5Ys0Fv cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI= cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZw= cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= @@ -770,6 +911,8 @@ cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgG cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= cloud.google.com/go/managedidentities v1.5.0 h1:ZRQ4k21/jAhrHBVKl/AY7SjgzeJwG1iZa+mJ82P+VNg= @@ -779,6 +922,8 @@ cloud.google.com/go/managedidentities v1.6.1/go.mod h1:h/irGhTN2SkZ64F43tfGPMbHn cloud.google.com/go/managedidentities v1.6.4 h1:SF/u1IJduMqQQdJA4MDyivlIQ4SrV5qAawkr/ZEREkY= cloud.google.com/go/managedidentities v1.6.5 h1:+bpih1piZVLxla/XBqeSUzJBp8gv9plGHIMAI7DLpDM= cloud.google.com/go/managedidentities v1.6.5/go.mod h1:fkFI2PwwyRQbjLxlm5bQ8SjtObFMW3ChBGNqaMcgZjI= +cloud.google.com/go/managedidentities v1.7.2 h1:oWxuIhIwQC1Vfs1SZi1x389W2TV9uyPsAyZMJgZDND4= +cloud.google.com/go/managedidentities v1.7.2/go.mod h1:t0WKYzagOoD3FNtJWSWcU8zpWZz2i9cw2sKa9RiPx5I= cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= cloud.google.com/go/maps v0.7.0 h1:mv9YaczD4oZBZkM5XJl6fXQ984IkJNHPwkc8MUsdkBo= @@ -788,6 +933,8 @@ cloud.google.com/go/maps v1.4.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9v cloud.google.com/go/maps v1.6.1 h1:2+eMp/1MvMPp5qrSOd3vtnLKa/pylt+krVRqET3jWsM= cloud.google.com/go/maps v1.6.4 h1:EVCZAiDvog9So46460BGbCasPhi613exoaQbpilMVlk= cloud.google.com/go/maps v1.6.4/go.mod h1:rhjqRy8NWmDJ53saCfsXQ0LKwBHfi6OSh5wkq6BaMhI= +cloud.google.com/go/maps v1.15.0 h1:bmFHlO6BL/smC6GD45r5j0ChjsyyevuJCSARdOL62TI= +cloud.google.com/go/maps v1.15.0/go.mod h1:ZFqZS04ucwFiHSNU8TBYDUr3wYhj5iBFJk24Ibvpf3o= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/mediatranslation v0.7.0 h1:anPxH+/WWt8Yc3EdoEJhPMBRF7EhIdz426A+tuoA0OU= @@ -797,6 +944,8 @@ cloud.google.com/go/mediatranslation v0.8.1/go.mod h1:L/7hBdEYbYHQJhX2sldtTO5SZZ cloud.google.com/go/mediatranslation v0.8.4 h1:VRCQfZB4s6jN0CSy7+cO3m4ewNwgVnaePanVCQh/9Z4= cloud.google.com/go/mediatranslation v0.8.5 h1:c76KdIXljQHSCb/Cy47S8H4s05A4zbK3pAFGzwcczZo= cloud.google.com/go/mediatranslation v0.8.5/go.mod h1:y7kTHYIPCIfgyLbKncgqouXJtLsU+26hZhHEEy80fSs= +cloud.google.com/go/mediatranslation v0.9.2 h1:p37R/k9+L33bUMO87gFyv93MwJ+9nuzVhXM5X+6ULwA= +cloud.google.com/go/mediatranslation v0.9.2/go.mod h1:1xyRoDYN32THzy+QaU62vIMciX0CFexplju9t30XwUc= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= @@ -808,6 +957,8 @@ cloud.google.com/go/memcache v1.10.1/go.mod h1:47YRQIarv4I3QS5+hoETgKO40InqzLP6k cloud.google.com/go/memcache v1.10.4 h1:cdex/ayDd294XBj2cGeMe6Y+H1JvhN8y78B9UW7pxuQ= cloud.google.com/go/memcache v1.10.5 h1:yeDv5qxRedFosvpMSEswrqUsJM5OdWvssPHFliNFTc4= cloud.google.com/go/memcache v1.10.5/go.mod h1:/FcblbNd0FdMsx4natdj+2GWzTq+cjZvMa1I+9QsuMA= +cloud.google.com/go/memcache v1.11.2 h1:GGgC2A9AClJN8VLbMUAPUxj/dNMFwz6Lj01gDxPw7os= +cloud.google.com/go/memcache v1.11.2/go.mod h1:jIzHn79b0m5wbkax2SdlW5vNSbpaEk0yWHbeLpMIYZE= cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= @@ -819,6 +970,8 @@ cloud.google.com/go/metastore v1.12.0/go.mod h1:uZuSo80U3Wd4zi6C22ZZliOUJ3XeM/Ml cloud.google.com/go/metastore v1.13.3 h1:94l/Yxg9oBZjin2bzI79oK05feYefieDq0o5fjLSkC8= cloud.google.com/go/metastore v1.13.4 h1:dR7vqWXlK6IYR8Wbu9mdFfwlVjodIBhd1JRrpZftTEg= cloud.google.com/go/metastore v1.13.4/go.mod h1:FMv9bvPInEfX9Ac1cVcRXp8EBBQnBcqH6gz3KvJ9BAE= +cloud.google.com/go/metastore v1.14.2 h1:Euc9kLTKS8T6M1JVqQavwDFHu9UtT1//lGXSKjpO3/0= +cloud.google.com/go/metastore v1.14.2/go.mod h1:dk4zOBhZIy3TFOQlI8sbOa+ef0FjAcCHEnd8dO2J+LE= cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= @@ -830,6 +983,8 @@ cloud.google.com/go/monitoring v1.16.3 h1:mf2SN9qSoBtIgiMA4R/y4VADPWZA7VCNJA079q cloud.google.com/go/monitoring v1.17.1/go.mod h1:SJzPMakCF0GHOuKEH/r4hxVKF04zl+cRPQyc3d/fqII= cloud.google.com/go/monitoring v1.18.0 h1:NfkDLQDG2UR3WYZVQE8kwSbUIEyIqJUPl+aOQdFH1T4= cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= @@ -842,6 +997,8 @@ cloud.google.com/go/networkconnectivity v1.13.0/go.mod h1:SAnGPes88pl7QRLUen2Hmc cloud.google.com/go/networkconnectivity v1.14.3 h1:e9lUkCe2BexsqsUc2bjV8+gFBpQa54J+/F3qKVtW+wA= cloud.google.com/go/networkconnectivity v1.14.4 h1:GBfXFhLyPspnaBE3nI/BRjdhW8vcbpT9QjE/4kDCDdc= cloud.google.com/go/networkconnectivity v1.14.4/go.mod h1:PU12q++/IMnDJAB+3r+tJtuCXCfwfN+C6Niyj6ji1Po= +cloud.google.com/go/networkconnectivity v1.15.2 h1:CuBLrRKhPbzXkFGADopQUpMcdY+SSfoy/3RqsMH2pq4= +cloud.google.com/go/networkconnectivity v1.15.2/go.mod h1:N1O01bEk5z9bkkWwXLKcN2T53QN49m/pSpjfUvlHDQY= cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= cloud.google.com/go/networkmanagement v1.6.0 h1:8KWEUNGcpSX9WwZXq7FtciuNGPdPdPN/ruDm769yAEM= @@ -851,6 +1008,8 @@ cloud.google.com/go/networkmanagement v1.9.0/go.mod h1:UTUaEU9YwbCAhhz3jEOHr+2/K cloud.google.com/go/networkmanagement v1.9.3 h1:HsQk4FNKJUX04k3OI6gUsoveiHMGvDRqlaFM2xGyvqU= cloud.google.com/go/networkmanagement v1.9.4 h1:aLV5GcosBNmd6M8+a0ekB0XlLRexv4fvnJJrYnqeBcg= cloud.google.com/go/networkmanagement v1.9.4/go.mod h1:daWJAl0KTFytFL7ar33I6R/oNBH8eEOX/rBNHrC/8TA= +cloud.google.com/go/networkmanagement v1.16.0 h1:oT7c2Oo9NT54XjnP4GMNj/HEywrFnBz0u6QLJ2iu8NE= +cloud.google.com/go/networkmanagement v1.16.0/go.mod h1:Yc905R9U5jik5YMt76QWdG5WqzPU4ZsdI/mLnVa62/Q= cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= @@ -861,6 +1020,8 @@ cloud.google.com/go/networksecurity v0.9.1/go.mod h1:MCMdxOKQ30wsBI1eI659f9kEp4w cloud.google.com/go/networksecurity v0.9.4 h1:947tNIPnj1bMGTIEBo3fc4QrrFKS5hh0bFVsHmFm4Vo= cloud.google.com/go/networksecurity v0.9.5 h1:+caSxBTj0E8OYVh/5wElFdjEMO1S/rZtE1152Cepchc= cloud.google.com/go/networksecurity v0.9.5/go.mod h1:KNkjH/RsylSGyyZ8wXpue8xpCEK+bTtvof8SBfIhMG8= +cloud.google.com/go/networksecurity v0.10.2 h1://zFZM8XZZs+3Y6QKuLqwD5tZ+B/17KUo/rJpGW2tJs= +cloud.google.com/go/networksecurity v0.10.2/go.mod h1:puU3Gwchd6Y/VTyMkL50GI2RSRMS3KXhcDBY1HSOcck= cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= @@ -873,6 +1034,8 @@ cloud.google.com/go/notebooks v1.10.0/go.mod h1:SOPYMZnttHxqot0SGSFSkRrwE29eqnKP cloud.google.com/go/notebooks v1.11.2 h1:eTOTfNL1yM6L/PCtquJwjWg7ZZGR0URFaFgbs8kllbM= cloud.google.com/go/notebooks v1.11.3 h1:FH48boYmrWVQ6k0Mx/WrnNafXncT5iSYxA8CNyWTgy0= cloud.google.com/go/notebooks v1.11.3/go.mod h1:0wQyI2dQC3AZyQqWnRsp+yA+kY4gC7ZIVP4Qg3AQcgo= +cloud.google.com/go/notebooks v1.12.2 h1:BHIH9kf/02wSCcLAVttEXHSFAgSotgRg2y1YjR7VDCc= +cloud.google.com/go/notebooks v1.12.2/go.mod h1:EkLwv8zwr8DUXnvzl944+sRBG+b73HEKzV632YYAGNI= cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= cloud.google.com/go/optimization v1.3.1 h1:dj8O4VOJRB4CUwZXdmwNViH1OtI0WtWL867/lnYH248= @@ -882,6 +1045,8 @@ cloud.google.com/go/optimization v1.5.0/go.mod h1:evo1OvTxeBRBu6ydPlrIRizKY/LJKo cloud.google.com/go/optimization v1.6.2 h1:iFsoexcp13cGT3k/Hv8PA5aK+FP7FnbhwDO9llnruas= cloud.google.com/go/optimization v1.6.3 h1:63NZaWyN+5rZEKHPX4ACpw3BjgyeuY8+rCehiCMaGPY= cloud.google.com/go/optimization v1.6.3/go.mod h1:8ve3svp3W6NFcAEFr4SfJxrldzhUl4VMUJmhrqVKtYA= +cloud.google.com/go/optimization v1.7.2 h1:yM4teRB60qyIm8cV4VRW4wepmHbXCoqv3QKGfKzylEQ= +cloud.google.com/go/optimization v1.7.2/go.mod h1:msYgDIh1SGSfq6/KiWJQ/uxMkWq8LekPyn1LAZ7ifNE= cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= cloud.google.com/go/orchestration v1.6.0 h1:Vw+CEXo8M/FZ1rb4EjcLv0gJqqw89b7+g+C/EmniTb8= @@ -891,6 +1056,8 @@ cloud.google.com/go/orchestration v1.8.1/go.mod h1:4sluRF3wgbYVRqz7zJ1/EUNc90TTp cloud.google.com/go/orchestration v1.8.4 h1:kgwZ2f6qMMYIVBtUGGoU8yjYWwMTHDanLwM/CQCFaoQ= cloud.google.com/go/orchestration v1.8.5 h1:YHgWMlrPttIVGItgGfuvO2KM7x+y9ivN/Yk92pMm1a4= cloud.google.com/go/orchestration v1.8.5/go.mod h1:C1J7HesE96Ba8/hZ71ISTV2UAat0bwN+pi85ky38Yq8= +cloud.google.com/go/orchestration v1.11.1 h1:uZOwdQoAamx8+X0UdMqY/lro3/h/Zhb7SnfArufNVcc= +cloud.google.com/go/orchestration v1.11.1/go.mod h1:RFHf4g88Lbx6oKhwFstYiId2avwb6oswGeAQ7Tjjtfw= cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= cloud.google.com/go/orgpolicy v1.10.0 h1:XDriMWug7sd0kYT1QKofRpRHzjad0bK8Q8uA9q+XrU4= @@ -900,6 +1067,8 @@ cloud.google.com/go/orgpolicy v1.11.1/go.mod h1:8+E3jQcpZJQliP+zaFfayC2Pg5bmhuLK cloud.google.com/go/orgpolicy v1.11.4 h1:RWuXQDr9GDYhjmrredQJC7aY7cbyqP9ZuLbq5GJGves= cloud.google.com/go/orgpolicy v1.12.1 h1:2JbXigqBJVp8Dx5dONUttFqewu4fP0p3pgOdIZAhpYU= cloud.google.com/go/orgpolicy v1.12.1/go.mod h1:aibX78RDl5pcK3jA8ysDQCFkVxLj3aOQqrbBaUL2V5I= +cloud.google.com/go/orgpolicy v1.14.1 h1:c1QLoM5v8/aDKgYVCUaC039lD3GPvqAhTVOwsGhIoZQ= +cloud.google.com/go/orgpolicy v1.14.1/go.mod h1:1z08Hsu1mkoH839X7C8JmnrqOkp2IZRSxiDw7W/Xpg4= cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= @@ -911,6 +1080,8 @@ cloud.google.com/go/osconfig v1.12.1/go.mod h1:4CjBxND0gswz2gfYRCUoUzCm9zCABp91E cloud.google.com/go/osconfig v1.12.4 h1:OrRCIYEAbrbXdhm13/JINn9pQchvTTIzgmOCA7uJw8I= cloud.google.com/go/osconfig v1.12.5 h1:Mo5jGAxOMKH/PmDY7fgY19yFcVbvwREb5D5zMPQjFfo= cloud.google.com/go/osconfig v1.12.5/go.mod h1:D9QFdxzfjgw3h/+ZaAb5NypM8bhOMqBzgmbhzWViiW8= +cloud.google.com/go/osconfig v1.14.2 h1:iBN87PQc+EGh5QqijM3CuxcibvDWmF+9k0eOJT27FO4= +cloud.google.com/go/osconfig v1.14.2/go.mod h1:kHtsm0/j8ubyuzGciBsRxFlbWVjc4c7KdrwJw0+g+pQ= cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= @@ -922,6 +1093,8 @@ cloud.google.com/go/oslogin v1.10.1/go.mod h1:x692z7yAue5nE7CsSnoG0aaMbNoRJRXO4s cloud.google.com/go/oslogin v1.12.2 h1:NP/KgsD9+0r9hmHC5wKye0vJXVwdciv219DtYKYjgqE= cloud.google.com/go/oslogin v1.13.1 h1:1K4nOT5VEZNt7XkhaTXupBYos5HjzvJMfhvyD2wWdFs= cloud.google.com/go/oslogin v1.13.1/go.mod h1:vS8Sr/jR7QvPWpCjNqy6LYZr5Zs1e8ZGW/KPn9gmhws= +cloud.google.com/go/oslogin v1.14.2 h1:6ehIKkALrLe9zUHwEmfXRVuSPm3HiUmEnnDRr7yLIo8= +cloud.google.com/go/oslogin v1.14.2/go.mod h1:M7tAefCr6e9LFTrdWRQRrmMeKHbkvc4D9g6tHIjHySA= cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= cloud.google.com/go/phishingprotection v0.7.0 h1:l6tDkT7qAEV49MNEJkEJTB6vOO/onbSOcNtAT09HPuA= @@ -931,6 +1104,8 @@ cloud.google.com/go/phishingprotection v0.8.1/go.mod h1:AxonW7GovcA8qdEk13NfHq9h cloud.google.com/go/phishingprotection v0.8.4 h1:sPLUQkHq6b4AL0czSJZ0jd6vL55GSTHz2B3Md+TCZI0= cloud.google.com/go/phishingprotection v0.8.5 h1:DH3WFLzEoJdW/6xgsmoDqOwT1xddFi7gKu0QGZQhpGU= cloud.google.com/go/phishingprotection v0.8.5/go.mod h1:g1smd68F7mF1hgQPuYn3z8HDbNre8L6Z0b7XMYFmX7I= +cloud.google.com/go/phishingprotection v0.9.2 h1:SaW0IPf/1fflnzomjy7+9EMtReXuxkYpUAf/77m5xL8= +cloud.google.com/go/phishingprotection v0.9.2/go.mod h1:mSCiq3tD8fTJAuXq5QBHFKZqMUy8SfWsbUM9NpzJIRQ= cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= @@ -941,6 +1116,8 @@ cloud.google.com/go/policytroubleshooter v1.9.0/go.mod h1:+E2Lga7TycpeSTj2FsH4oX cloud.google.com/go/policytroubleshooter v1.10.2 h1:sq+ScLP83d7GJy9+wpwYJVnY+q6xNTXwOdRIuYjvHT4= cloud.google.com/go/policytroubleshooter v1.10.3 h1:c0WOzC6hz964QWNBkyKfna8A2jOIx1zzZa43Gx/P09o= cloud.google.com/go/policytroubleshooter v1.10.3/go.mod h1:+ZqG3agHT7WPb4EBIRqUv4OyIwRTZvsVDHZ8GlZaoxk= +cloud.google.com/go/policytroubleshooter v1.11.2 h1:sTIH5AQ8tcgmnqrqlZfYWymjMhPh4ZEt4CvQGgG+kzc= +cloud.google.com/go/policytroubleshooter v1.11.2/go.mod h1:1TdeCRv8Qsjcz2qC3wFltg/Mjga4HSpv8Tyr5rzvPsw= cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= @@ -951,6 +1128,8 @@ cloud.google.com/go/privatecatalog v0.9.1/go.mod h1:0XlDXW2unJXdf9zFz968Hp35gl/b cloud.google.com/go/privatecatalog v0.9.4 h1:Vo10IpWKbNvc/z/QZPVXgCiwfjpWoZ/wbgful4Uh/4E= cloud.google.com/go/privatecatalog v0.9.5 h1:UZ0assTnATXSggoxUIh61RjTQ4P9zCMk/kEMbn0nMYA= cloud.google.com/go/privatecatalog v0.9.5/go.mod h1:fVWeBOVe7uj2n3kWRGlUQqR/pOd450J9yZoOECcQqJk= +cloud.google.com/go/privatecatalog v0.10.2 h1:01RPfn8IL2//8UHAmImRraTFYM/3gAEiIxudWLWrp+0= +cloud.google.com/go/privatecatalog v0.10.2/go.mod h1:o124dHoxdbO50ImR3T4+x3GRwBSTf4XTn6AatP8MgsQ= cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w= cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= @@ -962,12 +1141,16 @@ cloud.google.com/go/pubsub v1.33.0 h1:6SPCPvWav64tj0sVX/+npCBKhUi/UjJehy9op/V3p2 cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= cloud.google.com/go/pubsub v1.36.1 h1:dfEPuGCHGbWUhaMCTHUFjfroILEkx55iUmKBZTP5f+Y= cloud.google.com/go/pubsub v1.36.1/go.mod h1:iYjCa9EzWOoBiTdd4ps7QoMtMln5NwaZQpK1hbRfBDE= +cloud.google.com/go/pubsub v1.45.1 h1:ZC/UzYcrmK12THWn1P72z+Pnp2vu/zCZRXyhAfP1hJY= +cloud.google.com/go/pubsub v1.45.1/go.mod h1:3bn7fTmzZFwaUjllitv1WlsNMkqBgGUb3UdMhI54eCc= cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= cloud.google.com/go/pubsublite v1.7.0 h1:cb9fsrtpINtETHiJ3ECeaVzrfIVhcGjhhJEjybHXHao= cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= cloud.google.com/go/pubsublite v1.8.1 h1:pX+idpWMIH30/K7c0epN6V703xpIcMXWRjKJsz0tYGY= cloud.google.com/go/pubsublite v1.8.1/go.mod h1:fOLdU4f5xldK4RGJrBMm+J7zMWNj/k4PxwEZXy39QS0= +cloud.google.com/go/pubsublite v1.8.2 h1:jLQozsEVr+c6tOU13vDugtnaBSUy/PD5zK6mhm+uF1Y= +cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI= cloud.google.com/go/recaptchaenterprise v1.3.1 h1:u6EznTGzIdsyOsvm+Xkw0aSuKFXQlyjGE9a4exk6iNQ= cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= @@ -985,6 +1168,8 @@ cloud.google.com/go/recaptchaenterprise/v2 v2.8.3/go.mod h1:Dak54rw6lC2gBY8FBznp cloud.google.com/go/recaptchaenterprise/v2 v2.8.4 h1:KOlLHLv3h3HwcZAkx91ubM3Oztz3JtT3ZacAJhWDorQ= cloud.google.com/go/recaptchaenterprise/v2 v2.9.2 h1:U3Wfq12X9cVMuTpsWDSURnXF0Z9hSPTHj+xsnXDRLsw= cloud.google.com/go/recaptchaenterprise/v2 v2.9.2/go.mod h1:trwwGkfhCmp05Ll5MSJPXY7yvnO0p4v3orGANAFHAuU= +cloud.google.com/go/recaptchaenterprise/v2 v2.19.0 h1:J/J7ZeVOX+sqn0hxzkOBfnQfBAzPZt8KaAuQoarQWQM= +cloud.google.com/go/recaptchaenterprise/v2 v2.19.0/go.mod h1:vnbA2SpVPPwKeoFrCQxR+5a0JFRRytwBBG69Zj9pGfk= cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= cloud.google.com/go/recommendationengine v0.7.0 h1:VibRFCwWXrFebEWKHfZAt2kta6pS7Tlimsnms0fjv7k= @@ -994,6 +1179,8 @@ cloud.google.com/go/recommendationengine v0.8.1/go.mod h1:MrZihWwtFYWDzE6Hz5nKcN cloud.google.com/go/recommendationengine v0.8.4 h1:JRiwe4hvu3auuh2hujiTc2qNgPPfVp+Q8KOpsXlEzKQ= cloud.google.com/go/recommendationengine v0.8.5 h1:ineqLswaCSBY0csYv5/wuXJMBlxATK6Xc5jJkpiTEdM= cloud.google.com/go/recommendationengine v0.8.5/go.mod h1:A38rIXHGFvoPvmy6pZLozr0g59NRNREz4cx7F58HAsQ= +cloud.google.com/go/recommendationengine v0.9.2 h1:RHVdmoNBdzgRJXI/3SV+GB5TTv/umsVguiaEvmKOh98= +cloud.google.com/go/recommendationengine v0.9.2/go.mod h1:DjGfWZJ68ZF5ZuNgoTVXgajFAG0yLt4CJOpC0aMK3yw= cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= @@ -1005,6 +1192,8 @@ cloud.google.com/go/recommender v1.11.0/go.mod h1:kPiRQhPyTJ9kyXPCG6u/dlPLbYfFlk cloud.google.com/go/recommender v1.11.3 h1:VndmgyS/J3+izR8V8BHa7HV/uun8//ivQ3k5eVKKyyM= cloud.google.com/go/recommender v1.12.1 h1:LVLYS3r3u0MSCxQSDUtLSkporEGi9OAE6hGvayrZNPs= cloud.google.com/go/recommender v1.12.1/go.mod h1:gf95SInWNND5aPas3yjwl0I572dtudMhMIG4ni8nr+0= +cloud.google.com/go/recommender v1.13.2 h1:xDFzlFk5Xp5MXnac468eicKM3MUo6UNdxoYuBMOF1mE= +cloud.google.com/go/recommender v1.13.2/go.mod h1:XJau4M5Re8F4BM+fzF3fqSjxNJuM66fwF68VCy/ngGE= cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= @@ -1016,6 +1205,8 @@ cloud.google.com/go/redis v1.13.1/go.mod h1:VP7DGLpE91M6bcsDdMuyCm2hIpB6Vp2hI090 cloud.google.com/go/redis v1.14.1 h1:J9cEHxG9YLmA9o4jTSvWt/RuVEn6MTrPlYSCRHujxDQ= cloud.google.com/go/redis v1.14.2 h1:QF0maEdVv0Fj/2roU8sX3NpiDBzP9ICYTO+5F32gQNo= cloud.google.com/go/redis v1.14.2/go.mod h1:g0Lu7RRRz46ENdFKQ2EcQZBAJ2PtJHJLuiiRuEXwyQw= +cloud.google.com/go/redis v1.17.2 h1:QbW264RBH+NSVEQqlDoHfoxcreXK8QRRByTOR2CFbJs= +cloud.google.com/go/redis v1.17.2/go.mod h1:h071xkcTMnJgQnU/zRMOVKNj5J6AttG16RDo+VndoNo= cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= @@ -1027,6 +1218,8 @@ cloud.google.com/go/resourcemanager v1.9.1/go.mod h1:dVCuosgrh1tINZ/RwBufr8lULmW cloud.google.com/go/resourcemanager v1.9.4 h1:JwZ7Ggle54XQ/FVYSBrMLOQIKoIT/uer8mmNvNLK51k= cloud.google.com/go/resourcemanager v1.9.5 h1:AZWr1vWVDKGwfLsVhcN+vcwOz3xqqYxtmMa0aABCMms= cloud.google.com/go/resourcemanager v1.9.5/go.mod h1:hep6KjelHA+ToEjOfO3garMKi/CLYwTqeAw7YiEI9x8= +cloud.google.com/go/resourcemanager v1.10.2 h1:LpqZZGM0uJiu1YWM878AA8zZ/qOQ/Ngno60Q8RAraAI= +cloud.google.com/go/resourcemanager v1.10.2/go.mod h1:5f+4zTM/ZOTDm6MmPOp6BQAhR0fi8qFPnvVGSoWszcc= cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= cloud.google.com/go/resourcesettings v1.5.0 h1:8Dua37kQt27CCWHm4h/Q1XqCF6ByD7Ouu49xg95qJzI= @@ -1036,6 +1229,8 @@ cloud.google.com/go/resourcesettings v1.6.1/go.mod h1:M7mk9PIZrC5Fgsu1kZJci6mpgN cloud.google.com/go/resourcesettings v1.6.4 h1:yTIL2CsZswmMfFyx2Ic77oLVzfBFoWBYgpkgiSPnC4Y= cloud.google.com/go/resourcesettings v1.6.5 h1:BTr5MVykJwClASci/7Og4Qfx70aQ4n3epsNLj94ZYgw= cloud.google.com/go/resourcesettings v1.6.5/go.mod h1:WBOIWZraXZOGAgoR4ukNj0o0HiSMO62H9RpFi9WjP9I= +cloud.google.com/go/resourcesettings v1.8.2 h1:ISRX2HZHNS17F/EuIwzPrQwEyIyUJayGuLrS51yt6Wk= +cloud.google.com/go/resourcesettings v1.8.2/go.mod h1:uEgtPiMA+xuBUM4Exu+ZkNpMYP0BLlYeJbyNHfrc+U0= cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= @@ -1048,6 +1243,8 @@ cloud.google.com/go/retail v1.14.4 h1:geqdX1FNqqL2p0ADXjPpw8lq986iv5GrVcieTYafuJ cloud.google.com/go/retail v1.15.1/go.mod h1:In9nSBOYhLbDGa87QvWlnE1XA14xBN2FpQRiRsUs9wU= cloud.google.com/go/retail v1.16.0 h1:Fn1GuAua1c6crCGqfJ1qMxG1Xh10Tg/x5EUODEHMqkw= cloud.google.com/go/retail v1.16.0/go.mod h1:LW7tllVveZo4ReWt68VnldZFWJRzsh9np+01J9dYWzE= +cloud.google.com/go/retail v1.19.1 h1:FVzvA+VuEdNoMz2WzWZ5KwfG+CX+jSv+SOspyQPLuRs= +cloud.google.com/go/retail v1.19.1/go.mod h1:W48zg0zmt2JMqmJKCuzx0/0XDLtovwzGAeJjmv6VPaE= cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= @@ -1058,6 +1255,8 @@ cloud.google.com/go/run v1.2.0/go.mod h1:36V1IlDzQ0XxbQjUx6IYbw8H3TJnWvhii963WW3 cloud.google.com/go/run v1.3.3 h1:qdfZteAm+vgzN1iXzILo3nJFQbzziudkJrvd9wCf3FQ= cloud.google.com/go/run v1.3.4 h1:m9WDA7DzTpczhZggwYlZcBWgCRb+kgSIisWn1sbw2rQ= cloud.google.com/go/run v1.3.4/go.mod h1:FGieuZvQ3tj1e9GnzXqrMABSuir38AJg5xhiYq+SF3o= +cloud.google.com/go/run v1.7.0 h1:GJtHWUgi8CK+YPhmTR3tKBAmDmU9RRMYqiGKCmIgFG8= +cloud.google.com/go/run v1.7.0/go.mod h1:IvJOg2TBb/5a0Qkc6crn5yTy5nkjcgSWQLhgO8QL8PQ= cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= @@ -1072,6 +1271,8 @@ cloud.google.com/go/scheduler v1.10.4/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2Iy cloud.google.com/go/scheduler v1.10.5 h1:eMEettHlFhG5pXsoHouIM5nRT+k+zU4+GUvRtnxhuVI= cloud.google.com/go/scheduler v1.10.6 h1:5U8iXLoQ03qOB+ZXlAecU7fiE33+u3QiM9nh4cd0eTE= cloud.google.com/go/scheduler v1.10.6/go.mod h1:pe2pNCtJ+R01E06XCDOJs1XvAMbv28ZsQEbqknxGOuE= +cloud.google.com/go/scheduler v1.11.2 h1:PfkvJP1qKu9NvFB65Ja/s918bPZWMBcYkg35Ljdw1Oc= +cloud.google.com/go/scheduler v1.11.2/go.mod h1:GZSv76T+KTssX2I9WukIYQuQRf7jk1WI+LOcIEHUUHk= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= @@ -1082,6 +1283,8 @@ cloud.google.com/go/secretmanager v1.11.1/go.mod h1:znq9JlXgTNdBeQk9TBW/FnR/W4uC cloud.google.com/go/secretmanager v1.11.4 h1:krnX9qpG2kR2fJ+u+uNyNo+ACVhplIAS4Pu7u+4gd+k= cloud.google.com/go/secretmanager v1.11.5 h1:82fpF5vBBvu9XW4qj0FU2C6qVMtj1RM/XHwKXUEAfYY= cloud.google.com/go/secretmanager v1.11.5/go.mod h1:eAGv+DaCHkeVyQi0BeXgAHOU0RdrMeZIASKc+S7VqH4= +cloud.google.com/go/secretmanager v1.14.2 h1:2XscWCfy//l/qF96YE18/oUaNJynAx749Jg3u0CjQr8= +cloud.google.com/go/secretmanager v1.14.2/go.mod h1:Q18wAPMM6RXLC/zVpWTlqq2IBSbbm7pKBlM3lCKsmjw= cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= @@ -1095,6 +1298,8 @@ cloud.google.com/go/security v1.15.1/go.mod h1:MvTnnbsWnehoizHi09zoiZob0iCHVcL4A cloud.google.com/go/security v1.15.4 h1:sdnh4Islb1ljaNhpIXlIPgb3eYj70QWgPVDKOUYvzJc= cloud.google.com/go/security v1.15.5 h1:wTKJQ10j8EYgvE8Y+KhovxDRVDk2iv/OsxZ6GrLP3kE= cloud.google.com/go/security v1.15.5/go.mod h1:KS6X2eG3ynWjqcIX976fuToN5juVkF6Ra6c7MPnldtc= +cloud.google.com/go/security v1.18.2 h1:9Nzp9LGjiDvHqy7X7Q9GrS5lIHN0bI8RvDjkrl4ILO0= +cloud.google.com/go/security v1.18.2/go.mod h1:3EwTcYw8554iEtgK8VxAjZaq2unFehcsgFIF9nOvQmU= cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= @@ -1107,6 +1312,8 @@ cloud.google.com/go/securitycenter v1.23.0/go.mod h1:8pwQ4n+Y9WCWM278R8W3nF65QtY cloud.google.com/go/securitycenter v1.24.2 h1:qCEyXoJoxNKKA1bDywBjjqCB7ODXazzHnVWnG5Uqd1M= cloud.google.com/go/securitycenter v1.24.4 h1:/5jjkZ+uGe8hZ7pvd7pO30VW/a+pT2MrrdgOqjyucKQ= cloud.google.com/go/securitycenter v1.24.4/go.mod h1:PSccin+o1EMYKcFQzz9HMMnZ2r9+7jbc+LvPjXhpwcU= +cloud.google.com/go/securitycenter v1.35.2 h1:XkkE+IRE5/88drGPIuvETCSN7dAnWoqJahZzDbP5Hog= +cloud.google.com/go/securitycenter v1.35.2/go.mod h1:AVM2V9CJvaWGZRHf3eG+LeSTSissbufD27AVBI91C8s= cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= @@ -1125,6 +1332,8 @@ cloud.google.com/go/servicedirectory v1.11.0/go.mod h1:Xv0YVH8s4pVOwfM/1eMTl0XJ6 cloud.google.com/go/servicedirectory v1.11.3 h1:5niCMfkw+jifmFtbBrtRedbXkJm3fubSR/KHbxSJZVM= cloud.google.com/go/servicedirectory v1.11.4 h1:da7HFI1229kyzIyuVEzHXip0cw0d+E0s8mjQby0WN+k= cloud.google.com/go/servicedirectory v1.11.4/go.mod h1:Bz2T9t+/Ehg6x+Y7Ycq5xiShYLD96NfEsWNHyitj1qM= +cloud.google.com/go/servicedirectory v1.12.2 h1:W/oZmTUzlWbeSTujRbmG9v7HZyHcorj608tkcD3vVYE= +cloud.google.com/go/servicedirectory v1.12.2/go.mod h1:F0TJdFjqqotiZRlMXgIOzszaplk4ZAmUV8ovHo08M2U= cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= @@ -1144,6 +1353,8 @@ cloud.google.com/go/shell v1.7.1/go.mod h1:u1RaM+huXFaTojTbW4g9P5emOrrmLE69KrxqQ cloud.google.com/go/shell v1.7.4 h1:nurhlJcSVFZneoRZgkBEHumTYf/kFJptCK2eBUq/88M= cloud.google.com/go/shell v1.7.5 h1:3Fq2hzO0ZSyaqBboJrFkwwf/qMufDtqwwA6ep8EZxEI= cloud.google.com/go/shell v1.7.5/go.mod h1:hL2++7F47/IfpfTO53KYf1EC+F56k3ThfNEXd4zcuiE= +cloud.google.com/go/shell v1.8.2 h1:lSfdEng3n7zZHzC40BJ4trEMyme3CGnLLnA09MlLQdQ= +cloud.google.com/go/shell v1.8.2/go.mod h1:QQR12T6j/eKvqAQLv6R3ozeoqwJ0euaFSz2qLqG93Bs= cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= @@ -1156,6 +1367,8 @@ cloud.google.com/go/spanner v1.51.0/go.mod h1:c5KNo5LQ1X5tJwma9rSQZsXNBDNvj4/n8B cloud.google.com/go/spanner v1.53.0 h1:/NzWQJ1MEhdRcffiutRKbW/AIGVKhcTeivWTDjEyCCo= cloud.google.com/go/spanner v1.56.0 h1:o/Cv7/zZ1WgRXVCd5g3Nc23ZI39p/1pWFqFwvg6Wcu8= cloud.google.com/go/spanner v1.56.0/go.mod h1:DndqtUKQAt3VLuV2Le+9Y3WTnq5cNKrnLb/Piqcj+h0= +cloud.google.com/go/spanner v1.73.0 h1:0bab8QDn6MNj9lNK6XyGAVFhMlhMU2waePPa6GZNoi8= +cloud.google.com/go/spanner v1.73.0/go.mod h1:mw98ua5ggQXVWwp83yjwggqEmW9t8rjs9Po1ohcUGW4= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= @@ -1170,6 +1383,8 @@ cloud.google.com/go/speech v1.20.1/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWo cloud.google.com/go/speech v1.21.0 h1:qkxNao58oF8ghAHE1Eghen7XepawYEN5zuZXYWaUTA4= cloud.google.com/go/speech v1.21.1 h1:nuFc+Kj5B8de75nN4FdPyUbI2SiBoHZG6BLurXL56Q0= cloud.google.com/go/speech v1.21.1/go.mod h1:E5GHZXYQlkqWQwY5xRSLHw2ci5NMQNG52FfMU1aZrIA= +cloud.google.com/go/speech v1.25.2 h1:rKOXU9LAZTOYHhRNB4gZDekNjJx21TktQpetBa5IzOk= +cloud.google.com/go/speech v1.25.2/go.mod h1:KPFirZlLL8SqPaTtG6l+HHIFHPipjbemv4iFg7rTlYs= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.18.2 h1:5NQw6tOn3eMm0oE8vTkfjau18kjL79FlMjy/CHTpmoY= cloud.google.com/go/storage v1.18.2/go.mod h1:AiIj7BWXyhO5gGVmYJ+S8tbkCx3yb0IMjua8Aw4naVM= @@ -1186,6 +1401,8 @@ cloud.google.com/go/storage v1.33.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/ cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= +cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -1196,6 +1413,8 @@ cloud.google.com/go/storagetransfer v1.10.0/go.mod h1:DM4sTlSmGiNczmV6iZyceIh2db cloud.google.com/go/storagetransfer v1.10.3 h1:YM1dnj5gLjfL6aDldO2s4GeU8JoAvH1xyIwXre63KmI= cloud.google.com/go/storagetransfer v1.10.4 h1:dy4fL3wO0VABvzM05ycMUPFHxTPbJz9Em8ikAJVqSbI= cloud.google.com/go/storagetransfer v1.10.4/go.mod h1:vef30rZKu5HSEf/x1tK3WfWrL0XVoUQN/EPDRGPzjZs= +cloud.google.com/go/storagetransfer v1.11.2 h1:hMcP8ECmxedXjPxr2j3Ca45ro/TKEF+1YYjq2p5LMTI= +cloud.google.com/go/storagetransfer v1.11.2/go.mod h1:FcM29aY4EyZ3yVPmW5SxhqUdhjgPBUOFyy4rqiQbias= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= @@ -1207,6 +1426,8 @@ cloud.google.com/go/talent v1.6.2/go.mod h1:CbGvmKCG61mkdjcqTcLOkb2ZN1SrQI8MDyma cloud.google.com/go/talent v1.6.5 h1:LnRJhhYkODDBoTwf6BeYkiJHFw9k+1mAFNyArwZUZAs= cloud.google.com/go/talent v1.6.6 h1:JssV0CE3FNujuSWn7SkosOzg7qrMxVnt6txOfGcMSa4= cloud.google.com/go/talent v1.6.6/go.mod h1:y/WQDKrhVz12WagoarpAIyKKMeKGKHWPoReZ0g8tseQ= +cloud.google.com/go/talent v1.7.2 h1:KONR7KX/EXI3pO2cbSIDOBqhBzvgDS71vaMz8k4qRCg= +cloud.google.com/go/talent v1.7.2/go.mod h1:k1sqlDgS9gbc0gMTRuRQpX6C6VB7bGUxSPcoTRWJod8= cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= cloud.google.com/go/texttospeech v1.6.0 h1:H4g1ULStsbVtalbZGktyzXzw6jP26RjVGYx9RaYjBzc= @@ -1216,6 +1437,8 @@ cloud.google.com/go/texttospeech v1.7.1/go.mod h1:m7QfG5IXxeneGqTapXNxv2ItxP/FS0 cloud.google.com/go/texttospeech v1.7.4 h1:ahrzTgr7uAbvebuhkBAAVU6kRwVD0HWsmDsvMhtad5Q= cloud.google.com/go/texttospeech v1.7.5 h1:dxY2Q5mHCbrGa3oPR2O3PCicdnvKa1JmwGQK36EFLOw= cloud.google.com/go/texttospeech v1.7.5/go.mod h1:tzpCuNWPwrNJnEa4Pu5taALuZL4QRRLcb+K9pbhXT6M= +cloud.google.com/go/texttospeech v1.10.0 h1:icRAxYDtq3zO1T0YBT/fe8C/7pXoIqfkY4iYr5zG39I= +cloud.google.com/go/texttospeech v1.10.0/go.mod h1:215FpCOyRxxrS7DSb2t7f4ylMz8dXsQg8+Vdup5IhP4= cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= cloud.google.com/go/tpu v1.5.0 h1:/34T6CbSi+kTv5E19Q9zbU/ix8IviInZpzwz3rsFE+A= @@ -1225,6 +1448,8 @@ cloud.google.com/go/tpu v1.6.1/go.mod h1:sOdcHVIgDEEOKuqUoi6Fq53MKHJAtOwtz0GuKsW cloud.google.com/go/tpu v1.6.4 h1:XIEH5c0WeYGaVy9H+UueiTaf3NI6XNdB4/v6TFQJxtE= cloud.google.com/go/tpu v1.6.5 h1:C8YyYda8WtNdBoCgFwwBzZd+S6+EScHOxM/z1h0NNp8= cloud.google.com/go/tpu v1.6.5/go.mod h1:P9DFOEBIBhuEcZhXi+wPoVy/cji+0ICFi4TtTkMHSSs= +cloud.google.com/go/tpu v1.7.2 h1:xPBJd7xZgtl3CgrZoaUf7zFPVVj68jmzzGTSzkcsOtQ= +cloud.google.com/go/tpu v1.7.2/go.mod h1:0Y7dUo2LIbDUx0yQ/vnLC6e18FK6NrDfAhYS9wZ/2vs= cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= @@ -1235,6 +1460,8 @@ cloud.google.com/go/trace v1.10.1/go.mod h1:gbtL94KE5AJLH3y+WVpfWILmqgc6dXcqgNXd cloud.google.com/go/trace v1.10.4 h1:2qOAuAzNezwW3QN+t41BtkDJOG42HywL73q8x/f6fnM= cloud.google.com/go/trace v1.10.5 h1:0pr4lIKJ5XZFYD9GtxXEWr0KkVeigc3wlGpZco0X1oA= cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= @@ -1246,6 +1473,8 @@ cloud.google.com/go/translate v1.9.0/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNW cloud.google.com/go/translate v1.9.3 h1:t5WXTqlrk8VVJu/i3WrYQACjzYJiff5szARHiyqqPzI= cloud.google.com/go/translate v1.10.1 h1:upovZ0wRMdzZvXnu+RPam41B0mRJ+coRXFP2cYFJ7ew= cloud.google.com/go/translate v1.10.1/go.mod h1:adGZcQNom/3ogU65N9UXHOnnSvjPwA/jKQUMnsYXOyk= +cloud.google.com/go/translate v1.12.2 h1:qECivi8O+jFI/vnvN9elK6CME+WAWy56GIBszF+/rNc= +cloud.google.com/go/translate v1.12.2/go.mod h1:jjLVf2SVH2uD+BNM40DYvRRKSsuyKxVvs3YjTW/XSWY= cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= @@ -1258,6 +1487,8 @@ cloud.google.com/go/video v1.20.0/go.mod h1:U3G3FTnsvAGqglq9LxgqzOiBc/Nt8zis8S+8 cloud.google.com/go/video v1.20.3 h1:Xrpbm2S9UFQ1pZEeJt9Vqm5t2T/z9y/M3rNXhFoo8Is= cloud.google.com/go/video v1.20.4 h1:TXwotxkShP1OqgKsbd+b8N5hrIHavSyLGvYnLGCZ7xc= cloud.google.com/go/video v1.20.4/go.mod h1:LyUVjyW+Bwj7dh3UJnUGZfyqjEto9DnrvTe1f/+QrW0= +cloud.google.com/go/video v1.23.2 h1:CGAPOXTJMoZm9PeHkohBlMTy8lqN6VWCNDjp5VODfy8= +cloud.google.com/go/video v1.23.2/go.mod h1:rNOr2pPHWeCbW0QsOwJRIe0ZiuwHpHtumK0xbiYB1Ew= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= @@ -1269,6 +1500,8 @@ cloud.google.com/go/videointelligence v1.11.1/go.mod h1:76xn/8InyQHarjTWsBR058Sm cloud.google.com/go/videointelligence v1.11.4 h1:YS4j7lY0zxYyneTFXjBJUj2r4CFe/UoIi/PJG0Zt/Rg= cloud.google.com/go/videointelligence v1.11.5 h1:mYaWH8uhUCXLJCN3gdXswKzRa2+lK0zN6/KsIubm6pE= cloud.google.com/go/videointelligence v1.11.5/go.mod h1:/PkeQjpRponmOerPeJxNPuxvi12HlW7Em0lJO14FC3I= +cloud.google.com/go/videointelligence v1.12.2 h1:ZLElysepw9vfQGAKWfnxdnSnHSKbEn/nU/tmBnCJLfA= +cloud.google.com/go/videointelligence v1.12.2/go.mod h1:8xKGlq0lNVyT8JgTkkCUCpyNJnYYEJVWGdqzv+UcwR8= cloud.google.com/go/vision v1.2.0 h1:/CsSTkbmO9HC8iQpxbK8ATms3OQaX3YQUeTMGCxlaK4= cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= @@ -1284,6 +1517,8 @@ cloud.google.com/go/vision/v2 v2.7.5 h1:T/ujUghvEaTb+YnFY/jiYwVAkMbIC8EieK0CJo6B cloud.google.com/go/vision/v2 v2.7.6/go.mod h1:ZkvWTVNPBU3YZYzgF9Y1jwEbD1NBOCyJn0KFdQfE6Bw= cloud.google.com/go/vision/v2 v2.8.0 h1:W52z1b6LdGI66MVhE70g/NFty9zCYYcjdKuycqmlhtg= cloud.google.com/go/vision/v2 v2.8.0/go.mod h1:ocqDiA2j97pvgogdyhoxiQp2ZkDCyr0HWpicywGGRhU= +cloud.google.com/go/vision/v2 v2.9.2 h1:u4pu3gKps88oUe76WwVPeX9dgWVyyYopZ1s05FwsKEk= +cloud.google.com/go/vision/v2 v2.9.2/go.mod h1:WuxjVQdAy4j4WZqY5Rr655EdAgi8B707Vdb5T8c90uo= cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= @@ -1294,6 +1529,8 @@ cloud.google.com/go/vmmigration v1.7.1/go.mod h1:WD+5z7a/IpZ5bKK//YmT9E047AD+rjy cloud.google.com/go/vmmigration v1.7.4 h1:qPNdab4aGgtaRX+51jCOtJxlJp6P26qua4o1xxUDjpc= cloud.google.com/go/vmmigration v1.7.5 h1:5v9RT2vWyuw3pK2ox0HQpkoftO7Q7/8591dTxxQc79g= cloud.google.com/go/vmmigration v1.7.5/go.mod h1:pkvO6huVnVWzkFioxSghZxIGcsstDvYiVCxQ9ZH3eYI= +cloud.google.com/go/vmmigration v1.8.2 h1:Hpqv3fZ3Ri1OMhTNVJgxxsTou2ZlRzKbnc1dSybTP5Y= +cloud.google.com/go/vmmigration v1.8.2/go.mod h1:FBejrsr8ZHmJb949BSOyr3D+/yCp9z9Hk0WtsTiHc1Q= cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= cloud.google.com/go/vmwareengine v0.3.0 h1:b0NBu7S294l0gmtrT0nOJneMYgZapr5x9tVWvgDoVEM= @@ -1303,6 +1540,8 @@ cloud.google.com/go/vmwareengine v1.0.0/go.mod h1:Px64x+BvjPZwWuc4HdmVhoygcXqEkG cloud.google.com/go/vmwareengine v1.0.3 h1:WY526PqM6QNmFHSqe2sRfK6gRpzWjmL98UFkql2+JDM= cloud.google.com/go/vmwareengine v1.1.1 h1:EGdDi9QbqThfZq3ILcDK5g+m9jTevc34AY5tACx5v7k= cloud.google.com/go/vmwareengine v1.1.1/go.mod h1:nMpdsIVkUrSaX8UvmnBhzVzG7PPvNYc5BszcvIVudYs= +cloud.google.com/go/vmwareengine v1.3.2 h1:LmkojgSLvsRwU1+c0iiY2XoBkXYKzpArElHC9IDWakg= +cloud.google.com/go/vmwareengine v1.3.2/go.mod h1:JsheEadzT0nfXOGkdnwtS1FhFAnj4g8qhi4rKeLi/AU= cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= cloud.google.com/go/vpcaccess v1.6.0 h1:FOe6CuiQD3BhHJWt7E8QlbBcaIzVRddupwJlp7eqmn4= @@ -1312,6 +1551,8 @@ cloud.google.com/go/vpcaccess v1.7.1/go.mod h1:FogoD46/ZU+JUBX9D606X21EnxiszYi2t cloud.google.com/go/vpcaccess v1.7.4 h1:zbs3V+9ux45KYq8lxxn/wgXole6SlBHHKKyZhNJoS+8= cloud.google.com/go/vpcaccess v1.7.5 h1:XyL6hTLtEM/eE4F1GEge8xUN9ZCkiVWn44K/YA7z1rQ= cloud.google.com/go/vpcaccess v1.7.5/go.mod h1:slc5ZRvvjP78c2dnL7m4l4R9GwL3wDLcpIWz6P/ziig= +cloud.google.com/go/vpcaccess v1.8.2 h1:nvrkqAjS2sorOu4YGCIXWz+Kk+5aAAdnaMD2tnsqeFg= +cloud.google.com/go/vpcaccess v1.8.2/go.mod h1:4yvYKNjlNjvk/ffgZ0PuEhpzNJb8HybSM1otG2aDxnY= cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= @@ -1323,6 +1564,8 @@ cloud.google.com/go/webrisk v1.9.1/go.mod h1:4GCmXKcOa2BZcZPn6DCEvE7HypmEJcJkr4m cloud.google.com/go/webrisk v1.9.4 h1:iceR3k0BCRZgf2D/NiKviVMFfuNC9LmeNLtxUFRB/wI= cloud.google.com/go/webrisk v1.9.5 h1:251MvGuC8wisNN7+jqu9DDDZAi38KiMXxOpA/EWy4dE= cloud.google.com/go/webrisk v1.9.5/go.mod h1:aako0Fzep1Q714cPEM5E+mtYX8/jsfegAuS8aivxy3U= +cloud.google.com/go/webrisk v1.10.2 h1:X7zSwS1mX2bxoZ30Ozh6lqiSLezl7RMBWwp5a3Mkxp4= +cloud.google.com/go/webrisk v1.10.2/go.mod h1:c0ODT2+CuKCYjaeHO7b0ni4CUrJ95ScP5UFl9061Qq8= cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= cloud.google.com/go/websecurityscanner v1.5.0 h1:AHC1xmaNMOZtNqxI9Rmm87IJEyPaRkOxeI0gpAacXGk= @@ -1332,6 +1575,8 @@ cloud.google.com/go/websecurityscanner v1.6.1/go.mod h1:Njgaw3rttgRHXzwCB8kgCYqv cloud.google.com/go/websecurityscanner v1.6.4 h1:5Gp7h5j7jywxLUp6NTpjNPkgZb3ngl0tUSw6ICWvtJQ= cloud.google.com/go/websecurityscanner v1.6.5 h1:YqWZrZYabG88TZt7364XWRJGhxmxhony2ZUyZEYMF2k= cloud.google.com/go/websecurityscanner v1.6.5/go.mod h1:QR+DWaxAz2pWooylsBF854/Ijvuoa3FCyS1zBa1rAVQ= +cloud.google.com/go/websecurityscanner v1.7.2 h1:8/4rfJXcyxozbfzI0lDFPcPShRE6bJ4HQwgDAG9J4oQ= +cloud.google.com/go/websecurityscanner v1.7.2/go.mod h1:728wF9yz2VCErfBaACA5px2XSYHQgkK812NmHcUsDXA= cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= @@ -1343,6 +1588,8 @@ cloud.google.com/go/workflows v1.12.0/go.mod h1:PYhSk2b6DhZ508tj8HXKaBh+OFe+xdl0 cloud.google.com/go/workflows v1.12.3 h1:qocsqETmLAl34mSa01hKZjcqAvt699gaoFbooGGMvaM= cloud.google.com/go/workflows v1.12.4 h1:uHNmUiatTbPQ4H1pabwfzpfEYD4BBnqDHqMm2IesOh4= cloud.google.com/go/workflows v1.12.4/go.mod h1:yQ7HUqOkdJK4duVtMeBCAOPiN1ZF1E9pAMX51vpwB/w= +cloud.google.com/go/workflows v1.13.2 h1:jYIxrDOVCGvTBHIAVhqQ+P8fhE0trm+Hf2hgL1YzmK0= +cloud.google.com/go/workflows v1.13.2/go.mod h1:l5Wj2Eibqba4BsADIRzPLaevLmIuYF2W+wfFBkRG3vU= contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= contrib.go.opencensus.io/exporter/zipkin v0.1.2 h1:YqE293IZrKtqPnpwDPH/lOqTWD/s3Iwabycam74JV3g= contrib.go.opencensus.io/exporter/zipkin v0.1.2/go.mod h1:mP5xM3rrgOjpn79MM8fZbj3gsxcuytSqtH0dxSWW1RE= @@ -1412,7 +1659,16 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 h1:oVLqHXhnYtUwM89y9T1fXGaK9wTkXHgNp8/ZNMQzUxE= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20200415212048-7901bc822317/go.mod h1:DF8FZRxMHMGv/vP2lQP6h+dYzzjpuRn24VeRiYn3qjQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.2/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= github.com/IBM/sarama v1.40.1 h1:lL01NNg/iBeigUbT+wpPysuTYW6roHo6kc1QrffRf0k= github.com/IBM/sarama v1.40.1/go.mod h1:+5OFwA5Du9I6QrznhaMHsuwWdWZNMjaBSIxEWEgKOYE= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU= @@ -1506,6 +1762,8 @@ github.com/apache/arrow/go/v12 v12.0.1 h1:JsR2+hzYYjgSUkBSaahpqCetqZMr76djX80fF/ github.com/apache/arrow/go/v12 v12.0.1/go.mod h1:weuTY7JvTG/HDPtMQxEUp7pU73vkLWMLpY67QwZ/WWw= github.com/apache/arrow/go/v14 v14.0.2 h1:N8OkaJEOfI3mEZt07BIkvo4sC6XDbL+48MBPWO5IONw= github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybFg8QBQ5LU+eBY= +github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE= +github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs= github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= @@ -1603,6 +1861,8 @@ github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20211215200129- github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220228164355-396b2034c795/go.mod h1:8vJsEZ4iRqG+Vx6pKhWK6U00qcj0KC37IsfszMkY6UE= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20221004211355-a250ad2ca1e3 h1:Ted/bR1N6ltMrASdwRhX1BrGYSFg3aeGMlK8GlgkGh4= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20221004211355-a250ad2ca1e3/go.mod h1:m06KtrZgOloUaePAQMv+Ha8kRmTnKdozTHZrweepIrw= +github.com/bazelbuild/rules_go v0.49.0 h1:5vCbuvy8Q11g41lseGJDc5vxhDjJtfxr6nM/IC4VmqM= +github.com/bazelbuild/rules_go v0.49.0/go.mod h1:Dhcz716Kqg1RHNWos+N6MlXNkjNP2EwZQ0LukRKJfMs= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= @@ -1703,6 +1963,8 @@ github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnTh github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= @@ -1839,6 +2101,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHH github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -1915,8 +2178,6 @@ github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arX github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -1948,6 +2209,8 @@ github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjl github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les= github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= +github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= @@ -2236,6 +2499,8 @@ github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 h1:V2IgdyerlBa/MxaEFR github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b h1:fbskpz/cPqWH8VqkQ7LJghFkl2KPAiIFUHrTJ2O3RGk= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M= github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= github.com/google/cel-go v0.12.7 h1:jM6p55R0MKBg79hZjn1zs2OlrywZ1Vk00rxVvad1/O0= @@ -2266,6 +2531,8 @@ github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/ github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MGRbDfvEwGd0= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9 h1:OF1IPgv+F4NmqmJ98KTjdN97Vs1JxDPB3vbmYzV2dpk= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/go-pkcs11 v0.3.0 h1:PVRnTgtArZ3QQqTGtbtjtnIkzl2iY2kt24yqbrf7td8= +github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -2277,6 +2544,8 @@ github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1V github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -2291,10 +2560,14 @@ github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkj github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/cloud-bigtable-clients-test v0.0.2 h1:S+sCHWAiAc+urcEnvg5JYJUOdlQEm/SEzQ/c/IdAH5M= +github.com/googleapis/cloud-bigtable-clients-test v0.0.2/go.mod h1:mk3CrkrouRgtnhID6UZQDK3DrFFa7cYCAJcEmNsHYrY= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -2305,6 +2578,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nw github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= @@ -2322,6 +2597,8 @@ github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qK github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= @@ -2329,6 +2606,7 @@ github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 h1:tlyzajkF3030q6M8SvmJSemC9DTHL/xaMa18b65+JM4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gophercloud/gophercloud v1.5.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo= github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= @@ -2338,6 +2616,7 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww= @@ -2348,7 +2627,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaW github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= @@ -2379,7 +2657,6 @@ github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR3 github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= @@ -2404,8 +2681,6 @@ github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb-client-go/v2 v2.9.0 h1:1Ejxpt+cpWkadefxd5xvVx7pFgFaafdNp1ItfHzKRW4= github.com/influxdata/influxdb-client-go/v2 v2.9.0/go.mod h1:x7Jo5UHHl+w8wu8UnGiNobDDHygojXwJX4mx7rXGKMk= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= @@ -2441,6 +2716,7 @@ github.com/joefitzgerald/rainbow-reporter v0.1.0 h1:AuMG652zjdzI0YCCnXAqATtRBpGX github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0= @@ -2469,7 +2745,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= @@ -2487,6 +2762,8 @@ github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac h1:+2b6i github.com/linode/linodego v1.19.0/go.mod h1:XZFR+yJ9mm2kwf6itZ6SCpu+6w3KnIevV0Uu5HNWJgQ= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k= +github.com/lucasjones/reggen v0.0.0-20200904144131-37ba4fa293bb h1:w1g9wNDIE/pHSTmAaUhv4TZQuPBS6GV3mMz5hkgziIU= +github.com/lucasjones/reggen v0.0.0-20200904144131-37ba4fa293bb/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1 h1:erE0rdztuaDq3bpGifD95wfoPrSZc95nGA6tbiNYh6M= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= @@ -2494,6 +2771,8 @@ github.com/lyft/protoc-gen-star/v2 v2.0.1 h1:keaAo8hRuAT0O3DfJ/wM3rufbAjGeJ1lAtW github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/lyft/protoc-gen-star/v2 v2.0.3 h1:/3+/2sWyXeMLzKd1bX+ixWKgEMsULrIivpDsuaF441o= github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 h1:sIXJOMrYnQZJu7OB7ANSF4MYri2fTEGIsRLz6LwI4xE= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/lyft/protoc-gen-validate v0.0.13 h1:KNt/RhmQTOLr7Aj8PsJ7mTronaFyx80mRTT9qF261dA= github.com/marstr/guid v1.1.0 h1:/M4H/1G4avsieL6BbUwCOBzulmoeKVP5ux/3mQNnbyI= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd h1:HvFwW+cm9bCbZ/+vuGNq7CRWXql8c0y8nGeYpqmpvmk= @@ -2542,7 +2821,6 @@ github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9 github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/51gbeLHfKGNfgEQQIWrlbdaOsidbQ= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= @@ -2651,6 +2929,8 @@ github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM= +github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= @@ -2699,13 +2979,14 @@ github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 h1:Lt9DzQALzHoDwMBGJ6v8ObDPR0dzr2a6sXTB1Fq7IHs= github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245 h1:K1Xf3bKttbF+koVGaX5xngRIZ5bVjbmPnaxE/dR08uY= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= @@ -2726,6 +3007,8 @@ github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1 github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= @@ -2748,8 +3031,6 @@ github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRM github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= @@ -2761,10 +3042,10 @@ github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 h1:7z3LSn867ex6 github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d h1:X4+kt6zM/OVO6gbJdAfJR60MGPsqCzbtXNnjoGqdfAs= github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807 h1:LUsDduamlucuNnWcaTbXQ6aLILFcLXADpOzeEH3U+OI= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 h1:zLV6q4e8Jv9EHjNg/iHfzwDkCve6Ua5jCygptrtXHvI= @@ -2834,6 +3115,8 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M= +go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw= +go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= @@ -2898,6 +3181,8 @@ go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= go.opentelemetry.io/collector/pdata v1.0.0-rcv0014/go.mod h1:BRvDrx43kiSoUx3mr7SoA7h9B8+OY99mUK+CZSQFWW4= go.opentelemetry.io/collector/semconv v0.81.0/go.mod h1:TlYPtzvsXyHOgr5eATi43qEMqwSmIziivJB2uctKswo= +go.opentelemetry.io/contrib/detectors/gcp v1.31.0 h1:G1JQOreVrfhRkner+l4mrGxmfqYCAuy76asTDAo0xsA= +go.opentelemetry.io/contrib/detectors/gcp v1.31.0/go.mod h1:tzQL6E1l+iV44YFTkcAeNQqzXUiekSYP9jjJjXwEd00= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0/go.mod h1:E5NNboN0UqSAki0Atn9kVwaN7I+l25gGxDqBueo/74E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 h1:xFSRQBbXF6VvYRf2lqMJXxoB72XI1K/azav8TekHHSw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY= @@ -2910,6 +3195,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1 h1:sxoY9kG1s1WpSYNyzm24rlwH4lnRYFXUVVBmKMBfRgw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= @@ -2933,6 +3220,8 @@ go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZV go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI= @@ -2963,6 +3252,8 @@ go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xC go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= @@ -2973,6 +3264,9 @@ go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZ go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4= go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= @@ -2986,6 +3280,8 @@ go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40 go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= @@ -3034,12 +3330,17 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -3074,6 +3375,9 @@ golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -3118,7 +3422,12 @@ golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -3149,6 +3458,9 @@ golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2 golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -3160,7 +3472,10 @@ golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -3171,6 +3486,7 @@ golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -3198,6 +3514,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -3226,14 +3543,22 @@ golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808 h1:+Kc94D8UVEVxJnLXp/+FMfqQARZtWHfVrcRtcG8aT3g= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= +golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= @@ -3270,6 +3595,7 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -3304,7 +3630,10 @@ golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= @@ -3312,6 +3641,8 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3j golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485 h1:OB/uP/Puiu5vS5QMRPrXCDWUPb+kt8f1KW8oQzFejQw= @@ -3377,6 +3708,8 @@ google.golang.org/api v0.160.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9 google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= google.golang.org/api v0.164.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o= google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0= +google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= @@ -3506,6 +3839,7 @@ google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 h1:g/4bk7P6TPMkAUb google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -3525,12 +3859,18 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go. google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:E5//3O5ZIG2l71Xnt+P/CYUY8Bxs8E7WMoZ9tlcMbAY= +google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d/go.mod h1:2v7Z7gP2ZUOGsaFyxATQSRoBnKygqVq2Cwnvom7QiqY= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231009173412-8bfb1ae86b6c h1:9tZedXBlwql0v/dLZx1E4Rcz9ESc8j1KZk71903wKEg= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:itlFWGBbEyD32PUeJsTG8h8Wz7iJXfVK4gt1EJ+pAG0= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405 h1:o4S3HvTUEXgRsNSUQsALDVog0O9F/U1JJlHmmUN8Uas= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78 h1:YqFWYZXim8bG9v68xU8WjTZmYKb5M5dMeSOWIp6jogI= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20241223144023-3abc09e42ca8 h1:qlXhWiX84AGgaN7LuORWBEQCCTqj3szNbh2am45O3W8= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:bLYPejkLzwgJuAHlIk1gdPOlx9CUYXLZi2rZxL/ursM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= @@ -3553,6 +3893,15 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= @@ -3591,6 +3940,9 @@ google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFL google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= @@ -3600,6 +3952,11 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= @@ -3610,7 +3967,6 @@ gopkg.in/imdario/mergo.v0 v0.3.7 h1:QDotlIZtaO/p+Um0ok18HRTpq5i5/SAk/qprsor+9c8= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= @@ -3645,6 +4001,8 @@ k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= k8s.io/apiserver v0.31.0 h1:p+2dgJjy+bk+B1Csz+mc2wl5gHwvNkC9QJV+w55LVrY= k8s.io/apiserver v0.31.0/go.mod h1:KI9ox5Yu902iBnnyMmy7ajonhKnkeZYJhTZ/YI+WEMk= +k8s.io/apiserver v0.31.6 h1:FEhEGLsz1PbMOHeQZDbOUlMh36zRZbjgKwJCoMhdGmw= +k8s.io/apiserver v0.31.6/go.mod h1:dpFh+xqFQ02O8vLYCIqoiV7sJIpZsUULeNuag6Y9HGo= k8s.io/cli-runtime v0.17.3 h1:0ZlDdJgJBKsu77trRUynNiWsRuAvAVPBNaQfnt/1qtc= k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= @@ -3660,6 +4018,8 @@ k8s.io/code-generator v0.30.0 h1:3VUVqHvWFSVSm9kqL/G6kD4ZwNdHF6J/jPyo3Jgjy3k= k8s.io/code-generator v0.30.0/go.mod h1:mBMZhfRR4IunJUh2+7LVmdcWwpouCH5+LNPkZ3t/v7Q= k8s.io/code-generator v0.31.0 h1:w607nrMi1KeDKB3/F/J4lIoOgAwc+gV9ZKew4XRfMp8= k8s.io/code-generator v0.31.0/go.mod h1:84y4w3es8rOJOUUP1rLsIiGlO1JuEaPFXQPA9e/K6U0= +k8s.io/code-generator v0.31.6 h1:CX4/NGV5UIdt7+nYG/G4+eGHOvcXAlKWswUhPPOtPtc= +k8s.io/code-generator v0.31.6/go.mod h1:vbqDrvP5hJJ5S/jzBtyMJoH5kJBWZMo/DZwMYiOQniE= k8s.io/component-base v0.26.5/go.mod h1:wvfNAS05EtKdPeUxFceo8WNh8bGPcFY8QfPhv5MYjA4= k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo= k8s.io/component-base v0.27.6/go.mod h1:NvjLtaneUeb0GgMPpCBF+4LNB9GuhDHi16uUTjBhQfU= @@ -3667,6 +4027,8 @@ k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o= k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ= k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs= k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= +k8s.io/component-base v0.31.6 h1:FgI25PuZtCp2n7AFpOaDpMQOLieFdrpAbpeoZu7VhDI= +k8s.io/component-base v0.31.6/go.mod h1:aVRrh8lAI1kSShFmwcKLhc3msQoUcmFWPBDf0sXaISM= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -3688,6 +4050,8 @@ k8s.io/kms v0.30.0 h1:ZlnD/ei5lpvUlPw6eLfVvH7d8i9qZ6HwUQgydNVks8g= k8s.io/kms v0.30.0/go.mod h1:GrMurD0qk3G4yNgGcsCEmepqf9KyyIrTXYR2lyUOJC4= k8s.io/kms v0.31.0 h1:KchILPfB1ZE+ka7223mpU5zeFNkmb45jl7RHnlImUaI= k8s.io/kms v0.31.0/go.mod h1:OZKwl1fan3n3N5FFxnW5C4V3ygrah/3YXeJWS3O6+94= +k8s.io/kms v0.31.6 h1:p7OY+9Hp8nPtgzm0vT9TrERNigQQSu8tkgWqn+GvB2w= +k8s.io/kms v0.31.6/go.mod h1:OZKwl1fan3n3N5FFxnW5C4V3ygrah/3YXeJWS3O6+94= k8s.io/kube-aggregator v0.17.3 h1:U7U/XHnKwQlvFmsEE6ubpjF0Y4AVhKtXo+9I3d0L6rY= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY=