diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml index 2534adf79..0f459e9a2 100644 --- a/.github/workflows/cherry-pick.yml +++ b/.github/workflows/cherry-pick.yml @@ -3,6 +3,9 @@ on: issue_comment: types: [created] +env: + GH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + jobs: cherry-pick: @@ -12,10 +15,10 @@ jobs: steps: - uses: actions/checkout@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ env.GH_TOKEN }} fetch-depth: 0 - name: Automatic Cherry Pick uses: apecloud-inc/gha-cherry-pick@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ env.GH_TOKEN }} diff --git a/.github/workflows/release-kbcli.yml b/.github/workflows/release-kbcli.yml index b2915bf40..5d982156e 100644 --- a/.github/workflows/release-kbcli.yml +++ b/.github/workflows/release-kbcli.yml @@ -38,7 +38,7 @@ jobs: gorelease-assert: needs: [ create-release-jihulab ] name: Upload and release kbcli - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: install lib @@ -95,7 +95,7 @@ jobs: asset_content_type: application/octet-stream - name: Upload kbcli Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.TAG_NAME }}-kbcli path: | @@ -103,10 +103,11 @@ jobs: ./dist/*.zip if-no-files-found: error retention-days: 1 + overwrite: true upload-release-assert: needs: gorelease-assert - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -114,7 +115,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Download Kbcli Artifact - uses: actions/download-artifact@v3.0.2 + uses: actions/download-artifact@v4 with: name: ${{ env.TAG_NAME }}-kbcli path: ${{ github.workspace }}/dist @@ -137,7 +138,7 @@ jobs: remove-artifact: needs: upload-release-assert - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Remove kbcli Artifact uses: geekyeggo/delete-artifact@v2 diff --git a/.github/workflows/release-tag.yaml b/.github/workflows/release-tag.yaml index e02123666..4f58b257a 100644 --- a/.github/workflows/release-tag.yaml +++ b/.github/workflows/release-tag.yaml @@ -55,6 +55,15 @@ jobs: submodules: 'recursive' fetch-depth: 0 + - name: get ref name + id: get_ref_name + run: | + GITHUB_REF_NAME="${{ github.ref_name }}" + if [[ -z "${{ inputs.RELEASE_VERSION }}" && -n "${{ env.CUR_BRANCH }}" ]]; then + GITHUB_REF_NAME="${{ env.CUR_BRANCH }}" + fi + echo github_ref_name="${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT + - name: build kbcli embed chart id: build-kbcli-embed-chart run: | @@ -74,23 +83,23 @@ jobs: echo 'RELEASE_COMMIT='${RELEASE_COMMIT} >> $GITHUB_ENV - name: push kbcli embed chart changes to new branch - if: ${{ steps.build-kbcli-embed-chart.outputs.file_changes && github.ref_name == 'main' }} + if: ${{ steps.build-kbcli-embed-chart.outputs.file_changes && steps.get_ref_name.outputs.github_ref_name == 'main' }} uses: ad-m/github-push-action@master with: github_token: ${{ env.GITHUB_TOKEN }} branch: support/auto-update-kbcli-${{ env.RELEASE_COMMIT }} - name: auto create pr head new branch - if: ${{ steps.build-kbcli-embed-chart.outputs.file_changes && github.ref_name == 'main' }} + if: ${{ steps.build-kbcli-embed-chart.outputs.file_changes && steps.get_ref_name.outputs.github_ref_name == 'main' }} run: | gh pr create --head "support/auto-update-kbcli-${{ env.RELEASE_COMMIT }}" --title "chore: auto update kbcli embed chart changes" --body "" - name: push kbcli embed chart changes uses: ad-m/github-push-action@master - if: ${{ steps.build-kbcli-embed-chart.outputs.file_changes && github.ref_name != 'main' }} + if: ${{ steps.build-kbcli-embed-chart.outputs.file_changes && steps.get_ref_name.outputs.github_ref_name != 'main' }} with: github_token: ${{ env.GITHUB_TOKEN }} - branch: ${{ github.ref }} + branch: ${{ steps.get_ref_name.outputs.github_ref_name }} - name: push tag ${{ inputs.VERSION }} uses: mathieudutour/github-tag-action@v6.1 diff --git a/Makefile b/Makefile index 9fe8b2e0f..05f5f9e60 100644 --- a/Makefile +++ b/Makefile @@ -48,10 +48,6 @@ GOPROXY := https://proxy.golang.org endif export GOPROXY -# build tags -BUILD_TAGS="containers_image_openpgp" - - TAG_LATEST ?= false BUILDX_ENABLED ?= "" ifeq ($(BUILDX_ENABLED), "") @@ -104,7 +100,7 @@ fmt: ## Run go fmt against code. .PHONY: vet vet: ## Run go vet against code. - GOOS=$(GOOS) $(GO) vet -tags $(BUILD_TAGS) -mod=mod ./... + GOOS=$(GOOS) $(GO) vet -mod=mod ./... .PHONY: cue-fmt cue-fmt: cuetool ## Run cue fmt against code. @@ -124,7 +120,7 @@ golangci-lint: golangci generate ## Run golangci-lint against code. .PHONY: staticcheck staticcheck: staticchecktool generate ## Run staticcheck against code. - $(STATICCHECK) -tags $(BUILD_TAGS) ./... + $(STATICCHECK) ./... .PHONY: build-checks build-checks: generate fmt vet goimports lint-fast ## Run build checks. @@ -143,11 +139,11 @@ TEST_PACKAGES ?= ./pkg/... ./cmd/... OUTPUT_COVERAGE=-coverprofile cover.out .PHONY: test test: generate ## Run operator controller tests with current $KUBECONFIG context. if existing k8s cluster is k3d or minikube, specify EXISTING_CLUSTER_TYPE. - $(GO) test -tags $(BUILD_TAGS) -p 1 $(TEST_PACKAGES) $(OUTPUT_COVERAGE) + $(GO) test -p 1 $(TEST_PACKAGES) $(OUTPUT_COVERAGE) .PHONY: test-fast test-fast: - $(GO) test -tags $(BUILD_TAGS) -short $(TEST_PACKAGES) $(OUTPUT_COVERAGE) + $(GO) test -short $(TEST_PACKAGES) $(OUTPUT_COVERAGE) .PHONY: cover-report cover-report: ## Generate cover.html from cover.out @@ -179,7 +175,7 @@ CLI_LD_FLAGS ="-s -w \ -X github.com/apecloud/kbcli/version.DefaultKubeBlocksVersion=$(VERSION)" bin/kbcli.%: ## Cross build bin/kbcli.$(OS).$(ARCH). - GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO) build -tags $(BUILD_TAGS) -ldflags=${CLI_LD_FLAGS} -o $@ cmd/cli/main.go + GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO) build -ldflags=${CLI_LD_FLAGS} -o $@ cmd/cli/main.go .PHONY: fetch-addons fetch-addons: ## update addon submodule @@ -235,8 +231,9 @@ build-kbcli-embed-chart: helmtool fetch-addons create-kbcli-embed-charts-dir \ build-single-kbcli-embed-chart.postgresql \ build-single-kbcli-embed-chart.kafka \ build-single-kbcli-embed-chart.mongodb \ - build-single-kbcli-embed-chart.elasticsearch \ - build-single-kbcli-embed-chart.qdrant + build-single-kbcli-embed-chart.qdrant \ + build-single-kbcli-embed-chart.etcd \ + build-single-kbcli-embed-chart.rabbitmq .PHONY: kbcli kbcli: build-checks kbcli-fast ## Build bin/kbcli. @@ -248,7 +245,7 @@ clean-kbcli: ## Clean bin/kbcli*. .PHONY: kbcli-doc kbcli-doc: ## generate CLI command reference manual. - $(GO) run -tags $(BUILD_TAGS) ./hack/docgen/cli/main.go ./docs/user_docs/cli + $(GO) run ./hack/docgen/cli/main.go ./docs/user_docs/cli .PHONY: install-docker-buildx install-docker-buildx: ## Create `docker buildx` builder. diff --git a/addons b/addons index 81459e520..3dca32705 160000 --- a/addons +++ b/addons @@ -1 +1 @@ -Subproject commit 81459e520c4d796faa3ce0260d3883132df5e1d4 +Subproject commit 3dca32705114aed567891a7bf44201a069e9b32e diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 725ede5c7..cb7b61a2f 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/docker/Dockerfile b/docker/Dockerfile index 2aee1458c..573c16210 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -39,7 +39,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \ RUN --mount=type=bind,target=. \ --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg/mod \ - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="${LD_FLAGS}" -tags="containers_image_openpgp" -a -o /out/kbcli cmd/cli/main.go + CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="${LD_FLAGS}" -a -o /out/kbcli cmd/cli/main.go # Use alpine with tag 20230329 is corresponding to "edge" tag (latest release to date is 3.18) as of 20230625 FROM docker.io/alpine:edge as dist diff --git a/docker/docker.mk b/docker/docker.mk index 234ed4acd..3c485c3d2 100644 --- a/docker/docker.mk +++ b/docker/docker.mk @@ -1,5 +1,5 @@ # -#Copyright (C) 2022-2024 ApeCloud Co., Ltd +#Copyright (C) 2022-2025 ApeCloud Co., Ltd # #This file is part of KubeBlocks project # diff --git a/docs/user_docs/cli/_category_.yml b/docs/user_docs/cli/_category_.yml new file mode 100644 index 000000000..dff53aca8 --- /dev/null +++ b/docs/user_docs/cli/_category_.yml @@ -0,0 +1,5 @@ +position: 30 +label: Command Line +collapsible: true +collapsed: true +className: hide-children \ No newline at end of file diff --git a/docs/user_docs/cli/cli.md b/docs/user_docs/cli/cli.md index 5d04aed88..54487f0e6 100644 --- a/docs/user_docs/cli/cli.md +++ b/docs/user_docs/cli/cli.md @@ -14,6 +14,7 @@ Addon command. * [kbcli addon index](kbcli_addon_index.md) - Manage custom addon indexes * [kbcli addon install](kbcli_addon_install.md) - Install KubeBlocks addon * [kbcli addon list](kbcli_addon_list.md) - List addons. +* [kbcli addon purge](kbcli_addon_purge.md) - Purge the sub-resources of specified addon and versions * [kbcli addon search](kbcli_addon_search.md) - Search the addon from index * [kbcli addon uninstall](kbcli_addon_uninstall.md) - Uninstall an existed addon * [kbcli addon upgrade](kbcli_addon_upgrade.md) - Upgrade an existed addon to latest version or a specified version @@ -50,21 +51,20 @@ Cluster command. * [kbcli cluster describe-config](kbcli_cluster_describe-config.md) - Show details of a specific reconfiguring. * [kbcli cluster describe-ops](kbcli_cluster_describe-ops.md) - Show details of a specific OpsRequest. * [kbcli cluster describe-restore](kbcli_cluster_describe-restore.md) - Describe a restore -* [kbcli cluster diff-config](kbcli_cluster_diff-config.md) - Show the difference in parameters between the two submitted OpsRequest. * [kbcli cluster edit-backup-policy](kbcli_cluster_edit-backup-policy.md) - Edit backup policy * [kbcli cluster edit-config](kbcli_cluster_edit-config.md) - Edit the config file of the component. * [kbcli cluster explain-config](kbcli_cluster_explain-config.md) - List the constraint for supported configuration params. * [kbcli cluster expose](kbcli_cluster_expose.md) - Expose a cluster with a new endpoint, the new endpoint can be found by executing 'kbcli cluster describe NAME'. * [kbcli cluster label](kbcli_cluster_label.md) - Update the labels on cluster * [kbcli cluster list](kbcli_cluster_list.md) - List clusters. -* [kbcli cluster list-backup](kbcli_cluster_list-backup.md) - List backups. -* [kbcli cluster list-backup-policy](kbcli_cluster_list-backup-policy.md) - List backups policies. +* [kbcli cluster list-backup-policies](kbcli_cluster_list-backup-policies.md) - List backups policies. +* [kbcli cluster list-backups](kbcli_cluster_list-backups.md) - List backups. * [kbcli cluster list-components](kbcli_cluster_list-components.md) - List cluster components. * [kbcli cluster list-events](kbcli_cluster_list-events.md) - List cluster events. * [kbcli cluster list-instances](kbcli_cluster_list-instances.md) - List cluster instances. * [kbcli cluster list-logs](kbcli_cluster_list-logs.md) - List supported log files in cluster. * [kbcli cluster list-ops](kbcli_cluster_list-ops.md) - List all opsRequests. -* [kbcli cluster list-restore](kbcli_cluster_list-restore.md) - List restores. +* [kbcli cluster list-restores](kbcli_cluster_list-restores.md) - List restores. * [kbcli cluster logs](kbcli_cluster_logs.md) - Access cluster log file. * [kbcli cluster promote](kbcli_cluster_promote.md) - Promote a non-primary or non-leader instance as the new primary or leader of the cluster * [kbcli cluster rebuild-instance](kbcli_cluster_rebuild-instance.md) - Rebuild the specified instances in the cluster. @@ -77,6 +77,7 @@ Cluster command. * [kbcli cluster stop](kbcli_cluster_stop.md) - Stop the cluster and release all the pods of the cluster. * [kbcli cluster update](kbcli_cluster_update.md) - Update the cluster settings, such as enable or disable monitor or log. * [kbcli cluster upgrade](kbcli_cluster_upgrade.md) - Upgrade the service version(only support to upgrade minor version). +* [kbcli cluster upgrade-to-v1](kbcli_cluster_upgrade-to-v1.md) - upgrade cluster to v1 api version. * [kbcli cluster volume-expand](kbcli_cluster_volume-expand.md) - Expand volume with the specified components and volumeClaimTemplates in the cluster. * [kbcli cluster vscale](kbcli_cluster_vscale.md) - Vertically scale the specified components in the cluster. @@ -105,14 +106,6 @@ ComponentVersions command. * [kbcli componentversion list](kbcli_componentversion_list.md) - List ComponentVersion. -## [dashboard](kbcli_dashboard.md) - -List and open the KubeBlocks dashboards. - -* [kbcli dashboard list](kbcli_dashboard_list.md) - List all dashboards. -* [kbcli dashboard open](kbcli_dashboard_open.md) - Open one dashboard. - - ## [dataprotection](kbcli_dataprotection.md) Data protection command. @@ -123,11 +116,11 @@ Data protection command. * [kbcli dataprotection describe-backup-policy](kbcli_dataprotection_describe-backup-policy.md) - Describe a backup policy * [kbcli dataprotection describe-restore](kbcli_dataprotection_describe-restore.md) - Describe a restore * [kbcli dataprotection edit-backup-policy](kbcli_dataprotection_edit-backup-policy.md) - Edit backup policy -* [kbcli dataprotection list-action-set](kbcli_dataprotection_list-action-set.md) - List actionsets -* [kbcli dataprotection list-backup](kbcli_dataprotection_list-backup.md) - List backups. -* [kbcli dataprotection list-backup-policy](kbcli_dataprotection_list-backup-policy.md) - List backup policies -* [kbcli dataprotection list-backup-policy-template](kbcli_dataprotection_list-backup-policy-template.md) - List backup policy template -* [kbcli dataprotection list-restore](kbcli_dataprotection_list-restore.md) - List restores. +* [kbcli dataprotection list-action-sets](kbcli_dataprotection_list-action-sets.md) - List actionsets +* [kbcli dataprotection list-backup-policies](kbcli_dataprotection_list-backup-policies.md) - List backup policies +* [kbcli dataprotection list-backup-policy-templates](kbcli_dataprotection_list-backup-policy-templates.md) - List backup policy templates +* [kbcli dataprotection list-backups](kbcli_dataprotection_list-backups.md) - List backups. +* [kbcli dataprotection list-restores](kbcli_dataprotection_list-restores.md) - List restores. * [kbcli dataprotection restore](kbcli_dataprotection_restore.md) - Restore a new cluster from backup @@ -191,6 +184,17 @@ Report kubeblocks or cluster info. * [kbcli report kubeblocks](kbcli_report_kubeblocks.md) - Report KubeBlocks information, including deployments, events, logs, etc. +## [trace](kbcli_trace.md) + +trace management command + +* [kbcli trace create](kbcli_trace_create.md) - create a trace. +* [kbcli trace delete](kbcli_trace_delete.md) - Delete a trace. +* [kbcli trace list](kbcli_trace_list.md) - list all traces. +* [kbcli trace update](kbcli_trace_update.md) - update a trace. +* [kbcli trace watch](kbcli_trace_watch.md) - watch a trace. + + ## [version](kbcli_version.md) Print the version information, include kubernetes, KubeBlocks and kbcli version. diff --git a/docs/user_docs/cli/kbcli.md b/docs/user_docs/cli/kbcli.md index bdfb949cd..de0277101 100644 --- a/docs/user_docs/cli/kbcli.md +++ b/docs/user_docs/cli/kbcli.md @@ -60,7 +60,6 @@ kbcli [flags] * [kbcli clusterdefinition](kbcli_clusterdefinition.md) - ClusterDefinition command. * [kbcli componentdefinition](kbcli_componentdefinition.md) - ComponentDefinition command. * [kbcli componentversion](kbcli_componentversion.md) - ComponentVersions command. -* [kbcli dashboard](kbcli_dashboard.md) - List and open the KubeBlocks dashboards. * [kbcli dataprotection](kbcli_dataprotection.md) - Data protection command. * [kbcli kubeblocks](kbcli_kubeblocks.md) - KubeBlocks operation commands. * [kbcli ops-definition](kbcli_ops-definition.md) - ops-definitions command. @@ -68,6 +67,7 @@ kbcli [flags] * [kbcli playground](kbcli_playground.md) - Bootstrap or destroy a playground KubeBlocks in local host or cloud. * [kbcli plugin](kbcli_plugin.md) - Provides utilities for interacting with plugins. * [kbcli report](kbcli_report.md) - Report kubeblocks or cluster info. +* [kbcli trace](kbcli_trace.md) - trace management command * [kbcli version](kbcli_version.md) - Print the version information, include kubernetes, KubeBlocks and kbcli version. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_addon.md b/docs/user_docs/cli/kbcli_addon.md index 83a423e66..4948d30e6 100644 --- a/docs/user_docs/cli/kbcli_addon.md +++ b/docs/user_docs/cli/kbcli_addon.md @@ -43,6 +43,7 @@ Addon command. * [kbcli addon index](kbcli_addon_index.md) - Manage custom addon indexes * [kbcli addon install](kbcli_addon_install.md) - Install KubeBlocks addon * [kbcli addon list](kbcli_addon_list.md) - List addons. +* [kbcli addon purge](kbcli_addon_purge.md) - Purge the sub-resources of specified addon and versions * [kbcli addon search](kbcli_addon_search.md) - Search the addon from index * [kbcli addon uninstall](kbcli_addon_uninstall.md) - Uninstall an existed addon * [kbcli addon upgrade](kbcli_addon_upgrade.md) - Upgrade an existed addon to latest version or a specified version diff --git a/docs/user_docs/cli/kbcli_cluster_delete-account.md b/docs/user_docs/cli/kbcli_addon_purge.md similarity index 73% rename from docs/user_docs/cli/kbcli_cluster_delete-account.md rename to docs/user_docs/cli/kbcli_addon_purge.md index 93f6369bd..0d61ee16b 100644 --- a/docs/user_docs/cli/kbcli_cluster_delete-account.md +++ b/docs/user_docs/cli/kbcli_addon_purge.md @@ -1,32 +1,34 @@ --- -title: kbcli cluster delete-account +title: kbcli addon purge --- -Delete account for a cluster +Purge the sub-resources of specified addon and versions ``` -kbcli cluster delete-account [flags] +kbcli addon purge [flags] ``` ### Examples ``` - # delete account by name - kbcli cluster delete-account CLUSTERNAME --component COMPNAME --name USERNAME - # delete account with default component - kbcli cluster delete-account CLUSTERNAME --name USERNAME - # delete account for instance - kbcli cluster delete-account --instance INSTANCE --name USERNAME + # Purge specific versions of redis addon resources + kbcli addon purge redis --versions=0.9.1,0.9.2 + + # Purge all unused and outdated resources of redis addon + kbcli addon purge redis --all + + # Print the resources that would be purged, and no resource is actually purged + kbcli addon purge redis --dry-run ``` ### Options ``` - --auto-approve Skip interactive approval before deleting account - --component string Specify the name of component to be connected. If not specified, pick the first one. - -h, --help help for delete-account - -i, --instance string Specify the name of instance to be connected. - --name string Required user name, please specify it. + --all If set to true, all resources will be purged, including those that are unused and not the newest version. + --auto-approve Skip interactive approval before deleting + --dry-run If set to true, only print the resources that would be purged, and no resource is actually purged. + -h, --help help for purge + --versions strings Specify the versions of resources to purge. ``` ### Options inherited from parent commands @@ -55,7 +57,7 @@ kbcli cluster delete-account [flags] ### SEE ALSO -* [kbcli cluster](kbcli_cluster.md) - Cluster command. +* [kbcli addon](kbcli_addon.md) - Addon command. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_addon_search.md b/docs/user_docs/cli/kbcli_addon_search.md index 1fa3c142a..10995adff 100644 --- a/docs/user_docs/cli/kbcli_addon_search.md +++ b/docs/user_docs/cli/kbcli_addon_search.md @@ -5,7 +5,7 @@ title: kbcli addon search Search the addon from index ``` -kbcli addon search [flags] +kbcli addon search [ADDON_NAME] [flags] ``` ### Examples diff --git a/docs/user_docs/cli/kbcli_alert.md b/docs/user_docs/cli/kbcli_alert.md deleted file mode 100644 index 72abcd2cb..000000000 --- a/docs/user_docs/cli/kbcli_alert.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: kbcli alert ---- - -Manage alert receiver, include add, list and delete receiver. - -### Options - -``` - -h, --help help for alert -``` - -### Options inherited from parent commands - -``` - --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. - --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. - --as-uid string UID to impersonate for the operation. - --cache-dir string Default cache directory (default "$HOME/.kube/cache") - --certificate-authority string Path to a cert file for the certificate authority - --client-certificate string Path to a client certificate file for TLS - --client-key string Path to a client key file for TLS - --cluster string The name of the kubeconfig cluster to use - --context string The name of the kubeconfig context to use - --disable-compression If true, opt-out of response compression for all requests to the server - --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure - --kubeconfig string Path to the kubeconfig file to use for CLI requests. - --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request - --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") - -s, --server string The address and port of the Kubernetes API server - --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used - --token string Bearer token for authentication to the API server - --user string The name of the kubeconfig user to use -``` - -### SEE ALSO - - -* [kbcli alert add-receiver](kbcli_alert_add-receiver.md) - Add alert receiver, such as email, slack, webhook and so on. -* [kbcli alert config-smtpserver](kbcli_alert_config-smtpserver.md) - Set smtp server config -* [kbcli alert delete-receiver](kbcli_alert_delete-receiver.md) - Delete alert receiver. -* [kbcli alert list-receivers](kbcli_alert_list-receivers.md) - List all alert receivers. -* [kbcli alert list-smtpserver](kbcli_alert_list-smtpserver.md) - List alert smtp servers config. - -#### Go Back to [CLI Overview](cli.md) Homepage. - diff --git a/docs/user_docs/cli/kbcli_alert_add-receiver.md b/docs/user_docs/cli/kbcli_alert_add-receiver.md deleted file mode 100644 index 22bb55f0e..000000000 --- a/docs/user_docs/cli/kbcli_alert_add-receiver.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: kbcli alert add-receiver ---- - -Add alert receiver, such as email, slack, webhook and so on. - -``` -kbcli alert add-receiver [flags] -``` - -### Examples - -``` - # add webhook receiver without token, for example feishu - kbcli alert add-receiver --webhook='url=https://open.feishu.cn/open-apis/bot/v2/hook/foo' - - # add webhook receiver with token, for example feishu - kbcli alert add-receiver --webhook='url=https://open.feishu.cn/open-apis/bot/v2/hook/foo,token=XXX' - - # add email receiver - kbcli alert add-receiver --email='user1@kubeblocks.io,user2@kubeblocks.io' - - # add email receiver, and only receive alert from cluster mycluster - kbcli alert add-receiver --email='user1@kubeblocks.io,user2@kubeblocks.io' --cluster=mycluster - - # add email receiver, and only receive alert from cluster mycluster and alert severity is warning - kbcli alert add-receiver --email='user1@kubeblocks.io,user2@kubeblocks.io' --cluster=mycluster --severity=warning - - # add email receiver, and only receive alert from mysql clusters - kbcli alert add-receiver --email='user1@kubeblocks.io,user2@kubeblocks.io' --type=mysql - - # add email receiver, and only receive alert triggered by specified rule - kbcli alert add-receiver --email='user1@kubeblocks.io,user2@kubeblocks.io' --rule=MysqlDown - - # add slack receiver - kbcli alert add-receiver --slack api_url=https://hooks.slackConfig.com/services/foo,channel=monitor,username=kubeblocks-alert-bot -``` - -### Options - -``` - --cluster stringArray Cluster name, such as mycluster, more than one cluster can be specified, such as mycluster1,mycluster2 - --email stringArray Add email address, such as user@kubeblocks.io, more than one emailConfig can be specified separated by comma - -h, --help help for add-receiver - --repeat-interval string Repeat interval of current receiver - --rule stringArray Rule name, such as MysqlDown, more than one rule names can be specified, such as MysqlDown,MysqlRestarted - --severity stringArray Alert severity level, critical, warning or info, more than one severity level can be specified, such as critical,warning - --slack stringArray Add slack receiver, such as api_url=https://hooks.slackConfig.com/services/foo,channel=monitor,username=kubeblocks-alert-bot - --type stringArray Engine type, such as mysql, more than one types can be specified, such as mysql,postgresql,redis - --webhook stringArray Add webhook receiver, such as url=https://open.feishu.cn/open-apis/bot/v2/hook/foo,token=xxxxx -``` - -### Options inherited from parent commands - -``` - --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. - --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. - --as-uid string UID to impersonate for the operation. - --cache-dir string Default cache directory (default "$HOME/.kube/cache") - --certificate-authority string Path to a cert file for the certificate authority - --client-certificate string Path to a client certificate file for TLS - --client-key string Path to a client key file for TLS - --context string The name of the kubeconfig context to use - --disable-compression If true, opt-out of response compression for all requests to the server - --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure - --kubeconfig string Path to the kubeconfig file to use for CLI requests. - --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request - --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") - -s, --server string The address and port of the Kubernetes API server - --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used - --token string Bearer token for authentication to the API server - --user string The name of the kubeconfig user to use -``` - -### SEE ALSO - -* [kbcli alert](kbcli_alert.md) - Manage alert receiver, include add, list and delete receiver. - -#### Go Back to [CLI Overview](cli.md) Homepage. - diff --git a/docs/user_docs/cli/kbcli_alert_delete-receiver.md b/docs/user_docs/cli/kbcli_backuprepo_list-storage-provider.md similarity index 75% rename from docs/user_docs/cli/kbcli_alert_delete-receiver.md rename to docs/user_docs/cli/kbcli_backuprepo_list-storage-provider.md index d2d5014a4..af3e70e2a 100644 --- a/docs/user_docs/cli/kbcli_alert_delete-receiver.md +++ b/docs/user_docs/cli/kbcli_backuprepo_list-storage-provider.md @@ -1,24 +1,27 @@ --- -title: kbcli alert delete-receiver +title: kbcli backuprepo list-storage-provider --- -Delete alert receiver. +List storage providers. ``` -kbcli alert delete-receiver NAME [flags] +kbcli backuprepo list-storage-provider [flags] ``` ### Examples ``` - # delete a receiver named my-receiver, all receivers can be found by command: kbcli alert list-receivers - kbcli alert delete-receiver my-receiver + # List all storage provider + kbcli backuprepo list-sp ``` ### Options ``` - -h, --help help for delete-receiver + -h, --help help for list-storage-provider + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) ``` ### Options inherited from parent commands @@ -47,7 +50,7 @@ kbcli alert delete-receiver NAME [flags] ### SEE ALSO -* [kbcli alert](kbcli_alert.md) - Manage alert receiver, include add, list and delete receiver. +* [kbcli backuprepo](kbcli_backuprepo.md) - BackupRepo command. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_cluster.md b/docs/user_docs/cli/kbcli_cluster.md index 95de599b6..881191d83 100644 --- a/docs/user_docs/cli/kbcli_cluster.md +++ b/docs/user_docs/cli/kbcli_cluster.md @@ -52,21 +52,20 @@ Cluster command. * [kbcli cluster describe-config](kbcli_cluster_describe-config.md) - Show details of a specific reconfiguring. * [kbcli cluster describe-ops](kbcli_cluster_describe-ops.md) - Show details of a specific OpsRequest. * [kbcli cluster describe-restore](kbcli_cluster_describe-restore.md) - Describe a restore -* [kbcli cluster diff-config](kbcli_cluster_diff-config.md) - Show the difference in parameters between the two submitted OpsRequest. * [kbcli cluster edit-backup-policy](kbcli_cluster_edit-backup-policy.md) - Edit backup policy * [kbcli cluster edit-config](kbcli_cluster_edit-config.md) - Edit the config file of the component. * [kbcli cluster explain-config](kbcli_cluster_explain-config.md) - List the constraint for supported configuration params. * [kbcli cluster expose](kbcli_cluster_expose.md) - Expose a cluster with a new endpoint, the new endpoint can be found by executing 'kbcli cluster describe NAME'. * [kbcli cluster label](kbcli_cluster_label.md) - Update the labels on cluster * [kbcli cluster list](kbcli_cluster_list.md) - List clusters. -* [kbcli cluster list-backup](kbcli_cluster_list-backup.md) - List backups. -* [kbcli cluster list-backup-policy](kbcli_cluster_list-backup-policy.md) - List backups policies. +* [kbcli cluster list-backup-policies](kbcli_cluster_list-backup-policies.md) - List backups policies. +* [kbcli cluster list-backups](kbcli_cluster_list-backups.md) - List backups. * [kbcli cluster list-components](kbcli_cluster_list-components.md) - List cluster components. * [kbcli cluster list-events](kbcli_cluster_list-events.md) - List cluster events. * [kbcli cluster list-instances](kbcli_cluster_list-instances.md) - List cluster instances. * [kbcli cluster list-logs](kbcli_cluster_list-logs.md) - List supported log files in cluster. * [kbcli cluster list-ops](kbcli_cluster_list-ops.md) - List all opsRequests. -* [kbcli cluster list-restore](kbcli_cluster_list-restore.md) - List restores. +* [kbcli cluster list-restores](kbcli_cluster_list-restores.md) - List restores. * [kbcli cluster logs](kbcli_cluster_logs.md) - Access cluster log file. * [kbcli cluster promote](kbcli_cluster_promote.md) - Promote a non-primary or non-leader instance as the new primary or leader of the cluster * [kbcli cluster rebuild-instance](kbcli_cluster_rebuild-instance.md) - Rebuild the specified instances in the cluster. @@ -79,6 +78,7 @@ Cluster command. * [kbcli cluster stop](kbcli_cluster_stop.md) - Stop the cluster and release all the pods of the cluster. * [kbcli cluster update](kbcli_cluster_update.md) - Update the cluster settings, such as enable or disable monitor or log. * [kbcli cluster upgrade](kbcli_cluster_upgrade.md) - Upgrade the service version(only support to upgrade minor version). +* [kbcli cluster upgrade-to-v1](kbcli_cluster_upgrade-to-v1.md) - upgrade cluster to v1 api version. * [kbcli cluster volume-expand](kbcli_cluster_volume-expand.md) - Expand volume with the specified components and volumeClaimTemplates in the cluster. * [kbcli cluster vscale](kbcli_cluster_vscale.md) - Vertically scale the specified components in the cluster. diff --git a/docs/user_docs/cli/kbcli_cluster_backup.md b/docs/user_docs/cli/kbcli_cluster_backup.md index 928c825e5..d17c45b10 100644 --- a/docs/user_docs/cli/kbcli_cluster_backup.md +++ b/docs/user_docs/cli/kbcli_cluster_backup.md @@ -17,7 +17,7 @@ kbcli cluster backup NAME [flags] # create a backup with a specified method, run "kbcli cluster desc-backup-policy mycluster" to show supported backup methods kbcli cluster backup mycluster --method volume-snapshot - # create a backup with specified backup policy, run "kbcli cluster list-backup-policy mycluster" to show the cluster supported backup policies + # create a backup with specified backup policy, run "kbcli cluster list-backup-policies mycluster" to show the cluster supported backup policies kbcli cluster backup mycluster --method volume-snapshot --policy # create a backup from a parent backup diff --git a/docs/user_docs/cli/kbcli_clusterdefinition_list-components.md b/docs/user_docs/cli/kbcli_cluster_convert-to-v1.md similarity index 83% rename from docs/user_docs/cli/kbcli_clusterdefinition_list-components.md rename to docs/user_docs/cli/kbcli_cluster_convert-to-v1.md index 503f889a4..ef4dbaf0b 100644 --- a/docs/user_docs/cli/kbcli_clusterdefinition_list-components.md +++ b/docs/user_docs/cli/kbcli_cluster_convert-to-v1.md @@ -1,24 +1,29 @@ --- -title: kbcli clusterdefinition list-components +title: kbcli cluster convert-to-v1 --- -List cluster definition components. +convert cluster api version. ``` -kbcli clusterdefinition list-components [flags] +kbcli cluster convert-to-v1 [NAME] [flags] ``` ### Examples ``` - # List all components belonging to the cluster definition. - kbcli clusterdefinition list-components apecloud-mysql + # convert a v1alpha1 cluster + kbcli cluster convert-to-v1 mycluster + + # convert a v1alpha1 cluster with --dry-run + kbcli cluster convert-to-v1 mycluster --dry-run ``` ### Options ``` - -h, --help help for list-components + --dry-run dry run mode + -h, --help help for convert-to-v1 + --no-diff only print the new cluster yaml ``` ### Options inherited from parent commands @@ -47,7 +52,7 @@ kbcli clusterdefinition list-components [flags] ### SEE ALSO -* [kbcli clusterdefinition](kbcli_clusterdefinition.md) - ClusterDefinition command. +* [kbcli cluster](kbcli_cluster.md) - Cluster command. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_cluster_create-account.md b/docs/user_docs/cli/kbcli_cluster_create-account.md deleted file mode 100644 index da94e0de4..000000000 --- a/docs/user_docs/cli/kbcli_cluster_create-account.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: kbcli cluster create-account ---- - -Create account for a cluster - -``` -kbcli cluster create-account [flags] -``` - -### Examples - -``` - # create account with password - kbcli cluster create-account CLUSTERNAME --component COMPNAME --name USERNAME --password PASSWD - # create account without password - kbcli cluster create-account CLUSTERNAME --component COMPNAME --name USERNAME - # create account with default component - kbcli cluster create-account CLUSTERNAME --name USERNAME - # create account for instance - kbcli cluster create-account --instance INSTANCE --name USERNAME -``` - -### Options - -``` - --component string Specify the name of component to be connected. If not specified, pick the first one. - -h, --help help for create-account - -i, --instance string Specify the name of instance to be connected. - --name string Required. Specify the name of user, which must be unique. - -p, --password string Optional. Specify the password of user. The default value is empty, which means a random password will be generated. -``` - -### Options inherited from parent commands - -``` - --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. - --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. - --as-uid string UID to impersonate for the operation. - --cache-dir string Default cache directory (default "$HOME/.kube/cache") - --certificate-authority string Path to a cert file for the certificate authority - --client-certificate string Path to a client certificate file for TLS - --client-key string Path to a client key file for TLS - --cluster string The name of the kubeconfig cluster to use - --context string The name of the kubeconfig context to use - --disable-compression If true, opt-out of response compression for all requests to the server - --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure - --kubeconfig string Path to the kubeconfig file to use for CLI requests. - --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request - --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") - -s, --server string The address and port of the Kubernetes API server - --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used - --token string Bearer token for authentication to the API server - --user string The name of the kubeconfig user to use -``` - -### SEE ALSO - -* [kbcli cluster](kbcli_cluster.md) - Cluster command. - -#### Go Back to [CLI Overview](cli.md) Homepage. - diff --git a/docs/user_docs/cli/kbcli_cluster_create.md b/docs/user_docs/cli/kbcli_cluster_create.md index a6092d010..6c3805622 100644 --- a/docs/user_docs/cli/kbcli_cluster_create.md +++ b/docs/user_docs/cli/kbcli_cluster_create.md @@ -5,7 +5,7 @@ title: kbcli cluster create Create a cluster. ``` -kbcli cluster create [NAME] [flags] +kbcli cluster create [ClusterType] [flags] ``` ### Examples @@ -55,15 +55,14 @@ kbcli cluster create [NAME] [flags] * [kbcli cluster](kbcli_cluster.md) - Cluster command. * [kbcli cluster create apecloud-mysql](kbcli_cluster_create_apecloud-mysql.md) - Create a apecloud-mysql cluster. -* [kbcli cluster create elasticsearch](kbcli_cluster_create_elasticsearch.md) - Create a elasticsearch cluster. +* [kbcli cluster create etcd](kbcli_cluster_create_etcd.md) - Create a etcd cluster. * [kbcli cluster create kafka](kbcli_cluster_create_kafka.md) - Create a kafka cluster. -* [kbcli cluster create llm](kbcli_cluster_create_llm.md) - Create a llm cluster. * [kbcli cluster create mongodb](kbcli_cluster_create_mongodb.md) - Create a mongodb cluster. * [kbcli cluster create mysql](kbcli_cluster_create_mysql.md) - Create a mysql cluster. * [kbcli cluster create postgresql](kbcli_cluster_create_postgresql.md) - Create a postgresql cluster. * [kbcli cluster create qdrant](kbcli_cluster_create_qdrant.md) - Create a qdrant cluster. +* [kbcli cluster create rabbitmq](kbcli_cluster_create_rabbitmq.md) - Create a rabbitmq cluster. * [kbcli cluster create redis](kbcli_cluster_create_redis.md) - Create a redis cluster. -* [kbcli cluster create xinference](kbcli_cluster_create_xinference.md) - Create a xinference cluster. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_cluster_create_apecloud-mysql.md b/docs/user_docs/cli/kbcli_cluster_create_apecloud-mysql.md index b8ca2c109..e654ed265 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_apecloud-mysql.md +++ b/docs/user_docs/cli/kbcli_cluster_create_apecloud-mysql.md @@ -21,29 +21,37 @@ kbcli cluster create apecloud-mysql NAME [flags] ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") - --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) - --disable-exporter Enable or disable monitor. (default true) - --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") - --edit Edit the API resource before creating - -h, --help help for apecloud-mysql - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. - --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) - --mode string Cluster topology mode. Legal values [standalone, raftGroup]. (default "standalone") - --node-labels stringToString Node label selector (default []) - -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) - --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") - --proxy-enabled Enable proxy or not. - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. - --replicas int The number of replicas, for standalone mode, the replicas is 1, for raftGroup mode, the default replicas is 3. Value range [1, 5]. (default 1) - --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) - --storage-class-name string Storage class name of the data volume - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") - --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' - --topology-keys stringArray Topology keys for affinity - --version string Cluster version. (default "ac-mysql-8.0.30") + --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) + --disable-exporter Enable or disable monitor. (default true) + --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") + --edit Edit the API resource before creating + --etcd.local.etcdctl-api string (default "3") + --etcd.local.replicas int Value range [1, 3]. (default 3) + --etcd.local.resources.storage string (default "20Gi") + --etcd.local.service-version string (default "3.5.6") + --etcd.mode string Legal values [serviceRef, local]. (default "local") + --etcd.service-ref.cluster.component string (default "etcd") + --etcd.service-ref.cluster.credential string + --etcd.service-ref.cluster.name string + --etcd.service-ref.cluster.port string (default "client") + --etcd.service-ref.cluster.service string (default "headless") + --etcd.service-ref.namespace string (default "default") + --etcd.service-ref.service-descriptor string + -h, --help help for apecloud-mysql + --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) + --mode string Cluster topology mode. Legal values [standalone, raftGroup]. (default "standalone") + --node-labels stringToString Node label selector (default []) + -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) + --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") + --proxy-enabled Enable proxy or not. + --replicas int The number of replicas, for standalone mode, the replicas is 1, for raftGroup mode, the default replicas is 3. Value range [1, 5]. (default 1) + --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) + --storage-class-name string Storage class name of the data volume + --tenancy string Tenancy options, one of: (SharedNode, DedicatedNode) (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Delete, WipeOut]. (default "Delete") + --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' + --topology-keys stringArray Topology keys for affinity + --version string MySQL Service Version. (default "8.0.30") ``` ### Options inherited from parent commands diff --git a/docs/user_docs/cli/kbcli_cluster_create_elasticsearch.md b/docs/user_docs/cli/kbcli_cluster_create_etcd.md similarity index 69% rename from docs/user_docs/cli/kbcli_cluster_create_elasticsearch.md rename to docs/user_docs/cli/kbcli_cluster_create_etcd.md index 32188fa57..4ea3d7e6b 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_elasticsearch.md +++ b/docs/user_docs/cli/kbcli_cluster_create_etcd.md @@ -1,47 +1,45 @@ --- -title: kbcli cluster create elasticsearch +title: kbcli cluster create etcd --- -Create a elasticsearch cluster. +Create a etcd cluster. ``` -kbcli cluster create elasticsearch NAME [flags] +kbcli cluster create etcd NAME [flags] ``` ### Examples ``` # Create a cluster with the default values - kbcli cluster create elasticsearch + kbcli cluster create etcd # Create a cluster with the specified cpu, memory and storage - kbcli cluster create elasticsearch --cpu 1 --memory 2 --storage 10 + kbcli cluster create etcd --cpu 1 --memory 2 --storage 10 ``` ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") - --cpu float CPU cores. Value range [0.5, 64]. (default 1) + --client-service.node-port int Optional, if clientService type is NodePort, by default and for convenience, the Kubernetes control plane will allocate a port from a range (default: 30000-32767). + --client-service.port int The port on which the service will listen. (default 2379) + --client-service.role string Role of the service within the cluster. (default "leader") + --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) --disable-exporter Enable or disable monitor. (default true) --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") --edit Edit the API resource before creating - -h, --help help for elasticsearch - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. - --memory float Memory, the unit is Gi. Value range [1, 1000]. (default 2) - --mode string Mode for ElasticSearch Legal values [single-node, multi-node]. (default "multi-node") + -h, --help help for etcd + --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) --node-labels stringToString Node label selector (default []) -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. (default true) - --replicas int The number of replicas, for single-node mode, the replicas is 1, for multi-node mode, the default replicas is 3. Value range [1, 5]. (default 1) - --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") + --replicas int The number of replicas, the default replicas is 3. Value range [1, 5]. (default 3) + --storage float Data Storage size, the unit is Gi. Value range [1, 10000]. (default 10) + --tenancy string Tenancy options, one of: (SharedNode, DedicatedNode) (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Delete, WipeOut]. (default "Delete") + --tls-enable Enable TLS for etcd cluster --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' --topology-keys stringArray Topology keys for affinity - --version string The version of ElasticSearch. (default "8.8.2") ``` ### Options inherited from parent commands diff --git a/docs/user_docs/cli/kbcli_cluster_create_kafka.md b/docs/user_docs/cli/kbcli_cluster_create_kafka.md index 8d7998ea0..49980c8e8 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_kafka.md +++ b/docs/user_docs/cli/kbcli_cluster_create_kafka.md @@ -21,39 +21,40 @@ kbcli cluster create kafka NAME [flags] ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") - --broker-heap string Kafka broker's jvm heap setting. (default "-XshowSettings:vm -XX:MaxRAMPercentage=100 -Ddepth=64") - --broker-replicas int The number of Kafka broker replicas for separated mode. Value range [1, 100]. (default 1) - --controller-heap string Kafka controller's jvm heap setting for separated mode (default "-XshowSettings:vm -XX:MaxRAMPercentage=100 -Ddepth=64") - --controller-replicas int The number of Kafka controller replicas for separated mode. Legal values [1, 3, 5]. (default 1) - --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) - --disable-exporter Enable or disable monitor. (default true) - --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") - --edit Edit the API resource before creating - -h, --help help for kafka - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. - --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) - --meta-storage float Metadata Storage size, the unit is Gi. Value range [1, 10000]. (default 5) - --meta-storage-class string The StorageClass for Kafka Metadata Storage. - --mode string Mode for Kafka kraft cluster, 'combined' is combined Kafka controller and broker,'separated' is broker and controller running independently. Legal values [combined, separated]. (default "combined") - --monitor-enable Enable monitor for Kafka. (default true) - --monitor-replicas int The number of Kafka monitor replicas. Value range [1, 5]. (default 1) - --node-labels stringToString Node label selector (default []) - --node-port-enabled Whether NodePort service is enabled, default is false - -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) - --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. - --replicas int The number of Kafka broker replicas for combined mode. Legal values [1, 3, 5]. (default 1) - --sasl-enable Enable authentication using SASL/PLAIN for Kafka. - --storage float Data Storage size, the unit is Gi. Value range [1, 10000]. (default 10) - --storage-class string The StorageClass for Kafka Data Storage. - --storage-enable Enable storage for Kafka. - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") - --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' - --topology-keys stringArray Topology keys for affinity - --version string Cluster version. (default "kafka-3.3.2") + --broker-heap string Kafka broker's jvm heap setting. (default "-XshowSettings:vm -XX:MaxRAMPercentage=100 -Ddepth=64") + --broker-replicas int The number of Kafka broker replicas for separated mode. Value range [1, 100]. (default 1) + --controller-heap string Kafka controller's jvm heap setting for separated mode (default "-XshowSettings:vm -XX:MaxRAMPercentage=100 -Ddepth=64") + --controller-replicas int The number of Kafka controller replicas for separated mode. Legal values [1, 3, 5]. (default 1) + --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) + --disable-exporter Enable or disable monitor. (default true) + --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") + --edit Edit the API resource before creating + --fixed-pod-ip-enabled advertised.listeners Whether to enable fixed Pod IP mode in Kafka's advertised.listeners + -h, --help help for kafka + --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) + --meta-storage float Metadata Storage size, the unit is Gi. Value range [1, 10000]. (default 5) + --meta-storage-class string The StorageClass for Kafka Metadata Storage. + --mode string Mode for Kafka kraft cluster, 'combined' is combined Kafka controller and broker,'separated' is broker and controller running independently. Legal values [combined, separated]. (default "combined") + --monitor-enable Enable monitor for Kafka. (default true) + --monitor.limit.cpu float (default 0.5) + --monitor.limit.memory float (default 1) + --monitor.replicas int Number of replicas for the monitor component. Value range [1]. (default 1) + --monitor.request.cpu float (default 0.1) + --monitor.request.memory float (default 0.2) + --node-labels stringToString Node label selector (default []) + --node-port-enabled advertised.listeners Whether to enable NodePort mode in Kafka's advertised.listeners + -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) + --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") + --replicas int The number of Kafka broker replicas for combined mode. Legal values [1, 3, 5]. (default 1) + --sasl-enable Enable authentication using SASL/PLAIN for Kafka. + --storage float Data Storage size, the unit is Gi. Value range [1, 10000]. (default 10) + --storage-class string The StorageClass for Kafka Data Storage. + --storage-enable Enable storage for Kafka. + --tenancy string Tenancy options, one of: (SharedNode, DedicatedNode) (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Delete, WipeOut]. (default "Delete") + --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' + --topology-keys stringArray Topology keys for affinity + --version string Cluster version. (default "3.3.2") ``` ### Options inherited from parent commands diff --git a/docs/user_docs/cli/kbcli_cluster_create_llm.md b/docs/user_docs/cli/kbcli_cluster_create_llm.md deleted file mode 100644 index b923b7e10..000000000 --- a/docs/user_docs/cli/kbcli_cluster_create_llm.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: kbcli cluster create llm ---- - -Create a llm cluster. - -``` -kbcli cluster create llm NAME [flags] -``` - -### Examples - -``` - # Create a cluster with the default values - kbcli cluster create llm - - # Create a cluster with the specified cpu, memory and storage - kbcli cluster create llm --cpu 1 --memory 2 --storage 10 -``` - -### Options - -``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") - --cpu float CPU cores. Value range [0, 64]. - --cpu-mode Set to true if no GPU is available, default true (default true) - --disable-exporter Enable or disable monitor. (default true) - --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") - --edit Edit the API resource before creating - --extra-args string extra arguments that will be passed to run model (default "--trust-remote-code") - --gpu float GPU cores. Value range [0, 64]. (default 1) - -h, --help help for llm - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. - --memory float Memory, the unit is Gi. Value range [0, 1000]. - --model string Model name (default "facebook/opt-125m") - --node-labels stringToString Node label selector (default []) - -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) - --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --quantize string Model's quantized file name, only work for CPU mode - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. - --replicas int The number of replicas, for standalone mode, the replicas is 1, for replication mode, the default replicas is 2. Value range [1, 5]. (default 1) - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") - --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' - --topology-keys stringArray Topology keys for affinity - --url string Model URL, only work for CPU mode - --version string Cluster version. -``` - -### Options inherited from parent commands - -``` - --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. - --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. - --as-uid string UID to impersonate for the operation. - --cache-dir string Default cache directory (default "$HOME/.kube/cache") - --certificate-authority string Path to a cert file for the certificate authority - --client-certificate string Path to a client certificate file for TLS - --client-key string Path to a client key file for TLS - --cluster string The name of the kubeconfig cluster to use - --context string The name of the kubeconfig context to use - --disable-compression If true, opt-out of response compression for all requests to the server - --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure - --kubeconfig string Path to the kubeconfig file to use for CLI requests. - --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request - --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") - -s, --server string The address and port of the Kubernetes API server - --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used - --token string Bearer token for authentication to the API server - --user string The name of the kubeconfig user to use -``` - -### SEE ALSO - -* [kbcli cluster create](kbcli_cluster_create.md) - Create a cluster. - -#### Go Back to [CLI Overview](cli.md) Homepage. - diff --git a/docs/user_docs/cli/kbcli_cluster_create_mongodb.md b/docs/user_docs/cli/kbcli_cluster_create_mongodb.md index 38e2d2bcc..96b9b6952 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_mongodb.md +++ b/docs/user_docs/cli/kbcli_cluster_create_mongodb.md @@ -21,28 +21,25 @@ kbcli cluster create mongodb NAME [flags] ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) --disable-exporter Enable or disable monitor. (default true) --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") --edit Edit the API resource before creating -h, --help help for mongodb - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. + --hostnetwork string Legal values [enabled, disabled]. (default "enabled") --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) --mode string Cluster topology mode. Legal values [standalone, replicaset]. (default "standalone") --node-labels stringToString Node label selector (default []) -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. --replicas int The number of replicas, for standalone mode, the replicas is 1, for replicaset mode, the default replicas is 3. Value range [1, 5]. (default 1) --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) --storage-class-name string Storage class name of the data volume - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") + --tenancy string Tenancy options, one of: (SharedNode, DedicatedNode) (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Delete, WipeOut]. (default "Delete") --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' --topology-keys stringArray Topology keys for affinity - --version string Cluster version. Legal values [7.0.12, 6.0.16, 5.0.28, 4.4.29, 4.2.24, 4.0.28]. (default "6.0.16") + --version string Cluster version. Legal values [8.0.8, 8.0.6, 8.0.4, 7.0.19, 7.0.16, 7.0.12, 6.0.22, 6.0.20, 6.0.16, 5.0.30, 5.0.28, 4.4.29, 4.2.24, 4.0.28]. (default "6.0.16") ``` ### Options inherited from parent commands diff --git a/docs/user_docs/cli/kbcli_cluster_create_mysql.md b/docs/user_docs/cli/kbcli_cluster_create_mysql.md index 34b5ad97f..9cbe561ee 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_mysql.md +++ b/docs/user_docs/cli/kbcli_cluster_create_mysql.md @@ -21,27 +21,29 @@ kbcli cluster create mysql NAME [flags] ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") - --cpu float CPU cores. Value range [0.5, 64]. (default 1) - --disable-exporter Enable or disable monitor. (default true) - --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") - --edit Edit the API resource before creating - -h, --help help for mysql - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. - --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 1) - --mode string Cluster topology mode. Legal values [standalone, replication, raftGroup]. (default "standalone") - --node-labels stringToString Node label selector (default []) - -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) - --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. - --replicas int The number of replicas. Value range [1, 5]. (default 1) - --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") - --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' - --topology-keys stringArray Topology keys for affinity - --version string Cluster version, run "kbcli cv list --devel" to see all versions. Legal values [mysql-8.4, mysql-8.0, mysql-5.7]. (default "mysql-8.0") + --cpu float CPU cores. Value range [0.5, 64]. (default 1) + --disable-exporter Enable or disable monitor. (default true) + --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") + --edit Edit the API resource before creating + -h, --help help for mysql + --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 1) + --node-labels stringToString Node label selector (default []) + --orchestrator.cluster-service-selector.cluster-name string orchestrator cluster name for service selector + --orchestrator.cluster-service-selector.namespace string orchestrator cluster namespace for service selector + --orchestrator.service-reference.endpoint string Endpoint name of the service reference, format: : + -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) + --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") + --proxysql.cpu float (default 1) + --proxysql.memory float Memory, the unit is Gi. (default 1) + --proxysql.replicas int (default 1) + --replicas int The number of replicas. Value range [1, 5]. (default 1) + --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) + --tenancy string Tenancy options, one of: (SharedNode, DedicatedNode) (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Delete, WipeOut]. (default "Delete") + --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' + --topology string Topology type of the serve. Note that under the orc/orc-proxysql topology, it is necessary to specify the Orchestrator cluster information. You should choose either orchestrator.cluster-service-selector or orchestrator.service-reference. This means that depending on your setup, you will configure one of these options to properly integrate with the Orchestrator service for managing your MySQL cluster. Legal values [semisync, semisync-proxysql, mgr, mgr-proxysql, orc, orc-proxysql]. (default "semisync") + --topology-keys stringArray Topology keys for affinity + --version string MySQL version Legal values [8.0.39, 8.0.38, 8.0.37, 8.0.36, 8.4.2, 8.4.1, 8.4.0, 5.7.44]. (default "8.0.39") ``` ### Options inherited from parent commands diff --git a/docs/user_docs/cli/kbcli_cluster_create_oriol.md b/docs/user_docs/cli/kbcli_cluster_create_oriol.md index c6aa79bff..dfe0d62f6 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_oriol.md +++ b/docs/user_docs/cli/kbcli_cluster_create_oriol.md @@ -21,23 +21,30 @@ kbcli cluster create oriol NAME [flags] ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") - --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) - --etcd.cluster string The patroni dependency etcd cluster name (default "etcd") - --etcd.namespace string The patroni dependency etcd cluster namespace (default "default") - -h, --help help for oriol - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. - --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) - --mode string Legal values [standalone, replication]. (default "standalone") - --monitor-enabled Enable or disable monitor. - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. - --replicas int Value range [1, 5]. (default 1) - --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) - --storage-class-name string Storage class name of the data volume - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") - --version string (default "orioledb-beta1") + --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") + --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) + --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") + --edit Edit the API resource before creating + --etcd.cluster string The patroni dependency etcd cluster name (default "etcd") + --etcd.namespace string The patroni dependency etcd cluster namespace (default "default") + -h, --help help for oriol + --host-network-accessible Specify whether the cluster can be accessed from within the VPC. + --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) + --mode string Legal values [standalone, replication]. (default "standalone") + --monitor-enabled Enable or disable monitor. + --node-labels stringToString Node label selector (default []) + -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) + --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") + --publicly-accessible Specify whether the cluster can be accessed from the public internet. + --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. + --replicas int Value range [1, 5]. (default 1) + --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) + --storage-class-name string Storage class name of the data volume + --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") + --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' + --topology-keys stringArray Topology keys for affinity + --version string (default "orioledb-beta1") ``` ### Options inherited from parent commands @@ -53,13 +60,10 @@ kbcli cluster create oriol NAME [flags] --cluster string The name of the kubeconfig cluster to use --context string The name of the kubeconfig context to use --disable-compression If true, opt-out of response compression for all requests to the server - --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") - --edit Edit the API resource before creating --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --kubeconfig string Path to the kubeconfig file to use for CLI requests. --match-server-version Require server version to match client version -n, --namespace string If present, the namespace scope for this CLI request - -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") -s, --server string The address and port of the Kubernetes API server --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used diff --git a/docs/user_docs/cli/kbcli_cluster_create_postgresql.md b/docs/user_docs/cli/kbcli_cluster_create_postgresql.md index 8d8ed6042..8a17025e9 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_postgresql.md +++ b/docs/user_docs/cli/kbcli_cluster_create_postgresql.md @@ -21,25 +21,20 @@ kbcli cluster create postgresql NAME [flags] ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) --disable-exporter Enable or disable monitor. (default true) --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") --edit Edit the API resource before creating -h, --help help for postgresql - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) - --mode string Cluster topology mode. Legal values [standalone, replication]. (default "standalone") --node-labels stringToString Node label selector (default []) -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. --replicas int The number of replicas, for standalone mode, the replicas is 1, for replication mode, the default replicas is 2. Value range [1, 5]. (default 1) --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) --storage-class-name string Storage class name of the data volume - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") + --tenancy string Tenancy options, one of: (SharedNode, DedicatedNode) (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Delete, WipeOut]. (default "Delete") --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' --topology-keys stringArray Topology keys for affinity --version string service version. (default "15.7.0") diff --git a/docs/user_docs/cli/kbcli_cluster_create_qdrant.md b/docs/user_docs/cli/kbcli_cluster_create_qdrant.md index 49e0bfdd6..be0d2f7ba 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_qdrant.md +++ b/docs/user_docs/cli/kbcli_cluster_create_qdrant.md @@ -21,24 +21,20 @@ kbcli cluster create qdrant NAME [flags] ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") --cpu float CPU cores. Value range [0.5, 64]. (default 1) --disable-exporter Enable or disable monitor. (default true) --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") --edit Edit the API resource before creating -h, --help help for qdrant - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 2) --node-labels stringToString Node label selector (default []) -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. --replicas int The number of replicas. Value range [1, 16]. (default 1) --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) --storage-class-name string Storage class name of the data volume - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") + --tenancy string Tenancy options, one of: (SharedNode, DedicatedNode) (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Delete, WipeOut]. (default "Delete") --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' --topology-keys stringArray Topology keys for affinity --version string The version of Qdrant. (default "1.10.0") diff --git a/docs/user_docs/cli/kbcli_cluster_create_xinference.md b/docs/user_docs/cli/kbcli_cluster_create_rabbitmq.md similarity index 72% rename from docs/user_docs/cli/kbcli_cluster_create_xinference.md rename to docs/user_docs/cli/kbcli_cluster_create_rabbitmq.md index e021d8455..5becf0bb4 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_xinference.md +++ b/docs/user_docs/cli/kbcli_cluster_create_rabbitmq.md @@ -1,47 +1,44 @@ --- -title: kbcli cluster create xinference +title: kbcli cluster create rabbitmq --- -Create a xinference cluster. +Create a rabbitmq cluster. ``` -kbcli cluster create xinference NAME [flags] +kbcli cluster create rabbitmq NAME [flags] ``` ### Examples ``` # Create a cluster with the default values - kbcli cluster create xinference + kbcli cluster create rabbitmq # Create a cluster with the specified cpu, memory and storage - kbcli cluster create xinference --cpu 1 --memory 2 --storage 10 + kbcli cluster create rabbitmq --cpu 1 --memory 2 --storage 10 ``` ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") - --cpu float CPU cores. Value range [0, 64]. - --cpu-mode Set to true if no GPU is available + --cpu float CPU cores. Value range [0.1, 64]. (default 0.5) --disable-exporter Enable or disable monitor. (default true) --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") --edit Edit the API resource before creating - --gpu float GPU cores. Value range [0, 64]. (default 1) - -h, --help help for xinference - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. - --memory float Memory, the unit is Gi. Value range [0, 1000]. + -h, --help help for rabbitmq + --memory float Memory, the unit is Gi. Value range [0.1, 1000]. (default 0.5) + --mode string Cluster topology mode. Legal values [singlenode, clustermode]. (default "singlenode") --node-labels stringToString Node label selector (default []) -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. - --replicas int The number of replicas, for standalone mode, the replicas is 1, for replication mode, the default replicas is 2. Value range [1, 5]. (default 1) - --shm-size string shm size (default "64Mi") - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") + --replicas int The number of replicas, for standalone mode, the replicas is 1, for replicaset mode, the default replicas is 3. Value range [1, 5]. (default 1) + --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) + --storage-class-name string Storage class name of the data volume + --tenancy string Tenancy options, one of: (SharedNode, DedicatedNode) (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Delete, WipeOut]. (default "Delete") --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' --topology-keys stringArray Topology keys for affinity + --version string Cluster version. Legal values [4.0.9, 3.13.7, 3.13.2, 3.12.14, 3.11.28, 3.10.25, 3.9.29, 3.8.14]. (default "3.13.7") ``` ### Options inherited from parent commands diff --git a/docs/user_docs/cli/kbcli_cluster_create_redis.md b/docs/user_docs/cli/kbcli_cluster_create_redis.md index 3d3e24ee8..5c6e18171 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_redis.md +++ b/docs/user_docs/cli/kbcli_cluster_create_redis.md @@ -21,39 +21,43 @@ kbcli cluster create redis NAME [flags] ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") - --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) - --disable-exporter Enable or disable monitor. (default true) - --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") - --edit Edit the API resource before creating - -h, --help help for redis - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. - --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) - --mode string Cluster topology mode. Legal values [standalone, replication, cluster, replication-twemproxy]. (default "replication") - --node-labels stringToString Node label selector (default []) - --node-port-enabled Whether NodePort service is enabled, default is true - -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) - --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. - --redis-cluster.shard-count float The number of shards in the redis cluster Value range [3, 2048]. (default 3) - --replicas int The number of replicas, for standalone mode, the replicas is 1, for replication mode, the default replicas is 2. Value range [1, 5]. (default 1) - --sentinel.cpu float Sentinel component cpu cores. Value range [0.1, 8]. (default 0.2) - --sentinel.enabled Whether have sentinel component, default is true (default true) - --sentinel.memory float Sentinel component memory, the unit is Gi. Value range [0.1, 4]. (default 0.2) - --sentinel.replicas float Sentinel component replicas Value range [1, 5]. (default 3) - --sentinel.storage float Sentinel component storage size, the unit is Gi. Value range [1, 1024]. (default 20) - --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) - --storage-class-name string Storage class name of the data volume - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") - --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' - --topology-keys stringArray Topology keys for affinity - --twemproxy.cpu float twemproxy component cpu cores. Value range [0.1, 8]. (default 0.2) - --twemproxy.enabled Whether have twemproxy component, default is false - --twemproxy.memory float twemproxy component memory, the unit is Gi. Value range [0.1, 4]. (default 0.2) - --twemproxy.replicas float twemproxy component replicas Value range [1, 5]. (default 3) - --version string Cluster version. (default "redis-7.0.6") + --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) + --custom-secret-name string the secret must contain keys named 'username' and 'password' + --custom-secret-namespace string the secret must contain keys named 'username' and 'password' + --disable-exporter Enable or disable monitor. (default true) + --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") + --edit Edit the API resource before creating + -h, --help help for redis + --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) + --mode string Cluster topology mode. Legal values [standalone, replication, cluster, replication-twemproxy]. (default "replication") + --node-labels stringToString Node label selector (default []) + --node-port-enabled Whether NodePort service is enabled, default is true + -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) + --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") + --redis-cluster.custom-secret-name string the secret must contain keys named 'username' and 'password' + --redis-cluster.custom-secret-namespace string the secret must contain keys named 'username' and 'password' + --redis-cluster.shard-count float The number of shards in the redis cluster Value range [3, 2048]. (default 3) + --replicas int The number of replicas, for standalone mode, the replicas is 1, for replication mode, the default replicas is 2. Value range [1, 5]. (default 1) + --sentinel.cpu float Sentinel component cpu cores. Value range [0.1, 8]. (default 0.2) + --sentinel.custom-master-name string Name of the master node monitored by Sentinel. If empty, a default value will be used. + --sentinel.custom-secret-name string the secret must contain keys named 'username' and 'password' + --sentinel.custom-secret-namespace string the secret must contain keys named 'username' and 'password' + --sentinel.enabled Whether have sentinel component, default is true (default true) + --sentinel.memory float Sentinel component memory, the unit is Gi. Value range [0.1, 4]. (default 0.2) + --sentinel.replicas float Sentinel component replicas Value range [1, 5]. (default 3) + --sentinel.storage float Sentinel component storage size, the unit is Gi. Value range [1, 1024]. (default 20) + --sentinel.storage-class-name string Sentinel component storage class name + --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) + --storage-class-name string Storage class name of the data volume + --tenancy string Tenancy options, one of: (SharedNode, DedicatedNode) (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Delete, WipeOut]. (default "Delete") + --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' + --topology-keys stringArray Topology keys for affinity + --twemproxy.cpu float twemproxy component cpu cores. Value range [0.1, 8]. (default 0.2) + --twemproxy.enabled Whether have twemproxy component, default is false + --twemproxy.memory float twemproxy component memory, the unit is Gi. Value range [0.1, 4]. (default 0.2) + --twemproxy.replicas float twemproxy component replicas Value range [1, 5]. (default 3) + --version string Cluster version. (default "7.2.7") ``` ### Options inherited from parent commands diff --git a/docs/user_docs/cli/kbcli_cluster_create_tidb.md b/docs/user_docs/cli/kbcli_cluster_create_tidb.md index 554d51ddf..b6d711e06 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_tidb.md +++ b/docs/user_docs/cli/kbcli_cluster_create_tidb.md @@ -21,18 +21,26 @@ kbcli cluster create tidb NAME [flags] ### Options ``` - -h, --help help for tidb - --pd.cpu float CPU cores. Value range [2, 64]. (default 2) - --pd.memory float Memory, the unit is Gi. Value range [4, 1000]. (default 4) - --pd.replicas int The number of replicas Value range [1, 5]. (default 3) - --pd.storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") - --tidb.cpu float CPU cores. Value range [2, 64]. (default 2) - --tidb.replicas int The number of replicas Value range [1, 5]. (default 2) - --tikv.cpu float CPU cores. Value range [2, 64]. (default 2) - --tikv.memory float Memory, the unit is Gi. Value range [4, 1000]. (default 4) - --tikv.replicas int The number of replicas Value range [1, 5]. (default 3) - --tikv.storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) + --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") + --edit Edit the API resource before creating + -h, --help help for tidb + --node-labels stringToString Node label selector (default []) + -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) + --pd.cpu float CPU cores. Value range [2, 64]. (default 2) + --pd.memory float Memory, the unit is Gi. Value range [4, 1000]. (default 4) + --pd.replicas int The number of replicas Value range [1, 5]. (default 3) + --pd.storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) + --pod-anti-affinity string Pod anti-affinity type, one of: (Preferred, Required) (default "Preferred") + --tenancy string Tenancy options, one of: (SharedNode, DedicatedNode) (default "SharedNode") + --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") + --tidb.cpu float CPU cores. Value range [2, 64]. (default 2) + --tidb.replicas int The number of replicas Value range [1, 5]. (default 2) + --tikv.cpu float CPU cores. Value range [2, 64]. (default 2) + --tikv.memory float Memory, the unit is Gi. Value range [4, 1000]. (default 4) + --tikv.replicas int The number of replicas Value range [1, 5]. (default 3) + --tikv.storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) + --tolerations strings Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' + --topology-keys stringArray Topology keys for affinity ``` ### Options inherited from parent commands @@ -48,13 +56,10 @@ kbcli cluster create tidb NAME [flags] --cluster string The name of the kubeconfig cluster to use --context string The name of the kubeconfig context to use --disable-compression If true, opt-out of response compression for all requests to the server - --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") - --edit Edit the API resource before creating --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --kubeconfig string Path to the kubeconfig file to use for CLI requests. --match-server-version Require server version to match client version -n, --namespace string If present, the namespace scope for this CLI request - -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") -s, --server string The address and port of the Kubernetes API server --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used diff --git a/docs/user_docs/cli/kbcli_cluster_delete.md b/docs/user_docs/cli/kbcli_cluster_delete.md index 2f68418b8..435a7a897 100644 --- a/docs/user_docs/cli/kbcli_cluster_delete.md +++ b/docs/user_docs/cli/kbcli_cluster_delete.md @@ -13,8 +13,12 @@ kbcli cluster delete NAME [flags] ``` # delete a cluster named mycluster kbcli cluster delete mycluster + # delete a cluster by label selector kbcli cluster delete --selector clusterdefinition.kubeblocks.io/name=apecloud-mysql + + # delete a cluster named mycluster forcedly + kbcli cluster delete mycluster --force ``` ### Options @@ -26,7 +30,6 @@ kbcli cluster delete NAME [flags] --grace-period int Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. Set to 1 for immediate shutdown. Can only be set to 0 when --force is true (force deletion). (default -1) -h, --help help for delete --now If true, resources are signaled for immediate shutdown (same as --grace-period=1). - --rbac-enabled Specify whether rbac resources will be deleted by kbcli -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. ``` diff --git a/docs/user_docs/cli/kbcli_cluster_describe-backup.md b/docs/user_docs/cli/kbcli_cluster_describe-backup.md index 1aa5eb805..898aebd53 100644 --- a/docs/user_docs/cli/kbcli_cluster_describe-backup.md +++ b/docs/user_docs/cli/kbcli_cluster_describe-backup.md @@ -11,8 +11,11 @@ kbcli cluster describe-backup BACKUP-NAME [flags] ### Examples ``` + # describe backups of the cluster + kbcli cluster describe-backup + # describe a backup - kbcli cluster describe-backup backup-default-mycluster-20230616190023 + kbcli cluster describe-backup --names ``` ### Options diff --git a/docs/user_docs/cli/kbcli_cluster_describe-config.md b/docs/user_docs/cli/kbcli_cluster_describe-config.md index a73bb87d4..8c9727483 100644 --- a/docs/user_docs/cli/kbcli_cluster_describe-config.md +++ b/docs/user_docs/cli/kbcli_cluster_describe-config.md @@ -31,7 +31,6 @@ kbcli cluster describe-config [flags] --config-file strings Specify the name of the configuration file to be describe (e.g. for mysql: --config-file=my.cnf). If unset, all files. --config-specs strings Specify the name of the configuration template to describe. (e.g. for apecloud-mysql: --config-specs=mysql-3node-tpl) -h, --help help for describe-config - --show-detail If true, the content of the files specified by config-file will be printed. ``` ### Options inherited from parent commands diff --git a/docs/user_docs/cli/kbcli_alert_list-receivers.md b/docs/user_docs/cli/kbcli_cluster_describe-restore.md similarity index 88% rename from docs/user_docs/cli/kbcli_alert_list-receivers.md rename to docs/user_docs/cli/kbcli_cluster_describe-restore.md index ce304718f..047a85776 100644 --- a/docs/user_docs/cli/kbcli_alert_list-receivers.md +++ b/docs/user_docs/cli/kbcli_cluster_describe-restore.md @@ -1,24 +1,24 @@ --- -title: kbcli alert list-receivers +title: kbcli cluster describe-restore --- -List all alert receivers. +Describe a restore ``` -kbcli alert list-receivers [flags] +kbcli cluster describe-restore NAME [flags] ``` ### Examples ``` - # list all alert receivers - kbcli alert list-receivers + # describe a restore + kbcli cluster describe-restore ``` ### Options ``` - -h, --help help for list-receivers + -h, --help help for describe-restore ``` ### Options inherited from parent commands @@ -47,7 +47,7 @@ kbcli alert list-receivers [flags] ### SEE ALSO -* [kbcli alert](kbcli_alert.md) - Manage alert receiver, include add, list and delete receiver. +* [kbcli cluster](kbcli_cluster.md) - Cluster command. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_cluster_expose.md b/docs/user_docs/cli/kbcli_cluster_expose.md index 96487931a..ac5cc1e6b 100644 --- a/docs/user_docs/cli/kbcli_cluster_expose.md +++ b/docs/user_docs/cli/kbcli_cluster_expose.md @@ -33,6 +33,7 @@ kbcli cluster expose NAME --enable=[true|false] --type=[intranet|internet] [flag -h, --help help for expose --name string OpsRequest name. if not specified, it will be randomly generated -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) + --role-selector roleSelector The Component's exposed Services may target replicas based on their roles using roleSelector, this flag must be set when the component specified has roles --sub-type string Expose sub type, currently supported types are 'NodePort', 'LoadBalancer', only available if type is intranet (default "LoadBalancer") --ttlSecondsAfterSucceed int Time to live after the OpsRequest succeed --type string Expose type, currently supported types are 'intranet', 'internet' diff --git a/docs/user_docs/cli/kbcli_cluster_list-backup-policy.md b/docs/user_docs/cli/kbcli_cluster_list-backup-policies.md similarity index 94% rename from docs/user_docs/cli/kbcli_cluster_list-backup-policy.md rename to docs/user_docs/cli/kbcli_cluster_list-backup-policies.md index eb6fbcd46..8708d98c1 100644 --- a/docs/user_docs/cli/kbcli_cluster_list-backup-policy.md +++ b/docs/user_docs/cli/kbcli_cluster_list-backup-policies.md @@ -1,18 +1,18 @@ --- -title: kbcli cluster list-backup-policy +title: kbcli cluster list-backup-policies --- List backups policies. ``` -kbcli cluster list-backup-policy [flags] +kbcli cluster list-backup-policies [flags] ``` ### Examples ``` # list all backup policies - kbcli cluster list-backup-policy + kbcli cluster list-backup-policies # using short cmd to list backup policy of the specified cluster kbcli cluster list-bp mycluster @@ -22,7 +22,7 @@ kbcli cluster list-backup-policy [flags] ``` -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. - -h, --help help for list-backup-policy + -h, --help help for list-backup-policies --names strings The backup policy name to get the details. -n, --namespace string specified the namespace -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) diff --git a/docs/user_docs/cli/kbcli_cluster_list-backups.md b/docs/user_docs/cli/kbcli_cluster_list-backups.md index 5a3b6baf7..6931bb567 100644 --- a/docs/user_docs/cli/kbcli_cluster_list-backups.md +++ b/docs/user_docs/cli/kbcli_cluster_list-backups.md @@ -13,17 +13,24 @@ kbcli cluster list-backups [flags] ``` # list all backups kbcli cluster list-backups + + # list all backups of the cluster + kbcli cluster list-backups + + # list the specified backups + kbcli cluster list-backups --names b1,b2 ``` ### Options ``` - -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. - -h, --help help for list-backups - --name string The backup name to get the details. - -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) - -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. - --show-labels When printing, show all labels as the last column (default hide labels column) + -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. + -h, --help help for list-backups + --names strings The backup name to get the details. + -n, --namespace string specified the namespace + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) ``` ### Options inherited from parent commands @@ -42,7 +49,6 @@ kbcli cluster list-backups [flags] --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --kubeconfig string Path to the kubeconfig file to use for CLI requests. --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") -s, --server string The address and port of the Kubernetes API server --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used diff --git a/docs/user_docs/cli/kbcli_cluster_grant-role.md b/docs/user_docs/cli/kbcli_cluster_list-restores.md similarity index 67% rename from docs/user_docs/cli/kbcli_cluster_grant-role.md rename to docs/user_docs/cli/kbcli_cluster_list-restores.md index 6da4ee6ed..5d93e28b3 100644 --- a/docs/user_docs/cli/kbcli_cluster_grant-role.md +++ b/docs/user_docs/cli/kbcli_cluster_list-restores.md @@ -1,32 +1,36 @@ --- -title: kbcli cluster grant-role +title: kbcli cluster list-restores --- -Grant role to account +List restores. ``` -kbcli cluster grant-role [flags] +kbcli cluster list-restores [flags] ``` ### Examples ``` - # grant role to user - kbcli cluster grant-role CLUSTERNAME --component COMPNAME --name USERNAME --role ROLENAME - # grant role to user with default component - kbcli cluster grant-role CLUSTERNAME --name USERNAME --role ROLENAME - # grant role to user for instance - kbcli cluster grant-role --instance INSTANCE --name USERNAME --role ROLENAME + # list all restores + kbcli cluster list-restores + + # list all restores of the cluster + kbcli cluster list-restores + + # list the specified restores + kbcli cluster list-restores --names r1,r2 ``` ### Options ``` - --component string Specify the name of component to be connected. If not specified, pick the first one. - -h, --help help for grant-role - -i, --instance string Specify the name of instance to be connected. - --name string Required user name, please specify it. - -r, --role string Role name should be one of [SUPERUSER, READWRITE, READONLY]. + -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. + -h, --help help for list-restores + --names strings List restores in the specified cluster + -n, --namespace string specified the namespace + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) ``` ### Options inherited from parent commands @@ -45,7 +49,6 @@ kbcli cluster grant-role [flags] --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --kubeconfig string Path to the kubeconfig file to use for CLI requests. --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") -s, --server string The address and port of the Kubernetes API server --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used diff --git a/docs/user_docs/cli/kbcli_cluster_list.md b/docs/user_docs/cli/kbcli_cluster_list.md index 84d93dc77..53f15f0e8 100644 --- a/docs/user_docs/cli/kbcli_cluster_list.md +++ b/docs/user_docs/cli/kbcli_cluster_list.md @@ -36,6 +36,7 @@ kbcli cluster list [NAME] [flags] -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. --show-labels When printing, show all labels as the last column (default hide labels column) + --status string Filter objects by given status. ``` ### Options inherited from parent commands diff --git a/docs/user_docs/cli/kbcli_cluster_promote.md b/docs/user_docs/cli/kbcli_cluster_promote.md index d32b072be..ad6f60d2f 100644 --- a/docs/user_docs/cli/kbcli_cluster_promote.md +++ b/docs/user_docs/cli/kbcli_cluster_promote.md @@ -5,32 +5,26 @@ title: kbcli cluster promote Promote a non-primary or non-leader instance as the new primary or leader of the cluster ``` -kbcli cluster promote NAME [--component=] [--instance ] [flags] +kbcli cluster promote NAME [--instance ] [flags] ``` ### Examples ``` # Promote the instance mycluster-mysql-1 as the new primary or leader. - kbcli cluster promote mycluster --instance mycluster-mysql-1 - - # Promote a non-primary or non-leader instance as the new primary or leader, the new primary or leader is determined by the system. - kbcli cluster promote mycluster - - # If the cluster has multiple components, you need to specify a component, otherwise an error will be reported. - kbcli cluster promote mycluster --component=mysql --instance mycluster-mysql-1 + kbcli cluster promote mycluster --candidate mycluster-mysql-1 ``` ### Options ``` --auto-approve Skip interactive approval before promote the instance - --component string Specify the component name of the cluster, if the cluster has multiple components, you need to specify a component + --candidate string Specify the instance name as the new primary or leader of the cluster, you can get the instance name by running "kbcli cluster list-instances" --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") --edit Edit the API resource before creating --force skip the pre-checks of the opsRequest to run the opsRequest forcibly -h, --help help for promote - --instance string Specify the instance name as the new primary or leader of the cluster, you can get the instance name by running "kbcli cluster list-instances" + --instance string Specify the instance name that will transfer its role to the candidate pod, If not set, the current primary or leader of the cluster will be used. --name string OpsRequest name. if not specified, it will be randomly generated -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) --ttlSecondsAfterSucceed int Time to live after the OpsRequest succeed diff --git a/docs/user_docs/cli/kbcli_cluster_rebuild-instance.md b/docs/user_docs/cli/kbcli_cluster_rebuild-instance.md index 2d8598784..fd1a83b6a 100644 --- a/docs/user_docs/cli/kbcli_cluster_rebuild-instance.md +++ b/docs/user_docs/cli/kbcli_cluster_rebuild-instance.md @@ -39,6 +39,7 @@ kbcli cluster rebuild-instance NAME [flags] --node strings specified the target node which rebuilds the instance on the node otherwise will rebuild on a random node. format: insName1=nodeName,insName2=nodeName -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) --restore-env stringArray provide the necessary env for the 'Restore' operation from the backup. format: key1=value, key2=value + --source-backup-target string To rebuild a sharding component instance from a backup, you can specify the name of the source backup target --ttlSecondsAfterSucceed int Time to live after the OpsRequest succeed ``` diff --git a/docs/user_docs/cli/kbcli_cluster_restore.md b/docs/user_docs/cli/kbcli_cluster_restore.md index 4b00e5525..1d701860c 100644 --- a/docs/user_docs/cli/kbcli_cluster_restore.md +++ b/docs/user_docs/cli/kbcli_cluster_restore.md @@ -19,6 +19,7 @@ kbcli cluster restore [flags] ``` --backup string Backup name + --backup-namespace string Backup namespace --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") --edit Edit the API resource before creating -h, --help help for restore diff --git a/docs/user_docs/cli/kbcli_cluster_revoke-role.md b/docs/user_docs/cli/kbcli_cluster_revoke-role.md deleted file mode 100644 index 878bbd0d2..000000000 --- a/docs/user_docs/cli/kbcli_cluster_revoke-role.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: kbcli cluster revoke-role ---- - -Revoke role from account - -``` -kbcli cluster revoke-role [flags] -``` - -### Examples - -``` - # revoke role from user - kbcli cluster revoke-role CLUSTERNAME --component COMPNAME --name USERNAME --role ROLENAME - # revoke role from user with default component - kbcli cluster revoke-role CLUSTERNAME --name USERNAME --role ROLENAME - # revoke role from user for instance - kbcli cluster revoke-role --instance INSTANCE --name USERNAME --role ROLENAME -``` - -### Options - -``` - --component string Specify the name of component to be connected. If not specified, pick the first one. - -h, --help help for revoke-role - -i, --instance string Specify the name of instance to be connected. - --name string Required user name, please specify it. - -r, --role string Role name should be one of [SUPERUSER, READWRITE, READONLY]. -``` - -### Options inherited from parent commands - -``` - --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. - --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. - --as-uid string UID to impersonate for the operation. - --cache-dir string Default cache directory (default "$HOME/.kube/cache") - --certificate-authority string Path to a cert file for the certificate authority - --client-certificate string Path to a client certificate file for TLS - --client-key string Path to a client key file for TLS - --cluster string The name of the kubeconfig cluster to use - --context string The name of the kubeconfig context to use - --disable-compression If true, opt-out of response compression for all requests to the server - --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure - --kubeconfig string Path to the kubeconfig file to use for CLI requests. - --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request - --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") - -s, --server string The address and port of the Kubernetes API server - --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used - --token string Bearer token for authentication to the API server - --user string The name of the kubeconfig user to use -``` - -### SEE ALSO - -* [kbcli cluster](kbcli_cluster.md) - Cluster command. - -#### Go Back to [CLI Overview](cli.md) Homepage. - diff --git a/docs/user_docs/cli/kbcli_cluster_hscale.md b/docs/user_docs/cli/kbcli_cluster_scale-in.md similarity index 82% rename from docs/user_docs/cli/kbcli_cluster_hscale.md rename to docs/user_docs/cli/kbcli_cluster_scale-in.md index 7f94dc151..b6d37547e 100644 --- a/docs/user_docs/cli/kbcli_cluster_hscale.md +++ b/docs/user_docs/cli/kbcli_cluster_scale-in.md @@ -1,18 +1,24 @@ --- -title: kbcli cluster hscale +title: kbcli cluster scale-in --- -Horizontally scale the specified components in the cluster. +scale in replicas of the specified components in the cluster. ``` -kbcli cluster hscale NAME [flags] +kbcli cluster scale-in Replicas [flags] ``` ### Examples ``` - # expand storage resources of specified components, separate with commas for multiple components - kbcli cluster hscale mycluster --components=mysql --replicas=3 + # scale in 2 replicas + kbcli cluster scale-in mycluster --components=mysql --replicas=2 + + # offline specified instances + kbcli cluster scale-in mycluster --components=mysql --offline-instances pod1 + + # scale in 2 replicas, one of them is specified by "--offline-instances". + kbcli cluster scale-out mycluster --components=mysql --replicas=2 --offline-instances pod1 ``` ### Options @@ -23,10 +29,11 @@ kbcli cluster hscale NAME [flags] --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") --edit Edit the API resource before creating --force skip the pre-checks of the opsRequest to run the opsRequest forcibly - -h, --help help for hscale + -h, --help help for scale-in --name string OpsRequest name. if not specified, it will be randomly generated + --offline-instances strings offline the specified instances -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) - --replicas int Replicas with the specified components + --replicas string Replicas with the specified components --ttlSecondsAfterSucceed int Time to live after the OpsRequest succeed ``` diff --git a/docs/user_docs/cli/kbcli_cluster_create_orioledb.md b/docs/user_docs/cli/kbcli_cluster_scale-out.md similarity index 57% rename from docs/user_docs/cli/kbcli_cluster_create_orioledb.md rename to docs/user_docs/cli/kbcli_cluster_scale-out.md index 0e39a2031..e737c8d66 100644 --- a/docs/user_docs/cli/kbcli_cluster_create_orioledb.md +++ b/docs/user_docs/cli/kbcli_cluster_scale-out.md @@ -1,42 +1,40 @@ --- -title: kbcli cluster create orioledb +title: kbcli cluster scale-out --- -Create a orioledb cluster. +scale out replicas of the specified components in the cluster. ``` -kbcli cluster create orioledb NAME [flags] +kbcli cluster scale-out Replicas [flags] ``` ### Examples ``` - # Create a cluster with the default values - kbcli cluster create orioledb + # scale out 2 replicas + kbcli cluster scale-out mycluster --components=mysql --replicas=2 - # Create a cluster with the specified cpu, memory and storage - kbcli cluster create orioledb --cpu 1 --memory 2 --storage 10 + # to bring the offline instances specified in compSpec.offlineInstances online. + kbcli cluster scale-out mycluster --components=mysql --online-instances pod1 + + # scale out 2 replicas, one of which is an instance that has already been taken offline. + kbcli cluster scale-out mycluster --components=mysql --replicas=2 --online-instances pod1 ``` ### Options ``` - --availability-policy string The availability policy of cluster. Legal values [none, node, zone]. (default "node") - --cpu float CPU cores. Value range [0.5, 64]. (default 0.5) - --etcd.cluster string The patroni dependency etcd cluster name (default "etcd") - --etcd.namespace string The patroni dependency etcd cluster namespace (default "default") - -h, --help help for orioledb - --host-network-accessible Specify whether the cluster can be accessed from within the VPC. - --memory float Memory, the unit is Gi. Value range [0.5, 1000]. (default 0.5) - --mode string Legal values [standalone, replication]. (default "standalone") - --monitoring-interval int The monitoring interval of cluster, 0 is disabled, the unit is second. Value range [0, 60]. - --publicly-accessible Specify whether the cluster can be accessed from the public internet. - --rbac-enabled Specify whether rbac resources will be created by client, otherwise KubeBlocks server will try to create rbac resources. - --replicas int Value range [1, 5]. (default 1) - --storage float Storage size, the unit is Gi. Value range [1, 10000]. (default 20) - --tenancy string The tenancy of cluster. Legal values [SharedNode, DedicatedNode]. (default "SharedNode") - --termination-policy string The termination policy of cluster. Legal values [DoNotTerminate, Halt, Delete, WipeOut]. (default "Delete") - --version string + --auto-approve Skip interactive approval before horizontally scaling the cluster + --components strings Component names to this operations + --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") + --edit Edit the API resource before creating + --force skip the pre-checks of the opsRequest to run the opsRequest forcibly + -h, --help help for scale-out + --name string OpsRequest name. if not specified, it will be randomly generated + --online-instances strings online the specified instances which have been offline + -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) + --replicas string Replica changes with the specified components + --ttlSecondsAfterSucceed int Time to live after the OpsRequest succeed ``` ### Options inherited from parent commands @@ -52,13 +50,10 @@ kbcli cluster create orioledb NAME [flags] --cluster string The name of the kubeconfig cluster to use --context string The name of the kubeconfig context to use --disable-compression If true, opt-out of response compression for all requests to the server - --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") - --edit Edit the API resource before creating --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --kubeconfig string Path to the kubeconfig file to use for CLI requests. --match-server-version Require server version to match client version -n, --namespace string If present, the namespace scope for this CLI request - -o, --output format Prints the output in the specified format. Allowed values: JSON and YAML (default yaml) --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") -s, --server string The address and port of the Kubernetes API server --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used @@ -68,7 +63,7 @@ kbcli cluster create orioledb NAME [flags] ### SEE ALSO -* [kbcli cluster create](kbcli_cluster_create.md) - Create a cluster. +* [kbcli cluster](kbcli_cluster.md) - Cluster command. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_cluster_start.md b/docs/user_docs/cli/kbcli_cluster_start.md index c6e2ae88e..dbc15d913 100644 --- a/docs/user_docs/cli/kbcli_cluster_start.md +++ b/docs/user_docs/cli/kbcli_cluster_start.md @@ -13,11 +13,15 @@ kbcli cluster start NAME [flags] ``` # start the cluster when cluster is stopped kbcli cluster start mycluster + + # start the component of the cluster when cluster is stopped + kbcli cluster start mycluster --components=mysql ``` ### Options ``` + --components strings Component names to this operations --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") --edit Edit the API resource before creating --force skip the pre-checks of the opsRequest to run the opsRequest forcibly diff --git a/docs/user_docs/cli/kbcli_cluster_stop.md b/docs/user_docs/cli/kbcli_cluster_stop.md index a4a6f2e21..2acbb5253 100644 --- a/docs/user_docs/cli/kbcli_cluster_stop.md +++ b/docs/user_docs/cli/kbcli_cluster_stop.md @@ -13,12 +13,16 @@ kbcli cluster stop NAME [flags] ``` # stop the cluster and release all the pods of the cluster kbcli cluster stop mycluster + + # stop the component of the cluster and release all the pods of the component + kbcli cluster stop mycluster --components=mysql ``` ### Options ``` --auto-approve Skip interactive approval before stopping the cluster + --components strings Component names to this operations --dry-run string[="unchanged"] Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent. (default "none") --edit Edit the API resource before creating --force skip the pre-checks of the opsRequest to run the opsRequest forcibly diff --git a/docs/user_docs/cli/kbcli_cluster_update.md b/docs/user_docs/cli/kbcli_cluster_update.md index 72701c33f..62077f100 100644 --- a/docs/user_docs/cli/kbcli_cluster_update.md +++ b/docs/user_docs/cli/kbcli_cluster_update.md @@ -17,9 +17,6 @@ kbcli cluster update NAME [flags] # enable cluster monitor kbcli cluster update mycluster --monitor=true - # enable all logs - kbcli cluster update mycluster --enable-all-logs=true - # update cluster tolerations kbcli cluster update mycluster --tolerations='"key=engineType,value=mongo,operator=Equal,effect=NoSchedule","key=diskType,value=ssd,operator=Equal,effect=NoSchedule"' @@ -64,14 +61,13 @@ kbcli cluster update NAME [flags] --disable-exporter Enable or disable monitoring (default true) --dry-run string[="unchanged"] Must be "none", "server", or "client". If client strategy, only print the object that would be sent, without sending it. If server strategy, submit server-side request without persisting the resource. (default "none") --edit Edit the API resource - --enable-all-logs Enable advanced application all log extraction, set to true will ignore enabledLogs of component level, default is false -h, --help help for update -o, --output string Output format. One of: (json, yaml, name, go-template, go-template-file, template, templatefile, jsonpath, jsonpath-as-json, jsonpath-file). --pitr-enabled Specify whether enabled point in time recovery --runtime-class-name string Specifies runtimeClassName for all Pods managed by this Cluster. --show-managed-fields If true, keep the managedFields when printing objects in JSON or YAML format. --template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. - --termination-policy string Termination policy, one of: (DoNotTerminate, Halt, Delete, WipeOut) (default "Delete") + --termination-policy string Termination policy, one of: (DoNotTerminate, Delete, WipeOut) (default "Delete") --tolerations strings Tolerations for cluster, such as "key=value:effect, key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"' ``` diff --git a/docs/user_docs/cli/kbcli_clusterdefinition_list-service-reference.md b/docs/user_docs/cli/kbcli_clusterdefinition_list-service-reference.md deleted file mode 100644 index 07db728e7..000000000 --- a/docs/user_docs/cli/kbcli_clusterdefinition_list-service-reference.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: kbcli clusterdefinition list-service-reference ---- - -List cluster references declared in a cluster definition. - -``` -kbcli clusterdefinition list-service-reference [flags] -``` - -### Examples - -``` - # List cluster references name declared in a cluster definition. - kbcli clusterdefinition list-service-reference orioledb -``` - -### Options - -``` - -h, --help help for list-service-reference -``` - -### Options inherited from parent commands - -``` - --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. - --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. - --as-uid string UID to impersonate for the operation. - --cache-dir string Default cache directory (default "$HOME/.kube/cache") - --certificate-authority string Path to a cert file for the certificate authority - --client-certificate string Path to a client certificate file for TLS - --client-key string Path to a client key file for TLS - --cluster string The name of the kubeconfig cluster to use - --context string The name of the kubeconfig context to use - --disable-compression If true, opt-out of response compression for all requests to the server - --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure - --kubeconfig string Path to the kubeconfig file to use for CLI requests. - --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request - --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") - -s, --server string The address and port of the Kubernetes API server - --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used - --token string Bearer token for authentication to the API server - --user string The name of the kubeconfig user to use -``` - -### SEE ALSO - -* [kbcli clusterdefinition](kbcli_clusterdefinition.md) - ClusterDefinition command. - -#### Go Back to [CLI Overview](cli.md) Homepage. - diff --git a/docs/user_docs/cli/kbcli_clusterversion.md b/docs/user_docs/cli/kbcli_clusterversion.md deleted file mode 100644 index 3a11304dd..000000000 --- a/docs/user_docs/cli/kbcli_clusterversion.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: kbcli clusterversion ---- - -ClusterVersion command. - -### Options - -``` - -h, --help help for clusterversion -``` - -### Options inherited from parent commands - -``` - --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. - --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. - --as-uid string UID to impersonate for the operation. - --cache-dir string Default cache directory (default "$HOME/.kube/cache") - --certificate-authority string Path to a cert file for the certificate authority - --client-certificate string Path to a client certificate file for TLS - --client-key string Path to a client key file for TLS - --cluster string The name of the kubeconfig cluster to use - --context string The name of the kubeconfig context to use - --disable-compression If true, opt-out of response compression for all requests to the server - --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure - --kubeconfig string Path to the kubeconfig file to use for CLI requests. - --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request - --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") - -s, --server string The address and port of the Kubernetes API server - --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used - --token string Bearer token for authentication to the API server - --user string The name of the kubeconfig user to use -``` - -### SEE ALSO - - -* [kbcli clusterversion list](kbcli_clusterversion_list.md) - List ClusterVersions. -* [kbcli clusterversion set-default](kbcli_clusterversion_set-default.md) - Set the clusterversion to the default clusterversion for its clusterdefinition. -* [kbcli clusterversion unset-default](kbcli_clusterversion_unset-default.md) - Unset the clusterversion if it's default. - -#### Go Back to [CLI Overview](cli.md) Homepage. - diff --git a/docs/user_docs/cli/kbcli_clusterversion_list.md b/docs/user_docs/cli/kbcli_clusterversion_list.md deleted file mode 100644 index 8f9efdfeb..000000000 --- a/docs/user_docs/cli/kbcli_clusterversion_list.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: kbcli clusterversion list ---- - -List ClusterVersions. - -``` -kbcli clusterversion list [flags] -``` - -### Examples - -``` - # list all ClusterVersions - kbcli clusterversion list -``` - -### Options - -``` - --cluster-definition string Specify cluster definition, run "kbcli clusterdefinition list" to show all available cluster definition - -h, --help help for list - -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) - -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. - --show-labels When printing, show all labels as the last column (default hide labels column) -``` - -### Options inherited from parent commands - -``` - --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. - --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. - --as-uid string UID to impersonate for the operation. - --cache-dir string Default cache directory (default "$HOME/.kube/cache") - --certificate-authority string Path to a cert file for the certificate authority - --client-certificate string Path to a client certificate file for TLS - --client-key string Path to a client key file for TLS - --cluster string The name of the kubeconfig cluster to use - --context string The name of the kubeconfig context to use - --disable-compression If true, opt-out of response compression for all requests to the server - --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure - --kubeconfig string Path to the kubeconfig file to use for CLI requests. - --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request - --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") - -s, --server string The address and port of the Kubernetes API server - --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used - --token string Bearer token for authentication to the API server - --user string The name of the kubeconfig user to use -``` - -### SEE ALSO - -* [kbcli clusterversion](kbcli_clusterversion.md) - ClusterVersion command. - -#### Go Back to [CLI Overview](cli.md) Homepage. - diff --git a/docs/user_docs/cli/kbcli_clusterversion_set-default.md b/docs/user_docs/cli/kbcli_clusterversion_set-default.md deleted file mode 100644 index 05b0702a2..000000000 --- a/docs/user_docs/cli/kbcli_clusterversion_set-default.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: kbcli clusterversion set-default ---- - -Set the clusterversion to the default clusterversion for its clusterdefinition. - -``` -kbcli clusterversion set-default NAME [flags] -``` - -### Examples - -``` - # set ac-mysql-8.0.30 as the default clusterversion - kbcli clusterversion set-default ac-mysql-8.0.30 -``` - -### Options - -``` - -h, --help help for set-default -``` - -### Options inherited from parent commands - -``` - --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. - --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. - --as-uid string UID to impersonate for the operation. - --cache-dir string Default cache directory (default "$HOME/.kube/cache") - --certificate-authority string Path to a cert file for the certificate authority - --client-certificate string Path to a client certificate file for TLS - --client-key string Path to a client key file for TLS - --cluster string The name of the kubeconfig cluster to use - --context string The name of the kubeconfig context to use - --disable-compression If true, opt-out of response compression for all requests to the server - --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure - --kubeconfig string Path to the kubeconfig file to use for CLI requests. - --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request - --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") - -s, --server string The address and port of the Kubernetes API server - --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used - --token string Bearer token for authentication to the API server - --user string The name of the kubeconfig user to use -``` - -### SEE ALSO - -* [kbcli clusterversion](kbcli_clusterversion.md) - ClusterVersion command. - -#### Go Back to [CLI Overview](cli.md) Homepage. - diff --git a/docs/user_docs/cli/kbcli_componentdefinition.md b/docs/user_docs/cli/kbcli_componentdefinition.md new file mode 100644 index 000000000..66476b89a --- /dev/null +++ b/docs/user_docs/cli/kbcli_componentdefinition.md @@ -0,0 +1,44 @@ +--- +title: kbcli componentdefinition +--- + +ComponentDefinition command. + +### Options + +``` + -h, --help help for componentdefinition +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + -n, --namespace string If present, the namespace scope for this CLI request + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + + +* [kbcli componentdefinition describe](kbcli_componentdefinition_describe.md) - Describe ComponentDefinition. +* [kbcli componentdefinition list](kbcli_componentdefinition_list.md) - List ComponentDefinition. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_clusterversion_unset-default.md b/docs/user_docs/cli/kbcli_componentdefinition_describe.md similarity index 85% rename from docs/user_docs/cli/kbcli_clusterversion_unset-default.md rename to docs/user_docs/cli/kbcli_componentdefinition_describe.md index 5543dc71a..7c191f54e 100644 --- a/docs/user_docs/cli/kbcli_clusterversion_unset-default.md +++ b/docs/user_docs/cli/kbcli_componentdefinition_describe.md @@ -1,24 +1,24 @@ --- -title: kbcli clusterversion unset-default +title: kbcli componentdefinition describe --- -Unset the clusterversion if it's default. +Describe ComponentDefinition. ``` -kbcli clusterversion unset-default NAME [flags] +kbcli componentdefinition describe [flags] ``` ### Examples ``` - # unset ac-mysql-8.0.30 to default clusterversion if it's default - kbcli clusterversion unset-default ac-mysql-8.0.30 + # describe a specified component definition + kbcli componentdefinition describe mycomponentdef ``` ### Options ``` - -h, --help help for unset-default + -h, --help help for describe ``` ### Options inherited from parent commands @@ -47,7 +47,7 @@ kbcli clusterversion unset-default NAME [flags] ### SEE ALSO -* [kbcli clusterversion](kbcli_clusterversion.md) - ClusterVersion command. +* [kbcli componentdefinition](kbcli_componentdefinition.md) - ComponentDefinition command. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_componentdefinition_list.md b/docs/user_docs/cli/kbcli_componentdefinition_list.md new file mode 100644 index 000000000..9fa47063a --- /dev/null +++ b/docs/user_docs/cli/kbcli_componentdefinition_list.md @@ -0,0 +1,59 @@ +--- +title: kbcli componentdefinition list +--- + +List ComponentDefinition. + +``` +kbcli componentdefinition list [flags] +``` + +### Examples + +``` + # list all ComponentDefinitions + kbcli componentdefinition list + + # list all ComponentDefinitions by alias + kbcli cmpd list +``` + +### Options + +``` + -h, --help help for list + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + -n, --namespace string If present, the namespace scope for this CLI request + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + +* [kbcli componentdefinition](kbcli_componentdefinition.md) - ComponentDefinition command. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_componentversion.md b/docs/user_docs/cli/kbcli_componentversion.md new file mode 100644 index 000000000..0e9c31526 --- /dev/null +++ b/docs/user_docs/cli/kbcli_componentversion.md @@ -0,0 +1,44 @@ +--- +title: kbcli componentversion +--- + +ComponentVersions command. + +### Options + +``` + -h, --help help for componentversion +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + -n, --namespace string If present, the namespace scope for this CLI request + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + + +* [kbcli componentversion describe](kbcli_componentversion_describe.md) - Describe ComponentVersion. +* [kbcli componentversion list](kbcli_componentversion_list.md) - List ComponentVersion. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_componentversion_describe.md b/docs/user_docs/cli/kbcli_componentversion_describe.md new file mode 100644 index 000000000..2e65ea6fa --- /dev/null +++ b/docs/user_docs/cli/kbcli_componentversion_describe.md @@ -0,0 +1,53 @@ +--- +title: kbcli componentversion describe +--- + +Describe ComponentVersion. + +``` +kbcli componentversion describe [flags] +``` + +### Examples + +``` + # describe a specified componentversion + kbcli componentversion describe mycomponentversion +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + -n, --namespace string If present, the namespace scope for this CLI request + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + +* [kbcli componentversion](kbcli_componentversion.md) - ComponentVersions command. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_componentversion_list.md b/docs/user_docs/cli/kbcli_componentversion_list.md new file mode 100644 index 000000000..6bf3dec04 --- /dev/null +++ b/docs/user_docs/cli/kbcli_componentversion_list.md @@ -0,0 +1,59 @@ +--- +title: kbcli componentversion list +--- + +List ComponentVersion. + +``` +kbcli componentversion list [flags] +``` + +### Examples + +``` + # list all ComponentVersions + kbcli componentversion list + + # list all ComponentVersions by alias + kbcli cmpv list +``` + +### Options + +``` + -h, --help help for list + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + -n, --namespace string If present, the namespace scope for this CLI request + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + +* [kbcli componentversion](kbcli_componentversion.md) - ComponentVersions command. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_dataprotection.md b/docs/user_docs/cli/kbcli_dataprotection.md index 2a02a1dae..7d23e1827 100644 --- a/docs/user_docs/cli/kbcli_dataprotection.md +++ b/docs/user_docs/cli/kbcli_dataprotection.md @@ -43,11 +43,11 @@ Data protection command. * [kbcli dataprotection describe-backup-policy](kbcli_dataprotection_describe-backup-policy.md) - Describe a backup policy * [kbcli dataprotection describe-restore](kbcli_dataprotection_describe-restore.md) - Describe a restore * [kbcli dataprotection edit-backup-policy](kbcli_dataprotection_edit-backup-policy.md) - Edit backup policy -* [kbcli dataprotection list-action-set](kbcli_dataprotection_list-action-set.md) - List actionsets -* [kbcli dataprotection list-backup](kbcli_dataprotection_list-backup.md) - List backups. -* [kbcli dataprotection list-backup-policy](kbcli_dataprotection_list-backup-policy.md) - List backup policies -* [kbcli dataprotection list-backup-policy-template](kbcli_dataprotection_list-backup-policy-template.md) - List backup policy template -* [kbcli dataprotection list-restore](kbcli_dataprotection_list-restore.md) - List restores. +* [kbcli dataprotection list-action-sets](kbcli_dataprotection_list-action-sets.md) - List actionsets +* [kbcli dataprotection list-backup-policies](kbcli_dataprotection_list-backup-policies.md) - List backup policies +* [kbcli dataprotection list-backup-policy-templates](kbcli_dataprotection_list-backup-policy-templates.md) - List backup policy templates +* [kbcli dataprotection list-backups](kbcli_dataprotection_list-backups.md) - List backups. +* [kbcli dataprotection list-restores](kbcli_dataprotection_list-restores.md) - List restores. * [kbcli dataprotection restore](kbcli_dataprotection_restore.md) - Restore a new cluster from backup #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_dataprotection_backup.md b/docs/user_docs/cli/kbcli_dataprotection_backup.md index 2011c3dc0..5608cb7da 100644 --- a/docs/user_docs/cli/kbcli_dataprotection_backup.md +++ b/docs/user_docs/cli/kbcli_dataprotection_backup.md @@ -17,7 +17,7 @@ kbcli dataprotection backup NAME [flags] # create a backup with a specified method, run "kbcli cluster desc-backup-policy mycluster" to show supported backup methods kbcli dp backup mybackup --cluster mycluster --method mymethod - # create a backup with specified backup policy, run "kbcli cluster list-backup-policy mycluster" to show the cluster supported backup policies + # create a backup with specified backup policy, run "kbcli cluster list-backup-policies mycluster" to show the cluster supported backup policies kbcli dp backup mybackup --cluster mycluster --policy mypolicy # create a backup from a parent backup diff --git a/docs/user_docs/cli/kbcli_alert_list-smtpserver.md b/docs/user_docs/cli/kbcli_dataprotection_describe-restore.md similarity index 88% rename from docs/user_docs/cli/kbcli_alert_list-smtpserver.md rename to docs/user_docs/cli/kbcli_dataprotection_describe-restore.md index 84e0235af..474db84a6 100644 --- a/docs/user_docs/cli/kbcli_alert_list-smtpserver.md +++ b/docs/user_docs/cli/kbcli_dataprotection_describe-restore.md @@ -1,24 +1,24 @@ --- -title: kbcli alert list-smtpserver +title: kbcli dataprotection describe-restore --- -List alert smtp servers config. +Describe a restore ``` -kbcli alert list-smtpserver [flags] +kbcli dataprotection describe-restore NAME [flags] ``` ### Examples ``` - # list alert smtp servers config - kbcli alert list-smtpserver + # describe a restore + kbcli dp describe-restore ``` ### Options ``` - -h, --help help for list-smtpserver + -h, --help help for describe-restore ``` ### Options inherited from parent commands @@ -47,7 +47,7 @@ kbcli alert list-smtpserver [flags] ### SEE ALSO -* [kbcli alert](kbcli_alert.md) - Manage alert receiver, include add, list and delete receiver. +* [kbcli dataprotection](kbcli_dataprotection.md) - Data protection command. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_dataprotection_edit-backup-policy.md b/docs/user_docs/cli/kbcli_dataprotection_edit-backup-policy.md new file mode 100644 index 000000000..6a77bf016 --- /dev/null +++ b/docs/user_docs/cli/kbcli_dataprotection_edit-backup-policy.md @@ -0,0 +1,53 @@ +--- +title: kbcli dataprotection edit-backup-policy +--- + +Edit backup policy + +``` +kbcli dataprotection edit-backup-policy +``` + +### Examples + +``` + # edit backup policy + kbcli dp edit-backup-policy +``` + +### Options + +``` + -h, --help help for edit-backup-policy +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + -n, --namespace string If present, the namespace scope for this CLI request + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + +* [kbcli dataprotection](kbcli_dataprotection.md) - Data protection command. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_cluster_list-accounts.md b/docs/user_docs/cli/kbcli_dataprotection_list-action-sets.md similarity index 75% rename from docs/user_docs/cli/kbcli_cluster_list-accounts.md rename to docs/user_docs/cli/kbcli_dataprotection_list-action-sets.md index 353046a9e..d0e6d5567 100644 --- a/docs/user_docs/cli/kbcli_cluster_list-accounts.md +++ b/docs/user_docs/cli/kbcli_dataprotection_list-action-sets.md @@ -1,30 +1,27 @@ --- -title: kbcli cluster list-accounts +title: kbcli dataprotection list-action-sets --- -List accounts for a cluster +List actionsets ``` -kbcli cluster list-accounts [flags] +kbcli dataprotection list-action-sets [flags] ``` ### Examples ``` - # list all users for component - kbcli cluster list-accounts CLUSTERNAME --component COMPNAME - # list all users with default component - kbcli cluster list-accounts CLUSTERNAME - # list all users from instance - kbcli cluster list-accounts --instance INSTANCE + # list all action sets + kbcli dp list-as ``` ### Options ``` - --component string Specify the name of component to be connected. If not specified, pick the first one. - -h, --help help for list-accounts - -i, --instance string Specify the name of instance to be connected. + -h, --help help for list-action-sets + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) ``` ### Options inherited from parent commands @@ -53,7 +50,7 @@ kbcli cluster list-accounts [flags] ### SEE ALSO -* [kbcli cluster](kbcli_cluster.md) - Cluster command. +* [kbcli dataprotection](kbcli_dataprotection.md) - Data protection command. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_dataprotection_list-backup-policy.md b/docs/user_docs/cli/kbcli_dataprotection_list-backup-policies.md similarity index 94% rename from docs/user_docs/cli/kbcli_dataprotection_list-backup-policy.md rename to docs/user_docs/cli/kbcli_dataprotection_list-backup-policies.md index 8dc077d58..ab169b4e9 100644 --- a/docs/user_docs/cli/kbcli_dataprotection_list-backup-policy.md +++ b/docs/user_docs/cli/kbcli_dataprotection_list-backup-policies.md @@ -1,18 +1,18 @@ --- -title: kbcli dataprotection list-backup-policy +title: kbcli dataprotection list-backup-policies --- List backup policies ``` -kbcli dataprotection list-backup-policy [flags] +kbcli dataprotection list-backup-policies [flags] ``` ### Examples ``` # list all backup policies - kbcli dp list-backup-policy + kbcli dp list-backup-policies # using short cmd to list backup policy of the specified cluster kbcli dp list-bp mycluster @@ -23,7 +23,7 @@ kbcli dataprotection list-backup-policy [flags] ``` -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. --cluster string The cluster name - -h, --help help for list-backup-policy + -h, --help help for list-backup-policies -n, --namespace string specified the namespace -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. diff --git a/docs/user_docs/cli/kbcli_dataprotection_list-backup-policy-templates.md b/docs/user_docs/cli/kbcli_dataprotection_list-backup-policy-templates.md new file mode 100644 index 000000000..96d3bda70 --- /dev/null +++ b/docs/user_docs/cli/kbcli_dataprotection_list-backup-policy-templates.md @@ -0,0 +1,56 @@ +--- +title: kbcli dataprotection list-backup-policy-templates +--- + +List backup policy templates + +``` +kbcli dataprotection list-backup-policy-templates [flags] +``` + +### Examples + +``` + # list all backup policy template + kbcli dp list-bpt +``` + +### Options + +``` + -h, --help help for list-backup-policy-templates + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + -n, --namespace string If present, the namespace scope for this CLI request + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + +* [kbcli dataprotection](kbcli_dataprotection.md) - Data protection command. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_dataprotection_list-backups.md b/docs/user_docs/cli/kbcli_dataprotection_list-backups.md index 5a2d1db48..5081f50e4 100644 --- a/docs/user_docs/cli/kbcli_dataprotection_list-backups.md +++ b/docs/user_docs/cli/kbcli_dataprotection_list-backups.md @@ -21,11 +21,13 @@ kbcli dataprotection list-backups [flags] ### Options ``` - --cluster string List backups in the specified cluster - -h, --help help for list-backups - -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) - -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. - --show-labels When printing, show all labels as the last column (default hide labels column) + -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. + --cluster string List backups in the specified cluster + -h, --help help for list-backups + -n, --namespace string specified the namespace + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) ``` ### Options inherited from parent commands @@ -43,7 +45,6 @@ kbcli dataprotection list-backups [flags] --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --kubeconfig string Path to the kubeconfig file to use for CLI requests. --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") -s, --server string The address and port of the Kubernetes API server --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used diff --git a/docs/user_docs/cli/kbcli_dataprotection_list-restores.md b/docs/user_docs/cli/kbcli_dataprotection_list-restores.md new file mode 100644 index 000000000..989702d6b --- /dev/null +++ b/docs/user_docs/cli/kbcli_dataprotection_list-restores.md @@ -0,0 +1,57 @@ +--- +title: kbcli dataprotection list-restores +--- + +List restores. + +``` +kbcli dataprotection list-restores [flags] +``` + +### Examples + +``` + # list all restores + kbcli dp list-restores +``` + +### Options + +``` + -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. + --cluster string List restores in the specified cluster + -h, --help help for list-restores + -n, --namespace string specified the namespace + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + +* [kbcli dataprotection](kbcli_dataprotection.md) - Data protection command. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_ops-definition.md b/docs/user_docs/cli/kbcli_ops-definition.md new file mode 100644 index 000000000..a7b927256 --- /dev/null +++ b/docs/user_docs/cli/kbcli_ops-definition.md @@ -0,0 +1,44 @@ +--- +title: kbcli ops-definition +--- + +ops-definitions command. + +### Options + +``` + -h, --help help for ops-definition +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + -n, --namespace string If present, the namespace scope for this CLI request + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + + +* [kbcli ops-definition describe](kbcli_ops-definition_describe.md) - Describe OpsDefinition. +* [kbcli ops-definition list](kbcli_ops-definition_list.md) - List OpsDefinition. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_ops-definition_describe.md b/docs/user_docs/cli/kbcli_ops-definition_describe.md new file mode 100644 index 000000000..f61dcc338 --- /dev/null +++ b/docs/user_docs/cli/kbcli_ops-definition_describe.md @@ -0,0 +1,53 @@ +--- +title: kbcli ops-definition describe +--- + +Describe OpsDefinition. + +``` +kbcli ops-definition describe [flags] +``` + +### Examples + +``` + # describe a specified ops-definition + kbcli ops-definition describe my-ops-definition +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + -n, --namespace string If present, the namespace scope for this CLI request + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + +* [kbcli ops-definition](kbcli_ops-definition.md) - ops-definitions command. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_cluster_describe-account.md b/docs/user_docs/cli/kbcli_ops-definition_list.md similarity index 69% rename from docs/user_docs/cli/kbcli_cluster_describe-account.md rename to docs/user_docs/cli/kbcli_ops-definition_list.md index 3a050fcca..831bad017 100644 --- a/docs/user_docs/cli/kbcli_cluster_describe-account.md +++ b/docs/user_docs/cli/kbcli_ops-definition_list.md @@ -1,31 +1,32 @@ --- -title: kbcli cluster describe-account +title: kbcli ops-definition list --- -Describe account roles and related information +List OpsDefinition. ``` -kbcli cluster describe-account [flags] +kbcli ops-definition list [flags] ``` ### Examples ``` - # describe account and show role information - kbcli cluster describe-account CLUSTERNAME --component COMPNAME --name USERNAME - # describe account with default component - kbcli cluster describe-account CLUSTERNAME --name USERNAME - # describe account for instance - kbcli cluster describe-account --instance INSTANCE --name USERNAME + # list all ops-definitions + kbcli ops-definition list + + # list all ops-definitions by alias + kbcli ops-def list ``` ### Options ``` - --component string Specify the name of component to be connected. If not specified, pick the first one. - -h, --help help for describe-account - -i, --instance string Specify the name of instance to be connected. - --name string Required user name, please specify it. + -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. + -h, --help help for list + -n, --namespace string specified the namespace + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) ``` ### Options inherited from parent commands @@ -44,7 +45,6 @@ kbcli cluster describe-account [flags] --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --kubeconfig string Path to the kubeconfig file to use for CLI requests. --match-server-version Require server version to match client version - -n, --namespace string If present, the namespace scope for this CLI request --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") -s, --server string The address and port of the Kubernetes API server --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used @@ -54,7 +54,7 @@ kbcli cluster describe-account [flags] ### SEE ALSO -* [kbcli cluster](kbcli_cluster.md) - Cluster command. +* [kbcli ops-definition](kbcli_ops-definition.md) - ops-definitions command. #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_playground_init.md b/docs/user_docs/cli/kbcli_playground_init.md index 7b5be7d51..9e2924b2a 100644 --- a/docs/user_docs/cli/kbcli_playground_init.md +++ b/docs/user_docs/cli/kbcli_playground_init.md @@ -31,7 +31,8 @@ kbcli playground init [flags] kbcli cluster describe mycluster # connect to database - kbcli cluster connect mycluster + kbcli exec -it mycluster-mysql-0 bash + mysql -h 127.1 -u root -p$MYSQL_ROOT_PASSWORD # view the Grafana kbcli dashboard open kubeblocks-grafana @@ -43,13 +44,15 @@ kbcli playground init [flags] ### Options ``` - --auto-approve Skip interactive approval during the initialization of playground - --cloud-provider string Cloud provider type, one of [local aws] (default "local") - --cluster-type string Specify the cluster type to create, use 'kbcli cluster create --help' to get the available cluster type. (default "apecloud-mysql") - -h, --help help for init - --region string The region to create kubernetes cluster - --timeout duration Time to wait for init playground, such as --timeout=10m (default 10m0s) - --version string KubeBlocks version + --auto-approve Skip interactive approval during the initialization of playground + --cloud-provider string Cloud provider type, one of [local aws] (default "local") + --cluster-type string Specify the cluster type to create, use 'kbcli cluster create --help' to get the available cluster type. (default "mysql") + -h, --help help for init + --k3d-proxy-image string Specify k3d proxy image if you want to init playground locally (default "docker.io/apecloud/k3d-proxy:5.4.4") + --k3s-image string Specify k3s image that you want to use for the nodes if you want to init playground locally (default "rancher/k3s:v1.23.8-k3s1") + --region string The region to create kubernetes cluster + --timeout duration Time to wait for init playground, such as --timeout=10m (default 10m0s) + --version string KubeBlocks version ``` ### Options inherited from parent commands diff --git a/docs/user_docs/cli/kbcli_trace.md b/docs/user_docs/cli/kbcli_trace.md new file mode 100644 index 000000000..8d4ad716f --- /dev/null +++ b/docs/user_docs/cli/kbcli_trace.md @@ -0,0 +1,47 @@ +--- +title: kbcli trace +--- + +trace management command + +### Options + +``` + -h, --help help for trace +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + -n, --namespace string If present, the namespace scope for this CLI request + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + + +* [kbcli trace create](kbcli_trace_create.md) - create a trace. +* [kbcli trace delete](kbcli_trace_delete.md) - Delete a trace. +* [kbcli trace list](kbcli_trace_list.md) - list all traces. +* [kbcli trace update](kbcli_trace_update.md) - update a trace. +* [kbcli trace watch](kbcli_trace_watch.md) - watch a trace. + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_dashboard_open.md b/docs/user_docs/cli/kbcli_trace_create.md similarity index 70% rename from docs/user_docs/cli/kbcli_dashboard_open.md rename to docs/user_docs/cli/kbcli_trace_create.md index 9dc142ece..04ebcd28f 100644 --- a/docs/user_docs/cli/kbcli_dashboard_open.md +++ b/docs/user_docs/cli/kbcli_trace_create.md @@ -1,33 +1,34 @@ --- -title: kbcli dashboard open +title: kbcli trace create --- -Open one dashboard. +create a trace. ``` -kbcli dashboard open NAME [DASHBOARD-TYPE] [--port PORT] [flags] +kbcli trace create trace-name [flags] ``` ### Examples ``` - # Open a dashboard, such as kube-grafana - kbcli dashboard open kubeblocks-grafana + # create a trace for cluster has the same name 'pg-cluster' + kbcli trace create pg-cluster - # Open a dashboard with a specific local port - kbcli dashboard open kubeblocks-grafana --port 8080 + # create a trace for cluster has the name of 'pg-cluster' + kbcli trace create pg-cluster-trace --cluster-name pg-cluster - # for dashboard kubeblocks-grafana, support to direct the specified dashboard type - # now we support mysql,mongodb,postgresql,redis,weaviate,kafka,cadvisor,jmx and node - kbcli dashboard open kubeblocks-grafana mysql + # create a trace with custom locale, stateEvaluationExpression + kbcli trace create pg-cluster-trace --locale zh_cn --cel-state-evaluation-expression "has(object.status.phase) && object.status.phase == \"Running\"" ``` ### Options ``` - -h, --help help for open - --pod-running-timeout duration The time (like 5s, 2m, or 3h, higher than zero) to wait for at least one pod is running (default 1m0s) - --port string dashboard local port + --cel-state-evaluation-expression string Specify CEL state evaluation expression. + --cluster-name string Specify target cluster name. + --depth int Specify object tree depth to display. + -h, --help help for create + --locale string Specify locale. ``` ### Options inherited from parent commands @@ -56,7 +57,7 @@ kbcli dashboard open NAME [DASHBOARD-TYPE] [--port PORT] [flags] ### SEE ALSO -* [kbcli dashboard](kbcli_dashboard.md) - List and open the KubeBlocks dashboards. +* [kbcli trace](kbcli_trace.md) - trace management command #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_dashboard_list.md b/docs/user_docs/cli/kbcli_trace_delete.md similarity index 90% rename from docs/user_docs/cli/kbcli_dashboard_list.md rename to docs/user_docs/cli/kbcli_trace_delete.md index 03145a2ea..262c1701f 100644 --- a/docs/user_docs/cli/kbcli_dashboard_list.md +++ b/docs/user_docs/cli/kbcli_trace_delete.md @@ -1,24 +1,24 @@ --- -title: kbcli dashboard list +title: kbcli trace delete --- -List all dashboards. +Delete a trace. ``` -kbcli dashboard list [flags] +kbcli trace delete trace-name [flags] ``` ### Examples ``` - # List all dashboards - kbcli dashboard list + # Delete a trace + kbcli trace delete pg-cluster ``` ### Options ``` - -h, --help help for list + -h, --help help for delete ``` ### Options inherited from parent commands @@ -47,7 +47,7 @@ kbcli dashboard list [flags] ### SEE ALSO -* [kbcli dashboard](kbcli_dashboard.md) - List and open the KubeBlocks dashboards. +* [kbcli trace](kbcli_trace.md) - trace management command #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_trace_list.md b/docs/user_docs/cli/kbcli_trace_list.md new file mode 100644 index 000000000..b1c42c023 --- /dev/null +++ b/docs/user_docs/cli/kbcli_trace_list.md @@ -0,0 +1,57 @@ +--- +title: kbcli trace list +--- + +list all traces. + +``` +kbcli trace list [flags] +``` + +### Examples + +``` + # list all traces + kbcli trace list +``` + +### Options + +``` + -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. + -h, --help help for list + -n, --namespace string specified the namespace + -o, --output format prints the output in the specified format. Allowed values: table, json, yaml, wide (default table) + -l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints. + --show-labels When printing, show all labels as the last column (default hide labels column) +``` + +### Options inherited from parent commands + +``` + --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation. + --cache-dir string Default cache directory (default "$HOME/.kube/cache") + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --disable-compression If true, opt-out of response compression for all requests to the server + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + --match-server-version Require server version to match client version + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -s, --server string The address and port of the Kubernetes API server + --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use +``` + +### SEE ALSO + +* [kbcli trace](kbcli_trace.md) - trace management command + +#### Go Back to [CLI Overview](cli.md) Homepage. + diff --git a/docs/user_docs/cli/kbcli_alert_config-smtpserver.md b/docs/user_docs/cli/kbcli_trace_update.md similarity index 55% rename from docs/user_docs/cli/kbcli_alert_config-smtpserver.md rename to docs/user_docs/cli/kbcli_trace_update.md index 4524e03ab..53ba1c716 100644 --- a/docs/user_docs/cli/kbcli_alert_config-smtpserver.md +++ b/docs/user_docs/cli/kbcli_trace_update.md @@ -1,31 +1,33 @@ --- -title: kbcli alert config-smtpserver +title: kbcli trace update --- -Set smtp server config +update a trace. ``` -kbcli alert config-smtpserver [flags] +kbcli trace update trace-name [flags] ``` ### Examples ``` - - # Set smtp server config - kbcli alert config-smtpserver --smtp-from alert-test@apecloud.com --smtp-smarthost smtp.feishu.cn:587 --smtp-auth-username alert-test@apecloud.com --smtp-auth-password 123456abc --smtp-auth-identity alert-test@apecloud.com - + # update a trace with custom locale, stateEvaluationExpression + kbcli trace update pg-cluster-trace --locale zh_cn --cel-state-evaluation-expression "has(object.status.phase) && object.status.phase == \"Running\"" ``` ### Options ``` - -h, --help help for config-smtpserver - --smtp-auth-identity string The identity to authenticate to the smarthost. - --smtp-auth-password string The password to authenticate to the smarthost. - --smtp-auth-username string The username to authenticate to the smarthost. - --smtp-from string The email address to send alert. - --smtp-smarthost string The smtp host to send alert. + --allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true) + --cel-state-evaluation-expression string Specify CEL state evaluation expression. + --depth int Specify object tree depth to display. (default -1) + --dry-run string[="unchanged"] Must be "none", "server", or "client". If client strategy, only print the object that would be sent, without sending it. If server strategy, submit server-side request without persisting the resource. (default "none") + --edit Edit the API resource + -h, --help help for update + --locale string Specify locale. + -o, --output string Output format. One of: (json, yaml, name, go-template, go-template-file, template, templatefile, jsonpath, jsonpath-as-json, jsonpath-file). + --show-managed-fields If true, keep the managedFields when printing objects in JSON or YAML format. + --template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. ``` ### Options inherited from parent commands @@ -54,7 +56,7 @@ kbcli alert config-smtpserver [flags] ### SEE ALSO -* [kbcli alert](kbcli_alert.md) - Manage alert receiver, include add, list and delete receiver. +* [kbcli trace](kbcli_trace.md) - trace management command #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/docs/user_docs/cli/kbcli_dashboard.md b/docs/user_docs/cli/kbcli_trace_watch.md similarity index 89% rename from docs/user_docs/cli/kbcli_dashboard.md rename to docs/user_docs/cli/kbcli_trace_watch.md index 398d9ac0a..e1d35f1eb 100644 --- a/docs/user_docs/cli/kbcli_dashboard.md +++ b/docs/user_docs/cli/kbcli_trace_watch.md @@ -1,13 +1,24 @@ --- -title: kbcli dashboard +title: kbcli trace watch --- -List and open the KubeBlocks dashboards. +watch a trace. + +``` +kbcli trace watch trace-name [flags] +``` + +### Examples + +``` + # watch a trace + kbcli trace watch pg-cluster-trace +``` ### Options ``` - -h, --help help for dashboard + -h, --help help for watch ``` ### Options inherited from parent commands @@ -36,9 +47,7 @@ List and open the KubeBlocks dashboards. ### SEE ALSO - -* [kbcli dashboard list](kbcli_dashboard_list.md) - List all dashboards. -* [kbcli dashboard open](kbcli_dashboard_open.md) - Open one dashboard. +* [kbcli trace](kbcli_trace.md) - trace management command #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/go.mod b/go.mod index 989f92f53..8f0c2d840 100644 --- a/go.mod +++ b/go.mod @@ -1,33 +1,39 @@ module github.com/apecloud/kbcli -go 1.22.4 +go 1.22.7 require ( cuelang.org/go v0.8.0 + github.com/76creates/stickers v1.4.0 github.com/Masterminds/semver/v3 v3.3.0 + github.com/NimbleMarkets/ntcharts v0.1.2 github.com/apecloud/dbctl v0.0.0-20240827084000-68a1980b1a46 - github.com/apecloud/kubeblocks v1.0.0-alpha.11 + github.com/apecloud/kubeblocks v1.0.0-beta.45 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/briandowns/spinner v1.23.0 github.com/chaos-mesh/chaos-mesh/api v0.0.0-20230912020346-a5d89c1c90ad + github.com/charmbracelet/bubbles v0.20.0 + github.com/charmbracelet/bubbletea v1.1.1 + github.com/charmbracelet/lipgloss v1.0.0 github.com/containerd/stargz-snapshotter/estargz v0.15.1 - github.com/containers/common v0.60.2 + github.com/containers/common v0.60.4 github.com/docker/go-connections v0.5.0 github.com/dustin/go-humanize v1.0.1 github.com/evanphx/json-patch v5.9.0+incompatible github.com/fatih/color v1.16.0 github.com/ghodss/yaml v1.0.0 - github.com/go-git/go-git/v5 v5.6.1 + github.com/go-git/go-git/v5 v5.13.0 github.com/go-logr/logr v1.4.2 github.com/google/uuid v1.6.0 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hc-install v0.5.2 github.com/hashicorp/terraform-exec v0.18.0 github.com/jedib0t/go-pretty/v6 v6.4.6 - github.com/k3d-io/k3d/v5 v5.6.0 + github.com/k3d-io/k3d/v5 v5.7.5 github.com/kubernetes-csi/external-snapshotter/client/v3 v3.0.0 github.com/kubernetes-csi/external-snapshotter/client/v6 v6.2.0 github.com/leaanthony/debme v1.2.1 + github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e github.com/manifoldco/promptui v0.9.0 github.com/onsi/ginkgo/v2 v2.20.0 github.com/onsi/gomega v1.34.1 @@ -36,7 +42,8 @@ require ( github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851 github.com/replicatedhq/troubleshoot v0.57.0 github.com/robfig/cron/v3 v3.0.1 - github.com/sahilm/fuzzy v0.1.0 + github.com/sahilm/fuzzy v0.1.1 + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 github.com/sethvargo/go-password v0.2.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cast v1.7.0 @@ -44,12 +51,13 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stoewer/go-strcase v1.2.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/xeipuuv/gojsonschema v1.2.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.27.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/sync v0.8.0 + golang.org/x/mod v0.20.0 + golang.org/x/sync v0.10.0 gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.16.2 k8s.io/api v0.31.1 @@ -71,7 +79,7 @@ require ( require ( cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/storage v1.38.0 // indirect cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e // indirect @@ -85,31 +93,35 @@ require ( github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.12.5 // indirect - github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/Microsoft/hcsshim v0.12.8 // indirect + github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/StudioSol/set v1.0.0 // indirect - github.com/acomagu/bufpipe v1.0.4 // indirect github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect github.com/aws/aws-sdk-go v1.50.8 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bhmj/jsonslice v1.1.2 // indirect github.com/bhmj/xpression v0.9.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/c9s/goprocinfo v0.0.0-20170724085704-0010a05ce49f // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/charmbracelet/keygen v0.5.1 // indirect + github.com/charmbracelet/x/ansi v0.4.2 // indirect + github.com/charmbracelet/x/term v0.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/clbanning/mxj/v2 v2.5.7 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/containerd/cgroups/v3 v3.0.3 // indirect - github.com/containerd/containerd v1.7.18 // indirect - github.com/containerd/errdefs v0.1.0 // indirect + github.com/containerd/containerd v1.7.27 // indirect + github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect github.com/containers/image/v5 v5.32.2 // indirect github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect github.com/containers/ocicrypt v1.2.0 // indirect @@ -136,15 +148,17 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/proto v1.10.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/fasthttp/router v1.4.20 // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/go-errors/errors v1.4.2 // indirect - github.com/go-git/gcfg v1.5.0 // indirect - github.com/go-git/go-billy/v5 v5.4.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect @@ -163,13 +177,13 @@ require ( github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/goodhosts/hostsfile v0.1.1 // indirect + github.com/goodhosts/hostsfile v0.1.6 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/cel-go v0.17.8 // indirect github.com/google/certificate-transparency-go v1.1.5 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/go-containerregistry v0.20.0 // indirect + github.com/google/go-containerregistry v0.20.1 // indirect github.com/google/go-intervals v0.0.2 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect @@ -182,9 +196,10 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.0 // indirect + github.com/hashicorp/go-getter v1.7.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect @@ -212,11 +227,14 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lithammer/dedent v1.1.0 // indirect github.com/longhorn/go-iscsi-helper v0.0.0-20210330030558-49a327fb024e // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magefile/mage v1.15.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect @@ -226,24 +244,27 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/spdystream v0.4.0 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.5.0 // indirect - github.com/moby/sys/user v0.2.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns 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/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/montanaflynn/stats v0.6.6 // indirect - github.com/morikuni/aec v1.0.0 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.15.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect + github.com/nsf/termbox-go v1.1.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.1.13 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f // indirect @@ -251,7 +272,7 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_golang v1.20.2 // 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 @@ -259,17 +280,17 @@ require ( github.com/rancher/wharfie v0.6.2 // indirect github.com/redis/go-redis/v9 v9.5.3 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rubenv/sql-migrate v1.7.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/segmentio/ksuid v1.0.4 // indirect - github.com/sergi/go-diff v1.3.1 // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/skeema/knownhosts v1.1.0 // indirect + github.com/skeema/knownhosts v1.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -281,6 +302,8 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/ulikunitz/xz v0.5.12 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.50.0 // indirect github.com/vbatts/tar-split v0.11.5 // indirect github.com/vmware-tanzu/velero v1.13.2 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect @@ -298,36 +321,42 @@ require ( go.etcd.io/etcd/client/v3 v3.5.14 // indirect go.mongodb.org/mongo-driver v1.15.1 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/otel v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + go.opentelemetry.io/otel/sdk v1.33.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.33.0 // indirect + go.opentelemetry.io/otel/trace v1.33.0 // indirect + go.opentelemetry.io/proto/otlp v1.4.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect - go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/oauth2 v0.22.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.24.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/api v0.171.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/grpc v1.68.1 // indirect + google.golang.org/protobuf v1.35.2 // 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 gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiserver v0.31.1 // indirect - k8s.io/component-helpers v0.29.2 // indirect + k8s.io/component-helpers v0.29.14 // indirect oras.land/oras-go v1.2.5 // indirect periph.io/x/host/v3 v3.8.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect @@ -336,13 +365,6 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) -replace ( - github.com/docker/cli => github.com/docker/cli v24.0.7+incompatible - github.com/docker/distribution => github.com/docker/distribution v2.8.2+incompatible - github.com/docker/docker => github.com/moby/moby v24.0.7+incompatible - github.com/spf13/afero => github.com/spf13/afero v1.2.2 -) - replace ( // golang.org/x/exp => golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb k8s.io/api => k8s.io/api v0.29.2 @@ -354,6 +376,7 @@ replace ( k8s.io/cloud-provider => k8s.io/cloud-provider v0.29.2 k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.29.2 k8s.io/code-generator => k8s.io/code-generator v0.29.2 + k8s.io/component-base => k8s.io/component-base v0.29.2 k8s.io/component-helpers => k8s.io/component-helpers v0.29.2 k8s.io/controller-manager => k8s.io/controller-manager v0.29.2 k8s.io/cri-api => k8s.io/cri-api v0.29.2 @@ -374,5 +397,4 @@ replace ( k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.29.2 k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.29.2 k8s.io/sample-controller => k8s.io/sample-controller v0.29.2 - k8s.io/component-base => k8s.io/component-base v0.29.2 ) diff --git a/go.sum b/go.sum index a84966892..bd603dac8 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,6 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -178,8 +180,8 @@ cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZ cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -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/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/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -527,6 +529,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= @@ -612,6 +615,8 @@ gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zum git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg= github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0= +github.com/76creates/stickers v1.4.0 h1:UD1ShH0mndxzvuyO4Ho4Ct3+EB6FnTbCRecwPs/WFSo= +github.com/76creates/stickers v1.4.0/go.mod h1:OnGyCp42wnTwuZv2Ewh4dkvMuaiWMoH4I80yU2IJVmI= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= @@ -636,20 +641,19 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.12.5 h1:bpTInLlDy/nDRWFVcefDZZ1+U8tS+rz3MxjKgu9boo0= -github.com/Microsoft/hcsshim v0.12.5/go.mod h1:tIUGego4G1EN5Hb6KC90aDYiUI2dqLSTTOCjVNpOgZ8= +github.com/Microsoft/hcsshim v0.12.8 h1:BtDWYlFMcWhorrvSSo2M7z0csPdw6t7no/C3FsSvqiI= +github.com/Microsoft/hcsshim v0.12.8/go.mod h1:cibQ4BqhJ32FXDwPdQhKhwrwophnh3FuT4nwQZF907w= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NimbleMarkets/ntcharts v0.1.2 h1:iW1aiOif/Dm74sQd18opi10RMED5589cVhy9SGp98Tw= +github.com/NimbleMarkets/ntcharts v0.1.2/go.mod h1:WcHS7kc8oQctN1543DeV9a+gOrS4DDVfKp1N9RZFUqc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= -github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/StudioSol/set v1.0.0 h1:G27J71la+Da08WidabBkoRrvPLTa4cdCn0RjvyJ5WKQ= github.com/StudioSol/set v1.0.0/go.mod h1:hIUNZPo6rEGF43RlPXHq7Fjmf+HkVJBqAjtK7Z9LoIU= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412 h1:vOVO0ypMfTt6tZacyI0kp+iCZb1XSNiYDqnzBWYgfe4= github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412/go.mod h1:AI9hp1tkp10pAlK5TCwL+7yWbRgtDm9jhToq6qij2xs= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= @@ -661,6 +665,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -671,8 +677,8 @@ github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4x github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/apecloud/dbctl v0.0.0-20240827084000-68a1980b1a46 h1:+Jcc7IjDGxPgIfIkGX2Q5Yxj35U65zgcfjh0B9rDhjo= github.com/apecloud/dbctl v0.0.0-20240827084000-68a1980b1a46/go.mod h1:eksJtZ7z1nVcVLqDzAdcN5EfpHwXllIAvHZEks2zWys= -github.com/apecloud/kubeblocks v1.0.0-alpha.11 h1:LaOxEPJxM3wegH9j1Y2d27lXFy+XlKU8jnA+Y4OUNKU= -github.com/apecloud/kubeblocks v1.0.0-alpha.11/go.mod h1:7CCfj7PP5hW0Fkl4ZNL3LspOdTMeBm++odUUt1H8o38= +github.com/apecloud/kubeblocks v1.0.0-beta.45 h1:Q+1ctqex9qlPe1XhoFe8N59/hh8+DbWC9WUX1xBEnNA= +github.com/apecloud/kubeblocks v1.0.0-beta.45/go.mod h1:Mk5xRLm2MpxoTNZKEdDcrIY3I1EpokQBU3Q9Zwse8MI= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -683,6 +689,10 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.50.8 h1:gY0WoOW+/Wz6XmYSgDH9ge3wnAevYDSQWPxxJvqAkP4= github.com/aws/aws-sdk-go v1.50.8/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -716,8 +726,6 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bugsnag/panicwrap v1.3.4 h1:A6sXFtDGsgU/4BLf5JT0o5uYg3EeKgGx3Sfs+/uk3pU= github.com/bugsnag/panicwrap v1.3.4/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bxcodec/faker v2.0.1+incompatible h1:P0KUpUw5w6WJXwrPfv35oc91i4d8nf40Nwln+M/+faA= github.com/bxcodec/faker v2.0.1+incompatible/go.mod h1:BNzfpVdTwnFJ6GtfYTcQu6l6rHShT+veBxNCnjCx5XM= github.com/c9s/goprocinfo v0.0.0-20170724085704-0010a05ce49f h1:tRk+aBit+q3oqnj/1mF5HHhP2yxJM2lSa0afOJxQ3nE= @@ -736,8 +744,18 @@ github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNS github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/chaos-mesh/chaos-mesh/api v0.0.0-20230912020346-a5d89c1c90ad h1:DVxCvjXlmkm4idu4bAbI9P+D99BsVHTKOKbzRYTlFwU= github.com/chaos-mesh/chaos-mesh/api v0.0.0-20230912020346-a5d89c1c90ad/go.mod h1:Yi/tSmvDrnFgyZN4bsXm3gfXrp3zo1uytHmnPEYfquM= -github.com/charmbracelet/keygen v0.5.1 h1:zBkkYPtmKDVTw+cwUyY6ZwGDhRxXkEp0Oxs9sqMLqxI= -github.com/charmbracelet/keygen v0.5.1/go.mod h1:zznJVmK/GWB6dAtjluqn2qsttiCBhA5MZSiwb80fcHw= +github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= +github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= +github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY= +github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4= +github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= +github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk= +github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q= +github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= +github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= @@ -753,8 +771,6 @@ github.com/clbanning/mxj/v2 v2.5.7/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -774,18 +790,20 @@ github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEa github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= -github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= -github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= -github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= -github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= -github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= +github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= +github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= +github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= -github.com/containers/common v0.60.2 h1:utcwp2YkO8c0mNlwRxsxfOiqfj157FRrBjxgjR6f+7o= -github.com/containers/common v0.60.2/go.mod h1:I0upBi1qJX3QmzGbUOBN1LVP6RvkKhd3qQpZbQT+Q54= +github.com/containers/common v0.60.4 h1:H5+LAMHPZEqX6vVNOQ+IguVsaFl8kbO/SZ/VPXjxhy0= +github.com/containers/common v0.60.4/go.mod h1:I0upBi1qJX3QmzGbUOBN1LVP6RvkKhd3qQpZbQT+Q54= github.com/containers/image/v5 v5.32.2 h1:SzNE2Y6sf9b1GJoC8qjCuMBXwQrACFp4p0RK15+4gmQ= github.com/containers/image/v5 v5.32.2/go.mod h1:v1l73VeMugfj/QtKI+jhYbwnwFCFnNGckvbST3rQ5Hk= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= @@ -798,8 +816,8 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/corpix/uarand v0.1.1 h1:RMr1TWc9F4n5jiPDzFHtmaUXLKLNUFK0SgCLo4BhX/U= -github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU= +github.com/corpix/uarand v0.0.0-20170723150923-031be390f409 h1:9A+mfQmwzZ6KwUXPc8nHxFtKgn9VIvO3gXAOspIcE3s= +github.com/corpix/uarand v0.0.0-20170723150923-031be390f409/go.mod h1:JSm890tOkDN+M1jqN8pUGDKnzJrsVbJwSMHBY4zwz7M= 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -826,10 +844,13 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= -github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= +github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= @@ -851,6 +872,8 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn 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/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= +github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= +github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 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= @@ -873,6 +896,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= @@ -881,6 +906,8 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fasthttp/router v1.4.20 h1:yPeNxz5WxZGojzolKqiP15DTXnxZce9Drv577GBrDgU= +github.com/fasthttp/router v1.4.20/go.mod h1:um867yNQKtERxBm+C+yzgWxjspTiQoA8z86Ec3fK/tc= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -903,8 +930,8 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= @@ -912,15 +939,14 @@ github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3 github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= -github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= -github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= +github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= +github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -1038,8 +1064,8 @@ github.com/golangplus/testing v1.0.0 h1:+ZeeiKZENNOMkTTELoSySazi+XaEhVO0mb+eanrS github.com/golangplus/testing v1.0.0/go.mod h1:ZDreixUV3YzhoVraIDyOzHrr76p6NUh6k/pPg/Q3gYA= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= -github.com/goodhosts/hostsfile v0.1.1 h1:SqRUTFOshOCon0ZSXDrW1bkKZvs4+5pRgYFWySdaLno= -github.com/goodhosts/hostsfile v0.1.1/go.mod h1:lXcUP8xO4WR5vvuQ3F/N0bMQoclOtYKEEUnyY2jTusY= +github.com/goodhosts/hostsfile v0.1.6 h1:aK6DxpNV6pZ1NbdvNE2vYBMTnvIJF5O2J/8ZOlp2eMY= +github.com/goodhosts/hostsfile v0.1.6/go.mod h1:bkCocEIf3Ca0hcBustUZoWYhOgKUaIK+47m8fBjoBx8= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= @@ -1070,8 +1096,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.20.0 h1:wRqHpOeVh3DnenOrPy9xDOLdnLatiGuuNRVelR2gSbg= -github.com/google/go-containerregistry v0.20.0/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0= +github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM= github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -1094,6 +1120,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -1139,6 +1166,7 @@ github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5i github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -1159,8 +1187,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 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= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -1168,8 +1196,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.0 h1:bzrYP+qu/gMrL1au7/aDvkoOVGUJpeKBgbqRHACAFDY= -github.com/hashicorp/go-getter v1.7.0/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI5wE4= +github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= @@ -1194,10 +1222,9 @@ github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 h1:Mo9W14pwbO9VfRe+ygqZ8dFbPpoIK1HFrG/zjTuQ+nc= -github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= +github.com/icrowley/fake v0.0.0-20221112152111-d7b7e2276db2 h1:qU3v73XG4QAqCPHA4HOpfC1EfUvtLIDvQK4mNQ0LvgI= +github.com/icrowley/fake v0.0.0-20221112152111-d7b7e2276db2/go.mod h1:dQ6TM/OGAe+cMws81eTe4Btv1dKxfPZ2CX+YaAFAPN4= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 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.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -1215,7 +1242,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8 h1:CZkYfurY6KGhVtlalI4QwQ6T0Cu6iuY3e0x5RLu96WE= @@ -1243,8 +1269,8 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 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/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/k3d-io/k3d/v5 v5.6.0 h1:XMRSQXyPErOcDCdOJVi6HUPjJZuWd/N6Dss7QeCDRhk= -github.com/k3d-io/k3d/v5 v5.6.0/go.mod h1:t/hRD2heCSkO9TJJdzFT72jXGCY8PjsCsClgjcmMoAA= +github.com/k3d-io/k3d/v5 v5.7.5 h1:rLzUV1XmCLfFkHZEU1YocnGGo2ccHAvCj79tC0yncdI= +github.com/k3d-io/k3d/v5 v5.7.5/go.mod h1:bFlhRV/R1cPT42ZZzQAHPHUF33CbCT8VSbjtjTr3J1Y= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= @@ -1262,6 +1288,7 @@ github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -1300,11 +1327,17 @@ github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z github.com/longhorn/go-iscsi-helper v0.0.0-20210330030558-49a327fb024e h1:hz4quJkaJWDo+xW+G6wTF6d6/95QvJ+o2D0+bB/tJ1U= github.com/longhorn/go-iscsi-helper v0.0.0-20210330030558-49a327fb024e/go.mod h1:9z/y9glKmWEdV50tjlUPxFwi1goQfIrrsoZbnMyIZbY= github.com/longhorn/nsfilelock v0.0.0-20200723175406-fa7c83ad0003/go.mod h1:0CLeXlf59Lg6C0kjLSDf47ft73Dh37CwymYRKWwAn04= +github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e h1:OLwZ8xVaeVrru0xyeuOX+fne0gQTFEGlzfNjipCbxlU= +github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e/go.mod h1:NQ34EGeu8FAYGBMDzwhfNJL8YQYoWZP5xYJPRDAwN3E= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.5.3/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= @@ -1312,7 +1345,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -1323,8 +1355,11 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -1357,11 +1392,10 @@ github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 h1:BpfhmL github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM= +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/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/moby v24.0.7+incompatible h1:RrVT5IXBn85mRtFKP+gFwVLCcnNPZIgN3NVRJG9Le+4= -github.com/moby/moby v24.0.7+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= @@ -1371,8 +1405,10 @@ github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9Kou github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/user v0.2.0 h1:OnpapJsRp25vkhw8TFG6OLJODNh/3rEwRWtJ3kakwRM= -github.com/moby/sys/user v0.2.0/go.mod h1:RYstrcWOJpVh+6qzUqp2bU3eaRpdiQeKGlKitaH0PM8= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= 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= @@ -1388,6 +1424,12 @@ github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v6 github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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= @@ -1395,8 +1437,9 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840= github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= +github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= 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= @@ -1452,8 +1495,6 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.13 h1:98S2srgG9vw0zWcDpFMn5TRrh8kLxa/5OFUstuUhmRs= -github.com/opencontainers/runc v1.1.13/go.mod h1:R016aXacfp/gwQBYw2FDGa9m+n6atbLWrYY8hNMT/sA= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= @@ -1483,6 +1524,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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= @@ -1494,8 +1537,8 @@ github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -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_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= +github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -1538,8 +1581,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR 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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +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/rubenv/sql-migrate v1.7.0 h1:HtQq1xyTN2ISmQDggnh0c9U3JlP8apWh8YO2jzlXpTI= github.com/rubenv/sql-migrate v1.7.0/go.mod h1:S4wtDEG1CKn+0ShpTtzWhFpHHI5PvCUtiGI+C+Z2THE= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -1550,17 +1593,18 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= -github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/sebdah/goldie v1.0.0 h1:9GNhIat69MSlz/ndaBg48vl9dF5fI+NBB6kfOxgfkMc= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= @@ -1578,15 +1622,19 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= -github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= @@ -1620,8 +1668,9 @@ github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO 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.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/sylabs/sif/v2 v2.18.0 h1:eXugsS1qx7St2Wu/AJ21KnsQiVCpouPlTigABh+6KYI= @@ -1643,6 +1692,10 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1 github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= +github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -1720,27 +1773,35 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +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/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 h1:7F29RDmnlqk6B5d+sUqemt8TBfDqxryYW5gX6L74RFA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0/go.mod h1:ZiGDq7xwDMKmWDrN1XsXAj0iC7hns+2DhxBFSncNHSE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= +go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= +go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU= +go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= +go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -1749,26 +1810,24 @@ 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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 h1:zLxFnORHDFTSkJPawMU7LzsuGQJ4MUFS653jJHpORow= -go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= -golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= @@ -1776,8 +1835,8 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1880,6 +1939,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/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= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= @@ -1898,7 +1958,6 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= @@ -1917,8 +1976,8 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1951,8 +2010,8 @@ golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1972,8 +2031,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2022,12 +2081,14 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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= @@ -2035,6 +2096,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2061,7 +2123,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2077,13 +2138,12 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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= @@ -2097,8 +2157,8 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2119,8 +2179,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2183,6 +2243,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -2329,7 +2390,9 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -2434,15 +2497,15 @@ google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go. 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= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= -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-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= 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= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -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-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -2485,8 +2548,8 @@ google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= @@ -2506,8 +2569,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= @@ -2547,7 +2610,6 @@ 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-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/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 v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= @@ -2579,8 +2641,6 @@ k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= k8s.io/code-generator v0.29.2/go.mod h1:FwFi3C9jCrmbPjekhaCYcYG1n07CYiW1+PAPCockaos= k8s.io/component-base v0.29.2 h1:lpiLyuvPA9yV1aQwGLENYyK7n/8t6l3nn3zAtFTJYe8= k8s.io/component-base v0.29.2/go.mod h1:BfB3SLrefbZXiBfbM+2H1dlat21Uewg/5qtKOl8degM= -k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8= -k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w= k8s.io/component-helpers v0.29.2 h1:1kTIanIdqUVG2nW3e2ENVEaYbZKphqPgEdCmJvk71aw= k8s.io/component-helpers v0.29.2/go.mod h1:gFc/p60rYtpD8UCcNfPCmbokHT2uy0yDpmr/KKUMNAw= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= diff --git a/hack/boilerplate.cue.txt b/hack/boilerplate.cue.txt index c99f1aae2..8ae176ea2 100644 --- a/hack/boilerplate.cue.txt +++ b/hack/boilerplate.cue.txt @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 ApeCloud Co., Ltd +// Copyright (C) 2022-2025 ApeCloud Co., Ltd // // This file is part of KubeBlocks project // diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 97d74842e..8e68bb727 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/hack/boilerplate_apache2.go.txt b/hack/boilerplate_apache2.go.txt index 94b7f1831..f5e04483f 100644 --- a/hack/boilerplate_apache2.go.txt +++ b/hack/boilerplate_apache2.go.txt @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/hack/client-sdk-gen.sh b/hack/client-sdk-gen.sh index 141e34d36..e864f72ee 100755 --- a/hack/client-sdk-gen.sh +++ b/hack/client-sdk-gen.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Copyright (C) 2022-2024 ApeCloud Co., Ltd +# Copyright (C) 2022-2025 ApeCloud Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/hack/docgen/cli/main.go b/hack/docgen/cli/main.go index 32ec298c5..82db2cab2 100644 --- a/hack/docgen/cli/main.go +++ b/hack/docgen/cli/main.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/hack/install_cli.sh b/hack/install_cli.sh index 712e214a6..00b8ade13 100755 --- a/hack/install_cli.sh +++ b/hack/install_cli.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Copyright (C) 2022-2024 ApeCloud Co., Ltd +# Copyright (C) 2022-2025 ApeCloud Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/hack/install_cli_docker.sh b/hack/install_cli_docker.sh index debf66507..30ab043ed 100755 --- a/hack/install_cli_docker.sh +++ b/hack/install_cli_docker.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (C) 2022-2024 ApeCloud Co., Ltd +# Copyright (C) 2022-2025 ApeCloud Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/hack/license/header-check.sh b/hack/license/header-check.sh index de491b369..16e442503 100755 --- a/hack/license/header-check.sh +++ b/hack/license/header-check.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (C) 2022-2024 ApeCloud Co., Ltd +# Copyright (C) 2022-2025 ApeCloud Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ APACHE2_DIRS="apis/\|externalapis/" for file in $(git ls-files | grep '\.cue\|\.go$' | grep -v ${EXCLUDES_DIRS}); do echo -n "Header check: $file... " - if [[ -z $(cat ${file} | grep "Copyright (C) 2022-2024 ApeCloud Co., Ltd\|Code generated by") ]]; then + if [[ -z $(cat ${file} | grep "Copyright (C) 2022-2025 ApeCloud Co., Ltd\|Code generated by") ]]; then ERR=true fi if [ $ERR == true ]; then @@ -52,7 +52,7 @@ done for file in $(git ls-files | grep '\.go$' | grep ${APACHE2_DIRS}); do echo -n "Header check: $file... " - if [[ -z $(cat ${file} | grep "Copyright (C) 2022-2024 ApeCloud Co., Ltd\|Code generated by") ]]; then + if [[ -z $(cat ${file} | grep "Copyright (C) 2022-2025 ApeCloud Co., Ltd\|Code generated by") ]]; then ERR=true fi if [ $ERR == true ]; then diff --git a/pkg/action/create.go b/pkg/action/create.go index c8686d4b2..d1b7d372e 100755 --- a/pkg/action/create.go +++ b/pkg/action/create.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/create_test.go b/pkg/action/create_test.go index 4b9108a29..c16150aef 100755 --- a/pkg/action/create_test.go +++ b/pkg/action/create_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -54,7 +54,7 @@ var _ = Describe("Create", func() { "clusterDefRef": "test-def", "clusterVersionRef": "test-clusterversion-ref", "components": []string{}, - "terminationPolicy": "Halt", + "terminationPolicy": "WipeOut", } options = CreateOptions{ Factory: tf, diff --git a/pkg/action/custom_edit.go b/pkg/action/custom_edit.go index d55711413..bc5366d74 100644 --- a/pkg/action/custom_edit.go +++ b/pkg/action/custom_edit.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/custom_edit_test.go b/pkg/action/custom_edit_test.go index bff21507c..90bc33042 100644 --- a/pkg/action/custom_edit_test.go +++ b/pkg/action/custom_edit_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/delete.go b/pkg/action/delete.go index b05f0aa39..36e4f384c 100644 --- a/pkg/action/delete.go +++ b/pkg/action/delete.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/delete_test.go b/pkg/action/delete_test.go index e65dcdc73..cdbdb3202 100644 --- a/pkg/action/delete_test.go +++ b/pkg/action/delete_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/edit.go b/pkg/action/edit.go index b32b49f79..a16236eaf 100644 --- a/pkg/action/edit.go +++ b/pkg/action/edit.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/edit_test.go b/pkg/action/edit_test.go index 6dce7ee66..946b9edab 100644 --- a/pkg/action/edit_test.go +++ b/pkg/action/edit_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/exec.go b/pkg/action/exec.go index 47b406865..d14ea4d11 100644 --- a/pkg/action/exec.go +++ b/pkg/action/exec.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/exec_test.go b/pkg/action/exec_test.go index 95ef3f220..2deff01d7 100644 --- a/pkg/action/exec_test.go +++ b/pkg/action/exec_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/list.go b/pkg/action/list.go index 4f21cf210..587809d02 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -67,6 +67,7 @@ type ListOptions struct { // only return the result to caller. Print bool SortBy string + Status string genericiooptions.IOStreams } diff --git a/pkg/action/list_test.go b/pkg/action/list_test.go index 847b536f8..add4f85ef 100644 --- a/pkg/action/list_test.go +++ b/pkg/action/list_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/patch.go b/pkg/action/patch.go index 082cf7a57..3e7efb19d 100644 --- a/pkg/action/patch.go +++ b/pkg/action/patch.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/patch_test.go b/pkg/action/patch_test.go index af36c23f8..ff81a1750 100644 --- a/pkg/action/patch_test.go +++ b/pkg/action/patch_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/suite_test.go b/pkg/action/suite_test.go index e0dd36b15..f1801d737 100644 --- a/pkg/action/suite_test.go +++ b/pkg/action/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/action/template/cluster_operations_template.cue b/pkg/action/template/cluster_operations_template.cue index 57a67d8ec..b31748a26 100644 --- a/pkg/action/template/cluster_operations_template.cue +++ b/pkg/action/template/cluster_operations_template.cue @@ -1,4 +1,4 @@ -//Copyright (C) 2022-2024 ApeCloud Co., Ltd +//Copyright (C) 2022-2025 ApeCloud Co., Ltd // //This file is part of KubeBlocks project // @@ -30,14 +30,16 @@ options: { componentDefinitionName: string serviceVersion: string component: string + componentObjectName: string instance: string componentNames: [...string] instanceTPLNames: [...string] rebuildInstanceFrom: [ ...{ - componentName: string - backupName?: string - inPlace?: bool + componentName: string + backupName?: string + inPlace?: bool + sourceBackupTargetName?: string instances: [ ...{ name: string @@ -57,8 +59,8 @@ options: { replicas: string offlineInstancesToOnline: [...string] onlineInstancesToOffline: [...string] - scaleOut: bool - storage: string + scaleOut: bool + storage: string opsDefinitionName: string vctNames: [...string] keyValues: [string]: {string | null} @@ -122,6 +124,16 @@ content: { type: options.type ttlSecondsAfterSucceed: options.ttlSecondsAfterSucceed force: options.force + if options.type == "Stop" { + stop: [ for _, cName in options.componentNames { + componentName: cName + }] + } + if options.type == "Start" { + start: [ for _, cName in options.componentNames { + componentName: cName + }] + } if options.type == "Upgrade" { upgrade: #upgrade } @@ -143,7 +155,7 @@ content: { replicaChanges: strconv.Atoi(options.replicas) } if len(options.offlineInstancesToOnline) > 0 { - offlineInstancesToOnline: options.offlineInstancesToOnline + offlineInstancesToOnline: options.offlineInstancesToOnline } } } @@ -153,8 +165,8 @@ content: { replicaChanges: strconv.Atoi(options.replicas) } if len(options.onlineInstancesToOffline) > 0 { - onlineInstancesToOffline: options.onlineInstancesToOffline - } + onlineInstancesToOffline: options.onlineInstancesToOffline + } } } }] @@ -205,52 +217,13 @@ content: { }] } if options.type == "Reconfiguring" { - if len(options.componentNames) == 1 { - reconfigure: { - componentName: options.componentNames[0] - configurations: [ { - name: options.cfgTemplateName - if options.forceRestart { - policy: "simple" - } - keys: [{ - key: options.cfgFile - if options.fileContent != "" { - fileContent: options.fileContent - } - if options.hasPatch { - parameters: [ for k, v in options.keyValues { - key: k - value: v - }] - } - }] - }] - } - } - if len(options.componentNames) > 1 { - reconfigures: [ for _, cName in options.componentNames { - componentName: cName - configurations: [ { - name: options.cfgTemplateName - if options.forceRestart { - policy: "simple" - } - keys: [{ - key: options.cfgFile - if options.fileContent != "" { - fileContent: options.fileContent - } - if options.hasPatch { - parameters: [ for k, v in options.keyValues { - key: k - value: v - }] - } - }] - }] + reconfigures: [ for _, cName in options.componentNames { + componentName: cName + parameters: [ for k, v in options.keyValues { + key: k + value: v }] - } + }] } if options.type == "Expose" { expose: [ for _, cName in options.componentNames { @@ -273,13 +246,9 @@ content: { } if options.type == "Switchover" { switchover: [{ - componentName: options.component - if options.instance == "" { - instanceName: "*" - } - if options.instance != "" { - instanceName: options.instance - } + componentObjectName: options.componentObjectName + instanceName: options.instance + candidateName: options.candidate }] } if options.type == "RebuildInstance" { @@ -292,7 +261,7 @@ content: { { componentName: options.component if len(options.params) > 0 { - parameters: options.params + parameters: options.params } }, ] diff --git a/pkg/action/template/cluster_template.cue b/pkg/action/template/cluster_template.cue index c510f7c5d..ed01b6cc4 100644 --- a/pkg/action/template/cluster_template.cue +++ b/pkg/action/template/cluster_template.cue @@ -1,4 +1,4 @@ -//Copyright (C) 2022-2024 ApeCloud Co., Ltd +//Copyright (C) 2022-2025 ApeCloud Co., Ltd // //This file is part of KubeBlocks project // diff --git a/pkg/action/template/create_template_test.cue b/pkg/action/template/create_template_test.cue index 5fbf9db91..2fb007689 100644 --- a/pkg/action/template/create_template_test.cue +++ b/pkg/action/template/create_template_test.cue @@ -1,4 +1,4 @@ -//Copyright (C) 2022-2024 ApeCloud Co., Ltd +//Copyright (C) 2022-2025 ApeCloud Co., Ltd // //This file is part of KubeBlocks project // diff --git a/pkg/action/template/dns_chaos_template.cue b/pkg/action/template/dns_chaos_template.cue index e78940498..a45ea31bd 100644 --- a/pkg/action/template/dns_chaos_template.cue +++ b/pkg/action/template/dns_chaos_template.cue @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 ApeCloud Co., Ltd +// Copyright (C) 2022-2025 ApeCloud Co., Ltd // // This file is part of KubeBlocks project // diff --git a/pkg/action/template/http_chaos_template.cue b/pkg/action/template/http_chaos_template.cue index e4e58aa82..83c2ab91b 100644 --- a/pkg/action/template/http_chaos_template.cue +++ b/pkg/action/template/http_chaos_template.cue @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 ApeCloud Co., Ltd +// Copyright (C) 2022-2025 ApeCloud Co., Ltd // // This file is part of KubeBlocks project // diff --git a/pkg/action/template/io_chaos_template.cue b/pkg/action/template/io_chaos_template.cue index 2b39ace42..34819705b 100644 --- a/pkg/action/template/io_chaos_template.cue +++ b/pkg/action/template/io_chaos_template.cue @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 ApeCloud Co., Ltd +// Copyright (C) 2022-2025 ApeCloud Co., Ltd // // This file is part of KubeBlocks project // diff --git a/pkg/action/template/migration_template.cue b/pkg/action/template/migration_template.cue index 574c6e976..24d9e0c7e 100644 --- a/pkg/action/template/migration_template.cue +++ b/pkg/action/template/migration_template.cue @@ -1,4 +1,4 @@ -//Copyright (C) 2022-2024 ApeCloud Co., Ltd +//Copyright (C) 2022-2025 ApeCloud Co., Ltd // //This file is part of KubeBlocks project // diff --git a/pkg/action/template/network_chaos_template.cue b/pkg/action/template/network_chaos_template.cue index 4f5a4b26b..b43e08a89 100644 --- a/pkg/action/template/network_chaos_template.cue +++ b/pkg/action/template/network_chaos_template.cue @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 ApeCloud Co., Ltd +// Copyright (C) 2022-2025 ApeCloud Co., Ltd // // This file is part of KubeBlocks project // diff --git a/pkg/action/template/node_chaos_template.cue b/pkg/action/template/node_chaos_template.cue index 0308fbb7e..0cbdf51b8 100644 --- a/pkg/action/template/node_chaos_template.cue +++ b/pkg/action/template/node_chaos_template.cue @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 ApeCloud Co., Ltd +// Copyright (C) 2022-2025 ApeCloud Co., Ltd // // This file is part of KubeBlocks project // diff --git a/pkg/action/template/opsrequest_template.cue b/pkg/action/template/opsrequest_template.cue index 8abd7ba4a..ddc8760b5 100644 --- a/pkg/action/template/opsrequest_template.cue +++ b/pkg/action/template/opsrequest_template.cue @@ -1,4 +1,4 @@ -//Copyright (C) 2022-2024 ApeCloud Co., Ltd +//Copyright (C) 2022-2025 ApeCloud Co., Ltd // //This file is part of KubeBlocks project // diff --git a/pkg/action/template/pod_chaos_template.cue b/pkg/action/template/pod_chaos_template.cue index 461c3592a..f4bebf90e 100644 --- a/pkg/action/template/pod_chaos_template.cue +++ b/pkg/action/template/pod_chaos_template.cue @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 ApeCloud Co., Ltd +// Copyright (C) 2022-2025 ApeCloud Co., Ltd // // This file is part of KubeBlocks project // diff --git a/pkg/action/template/stress_chaos_template.cue b/pkg/action/template/stress_chaos_template.cue index 93425ba07..d0f6cc67e 100644 --- a/pkg/action/template/stress_chaos_template.cue +++ b/pkg/action/template/stress_chaos_template.cue @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 ApeCloud Co., Ltd +// Copyright (C) 2022-2025 ApeCloud Co., Ltd // // This file is part of KubeBlocks project // diff --git a/pkg/action/template/time_chaos_template.cue b/pkg/action/template/time_chaos_template.cue index 9d93c6e33..1639439a5 100644 --- a/pkg/action/template/time_chaos_template.cue +++ b/pkg/action/template/time_chaos_template.cue @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 ApeCloud Co., Ltd +// Copyright (C) 2022-2025 ApeCloud Co., Ltd // // This file is part of KubeBlocks project // diff --git a/pkg/action/template/trace_template.cue b/pkg/action/template/trace_template.cue new file mode 100644 index 000000000..b1b096b25 --- /dev/null +++ b/pkg/action/template/trace_template.cue @@ -0,0 +1,57 @@ +//Copyright (C) 2022-2025 ApeCloud Co., Ltd +// +//This file is part of KubeBlocks project +// +//This program is free software: you can redistribute it and/or modify +//it under the terms of the GNU Affero General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU Affero General Public License for more details. +// +//You should have received a copy of the GNU Affero General Public License +//along with this program. If not, see . + +// required, command line input options for parameters and flags +options: { + name: string + namespace: string + clusterName: string | *null + depth: int64 | *null + locale: string | *null + celStateEvaluationExpression: string | *null +} + +// required, k8s api resource content +content: { + apiVersion: "trace.kubeblocks.io/v1" + kind: "ReconciliationTrace" + metadata: { + name: options.name + namespace: options.namespace + } + spec: { + if options.clusterName != null { + targetObject: { + namespace: options.namespace + name: options.clusterName + } + } + if options.depth != null { + depth: options.depth + } + if options.locale != null { + locale: options.locale + } + if options.celStateEvaluationExpression != null { + stateEvaluationExpression: { + celExpression: { + expression: options.celStateEvaluationExpression + } + } + } + } +} diff --git a/pkg/action/template/volumesnapshotclass_template.cue b/pkg/action/template/volumesnapshotclass_template.cue index ebb89c1a8..aa736f492 100644 --- a/pkg/action/template/volumesnapshotclass_template.cue +++ b/pkg/action/template/volumesnapshotclass_template.cue @@ -1,4 +1,4 @@ -//Copyright (C) 2022-2024 ApeCloud Co., Ltd +//Copyright (C) 2022-2025 ApeCloud Co., Ltd // //This file is part of KubeBlocks project // diff --git a/pkg/cloudprovider/interface.go b/pkg/cloudprovider/interface.go index 43640b0b5..234ac8808 100644 --- a/pkg/cloudprovider/interface.go +++ b/pkg/cloudprovider/interface.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cloudprovider/k3d.go b/pkg/cloudprovider/k3d.go index ac26d5e96..e2a9b15d3 100644 --- a/pkg/cloudprovider/k3d.go +++ b/pkg/cloudprovider/k3d.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -52,11 +52,11 @@ var ( // all cluster will be created in this network, so they can communicate with each other CliDockerNetwork = "k3d-kbcli-playground" - // K3sImage is k3s image repo - K3sImage = "rancher/k3s:" + version.K3sImageTag + // K3sImageDefault is k3s image repo + K3sImageDefault = "rancher/k3s:" + version.K3sImageTag - // K3dProxyImage is k3d proxy image repo - K3dProxyImage = "docker.io/apecloud/k3d-proxy:" + version.K3dVersion + // K3dProxyImageDefault is k3d proxy image repo + K3dProxyImageDefault = "docker.io/apecloud/k3d-proxy:" + version.K3dVersion // K3dFixEnv KBEnvFix fixes.K3DFixEnv = "KB_FIX_MOUNTS" @@ -97,7 +97,7 @@ func (p *localCloudProvider) Name() string { func (p *localCloudProvider) CreateK8sCluster(clusterInfo *K8sClusterInfo) error { var err error - if p.cfg, err = buildClusterRunConfig(clusterInfo.ClusterName); err != nil { + if p.cfg, err = buildClusterRunConfig(clusterInfo.ClusterName, clusterInfo.K3sImage, clusterInfo.K3dProxyImage); err != nil { return err } @@ -209,9 +209,9 @@ func (p *localCloudProvider) GetClusterInfo() (*K8sClusterInfo, error) { } // buildClusterRunConfig returns the run-config for the k3d cluster -func buildClusterRunConfig(clusterName string) (config.ClusterConfig, error) { +func buildClusterRunConfig(clusterName string, k3sImage string, k3dProxyImage string) (config.ClusterConfig, error) { createOpts := buildClusterCreateOpts() - cluster, err := buildClusterConfig(clusterName, createOpts) + cluster, err := buildClusterConfig(clusterName, createOpts, k3sImage, k3dProxyImage) if err != nil { return config.ClusterConfig{}, err } @@ -239,7 +239,7 @@ func buildClusterCreateOpts() k3d.ClusterCreateOpts { return clusterCreateOpts } -func buildClusterConfig(clusterName string, opts k3d.ClusterCreateOpts) (k3d.Cluster, error) { +func buildClusterConfig(clusterName string, opts k3d.ClusterCreateOpts, k3sImage string, k3dProxyImage string) (k3d.Cluster, error) { var network = k3d.ClusterNetwork{ Name: CliDockerNetwork, External: false, @@ -273,16 +273,16 @@ func buildClusterConfig(clusterName string, opts k3d.ClusterCreateOpts) (k3d.Clu var nodes []*k3d.Node // build load balancer node - clusterConfig.ServerLoadBalancer = buildLoadbalancer(clusterConfig, opts) + clusterConfig.ServerLoadBalancer = buildLoadbalancer(clusterConfig, opts, k3dProxyImage) nodes = append(nodes, clusterConfig.ServerLoadBalancer.Node) // build k3d node serverNode := k3d.Node{ Name: k3dClient.GenerateNodeName(clusterConfig.Name, k3d.ServerRole, 0), Role: k3d.ServerRole, - Image: K3sImage, + Image: k3sImage, ServerOpts: k3d.ServerOpts{}, - Args: []string{"--disable=metrics-server", "--disable=traefik", "--disable=local-storage"}, + Args: []string{"--disable=metrics-server", "--disable=traefik"}, } nodes = append(nodes, &serverNode) @@ -317,7 +317,7 @@ func findAvailablePort(start int) (string, error) { return "", errors.New("can not find any available port") } -func buildLoadbalancer(cluster k3d.Cluster, opts k3d.ClusterCreateOpts) *k3d.Loadbalancer { +func buildLoadbalancer(cluster k3d.Cluster, opts k3d.ClusterCreateOpts, k3dProxyImage string) *k3d.Loadbalancer { lb := k3d.NewLoadbalancer() labels := map[string]string{} @@ -326,7 +326,7 @@ func buildLoadbalancer(cluster k3d.Cluster, opts k3d.ClusterCreateOpts) *k3d.Loa } lb.Node.Name = fmt.Sprintf("%s-%s-serverlb", k3d.DefaultObjectNamePrefix, cluster.Name) - lb.Node.Image = K3dProxyImage + lb.Node.Image = k3dProxyImage lb.Node.Ports = nat.PortMap{ k3d.DefaultAPIPort: []nat.PortBinding{cluster.KubeAPI.Binding}, } diff --git a/pkg/cloudprovider/k3d_test.go b/pkg/cloudprovider/k3d_test.go index 423b2f67d..bc463b910 100644 --- a/pkg/cloudprovider/k3d_test.go +++ b/pkg/cloudprovider/k3d_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -31,13 +31,30 @@ var _ = Describe("playground", func() { var ( provider = newLocalCloudProvider(os.Stdout, os.Stderr) clusterName = "k3d-kb-test" + image = "test.com/k3d-proxy:5.4.4" ) - It("k3d util function", func() { - config, err := buildClusterRunConfig("test") - Expect(err).ShouldNot(HaveOccurred()) - Expect(config.Name).Should(ContainSubstring("test")) - Expect(setUpK3d(context.Background(), nil)).Should(HaveOccurred()) - Expect(provider.DeleteK8sCluster(&K8sClusterInfo{ClusterName: clusterName})).Should(HaveOccurred()) + Context("k3d util function", func() { + It("with name", func() { + config, err := buildClusterRunConfig("test", "", "") + Expect(err).ShouldNot(HaveOccurred()) + Expect(config.Name).Should(ContainSubstring("test")) + Expect(setUpK3d(context.Background(), nil)).Should(HaveOccurred()) + Expect(provider.DeleteK8sCluster(&K8sClusterInfo{ClusterName: clusterName})).Should(HaveOccurred()) + }) + + It("with name and k3s image", func() { + config, err := buildClusterRunConfig("test", image, "") + Expect(err).ShouldNot(HaveOccurred()) + Expect(config.Cluster.Nodes[1].Image).Should(ContainSubstring("test.com")) + }) + + It("with name and k3d proxy image", func() { + config, err := buildClusterRunConfig("test", "", image) + Expect(err).ShouldNot(HaveOccurred()) + Expect(config.ServerLoadBalancer.Node.Image).Should(ContainSubstring("test.com")) + }) + }) + }) diff --git a/pkg/cloudprovider/provider.go b/pkg/cloudprovider/provider.go index 0b7219499..93a9093ac 100644 --- a/pkg/cloudprovider/provider.go +++ b/pkg/cloudprovider/provider.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cloudprovider/provider_test.go b/pkg/cloudprovider/provider_test.go index 133cd1351..16d29697d 100644 --- a/pkg/cloudprovider/provider_test.go +++ b/pkg/cloudprovider/provider_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cloudprovider/suite_test.go b/pkg/cloudprovider/suite_test.go index c8f03d902..a12b19f1c 100644 --- a/pkg/cloudprovider/suite_test.go +++ b/pkg/cloudprovider/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -33,6 +33,6 @@ func TestPlayground(t *testing.T) { var _ = BeforeSuite(func() { // set fake image info - K3sImage = "fake-k3s-image" - K3dProxyImage = "fake-k3d-proxy-image" + K3sImageDefault = "fake-k3s-image" + K3dProxyImageDefault = "fake-k3d-proxy-image" }) diff --git a/pkg/cloudprovider/terraform.go b/pkg/cloudprovider/terraform.go index 7fe448498..c12ffeeb6 100644 --- a/pkg/cloudprovider/terraform.go +++ b/pkg/cloudprovider/terraform.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cloudprovider/terraform_test.go b/pkg/cloudprovider/terraform_test.go index 4a88c9be8..e9a3a8263 100644 --- a/pkg/cloudprovider/terraform_test.go +++ b/pkg/cloudprovider/terraform_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cloudprovider/types.go b/pkg/cloudprovider/types.go index fac4e0c49..eff76844f 100644 --- a/pkg/cloudprovider/types.go +++ b/pkg/cloudprovider/types.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -68,6 +68,13 @@ type K8sClusterInfo struct { Region string `json:"region,omitempty"` KubeConfig string `json:"kube_config,omitempty"` KbcliVersion string `json:"kbcli_version,omitempty"` + K3dClusterInfo +} + +// K3dClusterInfo is the k3d cluster information for playground +type K3dClusterInfo struct { + K3sImage string `json:"k3s_image,omitempty"` + K3dProxyImage string `json:"k3d_proxy_image,omitempty"` } // IsValid checks if kubernetes cluster info is valid diff --git a/pkg/cluster/builtin_charts.go b/pkg/cluster/builtin_charts.go index 2e08ffde8..5685c03a0 100644 --- a/pkg/cluster/builtin_charts.go +++ b/pkg/cluster/builtin_charts.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -70,14 +70,12 @@ var ( redisChart embed.FS //go:embed charts/mongodb.tgz mongodbChart embed.FS - //go:embed charts/llm.tgz - llmChart embed.FS - //go:embed charts/xinference.tgz - xinferenceChart embed.FS - //go:embed charts/elasticsearch.tgz - elasticsearchChart embed.FS //go:embed charts/qdrant.tgz qdrantChart embed.FS + //go:embed charts/etcd.tgz + etcdChart embed.FS + //go:embed charts/rabbitmq.tgz + rabbitmqChart embed.FS ) var builtinClusterTypes = map[ClusterType]bool{} @@ -124,27 +122,21 @@ func init() { alias: "", }, - "llm": { - chartFS: llmChart, - name: "llm.tgz", - alias: "", - }, - - "xinference": { - chartFS: xinferenceChart, - name: "xinference.tgz", + "qdrant": { + chartFS: qdrantChart, + name: "qdrant.tgz", alias: "", }, - "elasticsearch": { - chartFS: elasticsearchChart, - name: "elasticsearch.tgz", + "etcd": { + chartFS: etcdChart, + name: "etcd.tgz", alias: "", }, - "qdrant": { - chartFS: qdrantChart, - name: "qdrant.tgz", + "rabbitmq": { + chartFS: rabbitmqChart, + name: "rabbitmq.tgz", alias: "", }, } diff --git a/pkg/cluster/charts/apecloud-mysql.tgz b/pkg/cluster/charts/apecloud-mysql.tgz index 1194a4669..9a9d4b8ee 100644 Binary files a/pkg/cluster/charts/apecloud-mysql.tgz and b/pkg/cluster/charts/apecloud-mysql.tgz differ diff --git a/pkg/cluster/charts/elasticsearch.tgz b/pkg/cluster/charts/elasticsearch.tgz index 6d6bfabf8..819dadfe3 100644 Binary files a/pkg/cluster/charts/elasticsearch.tgz and b/pkg/cluster/charts/elasticsearch.tgz differ diff --git a/pkg/cluster/charts/etcd.tgz b/pkg/cluster/charts/etcd.tgz new file mode 100644 index 000000000..3c46c4aee Binary files /dev/null and b/pkg/cluster/charts/etcd.tgz differ diff --git a/pkg/cluster/charts/kafka.tgz b/pkg/cluster/charts/kafka.tgz index 2627e2849..469a1e923 100644 Binary files a/pkg/cluster/charts/kafka.tgz and b/pkg/cluster/charts/kafka.tgz differ diff --git a/pkg/cluster/charts/mongodb.tgz b/pkg/cluster/charts/mongodb.tgz index fa95f9ed2..9dac5aebe 100644 Binary files a/pkg/cluster/charts/mongodb.tgz and b/pkg/cluster/charts/mongodb.tgz differ diff --git a/pkg/cluster/charts/mysql.tgz b/pkg/cluster/charts/mysql.tgz index 0802f5933..197899b07 100644 Binary files a/pkg/cluster/charts/mysql.tgz and b/pkg/cluster/charts/mysql.tgz differ diff --git a/pkg/cluster/charts/postgresql.tgz b/pkg/cluster/charts/postgresql.tgz index d2813938f..4d0d59a00 100644 Binary files a/pkg/cluster/charts/postgresql.tgz and b/pkg/cluster/charts/postgresql.tgz differ diff --git a/pkg/cluster/charts/qdrant.tgz b/pkg/cluster/charts/qdrant.tgz index 5f6e231cb..e57b39d4a 100644 Binary files a/pkg/cluster/charts/qdrant.tgz and b/pkg/cluster/charts/qdrant.tgz differ diff --git a/pkg/cluster/charts/rabbitmq.tgz b/pkg/cluster/charts/rabbitmq.tgz new file mode 100644 index 000000000..56af28fc7 Binary files /dev/null and b/pkg/cluster/charts/rabbitmq.tgz differ diff --git a/pkg/cluster/charts/redis.tgz b/pkg/cluster/charts/redis.tgz index 493a6aeeb..b8c8ec162 100644 Binary files a/pkg/cluster/charts/redis.tgz and b/pkg/cluster/charts/redis.tgz differ diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 29db32204..6827652f0 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -376,8 +376,7 @@ func (o *ClusterObjects) GetComponentInfo() []*ComponentInfo { if ins.Resources != nil { resources = *ins.Resources } - vcts := o.getCompTemplateVolumeClaimTemplates(&compSpec, ins) - setComponentInfos(compSpec, resources, vcts, ins.GetReplicas(), clusterCompName, ins.Name, isSharding) + setComponentInfos(compSpec, resources, compSpec.VolumeClaimTemplates, ins.GetReplicas(), clusterCompName, ins.Name, isSharding) } setComponentInfos(compSpec, compSpec.Resources, compSpec.VolumeClaimTemplates, compSpec.Replicas-tplReplicas, clusterCompName, "", isSharding) @@ -391,27 +390,6 @@ func (o *ClusterObjects) GetComponentInfo() []*ComponentInfo { return comps } -// getCompTemplateVolumeClaimTemplates merges volume claim for instance template -func (o *ClusterObjects) getCompTemplateVolumeClaimTemplates(compSpec *kbappsv1.ClusterComponentSpec, - template kbappsv1.InstanceTemplate) []kbappsv1.ClusterComponentVolumeClaimTemplate { - var vcts []kbappsv1.ClusterComponentVolumeClaimTemplate - for i := range compSpec.VolumeClaimTemplates { - insVctIndex := -1 - for j := range template.VolumeClaimTemplates { - if template.VolumeClaimTemplates[j].Name == compSpec.VolumeClaimTemplates[i].Name { - insVctIndex = j - break - } - } - if insVctIndex != -1 { - vcts = append(vcts, template.VolumeClaimTemplates[insVctIndex]) - } else { - vcts = append(vcts, compSpec.VolumeClaimTemplates[i]) - } - } - return vcts -} - func (o *ClusterObjects) GetInstanceInfo() []*InstanceInfo { var instances []*InstanceInfo for _, pod := range o.Pods.Items { @@ -423,7 +401,6 @@ func (o *ClusterObjects) GetInstanceInfo() []*InstanceInfo { Component: componentName, Status: o.getPodPhase(&pod), Role: getLabelVal(pod.Labels, constant.RoleLabelKey), - AccessMode: getLabelVal(pod.Labels, constant.AccessModeLabelKey), CreatedTime: util.TimeFormat(&pod.CreationTimestamp), } var componentSpec *kbappsv1.ClusterComponentSpec @@ -442,18 +419,7 @@ func (o *ClusterObjects) GetInstanceInfo() []*InstanceInfo { } } } - templateName := kbappsv1.GetInstanceTemplateName(o.Cluster.Name, componentName, pod.Name) - template := kbappsv1.InstanceTemplate{} - if templateName != "" { - for _, v := range componentSpec.Instances { - if v.Name == templateName { - template = v - break - } - } - } - vcts := o.getCompTemplateVolumeClaimTemplates(componentSpec, template) - instance.Storage = o.getStorageInfo(vcts, pod.Labels[constant.KBAppComponentLabelKey]) + instance.Storage = o.getStorageInfo(componentSpec.VolumeClaimTemplates, pod.Labels[constant.KBAppComponentLabelKey]) instance.ServiceVersion = componentSpec.ServiceVersion getInstanceNodeInfo(o.Nodes, &pod, instance) instance.CPU, instance.Memory = getResourceInfo(resource.PodRequestsAndLimits(&pod)) diff --git a/pkg/cluster/cluster_chart.go b/pkg/cluster/cluster_chart.go index a9a41daa1..8e0f57d66 100644 --- a/pkg/cluster/cluster_chart.go +++ b/pkg/cluster/cluster_chart.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/cluster_chart_test.go b/pkg/cluster/cluster_chart_test.go index 166ecaf78..e96e8347f 100644 --- a/pkg/cluster/cluster_chart_test.go +++ b/pkg/cluster/cluster_chart_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -76,7 +76,7 @@ var _ = Describe("cluster engine", func() { "all values are valid", map[string]interface{}{ "cpu": 1.0, - "terminationPolicy": "Halt", + "terminationPolicy": "WipeOut", }, true, }, diff --git a/pkg/cluster/cluster_test.go b/pkg/cluster/cluster_test.go index ba763f46d..a55858bc5 100644 --- a/pkg/cluster/cluster_test.go +++ b/pkg/cluster/cluster_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/component.go b/pkg/cluster/component.go index fc2f34a7a..bf15dff3d 100644 --- a/pkg/cluster/component.go +++ b/pkg/cluster/component.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/external_charts.go b/pkg/cluster/external_charts.go index fc83afc05..961c41b24 100644 --- a/pkg/cluster/external_charts.go +++ b/pkg/cluster/external_charts.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -21,7 +21,7 @@ package cluster import ( "compress/gzip" - "encoding/json" + "errors" "fmt" "io" "io/fs" @@ -29,6 +29,7 @@ import ( "path/filepath" "github.com/spf13/cobra" + "github.com/xeipuuv/gojsonschema" "gopkg.in/yaml.v2" "helm.sh/helm/v3/pkg/chart/loader" "k8s.io/klog" @@ -246,15 +247,6 @@ func (h *TypeInstance) PatchNewClusterType() error { return GlobalClusterChartConfig.WriteConfigs(CliClusterChartConfig) } -var StandardSchema = map[string]interface{}{ - "properties": map[string]interface{}{ - "replicas": nil, - "cpu": nil, - "memory": nil, - "storage": nil, - }, -} - func (h *TypeInstance) ValidateChartSchema() (bool, error) { file, err := h.loadChart() if err != nil { @@ -266,41 +258,31 @@ func (h *TypeInstance) ValidateChartSchema() (bool, error) { if err != nil { return false, err } - - data := c.Schema - if len(data) == 0 { + if len(c.Schema) == 0 { return false, fmt.Errorf("register cluster chart of %s failed, schema of the chart doesn't exist", h.Name) } - - var schema map[string]interface{} - if err := json.Unmarshal(data, &schema); err != nil { - return false, fmt.Errorf("register cluster chart of %s failed, error decoding JSON: %s", h.Name, err) + if c.Values == nil { + return false, fmt.Errorf("register cluster chart of %s failed, values of the chart doesn't exist", h.Name) } - if err := validateSchema(schema, StandardSchema); err != nil { - return false, err - } + schemaLoader := gojsonschema.NewStringLoader(string(c.Schema)) + valuesLoader := gojsonschema.NewGoLoader(c.Values) - return true, nil -} + result, err := gojsonschema.Validate(schemaLoader, valuesLoader) + if err != nil { + return false, fmt.Errorf("validation failed: %v", err) + } -func validateSchema(schema, standard map[string]interface{}) error { - for key, val := range standard { - if subStandard, ok := val.(map[string]interface{}); ok { - subSchema, ok := schema[key].(map[string]interface{}) - if !ok { - return fmt.Errorf("register cluster chart failed, schema missing required map key '%s'", key) - } - if err := validateSchema(subSchema, subStandard); err != nil { - return err - } - } else { - if _, exists := schema[key]; !exists { - return fmt.Errorf("register cluster chart failed, schema missing required key '%s'", key) - } + if result.Valid() { + return true, nil + } else { + fmt.Println("Values do not match the schema:") + res := "" + for _, desc := range result.Errors() { + res += fmt.Sprintf("- %s\n", desc) } + return false, errors.New(res) } - return nil } var _ chartLoader = &TypeInstance{} diff --git a/pkg/cluster/helper.go b/pkg/cluster/helper.go index c9fc2b505..298e328da 100644 --- a/pkg/cluster/helper.go +++ b/pkg/cluster/helper.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/helper_test.go b/pkg/cluster/helper_test.go index e87affb36..ceab3322f 100644 --- a/pkg/cluster/helper_test.go +++ b/pkg/cluster/helper_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/name_generator.go b/pkg/cluster/name_generator.go index 68247c37d..20262bb55 100644 --- a/pkg/cluster/name_generator.go +++ b/pkg/cluster/name_generator.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/name_generator_test.go b/pkg/cluster/name_generator_test.go index 9ab7dbbbd..ede1af10c 100644 --- a/pkg/cluster/name_generator_test.go +++ b/pkg/cluster/name_generator_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/printer.go b/pkg/cluster/printer.go index 74c41ab92..46bfe50b2 100644 --- a/pkg/cluster/printer.go +++ b/pkg/cluster/printer.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -21,6 +21,7 @@ package cluster import ( "io" + "sort" "strings" corev1 "k8s.io/api/core/v1" @@ -41,38 +42,38 @@ const ( ) type PrinterOptions struct { - ShowLabels bool + ShowLabels bool + StatusFilter string } type tblInfo struct { header []interface{} - addRow func(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) + addRow func(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) [][]interface{} getOptions GetOptions } var mapTblInfo = map[PrintType]tblInfo{ PrintClusters: { - header: []interface{}{"NAME", "NAMESPACE", "CLUSTER-DEFINITION", "VERSION", "TERMINATION-POLICY", "STATUS", "CREATED-TIME"}, - addRow: func(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) { + header: []interface{}{"NAME", "NAMESPACE", "CLUSTER-DEFINITION", "TERMINATION-POLICY", "STATUS", "CREATED-TIME"}, + addRow: func(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) [][]interface{} { c := objs.GetClusterInfo() - info := []interface{}{c.Name, c.Namespace, c.ClusterDefinition, c.ClusterVersion, c.TerminationPolicy, c.Status, c.CreatedTime} + info := []interface{}{c.Name, c.Namespace, c.ClusterDefinition, c.TerminationPolicy, c.Status, c.CreatedTime} if opt.ShowLabels { info = append(info, c.Labels) } - - tbl.AddRow(info...) + return [][]interface{}{info} }, getOptions: GetOptions{}, }, PrintWide: { - header: []interface{}{"NAME", "NAMESPACE", "CLUSTER-DEFINITION", "VERSION", "TERMINATION-POLICY", "STATUS", "INTERNAL-ENDPOINTS", "EXTERNAL-ENDPOINTS", "CREATED-TIME"}, - addRow: func(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) { + header: []interface{}{"NAME", "NAMESPACE", "CLUSTER-DEFINITION", "TERMINATION-POLICY", "STATUS", "INTERNAL-ENDPOINTS", "EXTERNAL-ENDPOINTS", "CREATED-TIME"}, + addRow: func(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) [][]interface{} { c := objs.GetClusterInfo() - info := []interface{}{c.Name, c.Namespace, c.ClusterDefinition, c.ClusterVersion, c.TerminationPolicy, c.Status, c.InternalEP, c.ExternalEP, c.CreatedTime} + info := []interface{}{c.Name, c.Namespace, c.ClusterDefinition, c.TerminationPolicy, c.Status, c.InternalEP, c.ExternalEP, c.CreatedTime} if opt.ShowLabels { info = append(info, c.Labels) } - tbl.AddRow(info...) + return [][]interface{}{info} }, getOptions: GetOptions{WithClusterDef: Maybe, WithService: Need, WithPod: Need}, }, @@ -100,13 +101,15 @@ var mapTblInfo = map[PrintType]tblInfo{ // Printer prints cluster info type Printer struct { - tbl *printer.TablePrinter - opt *PrinterOptions - tblInfo + tbl *printer.TablePrinter + opt *PrinterOptions + tblInfo tblInfo + pt PrintType + rows [][]interface{} } func NewPrinter(out io.Writer, printType PrintType, opt *PrinterOptions) *Printer { - p := &Printer{tbl: printer.NewTablePrinter(out)} + p := &Printer{tbl: printer.NewTablePrinter(out), pt: printType} p.tblInfo = mapTblInfo[printType] if opt == nil { @@ -123,48 +126,147 @@ func NewPrinter(out io.Writer, printType PrintType, opt *PrinterOptions) *Printe } func (p *Printer) AddRow(objs *ClusterObjects) { - p.addRow(p.tbl, objs, p.opt) + lines := p.tblInfo.addRow(p.tbl, objs, p.opt) + p.rows = append(p.rows, lines...) } func (p *Printer) Print() { + if p.pt == PrintClusters || p.pt == PrintWide { + p.filterByStatus() + p.sortRows() + } + + for _, row := range p.rows { + p.tbl.AddRow(row...) + } + p.tbl.Print() } func (p *Printer) GetterOptions() GetOptions { - return p.getOptions + return p.tblInfo.getOptions +} + +func (p *Printer) filterByStatus() { + if p.opt.StatusFilter == "" { + return + } + + statusIndex := 4 + + var filtered [][]interface{} + for _, r := range p.rows { + statusVal, _ := r[statusIndex].(string) + if strings.EqualFold(statusVal, p.opt.StatusFilter) { + filtered = append(filtered, r) + } + } + p.rows = filtered +} + +// sortRows Sort By namespace(1), clusterDef(2), status(4), name(0) +func (p *Printer) sortRows() { + // for PrintClusters and PrintWide + // NAME(0), NAMESPACE(1), CLUSTER-DEFINITION(2), STATUS(4) + sort.Slice(p.rows, func(i, j int) bool { + ri, rj := p.rows[i], p.rows[j] + + nsI, _ := ri[1].(string) + nsJ, _ := rj[1].(string) + if nsI != nsJ { + return nsI < nsJ + } + + cdI, _ := ri[2].(string) + cdJ, _ := rj[2].(string) + if cdI != cdJ { + return cdI < cdJ + } + + statusI, _ := ri[4].(string) + statusJ, _ := rj[4].(string) + if statusI != statusJ { + return compareStatus(statusI, statusJ) + } + + nameI, _ := ri[0].(string) + nameJ, _ := rj[0].(string) + return nameI < nameJ + }) +} + +// compareStatus compares statuses based on the desired order +func compareStatus(status1, status2 string) bool { + statusOrder := map[string]int{ + "Creating": 1, + "Running": 2, + "Updating": 3, + "Stopping": 4, + "Stopped": 5, + "Deleting": 6, + "Failed": 7, + "Abnormal": 8, + } + + order1, ok1 := statusOrder[status1] + order2, ok2 := statusOrder[status2] + + // unknown is the last + if !ok1 && !ok2 { + return status1 < status2 + } + if !ok1 { + return false + } + if !ok2 { + return true + } + + return order1 < order2 } -func AddLabelRow(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) { +func AddLabelRow(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) [][]interface{} { c := objs.GetClusterInfo() info := []interface{}{c.Name, c.Namespace} if opt.ShowLabels { labels := strings.ReplaceAll(c.Labels, ",", "\n") info = append(info, labels) } - tbl.AddRow(info...) + return [][]interface{}{info} } -func AddComponentRow(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) { +func AddComponentRow(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) [][]interface{} { components := objs.GetComponentInfo() + var rows [][]interface{} for _, c := range components { - tbl.AddRow(c.Name, c.NameSpace, c.Cluster, c.ComponentDef, c.Image) + row := []interface{}{c.Name, c.NameSpace, c.Cluster, c.ComponentDef, c.Image} + rows = append(rows, row) } + return rows } -func AddInstanceRow(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) { +func AddInstanceRow(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) [][]interface{} { instances := objs.GetInstanceInfo() + var rows [][]interface{} for _, instance := range instances { - tbl.AddRow(instance.Name, instance.Namespace, instance.Cluster, instance.Component, + row := []interface{}{ + instance.Name, instance.Namespace, instance.Cluster, instance.Component, instance.Status, instance.Role, instance.AccessMode, instance.AZ, instance.CPU, instance.Memory, - BuildStorageSize(instance.Storage), instance.Node, instance.CreatedTime) + BuildStorageSize(instance.Storage), instance.Node, instance.CreatedTime, + } + rows = append(rows, row) } + return rows } -func AddEventRow(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) { +func AddEventRow(tbl *printer.TablePrinter, objs *ClusterObjects, opt *PrinterOptions) [][]interface{} { events := util.SortEventsByLastTimestamp(objs.Events, "") + var rows [][]interface{} for _, event := range *events { e := event.(*corev1.Event) - tbl.AddRow(e.Namespace, util.GetEventTimeStr(e), e.Type, e.Reason, util.GetEventObject(e), e.Message) + row := []interface{}{e.Namespace, util.GetEventTimeStr(e), e.Type, e.Reason, util.GetEventObject(e), e.Message} + rows = append(rows, row) } + return rows } diff --git a/pkg/cluster/printer_test.go b/pkg/cluster/printer_test.go index c64323dde..737eeb6c4 100644 --- a/pkg/cluster/printer_test.go +++ b/pkg/cluster/printer_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/register.go b/pkg/cluster/register.go index fe30c0f98..3838243d7 100644 --- a/pkg/cluster/register.go +++ b/pkg/cluster/register.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/register_test.go b/pkg/cluster/register_test.go index a30726cbd..4b2b48558 100644 --- a/pkg/cluster/register_test.go +++ b/pkg/cluster/register_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -63,6 +63,24 @@ var _ = Describe("cluster register", func() { Expect(err).Should(HaveOccurred()) }) + It("test invalid chart", func() { + testPath := "./testdata" + chartName := "mock-chart-0.1.0.tgz" + fakeChart := &TypeInstance{ + Name: "mock-chart", + ChartName: chartName, + } + srcPath := filepath.Join(testPath, chartName) + destPath := filepath.Join(CliChartsCacheDir, chartName) + + srcFile, _ := os.Open(srcPath) + destFile, _ := os.Create(destPath) + _, _ = io.Copy(destFile, srcFile) + _, err := fakeChart.ValidateChartSchema() + Expect(err).Should(HaveOccurred()) + _ = os.Remove(destPath) + }) + Context("test Config reader", func() { var tempConfigPath string diff --git a/pkg/cluster/suite_test.go b/pkg/cluster/suite_test.go index a86a810ad..6b76ee049 100644 --- a/pkg/cluster/suite_test.go +++ b/pkg/cluster/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/testdata/mock-chart-0.1.0.tgz b/pkg/cluster/testdata/mock-chart-0.1.0.tgz new file mode 100644 index 000000000..67e2d3ae6 Binary files /dev/null and b/pkg/cluster/testdata/mock-chart-0.1.0.tgz differ diff --git a/pkg/cluster/types.go b/pkg/cluster/types.go index 0ffcaab86..dc2ffbc71 100644 --- a/pkg/cluster/types.go +++ b/pkg/cluster/types.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cluster/validate.go b/pkg/cluster/validate.go index cddd0094a..eb1ab012e 100644 --- a/pkg/cluster/validate.go +++ b/pkg/cluster/validate.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/addon/addon.go b/pkg/cmd/addon/addon.go index 435627236..7b7367710 100644 --- a/pkg/cmd/addon/addon.go +++ b/pkg/cmd/addon/addon.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -29,6 +29,9 @@ import ( "strconv" "strings" + extensionsv1alpha1 "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + viper "github.com/apecloud/kubeblocks/pkg/viperx" "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -40,14 +43,11 @@ import ( "k8s.io/cli-runtime/pkg/genericiooptions" discoverycli "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" "k8s.io/utils/strings/slices" - extensionsv1alpha1 "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/constant" - viper "github.com/apecloud/kubeblocks/pkg/viperx" - "github.com/apecloud/kbcli/pkg/action" clusterCmd "github.com/apecloud/kbcli/pkg/cmd/cluster" "github.com/apecloud/kbcli/pkg/cmd/plugin" @@ -81,6 +81,7 @@ type addonCmdOpts struct { Factory cmdutil.Factory dynamic dynamic.Interface + client kubernetes.Interface addon extensionsv1alpha1.Addon @@ -116,6 +117,7 @@ func NewAddonCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.C newInstallCmd(f, streams), newUninstallCmd(f, streams), newUpgradeCmd(f, streams), + newPurgeResourcesCmd(f, streams), ) return cmd } @@ -128,13 +130,13 @@ func newListCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Co Use: "list", Short: "List addons.", Aliases: []string{"ls"}, - ValidArgsFunction: util.ResourceNameCompletionFunc(f, o.GVR), + ValidArgsFunction: util.ResourceNameCompletionFunc(f, o.ListOptions.GVR), Run: func(cmd *cobra.Command, args []string) { - o.Names = args + o.ListOptions.Names = args util.CheckErr(addonListRun(o)) }, } - o.AddFlags(cmd, true) + o.ListOptions.AddFlags(cmd, true) cmd.Flags().StringArrayVar(&o.status, "status", []string{}, "Filter addons by status") cmd.Flags().BoolVar(&o.listEngines, "engines", false, "List engine addons only") return cmd @@ -223,9 +225,11 @@ func newEnableCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra. util.CheckErr(o.fetchAddonObj()) util.CheckErr(o.validate()) util.CheckErr(o.complete(o, cmd, []string{name})) - util.CheckErr(o.CmdComplete(cmd)) - util.CheckErr(o.Run()) - util.CheckErr(clusterCmd.RegisterClusterChart(f, streams, "", name, getAddonVersion(&o.addon), types.ClusterChartsRepoURL)) + util.CheckErr(o.PatchOptions.CmdComplete(cmd)) + util.CheckErr(o.PatchOptions.Run()) + if isEngineAddon(&o.addon) { + util.CheckErr(clusterCmd.RegisterClusterChart(f, streams, "", name, getAddonVersion(&o.addon), types.ClusterChartsRepoURL)) + } } }, } @@ -289,7 +293,7 @@ func newDisableCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra util.CheckErr(o.checkBeforeDisable()) util.CheckErr(o.complete(o, cmd, []string{name})) util.CheckErr(o.CmdComplete(cmd)) - util.CheckErr(o.Run()) + util.CheckErr(o.PatchOptions.Run()) } }, } @@ -299,13 +303,17 @@ func newDisableCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra } func (o *addonCmdOpts) init(args []string) error { - o.Names = args + o.PatchOptions.Names = args if o.dynamic == nil { var err error if o.dynamic, err = o.Factory.DynamicClient(); err != nil { return err } } + var err error + if o.client, err = o.Factory.KubernetesClientSet(); err != nil { + return err + } // setup _KUBE_SERVER_INFO if viper.Get(constant.CfgKeyServerInfo) == nil { @@ -345,7 +353,7 @@ func (o *addonCmdOpts) validate() error { } for _, s := range o.addon.Spec.Installable.Selectors { if !s.MatchesFromConfig() { - return fmt.Errorf("addon %s INSTALLABLE-SELECTOR has no matching requirement", o.Names) + return fmt.Errorf("addon %s INSTALLABLE-SELECTOR has no matching requirement", o.PatchOptions.Names) } } @@ -811,20 +819,20 @@ func (o *addonCmdOpts) buildPatch(flags []*pflag.Flag) error { if err != nil { return err } - o.Patch = string(bytes) + o.PatchOptions.Patch = string(bytes) return nil } func addonListRun(o *addonListOpts) error { // if format is JSON or YAML, use default printer to output the result. - if o.Format == printer.JSON || o.Format == printer.YAML { - _, err := o.Run() + if o.ListOptions.Format == printer.JSON || o.ListOptions.Format == printer.YAML { + _, err := o.ListOptions.Run() return err } // get and output the result - o.Print = false - r, err := o.Run() + o.ListOptions.Print = false + r, err := o.ListOptions.Run() if err != nil { return err } @@ -913,7 +921,7 @@ func addonListRun(o *addonListOpts) error { return nil } - if o.Format == printer.Wide { + if o.ListOptions.Format == printer.Wide { if err = printer.PrintTable(o.Out, nil, printRows, "NAME", "VERSION", "PROVIDER", "STATUS", "AUTO-INSTALL", "AUTO-INSTALLABLE-SELECTOR", "EXTRAS"); err != nil { return err diff --git a/pkg/cmd/addon/addon_test.go b/pkg/cmd/addon/addon_test.go index 1f01fdc4c..45f282056 100644 --- a/pkg/cmd/addon/addon_test.go +++ b/pkg/cmd/addon/addon_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/addon/index.go b/pkg/cmd/addon/index.go index a47372ed7..15d2717e4 100644 --- a/pkg/cmd/addon/index.go +++ b/pkg/cmd/addon/index.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/addon/index_test.go b/pkg/cmd/addon/index_test.go index 637582a35..653a00108 100644 --- a/pkg/cmd/addon/index_test.go +++ b/pkg/cmd/addon/index_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/addon/install.go b/pkg/cmd/addon/install.go index 83cdd359d..57336e3fa 100644 --- a/pkg/cmd/addon/install.go +++ b/pkg/cmd/addon/install.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -27,7 +27,13 @@ import ( "strings" "github.com/Masterminds/semver/v3" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + "github.com/apecloud/kubeblocks/pkg/constant" "github.com/spf13/cobra" + helmaction "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/releaseutil" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -37,13 +43,16 @@ import ( "k8s.io/client-go/kubernetes" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" + "sigs.k8s.io/yaml" extensionsv1alpha1 "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1" + "github.com/apecloud/kbcli/pkg/cluster" clusterCmd "github.com/apecloud/kbcli/pkg/cmd/cluster" "github.com/apecloud/kbcli/pkg/printer" "github.com/apecloud/kbcli/pkg/types" "github.com/apecloud/kbcli/pkg/util" + "github.com/apecloud/kbcli/pkg/util/helm" ) var addonInstallExample = templates.Examples(` @@ -131,10 +140,12 @@ func newInstallCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra PersistentPreRun: func(_ *cobra.Command, _ []string) { util.CheckErr(addDefaultIndex()) }, + ValidArgsFunction: addonNameCompletionFunc, Run: func(cmd *cobra.Command, args []string) { o.name = args[0] util.CheckErr(o.Complete()) util.CheckErr(o.Validate()) + util.CheckErr(o.process09ClusterDefAndComponentVersions()) util.CheckErr(o.Run(f, streams)) // avoid unnecessary messages for upgrade fmt.Fprintf(o.Out, "addon %s installed successfully\n", o.name) @@ -241,15 +252,18 @@ func (o *installOption) Validate() error { fmt.Fprint(o.Out, printer.BoldYellow("Warning: --force flag will skip version checks, which may result in the cluster not running correctly.\n")) return nil } - if o.addon.Annotations == nil || len(o.addon.Annotations[types.KBVersionValidateAnnotationKey]) == 0 { fmt.Fprint(o.Out, printer.BoldYellow(fmt.Sprintf(`Warning: The addon %s is missing annotations to validate KubeBlocks versions. It will automatically skip version checks, which may result in the cluster not running correctly. `, o.name))) - } else if ok, err = validateVersion(o.addon.Annotations[types.KBVersionValidateAnnotationKey], v.KubeBlocks); err == nil && !ok { - return fmt.Errorf("KubeBlocks version %s does not meet the requirements \"%s\" for addon installation\nUse --force option to skip this check", v.KubeBlocks, o.addon.Annotations[types.KBVersionValidateAnnotationKey]) + } else { + kbVersions := strings.Split(v.KubeBlocks, ",") + for _, kbVersion := range kbVersions { + if ok, err = validateVersion(o.addon.Annotations[types.KBVersionValidateAnnotationKey], kbVersion); err == nil && !ok { + return fmt.Errorf("KubeBlocks version %s does not meet the requirements \"%s\" for addon installation\nUse --force option to skip this check", v.KubeBlocks, o.addon.Annotations[types.KBVersionValidateAnnotationKey]) + } + } } - return err } @@ -300,3 +314,106 @@ func validateVersion(annotations, kbVersion string) (bool, error) { validate, _ := constraint.Validate(v) return validate, nil } + +func (o *installOption) process09ClusterDefAndComponentVersions() error { + kbDeploys, err := util.GetKBDeploys(o.Client, util.KubeblocksAppComponent, metav1.NamespaceAll) + if err != nil || len(kbDeploys) < 2 { + return err + } + var newKBNamespace string + for _, v := range kbDeploys { + if strings.HasPrefix(v.Labels[constant.AppVersionLabelKey], "1.0") { + newKBNamespace = v.Namespace + break + } + } + // 1. get manifests from the helm repo + chartsDownloader, err := helm.NewDownloader(helm.NewConfig(newKBNamespace, "", "", false)) + if err != nil { + return err + } + // DownloadTo can't specify the saved name, so download it to TempDir and rename it when copy + chartPath, _, err := chartsDownloader.DownloadTo(o.addon.Spec.Helm.ChartLocationURL, "", cluster.CliChartsCacheDir) + if err != nil { + return err + } + // 2. overwrite the spec of ClusterDefinition and ComponentVersion with the new version + actionCfg, err := helm.NewActionConfig(helm.NewConfig(newKBNamespace, "", "", false)) + if err != nil { + return err + } + chart, err := loader.Load(chartPath) + if err != nil { + return err + } + renderer := helmaction.NewInstall(actionCfg) + renderer.ReleaseName = o.addon.Name + "for-upgrade" + renderer.Namespace = newKBNamespace + renderer.DryRun = true + renderer.Replace = true + renderer.ClientOnly = true + valuesMap := map[string]interface{}{} + if o.addon.Spec.Helm != nil { + for _, v := range o.addon.Spec.Helm.InstallValues.SetValues { + keyValues := strings.Split(v, "=") + if len(keyValues) != 2 { + return fmt.Errorf("invalid install value: %s", v) + } + valuesMap[keyValues[0]] = keyValues[1] + } + } + release, err := renderer.Run(chart, valuesMap) + if err != nil { + return err + } + + updateObject := func(obj runtime.Object, gvr schema.GroupVersionResource) error { + unstructuredObj := obj.(*unstructured.Unstructured) + targetObj, err := o.Dynamic.Resource(gvr).Namespace("").Get(context.TODO(), unstructuredObj.GetName(), metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return err + } + annotations := targetObj.GetAnnotations() + annotations[constant.CRDAPIVersionAnnotationKey] = kbappsv1.GroupVersion.String() + annotations["meta.helm.sh/release-name"] = "kb-addon-" + o.addon.Name + annotations["meta.helm.sh/release-namespace"] = newKBNamespace + targetObj.SetAnnotations(annotations) + targetObj.Object["spec"] = unstructuredObj.Object["spec"] + if _, err = o.Dynamic.Resource(gvr).Namespace("").Update(context.TODO(), targetObj, metav1.UpdateOptions{}); err != nil { + return err + } + return nil + } + manifests := releaseutil.SplitManifests(release.Manifest) + for _, manifest := range manifests { + + // convert yaml to json + jsonData, err := yaml.YAMLToJSON([]byte(manifest)) + if err != nil { + return err + } + // check if jsonData is empty or null + if len(jsonData) == 0 || string(jsonData) == "null" { + continue + } + // get resource gvk + obj, gvk, err := unstructured.UnstructuredJSONScheme.Decode(jsonData, nil, nil) + if err != nil { + return err + } + switch gvk.Kind { + case types.KindClusterDef: + if err = updateObject(obj, types.ClusterDefGVR()); err != nil { + return err + } + case types.KindComponentVersion: + if err = updateObject(obj, types.ComponentVersionsGVR()); err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/cmd/addon/install_test.go b/pkg/cmd/addon/install_test.go index ff972cd3a..25c8a9d07 100644 --- a/pkg/cmd/addon/install_test.go +++ b/pkg/cmd/addon/install_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd # This file is part of KubeBlocks project diff --git a/pkg/cmd/addon/purge.go b/pkg/cmd/addon/purge.go new file mode 100644 index 000000000..b3d2b952b --- /dev/null +++ b/pkg/cmd/addon/purge.go @@ -0,0 +1,389 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package addon + +import ( + "context" + "fmt" + "io" + "regexp" + "sort" + + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/genericiooptions" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/apecloud/kbcli/pkg/util/prompt" + kbv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1" + + "github.com/apecloud/kbcli/pkg/types" + "github.com/apecloud/kbcli/pkg/util" +) + +const ( + versionPattern = `(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?)$` + helmReleaseNameKey = "meta.helm.sh/release-name" + helmReleaseNamePrefix = "kb-addon-" + helmResourcePolicyKey = "helm.sh/resource-policy" + helmResourcePolicyKeep = "keep" + + versionErrorTemplate = "failed to retrieve versions for resource %s: %v" +) + +// GVRsToPurge Resource types to be processed for deletion +var GVRsToPurge = []schema.GroupVersionResource{ + types.CompDefGVR(), + types.ConfigmapGVR(), + types.ConfigConstraintGVR(), + types.ConfigConstraintOldGVR(), +} + +var addonPurgeResourcesExample = templates.Examples(` + # Purge specific versions of redis addon resources + kbcli addon purge redis --versions=0.9.1,0.9.2 + + # Purge all unused and outdated resources of redis addon + kbcli addon purge redis --all + + # Print the resources that would be purged, and no resource is actually purged + kbcli addon purge redis --dry-run +`) + +// InUseVersionInfo defines a struct to hold version and associated cluster name +type InUseVersionInfo struct { + Version string + ClusterName string + ClusterNamespace string +} + +type purgeResourcesOption struct { + *baseOption + name string + versions []string + versionsInUse []InUseVersionInfo + all bool + dryRun bool + autoApprove bool + + // if set to true, the newest resources will also be deleted, and this flag is not open to user, only used to delete all the resources while addon uninstalling. + deleteNewestVersion bool +} + +func newPurgeResourcesOption(f cmdutil.Factory, streams genericiooptions.IOStreams) *purgeResourcesOption { + return &purgeResourcesOption{ + baseOption: &baseOption{ + Factory: f, + IOStreams: streams, + GVR: types.AddonGVR(), + }, + all: false, + deleteNewestVersion: false, + autoApprove: false, + } +} + +func newPurgeResourcesCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := newPurgeResourcesOption(f, streams) + cmd := &cobra.Command{ + Use: "purge", + Short: "Purge the sub-resources of specified addon and versions", + Example: addonPurgeResourcesExample, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, o.GVR), + Run: func(cmd *cobra.Command, args []string) { + util.CheckErr(o.baseOption.complete()) + util.CheckErr(o.Complete(args)) + util.CheckErr(o.Validate()) + util.CheckErr(o.Run()) + }, + } + cmd.Flags().StringSliceVar(&o.versions, "versions", nil, "Specify the versions of resources to purge.") + cmd.Flags().BoolVar(&o.all, "all", false, "If set to true, all resources will be purged, including those that are unused and not the newest version.") + cmd.Flags().BoolVar(&o.dryRun, "dry-run", false, "If set to true, only print the resources that would be purged, and no resource is actually purged.") + cmd.Flags().BoolVar(&o.autoApprove, "auto-approve", false, "Skip interactive approval before deleting") + + return cmd +} + +func (o *purgeResourcesOption) Complete(args []string) error { + if args == nil { + return fmt.Errorf("no addon provided; please specify the name of addon") + } + o.name = args[0] + versions, err := o.getExistedVersions(o.name) + if err != nil { + return fmt.Errorf(versionErrorTemplate, o.name, err) + } + newestVersion, err := o.getNewestVersion(o.name) + if err != nil { + return fmt.Errorf(versionErrorTemplate, o.name, err) + } + o.versionsInUse, err = o.getInUseVersions(o.name) + if err != nil { + return fmt.Errorf(versionErrorTemplate, o.name, err) + } + + // If --all flag is set, gather versions that should be purged + if o.all { + for k := range versions { + if !isVersionInUse(k, o.versionsInUse) && k != newestVersion { + o.versions = append(o.versions, k) + } + } + if o.deleteNewestVersion { + o.versions = append(o.versions, newestVersion) + } + } + return nil +} + +func (o *purgeResourcesOption) Validate() error { + if !o.all && o.versions == nil { + return fmt.Errorf("no versions specified and --all flag is not set; please specify versions or use --all") + } + versions, err := o.getExistedVersions(o.name) + if err != nil { + return fmt.Errorf(versionErrorTemplate, o.name, err) + } + newestVersion, err := o.getNewestVersion(o.name) + if err != nil { + return fmt.Errorf(versionErrorTemplate, o.name, err) + } + // Validate if versions are correct and not in use + for _, v := range o.versions { + if !versions[v] { + return fmt.Errorf("specified version %s does not exist for resource %s", v, o.name) + } + if !o.deleteNewestVersion && v == newestVersion { + return fmt.Errorf("specified version %s cannot be purged as it is the newest version", v) + } + if isVersionInUse(v, o.versionsInUse) { + return fmt.Errorf("specified version %s cannot be purged as it is currently in use", v) + } + } + + if newestVersion != "" { + fmt.Fprintf(o.Out, "The following version is the newest version:\n - %s\n", newestVersion) + } + if len(o.versionsInUse) > 0 { + fmt.Fprintf(o.Out, "The following versions are currently in use:\n") + var lastVersion string + for _, v := range o.versionsInUse { + if v.Version != lastVersion { + fmt.Fprintf(o.Out, " - Version: %s\n", v.Version) + lastVersion = v.Version + } + fmt.Fprintf(o.Out, " - Cluster: %s, Namespace: %s\n", v.ClusterName, v.ClusterNamespace) + } + } + if len(o.versions) > 0 { + fmt.Fprintf(o.Out, "The following versions will be purged:\n") + for _, version := range o.versions { + fmt.Fprintf(o.Out, " - %s\n", version) + } + } else { + fmt.Fprintf(o.Out, "No resources need to be purged:\n") + } + + return nil +} + +func (o *purgeResourcesOption) Run() error { + return o.cleanSubResources(o.name, o.versions) +} + +// isVersionInUse checks if a version is in use based on current clusters +func isVersionInUse(version string, inUseVersions []InUseVersionInfo) bool { + for _, inUse := range inUseVersions { + if inUse.Version == version { + return true + } + } + return false +} + +// extractVersion extracts the version from a resource name using the provided regex pattern +func extractVersion(name string) string { + versionRegex := regexp.MustCompile(versionPattern) + return versionRegex.FindString(name) +} + +// getExistedVersions gets all the existed versions of the specified addon by listing the component definitions +func (o *purgeResourcesOption) getExistedVersions(addonName string) (map[string]bool, error) { + resources, err := o.Dynamic.Resource(types.CompDefGVR()).Namespace(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to list resources for %s: %w", types.CompDefGVR(), err) + } + totalVersions := make(map[string]bool) + for _, item := range resources.Items { + annotations := item.GetAnnotations() + if annotations[helmReleaseNameKey] != helmReleaseNamePrefix+addonName { + continue + } + + version := extractVersion(item.GetName()) + if version != "" { + totalVersions[version] = true + } + } + + return totalVersions, nil +} + +// getNewestVersion retrieves the newest version of the addon +func (o *purgeResourcesOption) getNewestVersion(addonName string) (string, error) { + addon := &v1alpha1.Addon{} + err := util.GetK8SClientObject(o.Dynamic, addon, types.AddonGVR(), "", addonName) + if err != nil { + return "", fmt.Errorf("failed to get addon: %w", err) + } + return getAddonVersion(addon), nil +} + +// getInUseVersions retrieves the versions of resources that are currently in use, along with their associated cluster names +func (o *purgeResourcesOption) getInUseVersions(addonName string) ([]InUseVersionInfo, error) { + var inUseVersions []InUseVersionInfo + labelSelector := util.BuildClusterLabel("", []string{addonName}) + clusterList, err := o.Dynamic.Resource(types.ClusterGVR()).Namespace(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector}) + if err != nil { + return nil, fmt.Errorf("failed to list clusters: %w", err) + } + if clusterList != nil && len(clusterList.Items) > 0 { + for _, item := range clusterList.Items { + var cluster kbv1alpha1.Cluster + if err = runtime.DefaultUnstructuredConverter.FromUnstructured(item.Object, &cluster); err != nil { + return nil, fmt.Errorf("failed to convert cluster to structured object: %w", err) + } + for _, spec := range cluster.Spec.ComponentSpecs { + version := extractVersion(spec.ComponentDef) + if version != "" { + inUseVersions = append(inUseVersions, InUseVersionInfo{ + Version: version, + ClusterName: cluster.Name, + ClusterNamespace: cluster.Namespace, + }) + } + } + } + } + sort.SliceStable(inUseVersions, func(i, j int) bool { + return inUseVersions[i].Version < inUseVersions[j].Version + }) + return inUseVersions, nil +} + +// cleanSubResources Purges specified addon resources +func (o *purgeResourcesOption) cleanSubResources(addon string, versionsToPurge []string) error { + versions := make(map[string]bool) + for _, v := range versionsToPurge { + versions[v] = true + } + var resourcesToPurge []struct { + gvr schema.GroupVersionResource + name string + namespace string + version string + } + // Check if resources are available to purge + if len(versionsToPurge) == 0 { + return nil + } + fmt.Fprintf(o.Out, "The following resources will be purged:\n") + // Gather the resources to purge and print + for _, gvr := range GVRsToPurge { + resources, err := o.Dynamic.Resource(gvr).Namespace(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{}) + if err != nil { + return fmt.Errorf("failed to list resources for %s: %w", gvr.Resource, err) + } + + for _, item := range resources.Items { + // Skip resources not belong to specified addon + annotations := item.GetAnnotations() + if annotations[helmReleaseNameKey] != helmReleaseNamePrefix+addon { + continue + } + + // Skip resources of other versions. + name := item.GetName() + extractedVersion := extractVersion(name) + if extractedVersion == "" || !versions[extractedVersion] { + continue + } + + // Skip resources if the resource doesn't have the annotation helm.sh/resource-policy: keep + if annotations[helmResourcePolicyKey] != helmResourcePolicyKeep { + continue + } + + // Cache the resource to purge + resourcesToPurge = append(resourcesToPurge, struct { + gvr schema.GroupVersionResource + name string + namespace string + version string + }{ + gvr: gvr, + name: name, + namespace: item.GetNamespace(), + version: extractedVersion, + }) + + fmt.Fprintf(o.Out, " - %s/%s\n", gvr.Resource, name) + } + } + + if o.dryRun { + return nil + } + + // Confirm Purge + if !o.autoApprove { + if err := confirmPurge(o.In, o.Out); err != nil { + return err + } + } + + // Perform actual deletion + for _, resource := range resourcesToPurge { + err := o.Dynamic.Resource(resource.gvr).Namespace(resource.namespace).Delete(context.Background(), resource.name, metav1.DeleteOptions{}) + if err != nil { + return fmt.Errorf("failed to purge resource %s/%s: %w", resource.gvr.Resource, resource.name, err) + } + fmt.Fprintf(o.Out, "Purged resource: %s/%s\n", resource.gvr.Resource, resource.name) + } + + return nil +} + +func confirmPurge(in io.Reader, out io.Writer) error { + const confirmStr = "y" + _, err := prompt.NewPrompt(fmt.Sprintf("Do you want to proceed with purging the above resources? Please type \"%s\" to confirm:", confirmStr), + func(input string) error { + if input != confirmStr { + fmt.Fprintf(out, "Purge operation aborted.\n") + } + return nil + }, in).Run() + return err +} diff --git a/pkg/cmd/addon/purge_test.go b/pkg/cmd/addon/purge_test.go new file mode 100644 index 000000000..d5556be30 --- /dev/null +++ b/pkg/cmd/addon/purge_test.go @@ -0,0 +1,400 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package addon + +import ( + "bytes" + "fmt" + "io" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/genericiooptions" + clientfake "k8s.io/client-go/rest/fake" + clienttesting "k8s.io/client-go/testing" + cmdtesting "k8s.io/kubectl/pkg/cmd/testing" + + "github.com/apecloud/kbcli/pkg/testing" + "github.com/apecloud/kbcli/pkg/types" + + kbv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" +) + +var _ = Describe("purge_resources test", func() { + var ( + streams genericiooptions.IOStreams + tf *cmdtesting.TestFactory + in, out *bytes.Buffer + addonName = "redis" + newestVersion = "0.9.3" + inUseVersion = "0.9.2" + unusedVersion = "0.9.1" + testAddonGVR = types.AddonGVR() + testCompDefGVR = types.CompDefGVR() + testClusterGVR = types.ClusterGVR() + testUnusedConfigGVR = types.ConfigmapGVR() + testResourceAnnotKey = helmReleaseNameKey + ) + + // Helper functions to create fake resources + createAddon := func(name, version string) *unstructured.Unstructured { + addon := &v1alpha1.Addon{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + types.AddonVersionLabelKey: version, + }, + }, + Spec: v1alpha1.AddonSpec{ + Version: version, + }, + } + obj, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(addon) + u := &unstructured.Unstructured{Object: obj} + u.SetGroupVersionKind(testAddonGVR.GroupVersion().WithKind("Addon")) + return u + } + + createComponentDef := func(name, addon, version string) *unstructured.Unstructured { + u := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": testCompDefGVR.GroupVersion().String(), + "kind": "ComponentDefinition", + "metadata": map[string]interface{}{ + "name": name + "-" + version, + "annotations": map[string]interface{}{ + testResourceAnnotKey: helmReleaseNamePrefix + addon, + helmResourcePolicyKey: helmResourcePolicyKeep, + }, + }, + }, + } + return u + } + + createCluster := func(name, addon, version string) *unstructured.Unstructured { + cluster := &kbv1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + constant.ClusterDefLabelKey: addon, + }, + }, + Spec: kbv1alpha1.ClusterSpec{ + ComponentSpecs: []kbv1alpha1.ClusterComponentSpec{ + { + ComponentDef: fmt.Sprintf("redis-%s", version), + }, + }, + }, + } + obj, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(cluster) + u := &unstructured.Unstructured{Object: obj} + u.SetGroupVersionKind(testClusterGVR.GroupVersion().WithKind("Cluster")) + return u + } + + createUnusedConfig := func(addon, version string) *unstructured.Unstructured { + u := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": testUnusedConfigGVR.GroupVersion().String(), + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "config-" + unusedVersion, + "annotations": map[string]interface{}{ + testResourceAnnotKey: helmReleaseNamePrefix + addon, + helmResourcePolicyKey: helmResourcePolicyKeep, + }, + }, + }, + } + return u + } + + BeforeEach(func() { + streams, in, out, _ = genericiooptions.NewTestIOStreams() + tf = cmdtesting.NewTestFactory().WithNamespace(testNamespace) + tf.FakeDynamicClient = testing.FakeDynamicClient() + tf.Client = &clientfake.RESTClient{} + + // Populate dynamic client with test resources + // Addon + _, _ = tf.FakeDynamicClient.Invokes(clienttesting.NewCreateAction(testAddonGVR, "", createAddon(addonName, newestVersion)), nil) + + // ComponentDefs with different versions + _, _ = tf.FakeDynamicClient.Invokes(clienttesting.NewCreateAction(testCompDefGVR, types.DefaultNamespace, createComponentDef("redis", addonName, newestVersion)), nil) + _, _ = tf.FakeDynamicClient.Invokes(clienttesting.NewCreateAction(testCompDefGVR, types.DefaultNamespace, createComponentDef("redis", addonName, inUseVersion)), nil) + _, _ = tf.FakeDynamicClient.Invokes(clienttesting.NewCreateAction(testCompDefGVR, types.DefaultNamespace, createComponentDef("redis", addonName, unusedVersion)), nil) + + // Cluster using inUseVersion + _, _ = tf.FakeDynamicClient.Invokes(clienttesting.NewCreateAction(testClusterGVR, testNamespace, createCluster("test-cluster", addonName, inUseVersion)), nil) + + // Unused config resource for unusedVersion + _, _ = tf.FakeDynamicClient.Invokes(clienttesting.NewCreateAction(testUnusedConfigGVR, types.DefaultNamespace, createUnusedConfig(addonName, unusedVersion)), nil) + }) + + AfterEach(func() { + tf.Cleanup() + }) + + It("test purge_resources cmd creation", func() { + Expect(newPurgeResourcesCmd(tf, streams)).ShouldNot(BeNil()) + }) + + It("test baseOption complete", func() { + option := newPurgeResourcesOption(tf, streams) + option.autoApprove = true + Expect(option).ShouldNot(BeNil()) + Expect(option.baseOption.complete()).Should(Succeed()) + }) + + It("test no addon name provided", func() { + option := newPurgeResourcesOption(tf, streams) + option.autoApprove = true + err := option.Complete(nil) + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("no addon provided")) + }) + + It("test no versions and no --all", func() { + option := newPurgeResourcesOption(tf, streams) + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + // Validate should fail due to no versions and no all-unused-versions + err = option.Validate() + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("please specify versions or use --all")) + }) + + It("test specifying a non-existent version", func() { + option := newPurgeResourcesOption(tf, streams) + option.versions = []string{"1.0.0"} + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + err = option.Validate() + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("does not exist")) + }) + + It("test specifying newest version without deleteNewestVersion flag", func() { + option := newPurgeResourcesOption(tf, streams) + option.versions = []string{newestVersion} // newest version + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + err = option.Validate() + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("cannot be purged as it is the newest version")) + }) + + It("test specifying an in-use version", func() { + option := newPurgeResourcesOption(tf, streams) + option.versions = []string{inUseVersion} + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + err = option.Validate() + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("cannot be purged as it is currently in use")) + }) + + It("test specifying an unused old version directly", func() { + option := newPurgeResourcesOption(tf, streams) + option.versions = []string{unusedVersion} + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + // Validate should succeed + err = option.Validate() + Expect(err).ShouldNot(HaveOccurred()) + + // Run should1 purge resources associated with unusedVersion + err = option.Run() + Expect(err).ShouldNot(HaveOccurred()) + Expect(out.String()).To(ContainSubstring(unusedVersion)) + }) + + It("test no versions specified and --all flag is set", func() { + option := newPurgeResourcesOption(tf, streams) + option.all = true + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + // Validate should succeed now that we have automatically set unused versions + err = option.Validate() + Expect(err).ShouldNot(HaveOccurred()) + + // Run should purge all unused and non-newest versions. In this case, unusedVersion = "0.9.1" + err = option.Run() + Expect(err).ShouldNot(HaveOccurred()) + Expect(out.String()).To(ContainSubstring(unusedVersion)) + }) + + It("test dry-run flag", func() { + option := newPurgeResourcesOption(tf, streams) + option.versions = []string{unusedVersion} + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + option.dryRun = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + // Validate should succeed + err = option.Validate() + Expect(err).ShouldNot(HaveOccurred()) + + // Run should print the resources that would be purged without actually deleting them + err = option.Run() + Expect(err).ShouldNot(HaveOccurred()) + Expect(out.String()).To(ContainSubstring("The following versions will be purged")) + Expect(out.String()).To(ContainSubstring(unusedVersion)) + + // Check that no actual deletion occurred + Expect(out.String()).NotTo(ContainSubstring("Purged resource")) + }) + + It("test invalid version format", func() { + option := newPurgeResourcesOption(tf, streams) + option.versions = []string{"invalid-version"} + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + // Validate should fail due to invalid version format + err = option.Validate() + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("specified version invalid-version does not exist")) + }) + + It("test no versions specified and --all flag not set", func() { + option := newPurgeResourcesOption(tf, streams) + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + // Validate should fail because no versions specified and --all flag is not set + err = option.Validate() + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("please specify versions or use --all")) + }) + + It("test abort purge operation", func() { + option := newPurgeResourcesOption(tf, streams) + option.versions = []string{unusedVersion} + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + // Validate should succeed + err = option.Validate() + Expect(err).ShouldNot(HaveOccurred()) + + in.Write([]byte("n\n")) + option.In = io.NopCloser(in) + + // Run the operation + err = option.Run() + Expect(err).ShouldNot(HaveOccurred()) + + // Ensure the purge operation is aborted + Expect(out.String()).To(ContainSubstring("Purge operation aborted")) + }) + + It("test invalid addon name", func() { + option := newPurgeResourcesOption(tf, streams) + option.autoApprove = true + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + err := option.Complete([]string{"nonexistent-addon"}) + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to retrieve versions")) + }) + + It("test --all flag when no versions are in use", func() { + option := newPurgeResourcesOption(tf, streams) + option.all = true + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + // Validate should succeed as unused versions are included + err = option.Validate() + Expect(err).ShouldNot(HaveOccurred()) + + // Run should purge all unused versions. In this case, no versions are in use. + err = option.Run() + Expect(err).ShouldNot(HaveOccurred()) + Expect(out.String()).To(ContainSubstring(unusedVersion)) + }) + + It("test purging the latest version with --deleteNewestVersion flag", func() { + option := newPurgeResourcesOption(tf, streams) + option.versions = []string{newestVersion} + option.deleteNewestVersion = true + option.Dynamic = tf.FakeDynamicClient + option.Factory = tf + option.autoApprove = true + err := option.Complete([]string{addonName}) + Expect(err).ShouldNot(HaveOccurred()) + + // Validate should succeed + err = option.Validate() + Expect(err).ShouldNot(HaveOccurred()) + + // Run should purge the latest version if --deleteNewestVersion is set + err = option.Run() + Expect(err).ShouldNot(HaveOccurred()) + Expect(out.String()).To(ContainSubstring(newestVersion)) + }) + +}) diff --git a/pkg/cmd/addon/search.go b/pkg/cmd/addon/search.go index 9599aa8d8..bef916685 100644 --- a/pkg/cmd/addon/search.go +++ b/pkg/cmd/addon/search.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -70,79 +70,102 @@ type searchOpts struct { func newSearchCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { o := &searchOpts{} cmd := &cobra.Command{ - Use: "search", + Use: "search [ADDON_NAME]", Short: "Search the addon from index", Example: addonSearchExample, + Args: cobra.MaximumNArgs(1), PersistentPreRun: func(cmd *cobra.Command, _ []string) { util.CheckErr(util.EnableLogToFile(cmd.Flags())) util.CheckErr(addDefaultIndex()) }, + ValidArgsFunction: addonNameCompletionFunc, Run: func(_ *cobra.Command, args []string) { - if len(args) == 0 { - o.name = "" - } else { + if len(args) == 1 { o.name = args[0] + } else { + o.name = "" } - util.CheckErr(o.search(streams.Out, &addonListOpts{ - ListOptions: action.NewListOptions(f, streams, types.AddonGVR()), - })) + util.CheckErr(o.Run(streams.Out, f)) }, } cmd.Flags().StringVar(&o.path, "path", "", "the local directory contains addon CRs") return cmd } -func (o *searchOpts) search(out io.Writer, addonListOpts *addonListOpts) error { - var ( - err error - results []searchResult - ) - if o.path == "" { - dir, err := util.GetCliAddonDir() +func (o *searchOpts) Run(out io.Writer, f cmdutil.Factory) error { + listOpt := &addonListOpts{ + ListOptions: action.NewListOptions(f, genericiooptions.IOStreams{Out: out}, types.AddonGVR()), + } + + // Determine the directory to search in + dir := o.path + if dir == "" { + var err error + dir, err = util.GetCliAddonDir() if err != nil { return err } - if results, err = searchAddon(o.name, dir, ""); err != nil { - return err - } - } else { - if results, err = searchAddon(o.name, filepath.Dir(o.path), filepath.Base(o.path)); err != nil { - return err - } } - tbl := printer.NewTablePrinter(out) + // Search for addons based on the name or all addons if no name is specified + results, err := searchAddon(o.name, dir, "") + if err != nil { + return err + } + + // Display results based on whether a name is specified or not if o.name == "" { - tbl.AddRow("ADDON", "STATUS") - statusMap := map[bool]string{ - true: "installed", - false: "uninstalled", - } - results = uniqueByName(results) - err := checkAddonInstalled(&results, addonListOpts) - if err != nil { - return err - } - for _, res := range results { - tbl.AddRow(res.addon.Name, statusMap[res.isInstalled]) - } + return o.displayAllAddons(out, results, listOpt) } else { - tbl.AddRow("ADDON", "VERSION", "INDEX") - if len(results) == 0 { - fmt.Fprintf(out, "%s addon not found. Please update your index or check the addon name.\n"+ - "You can use the command 'kbcli addon index update --all=true' to update all indexes,\n"+ - "or specify a local path containing addons with the command 'kbcli addon search --path=/path/to/local/chart'", o.name) - return nil - } - for _, res := range results { - tbl.AddRow(res.addon.Name, getAddonVersion(res.addon), res.index.name) - } + return o.displaySingleAddon(out, results) + } +} + +// displayAllAddons lists all addons and indicates whether they are installed +func (o *searchOpts) displayAllAddons(out io.Writer, results []searchResult, listOpt *addonListOpts) error { + results = uniqueByName(results) + err := checkAddonInstalled(&results, listOpt) + if err != nil { + return err + } + + tbl := printer.NewTablePrinter(out) + tbl.AddRow("ADDON", "STATUS") + + statusMap := map[bool]string{ + true: "installed", + false: "uninstalled", + } + + for _, res := range results { + tbl.AddRow(res.addon.Name, statusMap[res.isInstalled]) } tbl.Print() + + return nil +} + +// displaySingleAddon shows detailed information for a specified addon +func (o *searchOpts) displaySingleAddon(out io.Writer, results []searchResult) error { + tbl := printer.NewTablePrinter(out) + tbl.AddRow("ADDON", "VERSION", "INDEX") + + if len(results) == 0 { + fmt.Fprintf(out, "%s addon not found. Please update your index or check the addon name.\n"+ + "You can use the command 'kbcli addon index update --all=true' to update all indexes,\n"+ + "or specify a local path containing addons with the command 'kbcli addon search --path=/path/to/local/addons'\n", o.name) + return nil + } + + for _, res := range results { + tbl.AddRow(res.addon.Name, getAddonVersion(res.addon), res.index.name) + } + tbl.Print() + return nil } -// searchAddon function will search for the addons with the specified name in the index of the specified directory and return them. +// searchAddon searches for addons that meet the specified criteria in a given directory func searchAddon(name string, indexDir string, theIndex string) ([]searchResult, error) { var res []searchResult searchInDir := func(i index) error { diff --git a/pkg/cmd/addon/search_test.go b/pkg/cmd/addon/search_test.go index d8f20260c..af910f35d 100644 --- a/pkg/cmd/addon/search_test.go +++ b/pkg/cmd/addon/search_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/addon/suite_test.go b/pkg/cmd/addon/suite_test.go index 1c0696693..91f3fae5c 100644 --- a/pkg/cmd/addon/suite_test.go +++ b/pkg/cmd/addon/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/addon/uninstall.go b/pkg/cmd/addon/uninstall.go index 3c8dd4747..ff90d7083 100644 --- a/pkg/cmd/addon/uninstall.go +++ b/pkg/cmd/addon/uninstall.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -79,7 +79,12 @@ func newUninstallCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cob func (o *uninstallOption) Run() error { for _, name := range o.names { - err := o.Dynamic.Resource(o.GVR).Delete(context.Background(), name, metav1.DeleteOptions{}) + // delete the resources, because some resources won't be deleted by helm. + err := o.deleteAllMultiVersionsResources(name) + if err != nil { + return err + } + err = o.Dynamic.Resource(o.GVR).Delete(context.Background(), name, metav1.DeleteOptions{}) if err != nil { return err } @@ -96,3 +101,26 @@ func (o *uninstallOption) checkBeforeUninstall() error { } return CheckAddonUsedByCluster(o.Dynamic, o.names, o.In) } + +func (o *uninstallOption) deleteAllMultiVersionsResources(name string) error { + dro := &purgeResourcesOption{ + baseOption: &baseOption{ + Factory: o.Factory, + IOStreams: o.IOStreams, + GVR: types.AddonGVR(), + }, + all: true, + deleteNewestVersion: true, + autoApprove: true, + } + if err := dro.baseOption.complete(); err != nil { + return err + } + if err := dro.Complete([]string{name}); err != nil { + return err + } + if err := dro.Run(); err != nil { + return err + } + return nil +} diff --git a/pkg/cmd/addon/uninstall_test.go b/pkg/cmd/addon/uninstall_test.go new file mode 100644 index 000000000..64e4d78aa --- /dev/null +++ b/pkg/cmd/addon/uninstall_test.go @@ -0,0 +1,58 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +# This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package addon + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/cli-runtime/pkg/genericiooptions" + clientfake "k8s.io/client-go/rest/fake" + cmdtesting "k8s.io/kubectl/pkg/cmd/testing" + + "github.com/apecloud/kbcli/pkg/testing" +) + +var _ = Describe("uninstall test", func() { + var ( + streams genericiooptions.IOStreams + tf *cmdtesting.TestFactory + ) + + BeforeEach(func() { + streams, _, _, _ = genericiooptions.NewTestIOStreams() + tf = cmdtesting.NewTestFactory().WithNamespace(testNamespace) + tf.FakeDynamicClient = testing.FakeDynamicClient() + tf.Client = &clientfake.RESTClient{} + }) + + AfterEach(func() { + tf.Cleanup() + }) + + It("text uninstall cmd", func() { + Expect(newUninstallCmd(tf, streams)).ShouldNot(BeNil()) + }) + + It("test baseOption complete", func() { + option := newUninstallOption(tf, streams) + Expect(option).ShouldNot(BeNil()) + Expect(option.baseOption.complete()).Should(Succeed()) + }) +}) diff --git a/pkg/cmd/addon/upgrade.go b/pkg/cmd/addon/upgrade.go index ed7d7dac9..6bdfe9f36 100644 --- a/pkg/cmd/addon/upgrade.go +++ b/pkg/cmd/addon/upgrade.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -23,8 +23,10 @@ import ( "context" "encoding/json" "fmt" + "strings" "github.com/Masterminds/semver/v3" + extensionsv1alpha1 "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ktypes "k8s.io/apimachinery/pkg/types" @@ -33,9 +35,6 @@ import ( "k8s.io/kubectl/pkg/util/templates" "github.com/apecloud/kbcli/pkg/printer" - - extensionsv1alpha1 "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1" - "github.com/apecloud/kbcli/pkg/types" "github.com/apecloud/kbcli/pkg/util" ) @@ -98,6 +97,9 @@ func newUpgradeCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra o.name = args[0] util.CheckErr(o.Complete()) util.CheckErr(o.Validate()) + if strings.HasPrefix(o.currentVersion, "0.9") { + util.CheckErr(o.process09ClusterDefAndComponentVersions()) + } util.CheckErr(o.Run(f, streams)) }, } diff --git a/pkg/cmd/addon/upgrade_test.go b/pkg/cmd/addon/upgrade_test.go index b2eaf135a..739642dda 100644 --- a/pkg/cmd/addon/upgrade_test.go +++ b/pkg/cmd/addon/upgrade_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd # This file is part of KubeBlocks project diff --git a/pkg/cmd/addon/util.go b/pkg/cmd/addon/util.go index df6ba2bf5..40923b0e0 100644 --- a/pkg/cmd/addon/util.go +++ b/pkg/cmd/addon/util.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -23,14 +23,20 @@ import ( "context" "fmt" "io" + "io/fs" + "os" + "path/filepath" "sort" "strings" + "github.com/spf13/cobra" "golang.org/x/exp/maps" metav1 "k8s.io/apimachinery/pkg/apis/meta/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/klog/v2" "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" extensionsv1alpha1 "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1" @@ -72,9 +78,9 @@ func uniqueByName(objects []searchResult) []searchResult { func checkAddonInstalled(objects *[]searchResult, o *addonListOpts) error { // list installed addons var installedAddons []string - o.Print = false + o.ListOptions.Print = false // get and output the result - o.Print = false + o.ListOptions.Print = false r, _ := o.Run() if r == nil { return nil @@ -130,7 +136,7 @@ func checkAddonInstalled(objects *[]searchResult, o *addonListOpts) error { func CheckAddonUsedByCluster(dynamic dynamic.Interface, addons []string, in io.Reader) error { labelSelecotor := util.BuildClusterLabel("", addons) - list, err := dynamic.Resource(types.ClusterGVR()).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelecotor}) + list, err := dynamic.Resource(types.ClusterGVR()).Namespace(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelecotor}) if err != nil { return err } @@ -151,3 +157,84 @@ func CheckAddonUsedByCluster(dynamic dynamic.Interface, addons []string, in io.R } return nil } + +// addonNameCompletionFunc provides name auto-completion for addons +func addonNameCompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + fmt.Printf("toComplete: %s\n", toComplete) + var addonDir string + var err error + addonDir, err = util.GetCliAddonDir() + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + // Retrieve all addon names + allAddons, err := getAllAddonNames(addonDir) + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + matches := fuzzyMatch(allAddons, toComplete) + return matches, cobra.ShellCompDirectiveNoFileComp +} + +// getAllAddonNames retrieves all addon names from the given directory +func getAllAddonNames(dir string) ([]string, error) { + var addonNames []string + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return nil + } + // Skip hidden directories + if strings.HasPrefix(d.Name(), ".") && d.IsDir() { + return filepath.SkipDir + } + if d.IsDir() { + return nil + } + if strings.HasSuffix(strings.ToLower(d.Name()), ".yaml") { + content, err := os.ReadFile(path) + if err != nil { + klog.V(2).Infof("read file %s error: %v", path, err) + return nil + } + addon := &extensionsv1alpha1.Addon{} + if yaml.Unmarshal(content, addon) != nil { + return nil + } + if addon.Kind == "Addon" && addon.Name != "" { + addonNames = append(addonNames, addon.Name) + } + } + return nil + }) + if err != nil { + return nil, err + } + + // Remove duplicates + nameSet := make(map[string]struct{}) + for _, n := range addonNames { + nameSet[n] = struct{}{} + } + var unique []string + for n := range nameSet { + unique = append(unique, n) + } + return unique, nil +} + +// fuzzyMatch performs simple substring fuzzy matching on names +func fuzzyMatch(names []string, prefix string) []string { + if prefix == "" { + return names + } + var matches []string + lp := strings.ToLower(prefix) + for _, n := range names { + if strings.Contains(strings.ToLower(n), lp) { + matches = append(matches, n) + } + } + return matches +} diff --git a/pkg/cmd/addon/util_test.go b/pkg/cmd/addon/util_test.go index 0ad53a0c2..4dcb910c0 100644 --- a/pkg/cmd/addon/util_test.go +++ b/pkg/cmd/addon/util_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd # This file is part of KubeBlocks project @@ -22,7 +22,6 @@ package addon import ( "net/http" - kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/runtime" @@ -33,6 +32,8 @@ import ( clientfake "k8s.io/client-go/rest/fake" cmdtesting "k8s.io/kubectl/pkg/cmd/testing" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + "github.com/apecloud/kbcli/pkg/testing" "github.com/apecloud/kbcli/pkg/types" ) @@ -70,5 +71,4 @@ var _ = Describe("addon util test", func() { It("text CheckAddonUsedByCluster", func() { Expect(CheckAddonUsedByCluster(tf.FakeDynamicClient, []string{fakeAddonName}, streams.In)).Should(HaveOccurred()) }) - }) diff --git a/pkg/cmd/backuprepo/backuprepo.go b/pkg/cmd/backuprepo/backuprepo.go index ce622ad4b..b5e80a958 100644 --- a/pkg/cmd/backuprepo/backuprepo.go +++ b/pkg/cmd/backuprepo/backuprepo.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd # This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/backuprepo_test.go b/pkg/cmd/backuprepo/backuprepo_test.go index ebf8b58ed..01649af31 100644 --- a/pkg/cmd/backuprepo/backuprepo_test.go +++ b/pkg/cmd/backuprepo/backuprepo_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/common.go b/pkg/cmd/backuprepo/common.go index 25a964073..ed8762eb4 100644 --- a/pkg/cmd/backuprepo/common.go +++ b/pkg/cmd/backuprepo/common.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/create.go b/pkg/cmd/backuprepo/create.go index 4ff4b51d4..7be78cca6 100644 --- a/pkg/cmd/backuprepo/create.go +++ b/pkg/cmd/backuprepo/create.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/create_test.go b/pkg/cmd/backuprepo/create_test.go index 8aec98296..74da38871 100644 --- a/pkg/cmd/backuprepo/create_test.go +++ b/pkg/cmd/backuprepo/create_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/delete.go b/pkg/cmd/backuprepo/delete.go index 910566a87..080b31b6c 100644 --- a/pkg/cmd/backuprepo/delete.go +++ b/pkg/cmd/backuprepo/delete.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/delete_test.go b/pkg/cmd/backuprepo/delete_test.go index c20efaa75..1695d6b37 100644 --- a/pkg/cmd/backuprepo/delete_test.go +++ b/pkg/cmd/backuprepo/delete_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/describe.go b/pkg/cmd/backuprepo/describe.go index 8ed913ff0..2d7027104 100644 --- a/pkg/cmd/backuprepo/describe.go +++ b/pkg/cmd/backuprepo/describe.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/describe_test.go b/pkg/cmd/backuprepo/describe_test.go index 2df9280f1..6b131e5e1 100644 --- a/pkg/cmd/backuprepo/describe_test.go +++ b/pkg/cmd/backuprepo/describe_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/list.go b/pkg/cmd/backuprepo/list.go index 40ec3747d..2b4e28226 100644 --- a/pkg/cmd/backuprepo/list.go +++ b/pkg/cmd/backuprepo/list.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -63,7 +63,7 @@ func newListCommand(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobr Example: listExample, ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.BackupRepoGVR()), Run: func(cmd *cobra.Command, args []string) { - o.Names = args + o.ListOptions.Names = args cmdutil.CheckErr(o.Complete()) cmdutil.CheckErr(printBackupRepoList(o)) }, @@ -74,7 +74,7 @@ func newListCommand(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobr func (o *listBackupRepoOptions) Complete() error { var err error - o.dynamic, err = o.Factory.DynamicClient() + o.dynamic, err = o.ListOptions.Factory.DynamicClient() if err != nil { return err } @@ -83,14 +83,14 @@ func (o *listBackupRepoOptions) Complete() error { func printBackupRepoList(o *listBackupRepoOptions) error { // if format is JSON or YAML, use default printer to output the result. - if o.Format == printer.JSON || o.Format == printer.YAML { + if o.ListOptions.Format == printer.JSON || o.ListOptions.Format == printer.YAML { _, err := o.Run() return err } backupRepoList, err := o.dynamic.Resource(types.BackupRepoGVR()).List(context.TODO(), metav1.ListOptions{ - LabelSelector: o.LabelSelector, - FieldSelector: o.FieldSelector, + LabelSelector: o.ListOptions.LabelSelector, + FieldSelector: o.ListOptions.FieldSelector, }) if err != nil { return err @@ -140,7 +140,7 @@ func printBackupRepoList(o *listBackupRepoOptions) error { return nil } - if err = printer.PrintTable(o.Out, nil, printRows, + if err = printer.PrintTable(o.ListOptions.Out, nil, printRows, "NAME", "STATUS", "STORAGE-PROVIDER", "ACCESS-METHOD", "DEFAULT", "BACKUPS", "TOTAL-SIZE"); err != nil { return err } diff --git a/pkg/cmd/backuprepo/list_test.go b/pkg/cmd/backuprepo/list_test.go index 4de328348..998f7c383 100644 --- a/pkg/cmd/backuprepo/list_test.go +++ b/pkg/cmd/backuprepo/list_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/storageprovider.go b/pkg/cmd/backuprepo/storageprovider.go index 50880a9f3..ca4d9493e 100644 --- a/pkg/cmd/backuprepo/storageprovider.go +++ b/pkg/cmd/backuprepo/storageprovider.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/suite_test.go b/pkg/cmd/backuprepo/suite_test.go index 1c909ff3b..4e68a4eba 100644 --- a/pkg/cmd/backuprepo/suite_test.go +++ b/pkg/cmd/backuprepo/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/update.go b/pkg/cmd/backuprepo/update.go index 25aafacc5..7316bd77b 100644 --- a/pkg/cmd/backuprepo/update.go +++ b/pkg/cmd/backuprepo/update.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/backuprepo/update_test.go b/pkg/cmd/backuprepo/update_test.go index 95147e2c6..87eeea640 100644 --- a/pkg/cmd/backuprepo/update_test.go +++ b/pkg/cmd/backuprepo/update_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cli.go b/pkg/cmd/cli.go index 6f26f84e1..02c538459 100644 --- a/pkg/cmd/cli.go +++ b/pkg/cmd/cli.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -44,7 +44,6 @@ import ( "github.com/apecloud/kbcli/pkg/cmd/clusterdefinition" "github.com/apecloud/kbcli/pkg/cmd/componentdefinition" "github.com/apecloud/kbcli/pkg/cmd/componentversion" - "github.com/apecloud/kbcli/pkg/cmd/dashboard" "github.com/apecloud/kbcli/pkg/cmd/dataprotection" "github.com/apecloud/kbcli/pkg/cmd/kubeblocks" "github.com/apecloud/kbcli/pkg/cmd/opsdefinition" @@ -52,6 +51,7 @@ import ( "github.com/apecloud/kbcli/pkg/cmd/playground" "github.com/apecloud/kbcli/pkg/cmd/plugin" "github.com/apecloud/kbcli/pkg/cmd/report" + "github.com/apecloud/kbcli/pkg/cmd/trace" "github.com/apecloud/kbcli/pkg/cmd/version" "github.com/apecloud/kbcli/pkg/types" "github.com/apecloud/kbcli/pkg/util" @@ -171,7 +171,6 @@ A Command Line Interface for KubeBlocks`, kubeblocks.NewKubeBlocksCmd(f, ioStreams), options.NewCmdOptions(ioStreams.Out), version.NewVersionCmd(f), - dashboard.NewDashboardCmd(f, ioStreams), clusterdefinition.NewClusterDefinitionCmd(f, ioStreams), componentdefinition.NewComponentDefinitionCmd(f, ioStreams), componentversion.NewComponentVersionCmd(f, ioStreams), @@ -181,6 +180,7 @@ A Command Line Interface for KubeBlocks`, report.NewReportCmd(f, ioStreams), backuprepo.NewBackupRepoCmd(f, ioStreams), dataprotection.NewDataProtectionCmd(f, ioStreams), + trace.NewTraceCmd(f, ioStreams), ) filters := []string{"options"} diff --git a/pkg/cmd/cli_test.go b/pkg/cmd/cli_test.go index 1f5427e8c..81b35a5e5 100644 --- a/pkg/cmd/cli_test.go +++ b/pkg/cmd/cli_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/cluster.go b/pkg/cmd/cluster/cluster.go index fd23585a4..26f48c107 100644 --- a/pkg/cmd/cluster/cluster.go +++ b/pkg/cmd/cluster/cluster.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -78,7 +78,6 @@ func NewClusterCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra NewEditConfigureCmd(f, streams), NewDescribeReconfigureCmd(f, streams), NewExplainReconfigureCmd(f, streams), - NewDiffConfigureCmd(f, streams), }, }, { @@ -103,6 +102,12 @@ func NewClusterCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra NewListLogsCmd(f, streams), }, }, + { + Message: "Convert API version Commands:", + Commands: []*cobra.Command{ + NewUpgradeToV1Cmd(f, streams), + }, + }, } // add subcommands diff --git a/pkg/cmd/cluster/cluster_test.go b/pkg/cmd/cluster/cluster_test.go index 5188d51d5..efa360ebc 100644 --- a/pkg/cmd/cluster/cluster_test.go +++ b/pkg/cmd/cluster/cluster_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -93,28 +93,8 @@ var _ = Describe("Cluster", func() { Expect(o.ChartInfo).ShouldNot(BeNil()) o.Format = printer.YAML - Expect(o.CreateOptions.Complete()).To(Succeed()) - o.Client = testing.FakeClientSet() - fakeDiscovery1, _ := o.Client.Discovery().(*fakediscovery.FakeDiscovery) - fakeDiscovery1.FakedServerVersion = &version.Info{Major: "1", Minor: "27", GitVersion: "v1.27.0"} - Expect(o.Complete(nil)).To(Succeed()) - Expect(o.Validate()).To(Succeed()) - Expect(o.Name).ShouldNot(BeEmpty()) - Expect(o.Run()).Should(Succeed()) - }) - It("should apply SharedNode tenancy and Preferred PodAntiAffinity", func() { - o, err := NewSubCmdsOptions(createOptions, clusterType) - Expect(err).Should(Succeed()) - o.Tenancy = "SharedNode" - o.TopologyKeys = []string{"test-topology1", "test-topology2"} - o.NodeLabels = map[string]string{"environment": "test-env", "region": "test-region"} - o.TolerationsRaw = []string{"testKey1=testValue1:NoSchedule", "testKey2=testValue2:NoExecute"} o.PodAntiAffinity = "Preferred" - - Expect(o).ShouldNot(BeNil()) - Expect(o.ChartInfo).ShouldNot(BeNil()) - o.Format = printer.YAML - + o.Tenancy = "SharedNode" Expect(o.CreateOptions.Complete()).To(Succeed()) o.Client = testing.FakeClientSet() fakeDiscovery1, _ := o.Client.Discovery().(*fakediscovery.FakeDiscovery) @@ -124,29 +104,6 @@ var _ = Describe("Cluster", func() { Expect(o.Name).ShouldNot(BeEmpty()) Expect(o.Run()).Should(Succeed()) }) - - It("should apply DedicatedNode tenancy and Required PodAntiAffinity", func() { - o, err := NewSubCmdsOptions(createOptions, clusterType) - Expect(err).Should(Succeed()) - o.Tenancy = "DedicatedNode" - o.TopologyKeys = []string{"test-region", "test-zone"} - o.NodeLabels = map[string]string{"cluster": "test-cluster", "env": "test-production"} - o.TolerationsRaw = []string{"testKey3=testValue3:NoSchedule", "testKey4=testValue4:NoExecute"} - o.PodAntiAffinity = "Required" - - Expect(o).ShouldNot(BeNil()) - Expect(o.ChartInfo).ShouldNot(BeNil()) - o.Format = printer.YAML - - Expect(o.CreateOptions.Complete()).To(Succeed()) - o.Client = testing.FakeClientSet() - fakeDiscovery2, _ := o.Client.Discovery().(*fakediscovery.FakeDiscovery) - fakeDiscovery2.FakedServerVersion = &version.Info{Major: "1", Minor: "27", GitVersion: "v1.27.0"} - Expect(o.Complete(nil)).To(Succeed()) - Expect(o.Validate()).To(Succeed()) - Expect(o.Name).ShouldNot(BeEmpty()) - Expect(o.Run()).Should(Succeed()) - }) }) Context("create validate", func() { @@ -162,6 +119,8 @@ var _ = Describe("Cluster", func() { } o.Name = "mycluster" o.ChartInfo, _ = cluster.BuildChartInfo(clusterType) + o.PodAntiAffinity = "Preferred" + o.Tenancy = "SharedNode" }) It("can validate the cluster name must begin with a letter and can only contain lowercase letters, numbers, and '-'.", func() { @@ -212,6 +171,98 @@ var _ = Describe("Cluster", func() { Expect(o.Validate()).Should(HaveOccurred()) }) + // Test case: invalid tenancy value + It("should fail when tenancy is invalid", func() { + o, err := NewSubCmdsOptions(createOptions, clusterType) + Expect(err).Should(Succeed()) + o.Tenancy = "InvalidTenancy" // Set invalid tenancy value + + Expect(o.Validate()).ShouldNot(Succeed()) // Validation should fail + }) + + // Test case: invalid podAntiAffinity value + It("should fail when podAntiAffinity is invalid", func() { + o, err := NewSubCmdsOptions(createOptions, clusterType) + Expect(err).Should(Succeed()) + o.PodAntiAffinity = "None" // Set invalid podAntiAffinity value + + Expect(o.Validate()).ShouldNot(Succeed()) // Validation should fail + }) + + // Test case: topologyKeys contains empty values + It("should fail when topologyKeys contains empty values", func() { + o, err := NewSubCmdsOptions(createOptions, clusterType) + Expect(err).Should(Succeed()) + o.TopologyKeys = []string{"", "valid-topology"} // One of the keys is empty + + Expect(o.Validate()).ShouldNot(Succeed()) // Validation should fail + }) + + // Test case: nodeLabels contains empty key or value + It("should fail when nodeLabels contains empty key or value", func() { + o, err := NewSubCmdsOptions(createOptions, clusterType) + Expect(err).Should(Succeed()) + o.NodeLabels = map[string]string{"environment": "", "": "test-region"} // Key or value is empty + + Expect(o.Validate()).ShouldNot(Succeed()) // Validation should fail + }) + + // Test case: tolerations contains empty key or operator + It("should fail when tolerations contains empty key or operator", func() { + o, err := NewSubCmdsOptions(createOptions, clusterType) + Expect(err).Should(Succeed()) + o.TolerationsRaw = []string{"testKey=:NoSchedule", "=testValue:NoExecute"} // Key or operator is empty + + Expect(o.Validate()).ShouldNot(Succeed()) // Validation should fail + }) + + // Test case: all inputs are valid + It("should apply SharedNode tenancy and Preferred PodAntiAffinity", func() { + o, err := NewSubCmdsOptions(createOptions, clusterType) + Expect(err).Should(Succeed()) + o.Tenancy = "SharedNode" + o.TopologyKeys = []string{"test-topology1", "test-topology2"} + o.NodeLabels = map[string]string{"environment": "test-env", "region": "test-region"} + o.TolerationsRaw = []string{"testKey1=testValue1:NoSchedule", "testKey2=testValue2:NoExecute"} + o.PodAntiAffinity = "Preferred" + + Expect(o).ShouldNot(BeNil()) + Expect(o.ChartInfo).ShouldNot(BeNil()) + o.Format = printer.YAML + + Expect(o.CreateOptions.Complete()).To(Succeed()) + o.Client = testing.FakeClientSet() + fakeDiscovery1, _ := o.Client.Discovery().(*fakediscovery.FakeDiscovery) + fakeDiscovery1.FakedServerVersion = &version.Info{Major: "1", Minor: "27", GitVersion: "v1.27.0"} + Expect(o.Complete(nil)).To(Succeed()) + Expect(o.Validate()).To(Succeed()) + Expect(o.Name).ShouldNot(BeEmpty()) + Expect(o.Run()).Should(Succeed()) + }) + + It("should apply DedicatedNode tenancy and Required PodAntiAffinity", func() { + o, err := NewSubCmdsOptions(createOptions, clusterType) + Expect(err).Should(Succeed()) + o.Tenancy = "DedicatedNode" + o.TopologyKeys = []string{"test-region", "test-zone"} + o.NodeLabels = map[string]string{"cluster": "test-cluster", "env": "test-production"} + o.TolerationsRaw = []string{"testKey3=testValue3:NoSchedule", "testKey4=testValue4:NoExecute"} + o.PodAntiAffinity = "Required" + + Expect(o).ShouldNot(BeNil()) + Expect(o.ChartInfo).ShouldNot(BeNil()) + o.Format = printer.YAML + + Expect(o.CreateOptions.Complete()).To(Succeed()) + o.Client = testing.FakeClientSet() + fakeDiscovery2, _ := o.Client.Discovery().(*fakediscovery.FakeDiscovery) + fakeDiscovery2.FakedServerVersion = &version.Info{Major: "1", Minor: "27", GitVersion: "v1.27.0"} + Expect(o.Complete(nil)).To(Succeed()) + Expect(o.Validate()).To(Succeed()) + Expect(o.Name).ShouldNot(BeEmpty()) + Expect(o.Run()).Should(Succeed()) + }) + }) Context("delete cluster", func() { diff --git a/pkg/cmd/cluster/config_diff.go b/pkg/cmd/cluster/config_diff.go deleted file mode 100644 index eb741ce3b..000000000 --- a/pkg/cmd/cluster/config_diff.go +++ /dev/null @@ -1,279 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package cluster - -import ( - "fmt" - "reflect" - - "github.com/spf13/cast" - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericiooptions" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/templates" - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/unstructured" - - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - - "github.com/apecloud/kbcli/pkg/printer" - "github.com/apecloud/kbcli/pkg/types" - "github.com/apecloud/kbcli/pkg/util" -) - -type configDiffOptions struct { - baseOptions *describeOpsOptions - - clusterName string - componentName string - templateNames []string - baseVersion *opsv1alpha1.OpsRequest - diffVersion *opsv1alpha1.OpsRequest -} - -var ( - diffConfigureExample = templates.Examples(` - # compare config files - kbcli cluster diff-config opsrequest1 opsrequest2`) -) - -func (o *configDiffOptions) complete(args []string) error { - isValidReconfigureOps := func(ops *opsv1alpha1.OpsRequest) bool { - return ops.Spec.Type == opsv1alpha1.ReconfiguringType && ops.Spec.Reconfigures != nil - } - - if len(args) != 2 { - return core.MakeError("missing opsrequest name") - } - - if err := o.baseOptions.complete(args); err != nil { - return err - } - - baseVersion := &opsv1alpha1.OpsRequest{} - diffVersion := &opsv1alpha1.OpsRequest{} - if err := util.GetResourceObjectFromGVR(types.OpsGVR(), client.ObjectKey{ - Namespace: o.baseOptions.namespace, - Name: args[0], - }, o.baseOptions.dynamic, baseVersion); err != nil { - return core.WrapError(err, "failed to get ops CR [%s]", args[0]) - } - if err := util.GetResourceObjectFromGVR(types.OpsGVR(), client.ObjectKey{ - Namespace: o.baseOptions.namespace, - Name: args[1], - }, o.baseOptions.dynamic, diffVersion); err != nil { - return core.WrapError(err, "failed to get ops CR [%s]", args[1]) - } - - if !isValidReconfigureOps(baseVersion) { - return core.MakeError("opsrequest is not valid reconfiguring operation [%s]", client.ObjectKeyFromObject(baseVersion)) - } - - if !isValidReconfigureOps(diffVersion) { - return core.MakeError("opsrequest is not valid reconfiguring operation [%s]", client.ObjectKeyFromObject(diffVersion)) - } - - if !o.maybeCompareOps(baseVersion, diffVersion) { - return core.MakeError("failed to diff, not same cluster, or same component, or template.") - } - - o.baseVersion = baseVersion - o.diffVersion = diffVersion - return nil -} - -func findTemplateStatusByName(status *opsv1alpha1.ReconfiguringStatus, tplName string) *opsv1alpha1.ConfigurationItemStatus { - if status == nil { - return nil - } - - for i := range status.ConfigurationStatus { - s := &status.ConfigurationStatus[i] - if s.Name == tplName { - return s - } - } - return nil -} - -func (o *configDiffOptions) validate() error { - var ( - baseStatus = o.baseVersion.Status - diffStatus = o.diffVersion.Status - ) - - if baseStatus.Phase != opsv1alpha1.OpsSucceedPhase { - return core.MakeError("require reconfiguring phase is success!, name: %s, phase: %s", o.baseVersion.Name, baseStatus.Phase) - } - if diffStatus.Phase != opsv1alpha1.OpsSucceedPhase { - return core.MakeError("require reconfiguring phase is success!, name: %s, phase: %s", o.diffVersion.Name, diffStatus.Phase) - } - - for _, tplName := range o.templateNames { - s1 := findTemplateStatusByName(baseStatus.ReconfiguringStatusAsComponent[o.componentName], tplName) - s2 := findTemplateStatusByName(diffStatus.ReconfiguringStatusAsComponent[o.componentName], tplName) - if s1 == nil || len(s1.LastAppliedConfiguration) == 0 { - return core.MakeError("invalid reconfiguring status. CR[%v]", client.ObjectKeyFromObject(o.baseVersion)) - } - if s2 == nil || len(s2.LastAppliedConfiguration) == 0 { - return core.MakeError("invalid reconfiguring status. CR[%v]", client.ObjectKeyFromObject(o.diffVersion)) - } - } - return nil -} - -func (o *configDiffOptions) run() error { - configDiffs := make(map[string][]core.VisualizedParam, len(o.templateNames)) - baseConfigs := make(map[string]map[string]unstructured.ConfigObject) - for _, tplName := range o.templateNames { - diff, baseObj, err := o.diffConfig(tplName) - if err != nil { - return err - } - configDiffs[tplName] = diff - baseConfigs[tplName] = baseObj - } - - printer.PrintTitle("DIFF-CONFIG RESULT") - for tplName, diff := range configDiffs { - configObjects := baseConfigs[tplName] - for _, params := range diff { - printer.PrintLineWithTabSeparator( - printer.NewPair(" ConfigFile", printer.BoldYellow(params.Key)), - printer.NewPair("TemplateName", tplName), - printer.NewPair("ComponentName", o.componentName), - printer.NewPair("ClusterName", o.clusterName), - printer.NewPair("UpdateType", string(params.UpdateType)), - ) - fmt.Fprintf(o.baseOptions.Out, "\n") - tbl := printer.NewTablePrinter(o.baseOptions.Out) - tbl.SetHeader("ParameterName", o.baseVersion.Name, o.diffVersion.Name) - configObj := configObjects[params.Key] - for _, v := range params.Parameters { - baseValue := "null" - if configObj != nil { - baseValue = cast.ToString(configObj.Get(v.Key)) - } - tbl.AddRow(v.Key, baseValue, v.Value) - } - tbl.Print() - fmt.Fprintf(o.baseOptions.Out, "\n\n") - } - } - return nil -} - -func (o *configDiffOptions) maybeCompareOps(base *opsv1alpha1.OpsRequest, diff *opsv1alpha1.OpsRequest) bool { - getClusterName := func(ops client.Object) string { - labels := ops.GetLabels() - if len(labels) == 0 { - return "" - } - return labels[constant.AppInstanceLabelKey] - } - // TODO: compare all reconfigures - getComponentName := func(ops opsv1alpha1.OpsRequestSpec) string { - return ops.Reconfigures[0].ComponentName - } - getTemplateName := func(ops opsv1alpha1.OpsRequestSpec) []string { - configs := ops.Reconfigures[0].Configurations - names := make([]string, len(configs)) - for i, config := range configs { - names[i] = config.Name - } - return names - } - - clusterName := getClusterName(base) - if len(clusterName) == 0 || clusterName != getClusterName(diff) { - return false - } - componentName := getComponentName(base.Spec) - if len(componentName) == 0 || componentName != getComponentName(diff.Spec) { - return false - } - templateNames := getTemplateName(base.Spec) - if len(templateNames) == 0 || !reflect.DeepEqual(templateNames, getTemplateName(diff.Spec)) { - return false - } - - o.clusterName = clusterName - o.componentName = componentName - o.templateNames = templateNames - return true -} - -func (o *configDiffOptions) diffConfig(tplName string) ([]core.VisualizedParam, map[string]unstructured.ConfigObject, error) { - var ( - tpl *appsv1alpha1.ComponentConfigSpec - configConstraint = &appsv1beta1.ConfigConstraint{} - ) - - tplList, err := util.GetConfigSpecsFromComponentName(o.baseOptions.dynamic, o.baseOptions.namespace, o.clusterName, o.componentName, true) - if err != nil { - return nil, nil, err - } - if tpl = findTplByName(tplList, tplName); tpl == nil { - return nil, nil, core.MakeError("not found template: %s", tplName) - } - if err := util.GetResourceObjectFromGVR(types.ConfigConstraintGVR(), client.ObjectKey{ - Namespace: "", - Name: tpl.ConfigConstraintRef, - }, o.baseOptions.dynamic, configConstraint); err != nil { - return nil, nil, err - } - - formatCfg := configConstraint.Spec.FileFormatConfig - base := findTemplateStatusByName(o.baseVersion.Status.ReconfiguringStatusAsComponent[o.componentName], tplName) - diff := findTemplateStatusByName(o.diffVersion.Status.ReconfiguringStatusAsComponent[o.componentName], tplName) - patch, _, err := core.CreateConfigPatch(base.LastAppliedConfiguration, diff.LastAppliedConfiguration, formatCfg.Format, tpl.Keys, false) - if err != nil { - return nil, nil, err - } - - baseConfigObj, err := core.LoadRawConfigObject(base.LastAppliedConfiguration, formatCfg, tpl.Keys) - if err != nil { - return nil, nil, err - } - return core.GenerateVisualizedParamsList(patch, formatCfg, nil), baseConfigObj, nil -} - -// NewDiffConfigureCmd shows the difference between two configuration version. -func NewDiffConfigureCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { - o := &configDiffOptions{baseOptions: newDescribeOpsOptions(f, streams)} - cmd := &cobra.Command{ - Use: "diff-config", - Short: "Show the difference in parameters between the two submitted OpsRequest.", - Aliases: []string{"diff"}, - Example: diffConfigureExample, - ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.OpsGVR()), - Run: func(cmd *cobra.Command, args []string) { - util.CheckErr(o.complete(args)) - util.CheckErr(o.validate()) - util.CheckErr(o.run()) - }, - } - return cmd -} diff --git a/pkg/cmd/cluster/config_edit.go b/pkg/cmd/cluster/config_edit.go index 89ffc1464..761c14e9d 100644 --- a/pkg/cmd/cluster/config_edit.go +++ b/pkg/cmd/cluster/config_edit.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -27,6 +27,11 @@ import ( "strings" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" + "github.com/apecloud/kubeblocks/pkg/configuration/core" + "github.com/apecloud/kubeblocks/pkg/configuration/validate" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/spf13/cobra" "golang.org/x/exp/slices" "k8s.io/apimachinery/pkg/util/sets" @@ -34,13 +39,6 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/cmd/util/editor" "k8s.io/kubectl/pkg/util/templates" - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/configuration/validate" "github.com/apecloud/kbcli/pkg/printer" "github.com/apecloud/kbcli/pkg/types" @@ -65,7 +63,7 @@ var ( func (o *editConfigOptions) Run(fn func() error) error { wrapper := o.wrapper - cfgEditContext := newConfigContext(o.CreateOptions, o.Name, wrapper.ComponentName(), wrapper.ConfigSpecName(), wrapper.ConfigFile()) + cfgEditContext := newConfigContext(o.CreateOptions, o.CreateOptions.Name, wrapper.ComponentName(), wrapper.ConfigSpecName(), wrapper.ConfigFile()) if err := cfgEditContext.prepare(); err != nil { return err } @@ -90,11 +88,10 @@ func (o *editConfigOptions) Run(fn func() error) error { fmt.Println("Edit cancelled, no changes made.") return nil } - util.DisplayDiffWithColor(o.IOStreams.Out, diff) + util.DisplayDiffWithColor(o.CreateOptions.IOStreams.Out, diff) - configSpec := wrapper.ConfigTemplateSpec() - if configSpec.ConfigConstraintRef != "" { - return o.runWithConfigConstraints(cfgEditContext, configSpec, fn) + if hasSchemaForFile(wrapper.rctx, wrapper.ConfigFile()) { + return o.runWithConfigConstraints(cfgEditContext, wrapper.rctx, fn) } yes, err := o.confirmReconfigure(fmt.Sprintf(fullRestartConfirmPrompt, printer.BoldRed(o.CfgFile))) @@ -110,7 +107,14 @@ func (o *editConfigOptions) Run(fn func() error) error { return fn() } -func (o *editConfigOptions) runWithConfigConstraints(cfgEditContext *configEditContext, configSpec *appsv1alpha1.ComponentConfigSpec, fn func() error) error { +func hasSchemaForFile(rctx *ReconfigureContext, configFile string) bool { + if rctx.ConfigRender == nil { + return false + } + return intctrlutil.GetComponentConfigDescription(&rctx.ConfigRender.Spec, configFile) != nil +} + +func (o *editConfigOptions) runWithConfigConstraints(cfgEditContext *configEditContext, rctx *ReconfigureContext, fn func() error) error { oldVersion := map[string]string{ o.CfgFile: cfgEditContext.getOriginal(), } @@ -118,19 +122,7 @@ func (o *editConfigOptions) runWithConfigConstraints(cfgEditContext *configEditC o.CfgFile: cfgEditContext.getEdited(), } - configConstraintKey := client.ObjectKey{ - Namespace: "", - Name: configSpec.ConfigConstraintRef, - } - configConstraint := appsv1beta1.ConfigConstraint{} - if err := util.GetResourceObjectFromGVR(types.ConfigConstraintGVR(), configConstraintKey, o.Dynamic, &configConstraint); err != nil { - return err - } - formatterConfig := configConstraint.Spec.FileFormatConfig - if formatterConfig == nil { - return core.MakeError("config spec[%s] not support reconfiguring!", configSpec.Name) - } - configPatch, fileUpdated, err := core.CreateConfigPatch(oldVersion, newVersion, formatterConfig.Format, configSpec.Keys, true) + configPatch, fileUpdated, err := core.CreateConfigPatch(oldVersion, newVersion, rctx.ConfigRender.Spec, true) if err != nil { return err } @@ -139,22 +131,33 @@ func (o *editConfigOptions) runWithConfigConstraints(cfgEditContext *configEditC return nil } - fmt.Fprintf(o.Out, "Config patch(updated parameters): \n%s\n\n", string(configPatch.UpdateConfig[o.CfgFile])) + fmt.Fprintf(o.CreateOptions.Out, "Config patch(updated parameters): \n%s\n\n", string(configPatch.UpdateConfig[o.CfgFile])) if !o.enableDelete { - if err := core.ValidateConfigPatch(configPatch, configConstraint.Spec.FileFormatConfig); err != nil { + if err := core.ValidateConfigPatch(configPatch, rctx.ConfigRender.Spec); err != nil { return err } } - params := core.GenerateVisualizedParamsList(configPatch, configConstraint.Spec.FileFormatConfig, nil) + params := core.GenerateVisualizedParamsList(configPatch, rctx.ConfigRender.Spec.Configs) + o.KeyValues = fromKeyValuesToMap(params, o.CfgFile) // check immutable parameters - if len(configConstraint.Spec.ImmutableParameters) > 0 { - if err = util.ValidateParametersModified2(sets.KeySet(fromKeyValuesToMap(params, o.CfgFile)), configConstraint.Spec); err != nil { - return err + if err = util.ValidateParametersModified2(sets.KeySet(fromKeyValuesToMap(params, o.CfgFile)), rctx.ParametersDefs, o.CfgFile); err != nil { + return err + } + + var config *parametersv1alpha1.ComponentConfigDescription + if config = intctrlutil.GetComponentConfigDescription(&rctx.ConfigRender.Spec, o.CfgFile); config == nil { + return fn() + } + var pd *parametersv1alpha1.ParametersDefinition + for _, paramsDef := range rctx.ParametersDefs { + if paramsDef.Spec.FileName == o.CfgFile { + pd = paramsDef + break } } - confirmPrompt, err := generateReconfiguringPrompt(fileUpdated, configPatch, &configConstraint.Spec, o.CfgFile) + confirmPrompt, err := generateReconfiguringPrompt(fileUpdated, configPatch, pd, o.CfgFile, config.FileFormatConfig) if err != nil { return err } @@ -166,29 +169,26 @@ func (o *editConfigOptions) runWithConfigConstraints(cfgEditContext *configEditC return nil } - validatedData := map[string]string{ - o.CfgFile: cfgEditContext.getEdited(), - } - options := validate.WithKeySelector(configSpec.Keys) - if err = validate.NewConfigValidator(&configConstraint.Spec, options).Validate(validatedData); err != nil { - return core.WrapError(err, "failed to validate edited config") + if pd != nil && pd.Spec.ParametersSchema != nil { + if err = validate.NewConfigValidator(pd.Spec.ParametersSchema, config.FileFormatConfig).Validate(cfgEditContext.getEdited()); err != nil { + return core.WrapError(err, "failed to validate edited config") + } } - o.KeyValues = fromKeyValuesToMap(params, o.CfgFile) return fn() } -func generateReconfiguringPrompt(fileUpdated bool, configPatch *core.ConfigPatchInfo, cc *appsv1beta1.ConfigConstraintSpec, fileName string) (string, error) { - if fileUpdated { +func generateReconfiguringPrompt(fileUpdated bool, configPatch *core.ConfigPatchInfo, pd *parametersv1alpha1.ParametersDefinition, fileName string, config *parametersv1alpha1.FileFormatConfig) (string, error) { + if fileUpdated || pd == nil { return restartConfirmPrompt, nil } - dynamicUpdated, err := core.IsUpdateDynamicParameters(cc, configPatch) + dynamicUpdated, err := core.IsUpdateDynamicParameters(config, &pd.Spec, configPatch) if err != nil { return "", nil } confirmPrompt := confirmApplyReconfigurePrompt - if !dynamicUpdated || !cfgcm.IsSupportReload(cc.ReloadAction) { + if !dynamicUpdated || !cfgcm.IsSupportReload(pd.Spec.ReloadAction) { confirmPrompt = restartConfirmPrompt } return confirmPrompt, nil @@ -199,14 +199,14 @@ func (o *editConfigOptions) confirmReconfigure(promptStr string) (bool, error) { const noStr = "no" confirmStr := []string{yesStr, noStr} - printer.Warning(o.Out, promptStr) + printer.Warning(o.CreateOptions.Out, promptStr) input, err := prompt.NewPrompt("Please type [Yes/No] to confirm:", func(input string) error { if !slices.Contains(confirmStr, strings.ToLower(input)) { return fmt.Errorf("typed \"%s\" does not match \"%s\"", input, confirmStr) } return nil - }, o.In).Run() + }, o.CreateOptions.In).Run() if err != nil { return false, err } @@ -239,7 +239,7 @@ func NewEditConfigureCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) Example: editConfigExample, ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.ClusterGVR()), Run: func(cmd *cobra.Command, args []string) { - o.Args = args + o.CreateOptions.Args = args cmdutil.CheckErr(o.CreateOptions.Complete()) util.CheckErr(o.Complete()) util.CheckErr(o.Validate()) diff --git a/pkg/cmd/cluster/config_observer.go b/pkg/cmd/cluster/config_observer.go index 4a3c71502..b33fb37af 100644 --- a/pkg/cmd/cluster/config_observer.go +++ b/pkg/cmd/cluster/config_observer.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -20,28 +20,20 @@ along with this program. If not, see . package cluster import ( - "context" - "encoding/json" "fmt" - "sort" - "strings" - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/spf13/cobra" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/genericiooptions" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/configuration/openapi" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" - "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kbcli/pkg/action" "github.com/apecloud/kbcli/pkg/printer" "github.com/apecloud/kbcli/pkg/types" "github.com/apecloud/kbcli/pkg/util" @@ -60,8 +52,7 @@ type configObserverOptions struct { truncDocument bool paramName string - keys []string - showDetail bool + keys []string } var ( @@ -104,80 +95,79 @@ func (r *configObserverOptions) complete2(args []string) error { return r.complete(args) } -func (r *configObserverOptions) run(printFn func(objects *ConfigRelatedObjects, component string) error) error { - objects, err := New(r.clusterName, r.namespace, r.dynamic, r.componentNames...).GetObjects() +func (r *configObserverOptions) run(printFn func(*ReconfigureContext) error) error { + wrapper, err := New(r.clusterName, r.namespace, r.describeOpsOptions, r.componentNames...) if err != nil { return err } - - components := r.componentNames - if len(components) == 0 { - components = getComponentNames(objects.Cluster) - } - - for _, component := range components { - fmt.Fprintf(r.Out, "component: %s\n", component) - if _, ok := objects.ConfigSpecs[component]; !ok { - fmt.Fprintf(r.Out, "not found component: %s and pass\n\n", component) - } - if err := printFn(objects, component); err != nil { + for _, rctx := range wrapper.rctxMap { + fmt.Fprintf(r.Out, "component: %s\n", rctx.CompName) + if err := printFn(rctx); err != nil { return err } } return nil } -func (r *configObserverOptions) printComponentConfigSpecsDescribe(objects *ConfigRelatedObjects, component string) error { - configSpecs, ok := objects.ConfigSpecs[component] - if !ok { - return cfgcore.MakeError("not found component: %s", component) +func (r *configObserverOptions) printComponentConfigSpecsDescribe(rctx *ReconfigureContext) error { + resolveParameterTemplate := func(tpl string) string { + for _, config := range rctx.Cmpd.Spec.Configs { + if config.Name == tpl { + return config.Template + } + } + return "" } - configs, err := r.getReconfigureMeta(configSpecs) - if err != nil { - return err + + if rctx.ConfigRender == nil || len(rctx.ConfigRender.Spec.Configs) == 0 { + return nil } - if r.showDetail { - r.printConfigureContext(configs, component) + tbl := printer.NewTablePrinter(r.Out) + printer.PrintTitle("ConfigSpecs Meta") + tbl.SetHeader("CONFIG-SPEC-NAME", "FILE", "TEMPLATE-NAME", "COMPONENT", "CLUSTER") + for _, info := range rctx.ConfigRender.Spec.Configs { + tbl.AddRow( + printer.BoldYellow(info.TemplateName), + printer.BoldYellow(info.Name), + printer.BoldYellow(resolveParameterTemplate(info.TemplateName)), + rctx.CompName, + rctx.Cluster.Name) } - printer.PrintComponentConfigMeta(configs, r.clusterName, component, r.Out) - return r.printConfigureHistory(component) + tbl.Print() + return nil } -func (r *configObserverOptions) printComponentExplainConfigure(objects *ConfigRelatedObjects, component string) error { - configSpecs := r.configSpecs - if len(configSpecs) == 0 { - configSpecs = objects.ConfigSpecs[component].listConfigSpecs(true) - } - for _, templateName := range configSpecs { - fmt.Println("template meta:") - printer.PrintLineWithTabSeparator( - printer.NewPair(" ConfigSpec", templateName), - printer.NewPair("ComponentName", component), - printer.NewPair("ClusterName", r.clusterName), - ) - if err := r.printExplainConfigure(objects.ConfigSpecs[component], templateName); err != nil { +func (r *configObserverOptions) printComponentExplainConfigure(rctx *ReconfigureContext) error { + for _, pd := range rctx.ParametersDefs { + if rctx.ConfigRender != nil { + config := intctrlutil.GetComponentConfigDescription(&rctx.ConfigRender.Spec, pd.Spec.FileName) + if config != nil { + fmt.Println("template meta:") + printer.PrintLineWithTabSeparator( + printer.NewPair(" FileName", pd.Spec.FileName), + printer.NewPair(" ConfigSpec", config.TemplateName), + printer.NewPair("ComponentName", rctx.CompName), + printer.NewPair("ClusterName", r.clusterName), + ) + } + } + if err := r.printExplainConfigure(&pd.Spec); err != nil { return err } } return nil } -func (r *configObserverOptions) printExplainConfigure(configSpecs configSpecsType, tplName string) error { - tpl := configSpecs.findByName(tplName) - if tpl == nil { - return nil - } - - confSpec := tpl.ConfigConstraint.Spec - if confSpec.ParametersSchema == nil { - fmt.Printf("\n%s\n", fmt.Sprintf(notConfigSchemaPrompt, printer.BoldYellow(tplName))) +func (r *configObserverOptions) printExplainConfigure(pdSpec *parametersv1alpha1.ParametersDefinitionSpec) error { + if pdSpec.ParametersSchema == nil { + fmt.Printf("\n%s\n", fmt.Sprintf(notConfigSchemaPrompt, printer.BoldYellow(pdSpec.FileName))) return nil } - schema := confSpec.ParametersSchema.DeepCopy() + schema := pdSpec.ParametersSchema.DeepCopy() if schema.SchemaInJSON == nil { if schema.CUE == "" { - fmt.Printf("\n%s\n", fmt.Sprintf(notConfigSchemaPrompt, printer.BoldYellow(tplName))) + fmt.Printf("\n%s\n", fmt.Sprintf(notConfigSchemaPrompt, printer.BoldYellow(pdSpec.FileName))) return nil } apiSchema, err := openapi.GenerateOpenAPISchema(schema.CUE, schema.TopLevelKey) @@ -191,96 +181,9 @@ func (r *configObserverOptions) printExplainConfigure(configSpecs configSpecsTyp schema.SchemaInJSON = apiSchema } return r.printConfigConstraint(schema.SchemaInJSON, - cfgutil.NewSet(confSpec.StaticParameters...), - cfgutil.NewSet(confSpec.DynamicParameters...), - cfgutil.NewSet(confSpec.ImmutableParameters...)) -} - -func (r *configObserverOptions) getReconfigureMeta(configSpecs configSpecsType) ([]types.ConfigTemplateInfo, error) { - configs := make([]types.ConfigTemplateInfo, 0) - configList := r.configSpecs - if len(configList) == 0 { - configList = configSpecs.listConfigSpecs(false) - } - for _, tplName := range configList { - tpl := configSpecs.findByName(tplName) - if tpl == nil || tpl.ConfigSpec == nil { - fmt.Fprintf(r.Out, "not found config spec: %s, and pass\n", tplName) - continue - } - if tpl.ConfigSpec == nil { - fmt.Fprintf(r.Out, "current configSpec[%s] not support reconfiguring and pass\n", tplName) - continue - } - configs = append(configs, types.ConfigTemplateInfo{ - Name: tplName, - TPL: *tpl.ConfigSpec, - CMObj: tpl.ConfigMap, - }) - } - return configs, nil -} - -func (r *configObserverOptions) printConfigureContext(configs []types.ConfigTemplateInfo, component string) { - printer.PrintTitle("Configures Context[${component-name}/${config-spec}/${file-name}]") - - keys := cfgutil.NewSet(r.keys...) - for _, info := range configs { - for key, context := range info.CMObj.Data { - if keys.Length() != 0 && !keys.InArray(key) { - continue - } - fmt.Fprintf(r.Out, "%s%s\n", - printer.BoldYellow(fmt.Sprintf("%s/%s/%s:\n", component, info.Name, key)), context) - } - } -} - -func (r *configObserverOptions) printConfigureHistory(component string) error { - printer.PrintTitle("History modifications") - - // filter reconfigure - // kubernetes not support fieldSelector with CRD: https://github.com/kubernetes/kubernetes/issues/51046 - listOptions := metav1.ListOptions{ - LabelSelector: strings.Join([]string{constant.AppInstanceLabelKey, r.clusterName}, "="), - } - - opsList, err := r.dynamic.Resource(types.OpsGVR()).Namespace(r.namespace).List(context.TODO(), listOptions) - if err != nil { - return err - } - // sort the unstructured objects with the creationTimestamp in positive order - sort.Sort(action.UnstructuredList(opsList.Items)) - tbl := printer.NewTablePrinter(r.Out) - tbl.SetHeader("OPS-NAME", "CLUSTER", "COMPONENT", "CONFIG-SPEC-NAME", "FILE", "STATUS", "POLICY", "PROGRESS", "CREATED-TIME", "VALID-UPDATED") - for _, obj := range opsList.Items { - ops := &opsv1alpha1.OpsRequest{} - if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ops); err != nil { - return err - } - if ops.Spec.Type != opsv1alpha1.ReconfiguringType { - continue - } - components := getComponentNameFromOps(ops) - if !strings.Contains(components, component) { - continue - } - phase := string(ops.Status.Phase) - tplNames := getTemplateNameFromOps(ops.Spec) - keyNames := getKeyNameFromOps(ops.Spec) - tbl.AddRow(ops.Name, - ops.Spec.GetClusterName(), - components, - tplNames, - keyNames, - phase, - getReconfigurePolicy(ops.Status, component), - ops.Status.Progress, - util.TimeFormat(&ops.CreationTimestamp), - getValidUpdatedParams(ops.Status, component)) - } - tbl.Print() - return nil + cfgutil.NewSet(pdSpec.StaticParameters...), + cfgutil.NewSet(pdSpec.DynamicParameters...), + cfgutil.NewSet(pdSpec.ImmutableParameters...)) } func (r *configObserverOptions) hasSpecificParam() bool { @@ -331,55 +234,6 @@ func (r *configObserverOptions) printConfigConstraint(schema *apiext.JSONSchemaP return nil } -func getReconfigurePolicy(status opsv1alpha1.OpsRequestStatus, component string) string { - reconfigureStatus := getReconfigureStatus(status, component) - if reconfigureStatus == nil || len(reconfigureStatus.ConfigurationStatus) == 0 { - return "" - } - - var policy string - reStatus := reconfigureStatus.ConfigurationStatus[0] - switch reStatus.UpdatePolicy { - case appsv1alpha1.SyncDynamicReloadPolicy: - policy = "syncDynamicReload" - case appsv1alpha1.AsyncDynamicReloadPolicy: - policy = "asyncDynamicReload" - case appsv1alpha1.DynamicReloadAndRestartPolicy: - policy = "dynamicReloadBeginRestart" - case appsv1alpha1.NormalPolicy, appsv1alpha1.RestartPolicy, appsv1alpha1.RollingPolicy: - policy = "restart" - default: - return "" - } - return printer.BoldYellow(policy) -} - -func getReconfigureStatus(status opsv1alpha1.OpsRequestStatus, component string) *opsv1alpha1.ReconfiguringStatus { - rStatus := status.ReconfiguringStatusAsComponent - var compRSStatus *opsv1alpha1.ReconfiguringStatus - if rStatus == nil && len(status.ReconfiguringStatusAsComponent) != 0 { - compRSStatus = status.ReconfiguringStatusAsComponent[component] - } - return compRSStatus -} - -func getValidUpdatedParams(status opsv1alpha1.OpsRequestStatus, component string) string { - reconfigureStatus := getReconfigureStatus(status, component) - if reconfigureStatus == nil || len(reconfigureStatus.ConfigurationStatus) == 0 { - return "" - } - - reStatus := reconfigureStatus.ConfigurationStatus[0] - if len(reStatus.UpdatedParameters.UpdatedKeys) == 0 { - return "" - } - b, err := json.Marshal(reStatus.UpdatedParameters.UpdatedKeys) - if err != nil { - return err.Error() - } - return string(b) -} - func isDynamicType(pt *parameterSchema, staticParameters, dynamicParameters, immutableParameters *cfgutil.Sets) bool { switch { case immutableParameters.InArray(pt.name): @@ -401,7 +255,6 @@ func isDynamicType(pt *parameterSchema, staticParameters, dynamicParameters, imm func NewDescribeReconfigureCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { o := &configObserverOptions{ isExplain: false, - showDetail: false, describeOpsOptions: newDescribeOpsOptions(f, streams), } cmd := &cobra.Command{ @@ -416,7 +269,6 @@ func NewDescribeReconfigureCmd(f cmdutil.Factory, streams genericiooptions.IOStr }, } o.addCommonFlags(cmd, f) - cmd.Flags().BoolVar(&o.showDetail, "show-detail", o.showDetail, "If true, the content of the files specified by config-file will be printed.") cmd.Flags().StringSliceVar(&o.keys, "config-file", nil, "Specify the name of the configuration file to be describe (e.g. for mysql: --config-file=my.cnf). If unset, all files.") return cmd } diff --git a/pkg/cmd/cluster/config_ops.go b/pkg/cmd/cluster/config_ops.go index 97bdb82d3..a0cb768c4 100644 --- a/pkg/cmd/cluster/config_ops.go +++ b/pkg/cmd/cluster/config_ops.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -25,18 +25,17 @@ import ( "strings" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/client/clientset/versioned" + cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" + "github.com/apecloud/kubeblocks/pkg/configuration/core" + configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration" + "github.com/apecloud/kubeblocks/pkg/controllerutil" + "github.com/apecloud/kubeblocks/pkg/generics" "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/cli-runtime/pkg/genericiooptions" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kbcli/pkg/printer" "github.com/apecloud/kbcli/pkg/types" @@ -48,7 +47,7 @@ type configOpsOptions struct { *OperationsOptions editMode bool - wrapper *configWrapper + wrapper *ReconfigureWrapper // config file replace replaceFile bool @@ -57,6 +56,8 @@ type configOpsOptions struct { ComponentName string LocalFilePath string `json:"localFilePath"` Parameters []string `json:"parameters"` + + clientSet versioned.Interface } var ( @@ -85,13 +86,18 @@ func (o *configOpsOptions) Complete() error { } } - wrapper, err := newConfigWrapper(o.CreateOptions, o.ComponentName, o.CfgTemplateName, o.CfgFile, o.KeyValues) + client := o.clientSet + if client == nil { + client = GetClientFromOptionsOrDie(o.Factory) + } + wrapper, err := newConfigWrapper(client, o.Namespace, o.Name, o.ComponentName, o.CfgTemplateName, o.CfgFile, o.KeyValues) if err != nil { return err } o.wrapper = wrapper - return wrapper.AutoFillRequiredParam() + // return wrapper.AutoFillRequiredParam() + return nil } func (o *configOpsOptions) validateReconfigureOptions() error { @@ -116,10 +122,6 @@ func (o *configOpsOptions) validateReconfigureOptions() error { // Validate command flags or args is legal func (o *configOpsOptions) Validate() error { - if err := o.wrapper.ValidateRequiredParam(o.replaceFile); err != nil { - return err - } - o.CfgFile = o.wrapper.ConfigFile() o.CfgTemplateName = o.wrapper.ConfigSpecName() if len(o.ComponentNames) == 0 { @@ -129,71 +131,80 @@ func (o *configOpsOptions) Validate() error { if o.editMode { return nil } - if err := o.validateConfigParams(o.wrapper.ConfigTemplateSpec()); err != nil { - return err - } - if err := util.ValidateParametersModified(o.wrapper.ConfigTemplateSpec(), sets.KeySet(o.KeyValues), o.Dynamic); err != nil { + + rctx := o.wrapper.rctx + tplObjs, err := resolveConfigTemplate(rctx, o.Dynamic) + if err != nil { return err } - o.printConfigureTips() - return nil -} -func (o *configOpsOptions) validateConfigParams(tpl *appsv1alpha1.ComponentConfigSpec) error { - configConstraintKey := client.ObjectKey{ - Namespace: "", - Name: tpl.ConfigConstraintRef, + classifyParams, err := configctrl.ClassifyComponentParameters(o.KeyValues, rctx.ParametersDefs, rctx.Cmpd.Spec.Configs, tplObjs, rctx.ConfigRender) + if err != nil { + return err } - configConstraint := appsv1beta1.ConfigConstraint{} - if err := util.GetResourceObjectFromGVR(types.ConfigConstraintGVR(), configConstraintKey, o.Dynamic, &configConstraint); err != nil { + if err := util.ValidateParametersModified(classifyParams, rctx.ParametersDefs); err != nil { return err } - var err error - var newConfigData map[string]string - if o.FileContent != "" { - newConfigData = map[string]string{o.CfgFile: o.FileContent} - } else { - newConfigData, err = controllerutil.MergeAndValidateConfigs(configConstraint.Spec, map[string]string{o.CfgFile: ""}, tpl.Keys, []core.ParamPairs{{ - Key: o.CfgFile, - UpdatedParams: core.FromStringMap(o.KeyValues), - }}) - } - if err != nil { + if err := o.validateConfigParams(o.wrapper.rctx, classifyParams); err != nil { return err } - return o.checkChangedParamsAndDoubleConfirm(&configConstraint.Spec, newConfigData, tpl) + + o.printConfigureTips(classifyParams) + return nil } -func (o *configOpsOptions) checkChangedParamsAndDoubleConfirm(cc *appsv1beta1.ConfigConstraintSpec, data map[string]string, tpl *appsv1alpha1.ComponentConfigSpec) error { - mockEmptyData := func(m map[string]string) map[string]string { - r := make(map[string]string, len(data)) - for key := range m { - r[key] = "" +func (o *configOpsOptions) validateConfigParams(rctx *ReconfigureContext, classifyParameters map[string]map[string]*parametersv1alpha1.ParametersInFile) error { + if o.FileContent != "" { + return o.confirmReconfigureWithRestart() + } + + checkRestart := func(params map[string]*parametersv1alpha1.ParametersInFile) bool { + for file := range params { + match := func(pd *parametersv1alpha1.ParametersDefinition) bool { + return pd.Spec.FileName == file && cfgcm.IsSupportReload(pd.Spec.ReloadAction) + } + if generics.FindFirstFunc(rctx.ParametersDefs, match) < 0 { + return true + } } - return r + return false } - if !cfgcm.IsSupportReload(cc.ReloadAction) { - return o.confirmReconfigureWithRestart() + transform := func(params map[string]*parametersv1alpha1.ParametersInFile) []core.ParamPairs { + var result []core.ParamPairs + for file, ps := range params { + result = append(result, core.ParamPairs{ + Key: file, + UpdatedParams: core.FromStringMap(ps.Parameters), + }) + } + return result } - configPatch, restart, err := core.CreateConfigPatch(mockEmptyData(data), data, cc.FileFormatConfig.Format, tpl.Keys, o.FileContent != "") - if err != nil { - return err + restart := false + for _, parameters := range classifyParameters { + _, err := controllerutil.MergeAndValidateConfigs(mockEmptyData(parameters), transform(parameters), rctx.ParametersDefs, rctx.ConfigRender.Spec.Configs) + if err != nil { + return err + } + if !restart { + restart = checkRestart(parameters) + } } + if restart { return o.confirmReconfigureWithRestart() } + return nil +} - dynamicUpdated, err := core.IsUpdateDynamicParameters(cc, configPatch) - if err != nil { - return nil - } - if dynamicUpdated { - return nil +func mockEmptyData(m map[string]*parametersv1alpha1.ParametersInFile) map[string]string { + r := make(map[string]string, len(m)) + for key := range m { + r[key] = "" } - return o.confirmReconfigureWithRestart() + return r } func (o *configOpsOptions) confirmReconfigureWithRestart() error { @@ -231,13 +242,17 @@ func (o *configOpsOptions) parseUpdatedParams() (map[string]string, error) { return keyValues, nil } -func (o *configOpsOptions) printConfigureTips() { +func (o *configOpsOptions) printConfigureTips(classifyParameters map[string]map[string]*parametersv1alpha1.ParametersInFile) { fmt.Println("Will updated configure file meta:") - printer.PrintLineWithTabSeparator( - printer.NewPair(" ConfigSpec", printer.BoldYellow(o.CfgTemplateName)), - printer.NewPair(" ConfigFile", printer.BoldYellow(o.CfgFile)), - printer.NewPair("ComponentName", o.ComponentName), - printer.NewPair("ClusterName", o.Name)) + for tpl, tplParams := range classifyParameters { + for file := range tplParams { + printer.PrintLineWithTabSeparator( + printer.NewPair(" ConfigSpec", printer.BoldYellow(tpl)), + printer.NewPair(" ConfigFile", printer.BoldYellow(file)), + printer.NewPair("ComponentName", o.ComponentName), + printer.NewPair("ClusterName", o.Name)) + } + } } // buildReconfigureCommonFlags build common flags for reconfigure command diff --git a/pkg/cmd/cluster/config_ops_test.go b/pkg/cmd/cluster/config_ops_test.go index 55dc3030e..9e18ab35d 100644 --- a/pkg/cmd/cluster/config_ops_test.go +++ b/pkg/cmd/cluster/config_ops_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -33,9 +33,8 @@ import ( cmdtesting "k8s.io/kubectl/pkg/cmd/testing" kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + kbfakeclient "github.com/apecloud/kubeblocks/pkg/client/clientset/versioned/fake" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/controller/builder" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" "github.com/apecloud/kbcli/pkg/testing" @@ -72,39 +71,17 @@ var _ = Describe("reconfigure test", func() { It("check params for reconfiguring operations", func() { const ( - ns = "default" - clusterName = "test-cluster" - statefulCompDefName = "replicasets" - statefulCompName = "mysql" - configSpecName = "mysql-config-tpl" - configVolumeName = "mysql-config" + ns = "default" + clusterName = "test-cluster" + statefulCompName = "mysql" + configSpecName = "mysql-config-tpl" ) By("Create configmap and config constraint obj") - configmap := testapps.NewCustomizedObj("resources/mysql-config-template.yaml", &corev1.ConfigMap{}, testapps.WithNamespace(ns)) - constraint := testapps.NewCustomizedObj("resources/mysql-config-constraint.yaml", - &appsv1beta1.ConfigConstraint{}) + configmap := testapps.NewCustomizedObj("resources/mysql-config-template.yaml", &corev1.ConfigMap{}, testapps.WithNamespace(ns), testapps.WithName(testing.FakeMysqlTemplateName)) componentConfig := testapps.NewConfigMap(ns, cfgcore.GetComponentCfgName(clusterName, statefulCompName, configSpecName), testapps.SetConfigMapData("my.cnf", "")) - By("Create a configuration obj") - configObj := builder.NewConfigurationBuilder(ns, cfgcore.GenerateComponentConfigurationName(clusterName, statefulCompName)). - ClusterRef(clusterName). - Component(statefulCompName). - AddConfigurationItem(kbappsv1.ComponentConfigSpec{ - ComponentTemplateSpec: kbappsv1.ComponentTemplateSpec{ - Name: configSpecName, - TemplateRef: configmap.Name, - Namespace: ns, - VolumeName: configVolumeName, - }, - ConfigConstraintRef: constraint.Name, - }). - GetObject() - By("creating a cluster") - clusterObj := testapps.NewClusterFactory(ns, clusterName, ""). - AddComponent(statefulCompName, statefulCompDefName).GetObject() - - objs := []runtime.Object{configmap, constraint, clusterObj, componentConfig, configObj} - ttf, ops := NewFakeOperationsOptions(ns, clusterObj.Name, objs...) + objs := []runtime.Object{configmap, componentConfig} + ttf, ops := NewFakeOperationsOptions(ns, clusterName, objs...) o := &configOpsOptions{ // nil cannot be set to a map struct in CueLang, so init the map of KeyValues. OperationsOptions: &OperationsOptions{ @@ -113,10 +90,16 @@ var _ = Describe("reconfigure test", func() { } o.KeyValues = make(map[string]*string) o.HasPatch = true + o.clientSet = kbfakeclient.NewSimpleClientset( + testing.FakeCluster(clusterName, ns), + testing.FakeCompDef(), + testing.FakeParameterDefinition(), + testing.FakeParameterConfigRenderer(), + ) defer ttf.Cleanup() By("validate reconfiguring parameters") - o.ComponentNames = []string{statefulCompName} + o.ComponentNames = []string{testing.ComponentName} _, err := o.parseUpdatedParams() Expect(err.Error()).To(ContainSubstring(missingUpdatedParametersErrMessage)) o.Parameters = []string{"abcd"} diff --git a/pkg/cmd/cluster/config_resource.go b/pkg/cmd/cluster/config_resource.go index a1e59a241..aebd3472f 100644 --- a/pkg/cmd/cluster/config_resource.go +++ b/pkg/cmd/cluster/config_resource.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -20,205 +20,52 @@ along with this program. If not, see . package cluster import ( - "golang.org/x/exp/slices" - corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/dynamic" - "sigs.k8s.io/controller-runtime/pkg/client" + "context" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - - "github.com/apecloud/kbcli/pkg/types" - "github.com/apecloud/kbcli/pkg/util" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + "github.com/apecloud/kubeblocks/pkg/client/clientset/versioned" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type configSpecsType []*configSpecMeta - -type configSpecMeta struct { - Spec appsv1alpha1.ComponentTemplateSpec - ConfigMap *corev1.ConfigMap - - ConfigSpec *appsv1alpha1.ComponentConfigSpec - ConfigConstraint *appsv1beta1.ConfigConstraint -} - -type ConfigRelatedObjects struct { - Cluster *appsv1alpha1.Cluster - ConfigSpecs map[string]configSpecsType - Configurations []*appsv1alpha1.Configuration -} - -type configObjectsWrapper struct { +type ConfigObjectsWrapper struct { namespace string clusterName string components []string - err error - cli dynamic.Interface -} - -func (c configSpecsType) findByName(name string) *configSpecMeta { - for _, spec := range c { - if spec.Spec.Name == name { - return spec - } - } - return nil -} - -func (c configSpecsType) listConfigSpecs(ccFilter bool) []string { - var names []string - for _, spec := range c { - if spec.ConfigSpec != nil && (!ccFilter || spec.ConfigConstraint != nil) { - names = append(names, spec.Spec.Name) - } - } - return names -} - -func New(clusterName string, namespace string, cli dynamic.Interface, component ...string) *configObjectsWrapper { - return &configObjectsWrapper{namespace, clusterName, component, nil, cli} + rctxMap map[string]*ReconfigureContext } -func (w *configObjectsWrapper) GetObjects() (*ConfigRelatedObjects, error) { - objects := &ConfigRelatedObjects{} - err := w.cluster(objects). - // clusterDefinition(objects). - // clusterVersion(objects). - compConfigurations(objects). - // comps(objects). - configSpecsObjects(objects). - finish() +func GetCluster(clientSet versioned.Interface, ns, clusterName string) (*appsv1.Cluster, error) { + clusterObj, err := clientSet.AppsV1().Clusters(ns).Get(context.TODO(), clusterName, metav1.GetOptions{}) if err != nil { return nil, err } - return objects, nil -} - -func (w *configObjectsWrapper) configMap(specName string, component string, out *configSpecMeta) *configObjectsWrapper { - fn := func() error { - key := client.ObjectKey{ - Namespace: w.namespace, - Name: core.GetComponentCfgName(w.clusterName, component, specName), - } - out.ConfigMap = &corev1.ConfigMap{} - return util.GetResourceObjectFromGVR(types.ConfigmapGVR(), key, w.cli, out.ConfigMap) - } - return w.objectWrapper(fn) -} - -func (w *configObjectsWrapper) configConstraint(specName string, out *configSpecMeta) *configObjectsWrapper { - fn := func() error { - if specName == "" { - return nil - } - key := client.ObjectKey{ - Namespace: "", - Name: specName, - } - out.ConfigConstraint = &appsv1beta1.ConfigConstraint{} - return util.GetResourceObjectFromGVR(types.ConfigConstraintGVR(), key, w.cli, out.ConfigConstraint) - } - return w.objectWrapper(fn) + return clusterObj, nil } -func (w *configObjectsWrapper) cluster(objects *ConfigRelatedObjects) *configObjectsWrapper { - fn := func() error { - clusterKey := client.ObjectKey{ - Namespace: w.namespace, - Name: w.clusterName, - } - objects.Cluster = &appsv1alpha1.Cluster{} - if err := util.GetResourceObjectFromGVR(types.ClusterGVR(), clusterKey, w.cli, objects.Cluster); err != nil { - return makeClusterNotExistErr(w.clusterName) - } - return nil - } - return w.objectWrapper(fn) -} - -func (w *configObjectsWrapper) compConfigurations(object *ConfigRelatedObjects) *configObjectsWrapper { - fn := func() error { - for _, comp := range object.Cluster.Spec.ComponentSpecs { - configKey := client.ObjectKey{ - Namespace: w.namespace, - Name: core.GenerateComponentConfigurationName(w.clusterName, comp.Name), - } - config := appsv1alpha1.Configuration{} - if err := util.GetResourceObjectFromGVR(types.ConfigurationGVR(), configKey, w.cli, &config); err != nil { - return err - } - object.Configurations = append(object.Configurations, &config) - } - return nil +func New(clusterName string, namespace string, options *describeOpsOptions, components ...string) (*ConfigObjectsWrapper, error) { + clientSet := GetClientFromOptionsOrDie(options.factory) + clusterObj, err := GetCluster(clientSet, namespace, clusterName) + if err != nil { + return nil, err } - return w.objectWrapper(fn) -} - -func (w *configObjectsWrapper) configSpecsObjects(objects *ConfigRelatedObjects) *configObjectsWrapper { - fn := func() error { - configSpecs := make(map[string]configSpecsType, len(objects.Configurations)) - for _, component := range objects.Configurations { - componentName := component.Spec.ComponentName - if len(w.components) != 0 && !slices.Contains(w.components, componentName) { - continue - } - if _, ok := configSpecs[componentName]; !ok { - configSpecs[componentName] = make(configSpecsType, 0) - } - componentConfigSpecs, err := w.genConfigSpecsByConfiguration(&component.Spec) - if err != nil { - return err - } - configSpecs[componentName] = append(configSpecs[componentName], componentConfigSpecs...) - } - objects.ConfigSpecs = configSpecs - return nil + if len(components) == 0 { + components = getComponentNames(clusterObj) } - return w.objectWrapper(fn) -} -func (w *configObjectsWrapper) finish() error { - return w.err -} - -func (w *configObjectsWrapper) genConfigSpecsByConfiguration(config *appsv1alpha1.ConfigurationSpec) (rets []*configSpecMeta, err error) { - if len(config.ConfigItemDetails) == 0 { - return - } - - for _, item := range config.ConfigItemDetails { - if item.ConfigSpec == nil { - continue - } - specMeta, err := w.transformConfigSpecMeta(*item.ConfigSpec, config.ComponentName) + rctxAsMap := make(map[string]*ReconfigureContext) + for _, compName := range components { + rctx, err := generateReconfigureContext(context.TODO(), clientSet, clusterName, compName, namespace) if err != nil { return nil, err } - rets = append(rets, specMeta) - } - return -} - -func (w *configObjectsWrapper) transformConfigSpecMeta(spec appsv1alpha1.ComponentConfigSpec, component string) (*configSpecMeta, error) { - specMeta := &configSpecMeta{ - Spec: spec.ComponentTemplateSpec, - ConfigSpec: spec.DeepCopy(), - } - err := w.configMap(spec.Name, component, specMeta). - configConstraint(spec.ConfigConstraintRef, specMeta). - finish() - if err != nil { - return nil, err + rctxAsMap[rctx.CompName] = rctx } - return specMeta, nil -} -func (w *configObjectsWrapper) objectWrapper(fn func() error) *configObjectsWrapper { - if w.err != nil { - return w - } - w.err = fn() - return w + return &ConfigObjectsWrapper{ + namespace: namespace, + components: components, + clusterName: clusterName, + rctxMap: rctxAsMap, + }, nil } diff --git a/pkg/cmd/cluster/config_util.go b/pkg/cmd/cluster/config_util.go index c13238ab3..4594252d4 100644 --- a/pkg/cmd/cluster/config_util.go +++ b/pkg/cmd/cluster/config_util.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -28,13 +28,16 @@ import ( "sort" "strings" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/generics" "github.com/spf13/cast" corev1 "k8s.io/api/core/v1" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/client-go/dynamic" "k8s.io/kubectl/pkg/cmd/util/editor" "sigs.k8s.io/controller-runtime/pkg/client" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" @@ -257,20 +260,40 @@ func generateParameterSchema(paramName string, property apiext.JSONSchemaProps) return pt, nil } -func getComponentNames(cluster *appsv1alpha1.Cluster) []string { +func getComponentNames(cluster *appsv1.Cluster) []string { var components []string for _, component := range cluster.Spec.ComponentSpecs { components = append(components, component.Name) } + for _, component := range cluster.Spec.Shardings { + components = append(components, component.Name) + } return components } -func findTplByName(tpls []appsv1alpha1.ComponentConfigSpec, tplName string) *appsv1alpha1.ComponentConfigSpec { - for i := range tpls { - tpl := &tpls[i] - if tpl.Name == tplName { - return tpl +func resolveConfigTemplate(rctx *ReconfigureContext, dynamic dynamic.Interface) (map[string]*corev1.ConfigMap, error) { + tpls := generics.Map(rctx.ConfigRender.Spec.Configs, func(parameter parametersv1alpha1.ComponentConfigDescription) string { + return parameter.TemplateName + }) + tplObjs := make(map[string]*corev1.ConfigMap, len(tpls)) + for _, tpl := range tpls { + if _, ok := tplObjs[tpl]; ok { + continue + } + index := generics.FindFirstFunc(rctx.Cmpd.Spec.Configs, func(spec appsv1.ComponentFileTemplate) bool { + return spec.Name == tpl + }) + if index < 0 { + return nil, makeConfigSpecNotExistErr(rctx.Cluster.Name, rctx.CompName, tpl) } + var cm = &corev1.ConfigMap{} + tplMeta := rctx.Cmpd.Spec.Configs[index] + key := client.ObjectKey{Namespace: tplMeta.Namespace, Name: tplMeta.Template} + if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), key, dynamic, cm); err != nil { + return nil, err + } + tplObjs[tpl] = cm } - return nil + + return tplObjs, nil } diff --git a/pkg/cmd/cluster/config_util_test.go b/pkg/cmd/cluster/config_util_test.go index c9d4a5876..89ceb67a5 100644 --- a/pkg/cmd/cluster/config_util_test.go +++ b/pkg/cmd/cluster/config_util_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/config_wrapper.go b/pkg/cmd/cluster/config_wrapper.go index 48b1edbf7..67765f86c 100644 --- a/pkg/cmd/cluster/config_wrapper.go +++ b/pkg/cmd/cluster/config_wrapper.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -20,208 +20,209 @@ along with this program. If not, see . package cluster import ( + "context" + "errors" + "fmt" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/client/clientset/versioned" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + "github.com/apecloud/kubeblocks/pkg/generics" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) - "github.com/apecloud/kubeblocks/pkg/configuration/core" +type ReconfigureContext struct { + Client versioned.Interface + Context context.Context - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" + Cluster *kbappsv1.Cluster + Cmpd *kbappsv1.ComponentDefinition + ConfigRender *parametersv1alpha1.ParamConfigRenderer + ParametersDefs []*parametersv1alpha1.ParametersDefinition - "github.com/apecloud/kbcli/pkg/action" - "github.com/apecloud/kbcli/pkg/cluster" - "github.com/apecloud/kbcli/pkg/types" - "github.com/apecloud/kbcli/pkg/util" -) + CompName string +} -type configWrapper struct { - action.CreateOptions - *kbappsv1.Cluster +type ReconfigureWrapper struct { + rctx *ReconfigureContext - clusterName string updatedParams map[string]*string // autofill field - componentName string configSpecName string configFileKey string - - configTemplateSpec appsv1alpha1.ComponentConfigSpec -} - -func (w *configWrapper) ConfigTemplateSpec() *appsv1alpha1.ComponentConfigSpec { - return &w.configTemplateSpec -} - -func (w *configWrapper) ConfigSpecName() string { - return w.configSpecName } -func (w *configWrapper) ComponentName() string { - return w.componentName -} - -func (w *configWrapper) ConfigFile() string { - return w.configFileKey -} - -// AutoFillRequiredParam auto fills required param. -func (w *configWrapper) AutoFillRequiredParam() error { - if err := w.fillComponent(); err != nil { - return err +func (w *ReconfigureWrapper) ConfigSpecName() string { + if w.configFileKey != "" { + return w.configFileKey } - if err := w.fillConfigSpec(); err != nil { - return err + file := w.ConfigFile() + if file != "" && w.rctx.ConfigRender != nil { + config := intctrlutil.GetComponentConfigDescription(&w.rctx.ConfigRender.Spec, file) + if config != nil { + return config.TemplateName + } } - return w.fillConfigFile() + return "" } -// ValidateRequiredParam validates required param. -func (w *configWrapper) ValidateRequiredParam(forceReplace bool) error { - // step1: check existence of component. - if w.Spec.GetComponentByName(w.componentName) == nil { - return makeComponentNotExistErr(w.clusterName, w.componentName) - } +func (w *ReconfigureWrapper) ComponentName() string { + return w.rctx.CompName +} - // step2: check existence of configmap - cmObj := corev1.ConfigMap{} - cmKey := client.ObjectKey{ - Name: core.GetComponentCfgName(w.clusterName, w.componentName, w.configSpecName), - Namespace: w.Namespace, - } - if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), cmKey, w.Dynamic, &cmObj); err != nil { - return err +func (w *ReconfigureWrapper) ConfigFile() string { + if w.configFileKey != "" { + return w.configFileKey } - - // step3: check existence of config file - if _, ok := cmObj.Data[w.configFileKey]; !ok { - return makeNotFoundConfigFileErr(w.configFileKey, w.configSpecName, cfgutil.ToSet(cmObj.Data).AsSlice()) + if w.rctx.ConfigRender != nil && len(w.rctx.ConfigRender.Spec.Configs) > 0 { + return w.rctx.ConfigRender.Spec.Configs[0].Name } + return "" +} - if !forceReplace && !util.IsSupportConfigFileReconfigure(w.configTemplateSpec, w.configFileKey) { - return makeNotSupportConfigFileUpdateErr(w.configFileKey, w.configTemplateSpec) +func GetClientFromOptionsOrDie(factory cmdutil.Factory) versioned.Interface { + config, err := factory.ToRESTConfig() + if err != nil { + panic(err) } - return nil + return versioned.NewForConfigOrDie(config) } -func (w *configWrapper) fillComponent() error { - if w.componentName != "" { - return nil - } - componentNames, err := util.GetComponentsFromResource(w.Namespace, w.clusterName, w.Spec.ComponentSpecs, w.Dynamic) +func newConfigWrapper(clientSet versioned.Interface, ns, clusterName, componentName, templateName, fileName string, params map[string]*string) (*ReconfigureWrapper, error) { + rctx, err := generateReconfigureContext(context.TODO(), clientSet, clusterName, componentName, ns) if err != nil { - return err + return nil, err } - if len(componentNames) != 1 { - return core.MakeError(multiComponentsErrorMessage) + if len(rctx.ParametersDefs) == 0 && rctx.ConfigRender == nil { + return nil, fmt.Errorf("the referenced component[%s] has no ParametersDefinitions or ParamConfigRenderer, and disable reconfigure", componentName) } - w.componentName = componentNames[0] - return nil + + return &ReconfigureWrapper{ + rctx: rctx, + configSpecName: templateName, + configFileKey: fileName, + updatedParams: params, + }, nil } -func (w *configWrapper) fillConfigSpec() error { - foundConfigSpec := func(configSpecs []appsv1alpha1.ComponentConfigSpec, name string) *appsv1alpha1.ComponentConfigSpec { - for _, configSpec := range configSpecs { - if configSpec.Name == name { - w.configTemplateSpec = configSpec - return &configSpec - } +func generateReconfigureContext(ctx context.Context, clientSet versioned.Interface, clusterName, componentName, ns string) (*ReconfigureContext, error) { + defaultCompName := func(clusterSpec kbappsv1.ClusterSpec) string { + switch { + case len(clusterSpec.ComponentSpecs) != 0: + return clusterSpec.ComponentSpecs[0].Name + case len(clusterSpec.Shardings) == 0: + return clusterSpec.Shardings[0].Name + default: + panic("cluster not have any component or sharding") } - return nil } - configSpecs, err := util.GetConfigSpecsFromComponentName(w.Dynamic, w.GetNamespace(), w.clusterName, w.componentName, w.configSpecName == "") + clusterObj, err := clientSet.AppsV1().Clusters(ns).Get(ctx, clusterName, metav1.GetOptions{}) if err != nil { - return err + return nil, err } - if len(configSpecs) == 0 { - return makeNotFoundTemplateErr(w.clusterName, w.componentName) + if componentName == "" { + componentName = defaultCompName(clusterObj.Spec) } - if w.configSpecName != "" { - if foundConfigSpec(configSpecs, w.configSpecName) == nil { - return makeConfigSpecNotExistErr(w.clusterName, w.componentName, w.configSpecName) - } - return nil + cmpd, err := resolveComponentDefObj(ctx, clientSet, clusterObj, componentName) + if err != nil { + return nil, err } - - w.configTemplateSpec = configSpecs[0] - if len(configSpecs) == 1 { - w.configSpecName = configSpecs[0].Name - return nil + rctx := &ReconfigureContext{ + Context: ctx, + Cmpd: cmpd, + Cluster: clusterObj, + Client: clientSet, + CompName: componentName, } - if len(w.updatedParams) == 0 { - return core.MakeError(multiConfigTemplateErrorMessage) - } - supportUpdatedTpl := make([]appsv1alpha1.ComponentConfigSpec, 0) - for _, configSpec := range configSpecs { - if ok, err := util.IsSupportReconfigureParams(configSpec, w.updatedParams, w.Dynamic); err == nil && ok { - supportUpdatedTpl = append(supportUpdatedTpl, configSpec) - } - } - if len(supportUpdatedTpl) == 1 { - w.configTemplateSpec = configSpecs[0] - w.configSpecName = supportUpdatedTpl[0].Name - return nil + if err = resolveCmpdParametersDefs(rctx); err != nil { + return nil, err } - return core.MakeError(multiConfigTemplateErrorMessage) + return rctx, nil } -func (w *configWrapper) fillConfigFile() error { - if w.configFileKey != "" { - return nil +func resolveComponentDefObj(ctx context.Context, client versioned.Interface, clusterObj *kbappsv1.Cluster, componentName string) (*kbappsv1.ComponentDefinition, error) { + resolveCmpd := func(cmpdName string) (*kbappsv1.ComponentDefinition, error) { + if cmpdName == "" { + return nil, errors.New("the referenced ComponentDefinition is empty") + } + return client.AppsV1(). + ComponentDefinitions(). + Get(ctx, cmpdName, metav1.GetOptions{}) + } + resolveShardingCmpd := func(cmpdName string) (*kbappsv1.ComponentDefinition, error) { + shardingCmpd, err := client.AppsV1(). + ShardingDefinitions(). + Get(ctx, cmpdName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + if shardingCmpd.Spec.Template.CompDef == "" { + return nil, errors.New("the referenced ShardingDefinition has no ComponentDefinition") + } + return resolveCmpd(shardingCmpd.Spec.Template.CompDef) } - if w.configTemplateSpec.TemplateRef == "" { - return makeNotFoundTemplateErr(w.clusterName, w.componentName) + compSpec := clusterObj.Spec.GetComponentByName(componentName) + if compSpec != nil { + return resolveCmpd(compSpec.ComponentDef) } - cmObj := corev1.ConfigMap{} - cmKey := client.ObjectKey{ - Name: core.GetComponentCfgName(w.clusterName, w.componentName, w.configSpecName), - Namespace: w.Namespace, - } - if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), cmKey, w.Dynamic, &cmObj); err != nil { - return err + shardingSpec := clusterObj.Spec.GetShardingByName(componentName) + if shardingSpec == nil { + return nil, makeComponentNotExistErr(clusterObj.Name, componentName) } - if len(cmObj.Data) == 0 { - return core.MakeError("not supported reconfiguring because there is no config file.") + if shardingSpec.ShardingDef != "" { + return resolveShardingCmpd(shardingSpec.ShardingDef) } + return resolveCmpd(shardingSpec.Template.ComponentDef) +} - keys := w.filterForReconfiguring(cmObj.Data) - if len(keys) == 1 { - w.configFileKey = keys[0] +func resolveCmpdParametersDefs(rctx *ReconfigureContext) error { + configRender, err := resolveComponentConfigRender(rctx, rctx.Cmpd) + if err != nil { + return err + } + if configRender == nil || len(configRender.Spec.ParametersDefs) == 0 { return nil } - return core.MakeError(multiConfigFileErrorMessage) -} - -func (w *configWrapper) filterForReconfiguring(data map[string]string) []string { - keys := make([]string, 0, len(data)) - for configFileKey := range data { - if util.IsSupportConfigFileReconfigure(w.configTemplateSpec, configFileKey) { - keys = append(keys, configFileKey) + rctx.ConfigRender = configRender + for _, defName := range configRender.Spec.ParametersDefs { + pd, err := rctx.Client.ParametersV1alpha1().ParametersDefinitions().Get(rctx.Context, defName, metav1.GetOptions{}) + if err != nil { + return err } + rctx.ParametersDefs = append(rctx.ParametersDefs, pd) } - return keys + return nil } -func newConfigWrapper(baseOptions action.CreateOptions, componentName, configSpec, configKey string, params map[string]*string) (*configWrapper, error) { - var err error - var clusterObj *kbappsv1.Cluster - - if clusterObj, err = cluster.GetClusterByName(baseOptions.Dynamic, baseOptions.Name, baseOptions.Namespace); err != nil { +func resolveComponentConfigRender(rctx *ReconfigureContext, cmpd *kbappsv1.ComponentDefinition) (*parametersv1alpha1.ParamConfigRenderer, error) { + pcrList, err := rctx.Client.ParametersV1alpha1().ParamConfigRenderers().List(rctx.Context, metav1.ListOptions{}) + if err != nil { return nil, err } - return &configWrapper{ - CreateOptions: baseOptions, - Cluster: clusterObj, - clusterName: baseOptions.Name, - componentName: componentName, - configSpecName: configSpec, - configFileKey: configKey, - updatedParams: params, - }, nil + + var prcs []parametersv1alpha1.ParamConfigRenderer + for i, item := range pcrList.Items { + if item.Spec.ComponentDef != cmpd.Name { + continue + } + if item.Spec.ServiceVersion == "" || item.Spec.ServiceVersion == cmpd.Spec.ServiceVersion { + prcs = append(prcs, pcrList.Items[i]) + } + } + if len(prcs) == 1 { + return &prcs[0], nil + } + if len(prcs) > 1 { + return nil, fmt.Errorf("the ParamConfigRenderer is ambiguous which referenced cmpd[%s], prcs: [%s]", cmpd.Namespace, + generics.Map(prcs, func(pcr parametersv1alpha1.ParamConfigRenderer) string { return pcr.Name })) + } + return nil, nil } diff --git a/pkg/cmd/cluster/connect.go b/pkg/cmd/cluster/connect.go index ef3d980ed..71daa366e 100644 --- a/pkg/cmd/cluster/connect.go +++ b/pkg/cmd/cluster/connect.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/connect_test.go b/pkg/cmd/cluster/connect_test.go index f8f8ac9c8..dbfb0343f 100644 --- a/pkg/cmd/cluster/connect_test.go +++ b/pkg/cmd/cluster/connect_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/constant.go b/pkg/cmd/cluster/constant.go index 0eb3f7fe9..e8c5b178d 100644 --- a/pkg/cmd/cluster/constant.go +++ b/pkg/cmd/cluster/constant.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -19,12 +19,4 @@ along with this program. If not, see . package cluster -const ( - saNamePrefix = "kb-" - roleNamePrefix = "kb-" - roleBindingNamePrefix = "kb-" - clusterRolePrefix = "kb-" - clusterRoleBindingPrefix = "kb-" -) - const apeCloudMysql = "apecloud-mysql" diff --git a/pkg/cmd/cluster/create.go b/pkg/cmd/cluster/create.go index bfd9a724f..e56fcf434 100755 --- a/pkg/cmd/cluster/create.go +++ b/pkg/cmd/cluster/create.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -20,6 +20,7 @@ along with this program. If not, see . package cluster import ( + "fmt" "io" "net/http" "os" @@ -54,11 +55,16 @@ type CreateOptions struct { func NewCreateCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { o := NewCreateOptions(f, streams) cmd := &cobra.Command{ - Use: "create [NAME]", + Use: "create [ClusterType]", Short: "Create a cluster.", Example: clusterCreateExample, Run: func(cmd *cobra.Command, args []string) { - println("no implement") + if len(args) == 0 { + fmt.Fprintf(o.Out, "A ClusterType shoule be specified to ") + _ = cmd.Help() + return + } + fmt.Fprintf(o.Out, "The engine type you specified is not implemented.") }, } diff --git a/pkg/cmd/cluster/create_subcmds.go b/pkg/cmd/cluster/create_subcmds.go index 50939300f..2c4acb3e5 100644 --- a/pkg/cmd/cluster/create_subcmds.go +++ b/pkg/cmd/cluster/create_subcmds.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -127,12 +127,22 @@ func buildCreateSubCmds(createOptions *action.CreateOptions) []*cobra.Command { util.CheckErr(addCreateFlags(cmd, o.Factory, o.ChartInfo, t.String())) // Schedule policy - cmd.Flags().StringVar(&o.PodAntiAffinity, "pod-anti-affinity", "Preferred", "Pod anti-affinity type, one of: (Preferred, Required)") - cmd.Flags().StringArrayVar(&o.TopologyKeys, "topology-keys", nil, "Topology keys for affinity") - cmd.Flags().StringToStringVar(&o.NodeLabels, "node-labels", nil, "Node label selector") - cmd.Flags().StringSliceVar(&o.TolerationsRaw, "tolerations", nil, `Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"'`) + if cmd.Flag("pod-anti-affinity") == nil { + cmd.Flags().StringVar(&o.PodAntiAffinity, "pod-anti-affinity", "Preferred", "Pod anti-affinity type, one of: (Preferred, Required)") + _ = cmd.Flags().SetAnnotation("pod-anti-affinity", cobra.BashCompCustom, []string{"Preferred", "Required"}) + } + if cmd.Flag("topology-keys") == nil { + cmd.Flags().StringArrayVar(&o.TopologyKeys, "topology-keys", nil, "Topology keys for affinity") + } + if cmd.Flag("node-labels") == nil { + cmd.Flags().StringToStringVar(&o.NodeLabels, "node-labels", nil, "Node label selector") + } + if cmd.Flag("tolerations") == nil { + cmd.Flags().StringSliceVar(&o.TolerationsRaw, "tolerations", nil, `Tolerations for cluster, such as "key=value:effect,key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"'`) + } if cmd.Flag("tenancy") == nil { cmd.Flags().StringVar(&o.Tenancy, "tenancy", "SharedNode", "Tenancy options, one of: (SharedNode, DedicatedNode)") + _ = cmd.Flags().SetAnnotation("tenancy", cobra.BashCompCustom, []string{"SharedNode", "DedicatedNode"}) } cmds = append(cmds, cmd) } @@ -150,6 +160,14 @@ func (o *CreateSubCmdsOptions) Complete(cmd *cobra.Command) error { } } + if o.Tenancy == "" { + o.Tenancy = "SharedNode" + } + + if o.PodAntiAffinity == "" { + o.PodAntiAffinity = "Preferred" + } + // get values from flags if cmd != nil { o.Values = getValuesFromFlags(cmd.LocalNonPersistentFlags()) @@ -226,6 +244,33 @@ func (o *CreateSubCmdsOptions) Validate() error { if len(o.Name) > 16 { return fmt.Errorf("cluster name should be less than 16 characters") } + if o.Tenancy != "SharedNode" && o.Tenancy != "DedicatedNode" { + return fmt.Errorf("tenancy must be one of: (SharedNode, DedicatedNode)") + } + if o.PodAntiAffinity != "Preferred" && o.PodAntiAffinity != "Required" { + return fmt.Errorf("podAntiAffinity must be one of: (Preferred, Required)") + } + if o.TopologyKeys != nil { + for _, key := range o.TopologyKeys { + if key == "" { + return fmt.Errorf("topologyKeys cannot be empty") + } + } + } + if o.NodeLabels != nil { + for k, v := range o.NodeLabels { + if k == "" || v == "" { + return fmt.Errorf("nodeLabels cannot be empty") + } + } + } + if o.Tolerations != nil { + for _, t := range o.Tolerations { + if t.Key == "" || t.Operator == "" { + return fmt.Errorf("tolerations cannot be empty") + } + } + } return cluster.ValidateValues(o.ChartInfo, o.Values) } @@ -355,7 +400,6 @@ func (o *CreateSubCmdsOptions) getObjectsInfo() ([]*objectInfo, error) { func (o *CreateSubCmdsOptions) getClusterObj(objs []*objectInfo) (*unstructured.Unstructured, error) { for _, obj := range objs { - fmt.Println(obj.gvr.String()) if obj.gvr == types.ClusterGVR() { return obj.obj, nil } diff --git a/pkg/cmd/cluster/create_subcmds_test.go b/pkg/cmd/cluster/create_subcmds_test.go index 5eef97a87..668cd6517 100644 --- a/pkg/cmd/cluster/create_subcmds_test.go +++ b/pkg/cmd/cluster/create_subcmds_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -96,6 +96,8 @@ var _ = Describe("create cluster by cluster type", func() { Expect(err).Should(Succeed()) Expect(o).ShouldNot(BeNil()) Expect(o.ChartInfo).ShouldNot(BeNil()) + o.PodAntiAffinity = "Preferred" + o.Tenancy = "SharedNode" By("complete") var mysqlCmd *cobra.Command @@ -139,6 +141,8 @@ var _ = Describe("create cluster by cluster type", func() { Expect(err).Should(Succeed()) Expect(o).ShouldNot(BeNil()) Expect(o.ChartInfo).ShouldNot(BeNil()) + o.PodAntiAffinity = "Preferred" + o.Tenancy = "SharedNode" By("complete") var shardCmd *cobra.Command diff --git a/pkg/cmd/cluster/create_test.go b/pkg/cmd/cluster/create_test.go index 4cc6f9d21..d0ae63ba1 100644 --- a/pkg/cmd/cluster/create_test.go +++ b/pkg/cmd/cluster/create_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/create_util.go b/pkg/cmd/cluster/create_util.go index e52a86a45..4440e3e55 100644 --- a/pkg/cmd/cluster/create_util.go +++ b/pkg/cmd/cluster/create_util.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -38,11 +38,7 @@ import ( ) var ( - resetEngineFlagValues = map[string]map[string]string{ - "elasticsearch": { - "rbac-enabled": "true", - }, - } + resetEngineFlagValues = map[string]map[string]string{} ) // addCreateFlags adds the flags for creating a cluster, these flags are built by the cluster schema. @@ -61,7 +57,7 @@ func addCreateFlags(cmd *cobra.Command, f cmdutil.Factory, c *cluster.ChartInfo, return err } - // reset engine related flags default value, such as rbac-enabled for elasticsearch should be true by default + // reset engine related flags default value resetEngineDefaultFlagsValue(cmd.Flags(), engine) return nil } @@ -94,7 +90,39 @@ func getValuesFromFlags(fs *flag.FlagSet) map[string]interface{} { } values[strcase.LowerCamelCase(f.Name)] = val }) - return values + // normalize the values + return flattenToNestedMap(values) +} + +// flattenToNestedMap takes a flat map with keys that can contain dots to represent nesting, +// and returns a nested map structure. +func flattenToNestedMap(flatMap map[string]interface{}) map[string]interface{} { + nestedMap := make(map[string]interface{}) + for key, value := range flatMap { + setNestedValue(nestedMap, strings.Split(key, "."), value) + } + return nestedMap +} + +// setNestedValue sets the value in the nested map for the given key path. +func setNestedValue(m map[string]interface{}, keys []string, value interface{}) { + if len(keys) == 0 { + return + } + + currentKey := keys[0] + if len(keys) == 1 { + m[currentKey] = value + return + } + + subMap, exists := m[currentKey].(map[string]interface{}) + if !exists || subMap == nil { + subMap = make(map[string]interface{}) + m[currentKey] = subMap + } + + setNestedValue(subMap, keys[1:], value) } func resetEngineDefaultFlagsValue(fs *flag.FlagSet, engine string) { diff --git a/pkg/cmd/cluster/create_util_test.go b/pkg/cmd/cluster/create_util_test.go index 236aae77e..9dcc85cc2 100644 --- a/pkg/cmd/cluster/create_util_test.go +++ b/pkg/cmd/cluster/create_util_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -191,13 +191,13 @@ metadata: "version": "1.0.0", "cpu": 1, "memory": 1, - "terminationPolicy": "Halt", + "terminationPolicy": "WipeOut", } helmValues := buildHelmValues(c, values) Expect(helmValues).ShouldNot(BeNil()) Expect(helmValues["version"]).Should(Equal("1.0.0")) Expect(helmValues[c.SubChartName]).ShouldNot(BeNil()) - Expect(helmValues[c.SubChartName].(map[string]interface{})["terminationPolicy"]).Should(Equal("Halt")) + Expect(helmValues[c.SubChartName].(map[string]interface{})["terminationPolicy"]).Should(Equal("WipeOut")) By("build object helm values") values = map[string]interface{}{ diff --git a/pkg/cmd/cluster/dataprotection.go b/pkg/cmd/cluster/dataprotection.go index 66e590ca9..2102fa3fe 100644 --- a/pkg/cmd/cluster/dataprotection.go +++ b/pkg/cmd/cluster/dataprotection.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -42,7 +42,7 @@ import ( var ( listBackupPolicyExample = templates.Examples(` # list all backup policies - kbcli cluster list-backup-policy + kbcli cluster list-backup-policies # using short cmd to list backup policy of the specified cluster kbcli cluster list-bp mycluster @@ -58,7 +58,7 @@ var ( # create a backup with a specified method, run "kbcli cluster desc-backup-policy mycluster" to show supported backup methods kbcli cluster backup mycluster --method volume-snapshot - # create a backup with specified backup policy, run "kbcli cluster list-backup-policy mycluster" to show the cluster supported backup policies + # create a backup with specified backup policy, run "kbcli cluster list-backup-policies mycluster" to show the cluster supported backup policies kbcli cluster backup mycluster --method volume-snapshot --policy # create a backup from a parent backup @@ -66,13 +66,13 @@ var ( `) listBackupExample = templates.Examples(` # list all backups - kbcli cluster list-backup + kbcli cluster list-backups # list all backups of the cluster - kbcli cluster list-backup + kbcli cluster list-backups # list the specified backups - kbcli cluster list-backup --names b1,b2 + kbcli cluster list-backups --names b1,b2 `) deleteBackupExample = templates.Examples(` # delete a backup named backup-name @@ -84,17 +84,20 @@ var ( `) listRestoreExample = templates.Examples(` # list all restores - kbcli cluster list-restore + kbcli cluster list-restores # list all restores of the cluster - kbcli cluster list-restore + kbcli cluster list-restores # list the specified restores - kbcli cluster list-restore --names r1,r2 + kbcli cluster list-restores --names r1,r2 `) describeBackupExample = templates.Examples(` + # describe backups of the cluster + kbcli cluster describe-backup + # describe a backup - kbcli cluster describe-backup backup-default-mycluster-20230616190023 + kbcli cluster describe-backup --names `) describeBackupPolicyExample = templates.Examples(` # describe the default backup policy of the cluster @@ -113,7 +116,7 @@ func NewCreateBackupCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) * customOutPut := func(opt *action.CreateOptions) { output := fmt.Sprintf("Backup %s created successfully, you can view the progress:", opt.Name) printer.PrintLine(output) - nextLine := fmt.Sprintf("\tkbcli cluster list-backup --name=%s -n %s", opt.Name, opt.Namespace) + nextLine := fmt.Sprintf("\tkbcli cluster list-backups --name=%s -n %s", opt.Name, opt.Namespace) printer.PrintLine(nextLine) } @@ -156,7 +159,7 @@ func NewCreateBackupCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) * func NewListBackupCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { o := action.NewListOptions(f, streams, types.BackupGVR()) cmd := &cobra.Command{ - Use: "list-backup", + Use: "list-backups", Short: "List backups.", Aliases: []string{"ls-backup"}, Example: listBackupExample, @@ -300,6 +303,7 @@ func NewCreateRestoreCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) } o.AddCommonFlags(cmd) cmd.Flags().StringVar(&o.RestoreSpec.BackupName, "backup", "", "Backup name") + cmd.Flags().StringVar(&o.RestoreSpec.BackupNamespace, "backup-namespace", "", "Backup namespace") cmd.Flags().StringVar(&o.RestoreSpec.RestorePointInTime, "restore-to-time", "", "point in time recovery(PITR)") cmd.Flags().StringVar(&restoreKey, "restore-key", "", "specify the key to restore in kv database, support multiple keys split by comma with wildcard pattern matching") cmd.Flags().BoolVar(&restoreKeyIgnoreErrors, "restore-key-ignore-errors", false, "whether or not to ignore errors when restore kv database by keys") @@ -311,7 +315,7 @@ func NewCreateRestoreCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) func NewListBackupPolicyCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { o := action.NewListOptions(f, streams, types.BackupPolicyGVR()) cmd := &cobra.Command{ - Use: "list-backup-policy", + Use: "list-backup-policies", Short: "List backups policies.", Aliases: []string{"list-bp"}, Example: listBackupPolicyExample, @@ -390,7 +394,7 @@ func describeBackupPolicies(o *dp.DescribeDPOptions, args []string) error { func NewListRestoreCommand(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { o := action.NewListOptions(f, streams, types.RestoreGVR()) cmd := &cobra.Command{ - Use: "list-restore", + Use: "list-restores", Short: "List restores.", Aliases: []string{"ls-restores"}, Example: listRestoreExample, diff --git a/pkg/cmd/cluster/dataprotection_test.go b/pkg/cmd/cluster/dataprotection_test.go index 77fd7dc84..ce2d12860 100644 --- a/pkg/cmd/cluster/dataprotection_test.go +++ b/pkg/cmd/cluster/dataprotection_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/delete.go b/pkg/cmd/cluster/delete.go index f4194837c..0ddb7d0dc 100644 --- a/pkg/cmd/cluster/delete.go +++ b/pkg/cmd/cluster/delete.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -20,24 +20,21 @@ along with this program. If not, see . package cluster import ( - "context" "fmt" "github.com/spf13/cobra" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/errors" "k8s.io/cli-runtime/pkg/genericiooptions" - "k8s.io/client-go/kubernetes" - "k8s.io/klog/v2" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kbcli/pkg/action" + "github.com/apecloud/kbcli/pkg/printer" "github.com/apecloud/kbcli/pkg/types" "github.com/apecloud/kbcli/pkg/util" ) @@ -46,11 +43,13 @@ var ( deleteExample = templates.Examples(` # delete a cluster named mycluster kbcli cluster delete mycluster + # delete a cluster by label selector kbcli cluster delete --selector clusterdefinition.kubeblocks.io/name=apecloud-mysql -`) - rbacEnabled = false + # delete a cluster named mycluster forcedly + kbcli cluster delete mycluster --force +`) ) func NewDeleteCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { @@ -68,7 +67,6 @@ func NewDeleteCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra. }, } o.AddFlags(cmd) - cmd.Flags().BoolVar(&rbacEnabled, "rbac-enabled", false, "Specify whether rbac resources will be deleted by kbcli") return cmd } @@ -89,98 +87,44 @@ func clusterPreDeleteHook(o *action.DeleteOptions, object runtime.Object) error if err != nil { return err } - if cluster.Spec.TerminationPolicy == appsv1alpha1.DoNotTerminate { + if cluster.Spec.TerminationPolicy == appsv1.DoNotTerminate { return fmt.Errorf("cluster %s is protected by termination policy %s, skip deleting", cluster.Name, appsv1alpha1.DoNotTerminate) } - return nil -} -func clusterPostDeleteHook(o *action.DeleteOptions, object runtime.Object) error { - if object == nil { - return nil - } - - c, err := getClusterFromObject(object) - if err != nil { - return err - } - - client, err := o.Factory.KubernetesClientSet() - if err != nil { - return err + if o.Force { + fmt.Fprint(o.Out, printer.BoldRed("WARNING: Using --force may lead to potential data loss or residual dirty data if the cluster depends on other clusters.\n")) + components := util.GetComponentsOrShards(cluster) + for _, componentName := range components { + dynamicClient, err := o.Factory.DynamicClient() + if err != nil { + return err + } + err = util.AddAnnotationToComponentOrShard(dynamicClient, constant.GenerateClusterComponentName(cluster.Name, componentName), cluster.Namespace, constant.SkipPreTerminateAnnotationKey, "true") + if err != nil { + return fmt.Errorf("failed to add annotation to component %s: %v", componentName, err) + } + } } - if err = deleteDependencies(client, c.Namespace, c.Name); err != nil { - return err - } return nil } -func deleteDependencies(client kubernetes.Interface, ns string, name string) error { - if !rbacEnabled { +func clusterPostDeleteHook(o *action.DeleteOptions, object runtime.Object) error { + if object == nil { return nil } - klog.V(1).Infof("delete dependencies for cluster %s", name) - var ( - saName = saNamePrefix + name - roleName = roleNamePrefix + name - roleBindingName = roleBindingNamePrefix + name - clusterRoleName = clusterRolePrefix + name - clusterRoleBindingName = clusterRoleBindingPrefix + name - allErr []error - ) - - // now, delete the dependencies, for postgresql, we delete sa, role and rolebinding - ctx := context.TODO() - gracePeriod := int64(0) - deleteOptions := metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod} - checkErr := func(err error) bool { - if err != nil && !apierrors.IsNotFound(err) { - return true - } - return false - } - - // delete cluster role binding - klog.V(1).Infof("delete cluster role binding %s", clusterRoleBindingName) - if err := client.RbacV1().ClusterRoleBindings().Delete(ctx, clusterRoleBindingName, deleteOptions); checkErr(err) { - allErr = append(allErr, err) - } + // currently no hook is defined - // delete cluster role - klog.V(1).Infof("delete cluster role %s", clusterRoleName) - if err := client.RbacV1().ClusterRoles().Delete(ctx, clusterRoleName, deleteOptions); checkErr(err) { - allErr = append(allErr, err) - } - - // delete role binding - klog.V(1).Infof("delete role binding %s", roleBindingName) - if err := client.RbacV1().RoleBindings(ns).Delete(ctx, roleBindingName, deleteOptions); checkErr(err) { - allErr = append(allErr, err) - } - - // delete role - klog.V(1).Infof("delete role %s", roleName) - if err := client.RbacV1().Roles(ns).Delete(ctx, roleName, deleteOptions); checkErr(err) { - allErr = append(allErr, err) - } - - // delete service account - klog.V(1).Infof("delete service account %s", saName) - if err := client.CoreV1().ServiceAccounts(ns).Delete(ctx, saName, deleteOptions); checkErr(err) { - allErr = append(allErr, err) - } - - return errors.NewAggregate(allErr) + return nil } -func getClusterFromObject(object runtime.Object) (*appsv1alpha1.Cluster, error) { - if object.GetObjectKind().GroupVersionKind().Kind != appsv1alpha1.ClusterKind { +func getClusterFromObject(object runtime.Object) (*appsv1.Cluster, error) { + if object.GetObjectKind().GroupVersionKind().Kind != appsv1.ClusterKind { return nil, fmt.Errorf("object %s is not of kind %s", object.GetObjectKind().GroupVersionKind().Kind, appsv1alpha1.ClusterKind) } u := object.(*unstructured.Unstructured) - cluster := &appsv1alpha1.Cluster{} + cluster := &appsv1.Cluster{} if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, cluster); err != nil { return nil, err } diff --git a/pkg/cmd/cluster/delete_ops.go b/pkg/cmd/cluster/delete_ops.go index fc62e42d9..2aab7d4d5 100644 --- a/pkg/cmd/cluster/delete_ops.go +++ b/pkg/cmd/cluster/delete_ops.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/delete_ops_test.go b/pkg/cmd/cluster/delete_ops_test.go index ec7018358..ef83a8645 100644 --- a/pkg/cmd/cluster/delete_ops_test.go +++ b/pkg/cmd/cluster/delete_ops_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/describe.go b/pkg/cmd/cluster/describe.go index 31d84743e..9b782a194 100644 --- a/pkg/cmd/cluster/describe.go +++ b/pkg/cmd/cluster/describe.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/describe_ops.go b/pkg/cmd/cluster/describe_ops.go index 8dc3e735f..c94d03d4c 100644 --- a/pkg/cmd/cluster/describe_ops.go +++ b/pkg/cmd/cluster/describe_ops.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -368,12 +368,7 @@ func (o *describeOpsOptions) getReconfiguringCommand(spec opsv1alpha1.OpsRequest } func generateReconfiguringCommand(clusterName string, updatedParams *opsv1alpha1.Reconfigure, components []string) []string { - if len(updatedParams.Configurations) == 0 { - return nil - } - - configuration := updatedParams.Configurations[0] - if len(configuration.Keys) == 0 { + if len(updatedParams.Parameters) == 0 { return nil } @@ -383,11 +378,8 @@ func generateReconfiguringCommand(clusterName string, updatedParams *opsv1alpha1 commandArgs = append(commandArgs, "configure") commandArgs = append(commandArgs, clusterName) commandArgs = append(commandArgs, fmt.Sprintf("--components=%s", strings.Join(components, ","))) - commandArgs = append(commandArgs, fmt.Sprintf("--config-spec=%s", configuration.Name)) - config := configuration.Keys[0] - commandArgs = append(commandArgs, fmt.Sprintf("--config-file=%s", config.Key)) - for _, p := range config.Parameters { + for _, p := range updatedParams.Parameters { if p.Value == nil { continue } diff --git a/pkg/cmd/cluster/describe_ops_test.go b/pkg/cmd/cluster/describe_ops_test.go index 809efa557..8967707b9 100644 --- a/pkg/cmd/cluster/describe_ops_test.go +++ b/pkg/cmd/cluster/describe_ops_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -138,7 +138,7 @@ var _ = Describe("Expose", func() { Phase: opsv1alpha1.OpsFailedPhase, Components: map[string]opsv1alpha1.OpsRequestComponentStatus{ componentName: { - Phase: appsv1.FailedClusterCompPhase, + Phase: appsv1.FailedComponentPhase, ProgressDetails: []opsv1alpha1.ProgressStatusDetail{ { ObjectKey: objectKey, diff --git a/pkg/cmd/cluster/describe_test.go b/pkg/cmd/cluster/describe_test.go index e5120005c..3cef99394 100644 --- a/pkg/cmd/cluster/describe_test.go +++ b/pkg/cmd/cluster/describe_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/errors.go b/pkg/cmd/cluster/errors.go index 4459d652b..cf9d65f3c 100644 --- a/pkg/cmd/cluster/errors.go +++ b/pkg/cmd/cluster/errors.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -20,26 +20,17 @@ along with this program. If not, see . package cluster import ( - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" ) var ( - clusterNotExistErrMessage = "cluster[name=%s] does not exist. Please check that is spelled correctly." componentNotExistErrMessage = "cluster[name=%s] does not have component[name=%s]. Please check that --component is spelled correctly." missingClusterArgErrMassage = "cluster name should be specified, using --help." missingUpdatedParametersErrMessage = "missing updated parameters, using --help." - multiComponentsErrorMessage = "when multi components exist, specify a component, using --components" - multiConfigTemplateErrorMessage = "when multi config templates exist, specify a config template, using --config-spec" - multiConfigFileErrorMessage = "when multi config files exist, specify a config file, using --config-file" - - notFoundValidConfigTemplateErrorMessage = "cannot find valid config templates for component[name=%s] in the cluster[name=%s]" - notFoundConfigSpecErrorMessage = "cannot find config spec[%s] for component[name=%s] in the cluster[name=%s]" - notFoundConfigFileErrorMessage = "cannot find config file[name=%s] in the configspec[name=%s], all configfiles: %v" - notSupportFileUpdateErrorMessage = "not supported file[%s] for updating, current supported files: %v" + notFoundConfigFileErrorMessage = "cannot find config file[name=%s] in the configspec[name=%s], all configfiles: %v" notConfigSchemaPrompt = "The config template[%s] is not defined in schema and parameter explanation info cannot be generated." cue2openAPISchemaFailedPrompt = "The cue schema may not satisfy the conversion constraints of openAPISchema and parameter explanation info cannot be generated." @@ -48,10 +39,6 @@ var ( confirmApplyReconfigurePrompt = "Are you sure you want to apply these changes?\n" ) -func makeClusterNotExistErr(clusterName string) error { - return cfgcore.MakeError(clusterNotExistErrMessage, clusterName) -} - func makeComponentNotExistErr(clusterName, component string) error { return cfgcore.MakeError(componentNotExistErrMessage, clusterName, component) } @@ -60,18 +47,10 @@ func makeConfigSpecNotExistErr(clusterName, component, configSpec string) error return cfgcore.MakeError(notFoundConfigSpecErrorMessage, configSpec, component, clusterName) } -func makeNotFoundTemplateErr(clusterName, component string) error { - return cfgcore.MakeError(notFoundValidConfigTemplateErrorMessage, component, clusterName) -} - func makeNotFoundConfigFileErr(configFile, configSpec string, all []string) error { return cfgcore.MakeError(notFoundConfigFileErrorMessage, configFile, configSpec, all) } -func makeNotSupportConfigFileUpdateErr(configFile string, configSpec appsv1alpha1.ComponentConfigSpec) error { - return cfgcore.MakeError(notSupportFileUpdateErrorMessage, configFile, configSpec.Keys) -} - func makeMissingClusterNameErr() error { return cfgcore.MakeError(missingClusterArgErrMassage) } diff --git a/pkg/cmd/cluster/label.go b/pkg/cmd/cluster/label.go index 4b72c1342..b1413dc8c 100644 --- a/pkg/cmd/cluster/label.go +++ b/pkg/cmd/cluster/label.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/label_test.go b/pkg/cmd/cluster/label_test.go index 2e579fe40..c7c77f289 100644 --- a/pkg/cmd/cluster/label_test.go +++ b/pkg/cmd/cluster/label_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/list.go b/pkg/cmd/cluster/list.go index b15216528..9fe9e2aa0 100644 --- a/pkg/cmd/cluster/list.go +++ b/pkg/cmd/cluster/list.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -93,6 +93,7 @@ func NewListCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Co }, } o.AddFlags(cmd) + cmd.Flags().StringVar(&o.Status, "status", "", "Filter objects by given status.") return cmd } @@ -151,7 +152,7 @@ func NewListEventsCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *co } func run(o *action.ListOptions, printType cluster.PrintType) error { - // if format is JSON or YAML, use default printer to output the result. + // if format is JSON or YAML, use default printer. if o.Format == printer.JSON || o.Format == printer.YAML { _, err := o.Run() return err @@ -185,7 +186,8 @@ func run(o *action.ListOptions, printType cluster.PrintType) error { } opt := &cluster.PrinterOptions{ - ShowLabels: o.ShowLabels, + ShowLabels: o.ShowLabels, + StatusFilter: o.Status, } p := cluster.NewPrinter(o.IOStreams.Out, printType, opt) diff --git a/pkg/cmd/cluster/list_logs.go b/pkg/cmd/cluster/list_logs.go index db26159a5..6eef0822c 100644 --- a/pkg/cmd/cluster/list_logs.go +++ b/pkg/cmd/cluster/list_logs.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -145,8 +145,7 @@ func (o *ListLogsOptions) printListLogs(dataObj *cluster.ClusterObjects) error { tbl := printer.NewTablePrinter(o.Out) logFilesData := o.gatherLogFilesData(dataObj.Cluster, dataObj.ClusterDef, dataObj.Pods) if len(logFilesData) == 0 { - fmt.Fprintf(o.ErrOut, "No log files found. You can enable the log feature with the kbcli command below.\n"+ - "kbcli cluster update %s --enable-all-logs=true --namespace %s\n", dataObj.Cluster.Name, dataObj.Cluster.Namespace) + fmt.Fprintf(o.ErrOut, "No log files found. \n") } else { tbl.SetHeader("INSTANCE", "LOG-TYPE", "FILE-PATH", "SIZE", "LAST-WRITTEN", "COMPONENT") for _, f := range logFilesData { diff --git a/pkg/cmd/cluster/list_logs_test.go b/pkg/cmd/cluster/list_logs_test.go index 117c7387f..e8b705d1e 100644 --- a/pkg/cmd/cluster/list_logs_test.go +++ b/pkg/cmd/cluster/list_logs_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/list_ops.go b/pkg/cmd/cluster/list_ops.go index 37b1396aa..c2aa82d49 100644 --- a/pkg/cmd/cluster/list_ops.go +++ b/pkg/cmd/cluster/list_ops.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -194,33 +194,6 @@ func getComponentNameFromOps(ops *opsv1alpha1.OpsRequest) string { return strings.Join(components, ",") } -func getTemplateNameFromOps(ops opsv1alpha1.OpsRequestSpec) string { - if ops.Type != opsv1alpha1.ReconfiguringType { - return "" - } - - tpls := make([]string, 0) - // TODO: support reconfigures - for _, config := range ops.Reconfigures[0].Configurations { - tpls = append(tpls, config.Name) - } - return strings.Join(tpls, ",") -} - -func getKeyNameFromOps(ops opsv1alpha1.OpsRequestSpec) string { - if ops.Type != opsv1alpha1.ReconfiguringType { - return "" - } - - keys := make([]string, 0) - for _, config := range ops.Reconfigures[0].Configurations { - for _, key := range config.Keys { - keys = append(keys, key.Key) - } - } - return strings.Join(keys, ",") -} - func (o *opsListOptions) containsIgnoreCase(s []string, e string) bool { for i := range s { if strings.EqualFold(s[i], e) { diff --git a/pkg/cmd/cluster/list_ops_test.go b/pkg/cmd/cluster/list_ops_test.go index 2e212765a..5f7e8bc2d 100644 --- a/pkg/cmd/cluster/list_ops_test.go +++ b/pkg/cmd/cluster/list_ops_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/list_test.go b/pkg/cmd/cluster/list_test.go index 340d9846a..d6482adea 100644 --- a/pkg/cmd/cluster/list_test.go +++ b/pkg/cmd/cluster/list_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -24,9 +24,9 @@ import ( "net/http" "strings" - kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -37,6 +37,7 @@ import ( clientfake "k8s.io/client-go/rest/fake" cmdtesting "k8s.io/kubectl/pkg/cmd/testing" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" "github.com/apecloud/kbcli/pkg/cluster" @@ -45,12 +46,6 @@ import ( ) var _ = Describe("list", func() { - var ( - streams genericiooptions.IOStreams - out *bytes.Buffer - tf *cmdtesting.TestFactory - ) - const ( namespace = "test" clusterName = "test" @@ -59,13 +54,32 @@ var _ = Describe("list", func() { verticalScalingReason = "VerticalScaling" ) + var ( + streams genericiooptions.IOStreams + out *bytes.Buffer + tf *cmdtesting.TestFactory + ) + + // httpResp returns a *http.Response for the given runtime.Object. + httpResp := func(codec runtime.Codec, obj runtime.Object) *http.Response { + return &http.Response{ + StatusCode: http.StatusOK, + Header: cmdtesting.DefaultHeader(), + Body: cmdtesting.ObjBody(codec, obj), + } + } + BeforeEach(func() { streams, _, out, _ = genericiooptions.NewTestIOStreams() tf = testing.NewTestFactory(namespace) - _ = kbappsv1.AddToScheme(scheme.Scheme) + // Prepare schemes + Expect(kbappsv1.AddToScheme(scheme.Scheme)).Should(Succeed()) codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) - cluster := testing.FakeCluster(clusterName, namespace, metav1.Condition{ + + By("Creating test clusters and pods") + // Prepare test data + baseCluster := testing.FakeCluster(clusterName, namespace, metav1.Condition{ Type: appsv1alpha1.ConditionTypeApplyResources, Status: metav1.ConditionFalse, Reason: "HorizontalScaleFailed", @@ -76,91 +90,194 @@ var _ = Describe("list", func() { Reason: verticalScalingReason, }) clusterWithVerticalScaling.Status.Phase = kbappsv1.UpdatingClusterPhase + clusterWithAbnormalPhase := testing.FakeCluster(clusterName2, namespace) clusterWithAbnormalPhase.Status.Phase = kbappsv1.AbnormalClusterPhase + pods := testing.FakePods(3, namespace, clusterName) - httpResp := func(obj runtime.Object) *http.Response { - return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, obj)} - } + By("Configuring the fake REST client responses") tf.UnstructuredClient = &clientfake.RESTClient{ GroupVersion: schema.GroupVersion{Group: types.AppsAPIGroup, Version: types.AppsAPIVersion}, NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, Client: clientfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { urlPrefix := "/api/v1/namespaces/" + namespace - return map[string]*http.Response{ - "/namespaces/" + namespace + "/clusters": httpResp(&kbappsv1.ClusterList{Items: []kbappsv1.Cluster{*cluster}}), - "/namespaces/" + namespace + "/clusters/" + clusterName: httpResp(cluster), - "/namespaces/" + namespace + "/clusters/" + clusterName1: httpResp(clusterWithVerticalScaling), - "/namespaces/" + namespace + "/clusters/" + clusterName2: httpResp(clusterWithAbnormalPhase), - "/namespaces/" + namespace + "/secrets": httpResp(testing.FakeSecrets(namespace, clusterName)), - "/api/v1/nodes/" + testing.NodeName: httpResp(testing.FakeNode()), - urlPrefix + "/services": httpResp(&corev1.ServiceList{}), - urlPrefix + "/secrets": httpResp(testing.FakeSecrets(namespace, clusterName)), - urlPrefix + "/pods": httpResp(pods), - urlPrefix + "/events": httpResp(testing.FakeEvents()), - }[req.URL.Path], nil + pathToResponse := map[string]*http.Response{ + "/namespaces/" + namespace + "/clusters": httpResp(codec, &kbappsv1.ClusterList{Items: []kbappsv1.Cluster{*baseCluster}}), + "/namespaces/" + namespace + "/clusters/" + clusterName: httpResp(codec, baseCluster), + "/namespaces/" + namespace + "/clusters/" + clusterName1: httpResp(codec, clusterWithVerticalScaling), + "/namespaces/" + namespace + "/clusters/" + clusterName2: httpResp(codec, clusterWithAbnormalPhase), + "/namespaces/" + namespace + "/secrets": httpResp(codec, testing.FakeSecrets(namespace, clusterName)), + "/api/v1/nodes/" + testing.NodeName: httpResp(codec, testing.FakeNode()), + urlPrefix + "/services": httpResp(codec, &corev1.ServiceList{}), + urlPrefix + "/secrets": httpResp(codec, testing.FakeSecrets(namespace, clusterName)), + urlPrefix + "/pods": httpResp(codec, pods), + urlPrefix + "/events": httpResp(codec, testing.FakeEvents()), + } + return pathToResponse[req.URL.Path], nil }), } tf.Client = tf.UnstructuredClient - tf.FakeDynamicClient = testing.FakeDynamicClient(cluster, clusterWithVerticalScaling, clusterWithAbnormalPhase, testing.FakeClusterDef()) + tf.FakeDynamicClient = testing.FakeDynamicClient( + baseCluster, + clusterWithVerticalScaling, + clusterWithAbnormalPhase, + testing.FakeClusterDef(), + ) }) AfterEach(func() { + By("Cleaning up the test factory") tf.Cleanup() }) - It("list", func() { + // Helper to run command and return output + runCmd := func(cmd *cobra.Command, args ...string) string { + out.Reset() + cmd.Run(cmd, args) + return out.String() + } + + It("list clusters by name", func() { + By("Running list command with cluster names") cmd := NewListCmd(tf, streams) - Expect(cmd).ShouldNot(BeNil()) + output := runCmd(cmd, clusterName, clusterName1, clusterName2) - cmd.Run(cmd, []string{clusterName, clusterName1, clusterName2}) - Expect(out.String()).Should(ContainSubstring(clusterName)) - Expect(out.String()).Should(ContainSubstring(string(appsv1alpha1.UpdatingClusterPhase))) - Expect(out.String()).Should(ContainSubstring(cluster.ConditionsError)) - Expect(out.String()).Should(ContainSubstring(string(appsv1alpha1.AbnormalClusterPhase))) + By("Checking output for expected clusters and statuses") + Expect(output).Should(ContainSubstring(clusterName)) + Expect(output).Should(ContainSubstring(string(appsv1alpha1.UpdatingClusterPhase))) + Expect(output).Should(ContainSubstring(cluster.ConditionsError)) + Expect(output).Should(ContainSubstring(string(appsv1alpha1.AbnormalClusterPhase))) }) - It("list instances", func() { + It("list instances for a specific cluster", func() { + By("Running list-instances command for a given cluster") cmd := NewListInstancesCmd(tf, streams) - Expect(cmd).ShouldNot(BeNil()) + output := runCmd(cmd, clusterName) - cmd.Run(cmd, []string{clusterName}) - Expect(out.String()).Should(ContainSubstring(testing.NodeName)) + By("Checking output for expected node name in instances list") + Expect(output).Should(ContainSubstring(testing.NodeName)) }) - It("list components", func() { + It("list components for a specific cluster", func() { + By("Running list-components command for a given cluster") cmd := NewListComponentsCmd(tf, streams) - Expect(cmd).ShouldNot(BeNil()) + output := runCmd(cmd, clusterName) - cmd.Run(cmd, []string{clusterName}) - Expect(out.String()).Should(ContainSubstring(testing.ComponentName)) + By("Checking output for expected component name") + Expect(output).Should(ContainSubstring(testing.ComponentName)) }) - It("list events", func() { + It("list events for a specific cluster", func() { + By("Running list-events command for a given cluster") cmd := NewListEventsCmd(tf, streams) - Expect(cmd).ShouldNot(BeNil()) + output := runCmd(cmd, clusterName) - cmd.Run(cmd, []string{clusterName}) - Expect(len(strings.Split(out.String(), "\n")) > 1).Should(BeTrue()) + By("Verifying that multiple events are returned") + Expect(len(strings.Split(strings.TrimSpace(output), "\n")) > 1).Should(BeTrue()) }) - It("output wide", func() { + It("output wide with cluster name", func() { + By("Setting output format to wide") cmd := NewListCmd(tf, streams) - Expect(cmd).ShouldNot(BeNil()) + Expect(cmd.Flags().Set("output", "wide")).Should(Succeed()) + + By("Running the command with a specific cluster") + output := runCmd(cmd, clusterName) + Expect(output).Should(ContainSubstring(clusterName)) + }) + It("output wide without specifying cluster names", func() { + By("Setting output format to wide without arguments") + cmd := NewListCmd(tf, streams) Expect(cmd.Flags().Set("output", "wide")).Should(Succeed()) - cmd.Run(cmd, []string{clusterName}) - Expect(out.String()).Should(ContainSubstring(clusterName)) + + output := runCmd(cmd) + Expect(output).Should(ContainSubstring(clusterName)) }) - It("output wide without args", func() { + It("should list clusters sorted and filtered by status", func() { + By("Preparing multiple clusters for sorting and filtering test") + clusterA := testing.FakeCluster("clusterA", "ns1", metav1.Condition{ + Type: appsv1alpha1.ConditionTypeReady, + Status: metav1.ConditionTrue, + Reason: "Ready", + }) + clusterA.Status.Phase = kbappsv1.RunningClusterPhase + clusterA.Spec.ClusterDef = "cd-mysql" + + clusterB := testing.FakeCluster("clusterB", "ns1", metav1.Condition{ + Type: appsv1alpha1.ConditionTypeReady, + Status: metav1.ConditionTrue, + Reason: "Ready", + }) + clusterB.Status.Phase = kbappsv1.CreatingClusterPhase + clusterB.Spec.ClusterDef = "cd-mysql" + + clusterC := testing.FakeCluster("clusterC", "ns2", metav1.Condition{ + Type: appsv1alpha1.ConditionTypeReady, + Status: metav1.ConditionFalse, + Reason: "Scaling", + }) + clusterC.Status.Phase = kbappsv1.UpdatingClusterPhase + clusterC.Spec.ClusterDef = "cd-postgres" + + codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) + tf.UnstructuredClient = &clientfake.RESTClient{ + GroupVersion: schema.GroupVersion{Group: types.AppsAPIGroup, Version: types.AppsAPIVersion}, + NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, + + Client: clientfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + pathToResponse := map[string]*http.Response{ + "/namespaces/" + namespace + "/clusters": httpResp(codec, &kbappsv1.ClusterList{ + Items: []kbappsv1.Cluster{*clusterA, *clusterB, *clusterC}, + }), + } + return pathToResponse[req.URL.Path], nil + }), + } + + tf.Client = tf.UnstructuredClient + + tf.FakeDynamicClient = testing.FakeDynamicClient(clusterA, clusterB, clusterC, testing.FakeClusterDef()) + + By("Listing all clusters across all namespaces") cmd := NewListCmd(tf, streams) - Expect(cmd).ShouldNot(BeNil()) + output := runCmd(cmd) - Expect(cmd.Flags().Set("output", "wide")).Should(Succeed()) - cmd.Run(cmd, []string{}) - Expect(out.String()).Should(ContainSubstring(clusterName)) + lines := strings.Split(strings.TrimSpace(output), "\n") + Expect(lines).To(ContainElements( + ContainSubstring("clusterB"), // Creating + ContainSubstring("clusterA"), // Running + ContainSubstring("clusterC"), // Updating + )) + + // Extract indices for validation + var bIndex, aIndex, cIndex int + for i, line := range lines { + switch { + case strings.Contains(line, "clusterB"): + bIndex = i + case strings.Contains(line, "clusterA"): + aIndex = i + case strings.Contains(line, "clusterC"): + cIndex = i + } + } + + Expect(bIndex).To(BeNumerically("<", aIndex)) // Creating < Running + Expect(aIndex).To(BeNumerically("<", cIndex)) // Running < Updating + + By("Filtering clusters by status Running") + out.Reset() + cmd = NewListCmd(tf, streams) + Expect(cmd.Flags().Set("status", "Running")).Should(Succeed()) + output = runCmd(cmd) + + // Only clusterA should remain (Running) + Expect(output).To(ContainSubstring("clusterA")) + Expect(output).NotTo(ContainSubstring("clusterB")) + Expect(output).NotTo(ContainSubstring("clusterC")) }) }) diff --git a/pkg/cmd/cluster/logs.go b/pkg/cmd/cluster/logs.go index fe3912b02..505c5f50b 100644 --- a/pkg/cmd/cluster/logs.go +++ b/pkg/cmd/cluster/logs.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/logs_test.go b/pkg/cmd/cluster/logs_test.go index 811dfaa7e..29320e13f 100644 --- a/pkg/cmd/cluster/logs_test.go +++ b/pkg/cmd/cluster/logs_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -154,9 +154,17 @@ var _ = Describe("logs", func() { compDefName := "component-type" compName := "component-name" compDef := testapps.NewComponentDefinitionFactory(compDefName). - AddLogConfig("slow", "/log/mysql/*slow.log"). - AddLogConfig("error", "/log/mysql/*.err"). Get() + compDef.Spec.LogConfigs = []kbappsv1.LogConfig{ + { + Name: "slow", + FilePathPattern: "/log/mysql/*slow.log", + }, + { + Name: "error", + FilePathPattern: "/log/mysql/*err", + }, + } pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", diff --git a/pkg/cmd/cluster/operations.go b/pkg/cmd/cluster/operations.go index bfab81346..8ced040bd 100755 --- a/pkg/cmd/cluster/operations.go +++ b/pkg/cmd/cluster/operations.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -22,6 +22,7 @@ package cluster import ( "context" "fmt" + "math" "strings" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" @@ -38,8 +39,10 @@ import ( apiruntime "k8s.io/apimachinery/pkg/runtime" apitypes "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/json" + "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/kube-openapi/pkg/validation/spec" + "k8s.io/kubectl/pkg/cmd/testing" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" "sigs.k8s.io/controller-runtime/pkg/client" @@ -103,17 +106,24 @@ type OperationsOptions struct { ExposeType string `json:"-"` ExposeSubType string `json:"-"` ExposeEnabled string `json:"exposeEnabled,omitempty"` + RoleSelector string `json:"roleSelector,omitempty"` Services []opsv1alpha1.OpsService `json:"services,omitempty"` // Switchover options - Component string `json:"component"` - Instance string `json:"instance"` - BackupName string `json:"-"` - Inplace bool `json:"-"` - InstanceNames []string `json:"-"` - Nodes []string `json:"-"` - RebuildInstanceFrom []opsv1alpha1.RebuildInstance `json:"rebuildInstanceFrom,omitempty"` - Env []string `json:"-"` + Component string `json:"component"` + ComponentObjectName string `json:"componentObjectName,omitempty"` + Instance string `json:"instance"` + Candidate string `json:"candidate"` + BackupName string `json:"-"` + Inplace bool `json:"-"` + InstanceNames []string `json:"-"` + Nodes []string `json:"-"` + RebuildInstanceFrom []opsv1alpha1.RebuildInstance `json:"rebuildInstanceFrom,omitempty"` + Env []string `json:"-"` + SourceBackupTargetName string `json:"-"` + + // Stop and Start options + isComponentsFlagOptional bool } func newBaseOperationsOptions(f cmdutil.Factory, streams genericiooptions.IOStreams, @@ -139,6 +149,7 @@ func newBaseOperationsOptions(f cmdutil.Factory, streams genericiooptions.IOStre GVR: types.OpsGVR(), CustomOutPut: customOutPut, }, + isComponentsFlagOptional: opsType == opsv1alpha1.StopType || opsType == opsv1alpha1.StartType, } o.OpsTypeLower = strings.ToLower(string(o.OpsType)) @@ -199,21 +210,6 @@ func (o *OperationsOptions) CompleteComponentsFlag() error { return nil } -func (o *OperationsOptions) CompleteSwitchoverOps() error { - clusterObj, err := cluster.GetClusterByName(o.Dynamic, o.Name, o.Namespace) - if err != nil { - return err - } - - if o.Component == "" { - if len(clusterObj.Spec.ComponentSpecs) > 1 { - return fmt.Errorf("there are multiple components in cluster, please use --component to specify the component for promote") - } - o.Component = clusterObj.Spec.ComponentSpecs[0].Name - } - return nil -} - func (o *OperationsOptions) validateUpgrade(cluster *appsv1.Cluster) error { if o.ComponentDefinitionName == "nil" && o.ServiceVersion == "nil" { return fmt.Errorf("missing component-def or service-version") @@ -337,7 +333,7 @@ func (o *OperationsOptions) Validate() error { } // common validate for componentOps - if o.HasComponentNamesFlag && len(o.ComponentNames) == 0 { + if o.HasComponentNamesFlag && !o.isComponentsFlagOptional && len(o.ComponentNames) == 0 { return fmt.Errorf(`missing components, please specify the "--components" flag for the cluster`) } if err = o.validateComponents(cluster); err != nil { @@ -429,70 +425,89 @@ func (o *OperationsOptions) validatePromote(clusterObj *appsv1.Cluster) error { podObj = &corev1.Pod{} ) - if o.Component == "" && o.Instance == "" { - return fmt.Errorf("at least one of --component or --instance is required") + if o.Candidate == "" { + return fmt.Errorf("--candidate is required") } // if the instance is not specified, do not need to check the validity of the instance - if o.Instance != "" { - // checks the validity of the instance whether it belongs to the current component and ensure it is not the primary or leader instance currently. - podKey := client.ObjectKey{ - Namespace: clusterObj.Namespace, - Name: o.Instance, - } - if err := util.GetResourceObjectFromGVR(types.PodGVR(), podKey, o.Dynamic, podObj); err != nil || podObj == nil { - return fmt.Errorf("instance %s not found, please check the validity of the instance using \"kbcli cluster list-instances\"", o.Instance) - } - if o.Component == "" { - o.Component = cluster.GetPodComponentName(podObj) - } + // checks the validity of the instance whether it belongs to the current component and ensure it is not the primary or leader instance currently. + podKey := client.ObjectKey{ + Namespace: clusterObj.Namespace, + Name: o.Candidate, } + if err := util.GetResourceObjectFromGVR(types.PodGVR(), podKey, o.Dynamic, podObj); err != nil || podObj == nil { + return fmt.Errorf("instance %s not found, please check the validity of the instance using \"kbcli cluster list-instances\"", o.Candidate) + } + o.ComponentObjectName = constant.GenerateClusterComponentName(clusterObj.Name, podObj.Labels[constant.KBAppComponentLabelKey]) getAndValidatePod := func(targetRoles ...string) error { // if the instance is not specified, do not need to check the validity of the instance - if o.Instance == "" { + if o.Candidate == "" { return nil } v, ok := podObj.Labels[constant.RoleLabelKey] if !ok || v == "" { - return fmt.Errorf("instance %s cannot be promoted because it had a invalid role label", o.Instance) + return fmt.Errorf("instance %s cannot be promoted because it had a invalid role label", o.Candidate) } for _, targetRole := range targetRoles { if v == targetRole { - return fmt.Errorf("instanceName %s cannot be promoted because it is already the targetRole %s instance", o.Instance, targetRole) + return fmt.Errorf("instanceName %s cannot be promoted because it is already the targetRole %s instance", o.Candidate, targetRole) } } - if cluster.GetPodComponentName(podObj) != o.Component || podObj.Labels[constant.AppInstanceLabelKey] != clusterObj.Name { - return fmt.Errorf("instanceName %s does not belong to the current component, please check the validity of the instance using \"kbcli cluster list-instances\"", o.Instance) + if podObj.Labels[constant.AppInstanceLabelKey] != clusterObj.Name { + return fmt.Errorf("instanceName %s does not belong to the current cluster, please check the validity of the instance using \"kbcli cluster list-instances\"", o.Candidate) } return nil } getTargetRole := func(roles []appsv1.ReplicaRole) (string, error) { + pods, err := o.Client.CoreV1().Pods(clusterObj.Namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s,%s=%s", constant.KBAppComponentLabelKey, + podObj.Labels[constant.KBAppComponentLabelKey], constant.AppInstanceLabelKey, clusterObj.Name), + }) + if err != nil { + return "", err + } + if o.Instance != "" { + for _, pod := range pods.Items { + if pod.Name == o.Instance { + return pod.Labels[constant.RoleLabelKey], nil + } + } + return "", fmt.Errorf("instance %s not found, please check the validity of the instance using \"kbcli cluster list-instances\"", o.Instance) + } targetRole := "" if len(roles) == 0 { return targetRole, fmt.Errorf("component has no roles definition, does not support switchover") } - for _, role := range roles { - if role.Serviceable && role.Writable { - if targetRole != "" { - return targetRole, fmt.Errorf("componentDefinition has more than role is serviceable and writable, does not support switchover") - } - targetRole = role.Name + // HACK: assume the role with highest priority to be writable + highestPriority := math.MinInt + var highestPriorityRole appsv1.ReplicaRole + for i := range compDefObj.Spec.Roles { + role := compDefObj.Spec.Roles[i] + if role.UpdatePriority > highestPriority { + highestPriority = role.UpdatePriority + highestPriorityRole = role + } + } + for _, pod := range pods.Items { + if pod.Labels[constant.RoleLabelKey] == highestPriorityRole.Name { + o.Instance = pod.Name + break } } - return targetRole, nil + return highestPriorityRole.Name, nil } // check componentDefinition exist compDefKey := client.ObjectKey{ Namespace: "", - Name: cluster.GetComponentSpec(clusterObj, o.Component).ComponentDef, + Name: cluster.GetComponentSpec(clusterObj, cluster.GetPodComponentName(podObj)).ComponentDef, } if err := util.GetResourceObjectFromGVR(types.CompDefGVR(), compDefKey, o.Dynamic, &compDefObj); err != nil { return err } if compDefObj.Spec.LifecycleActions == nil || compDefObj.Spec.LifecycleActions.Switchover == nil { - return fmt.Errorf(`this component "%s does not support switchover, you can define the switchover action in the componentDef "%s"`, o.Component, compDefKey.Name) + return fmt.Errorf(`this instance "%s does not support switchover, you can define the switchover action in the componentDef "%s"`, o.Candidate, compDefKey.Name) } targetRole, err := getTargetRole(compDefObj.Spec.Roles) if err != nil { @@ -585,13 +600,8 @@ func (o *OperationsOptions) fillExpose() error { } else { svc.ServiceType = corev1.ServiceTypeLoadBalancer } - - roleSelector, err := util.GetDefaultRoleSelector(o.Dynamic, clusterObj, componentSpec.ComponentDef, componentSpec.ComponentDef) - if err != nil { - return err - } - if len(roleSelector) > 0 { - svc.RoleSelector = roleSelector + if o.RoleSelector != "" { + svc.RoleSelector = o.RoleSelector } o.Services = append(o.Services, svc) return nil @@ -841,6 +851,7 @@ func NewExposeCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra. cmd.Flags().StringVar(&o.ExposeSubType, "sub-type", "LoadBalancer", "Expose sub type, currently supported types are 'NodePort', 'LoadBalancer', only available if type is intranet") cmd.Flags().StringVar(&o.ExposeEnabled, "enable", "", "Enable or disable the expose, values can be true or false") cmd.Flags().BoolVar(&o.AutoApprove, "auto-approve", false, "Skip interactive approval before exposing the cluster") + cmd.Flags().StringVar(&o.RoleSelector, "role-selector", "", "The Component's exposed Services may target replicas based on their roles using `roleSelector`, this flag must be set when the component specified has roles") util.CheckErr(cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{string(util.ExposeToIntranet), string(util.ExposeToInternet)}, cobra.ShellCompDirectiveNoFileComp @@ -859,11 +870,14 @@ func NewExposeCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra. var stopExample = templates.Examples(` # stop the cluster and release all the pods of the cluster kbcli cluster stop mycluster + + # stop the component of the cluster and release all the pods of the component + kbcli cluster stop mycluster --components=mysql `) // NewStopCmd creates a stop command func NewStopCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { - o := newBaseOperationsOptions(f, streams, opsv1alpha1.StopType, false) + o := newBaseOperationsOptions(f, streams, opsv1alpha1.StopType, true) cmd := &cobra.Command{ Use: "stop NAME", Short: "Stop the cluster and release all the pods of the cluster.", @@ -885,11 +899,14 @@ func NewStopCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Co var startExample = templates.Examples(` # start the cluster when cluster is stopped kbcli cluster start mycluster + + # start the component of the cluster when cluster is stopped + kbcli cluster start mycluster --components=mysql `) // NewStartCmd creates a start command func NewStartCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { - o := newBaseOperationsOptions(f, streams, opsv1alpha1.StartType, false) + o := newBaseOperationsOptions(f, streams, opsv1alpha1.StartType, true) o.AutoApprove = true cmd := &cobra.Command{ Use: "start NAME", @@ -976,20 +993,14 @@ func NewCancelCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra. var promoteExample = templates.Examples(` # Promote the instance mycluster-mysql-1 as the new primary or leader. - kbcli cluster promote mycluster --instance mycluster-mysql-1 - - # Promote a non-primary or non-leader instance as the new primary or leader, the new primary or leader is determined by the system. - kbcli cluster promote mycluster - - # If the cluster has multiple components, you need to specify a component, otherwise an error will be reported. - kbcli cluster promote mycluster --component=mysql --instance mycluster-mysql-1 + kbcli cluster promote mycluster --candidate mycluster-mysql-1 `) // NewPromoteCmd creates a promote command func NewPromoteCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { o := newBaseOperationsOptions(f, streams, opsv1alpha1.SwitchoverType, false) cmd := &cobra.Command{ - Use: "promote NAME [--component=] [--instance ]", + Use: "promote NAME [--instance ]", Short: "Promote a non-primary or non-leader instance as the new primary or leader of the cluster", Example: promoteExample, ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.ClusterGVR()), @@ -998,14 +1009,14 @@ func NewPromoteCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra cmdutil.BehaviorOnFatal(printer.FatalWithRedColor) cmdutil.CheckErr(o.Complete()) cmdutil.CheckErr(o.CompleteComponentsFlag()) - cmdutil.CheckErr(o.CompleteSwitchoverOps()) cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.Run()) }, } - flags.AddComponentFlag(f, cmd, &o.Component, "Specify the component name of the cluster, if the cluster has multiple components, you need to specify a component") - cmd.Flags().StringVar(&o.Instance, "instance", "", "Specify the instance name as the new primary or leader of the cluster, you can get the instance name by running \"kbcli cluster list-instances\"") + cmd.Flags().StringVar(&o.Candidate, "candidate", "", "Specify the instance name as the new primary or leader of the cluster, you can get the instance name by running \"kbcli cluster list-instances\"") + cmd.Flags().StringVar(&o.Instance, "instance", "", "Specify the instance name that will transfer its role to the candidate pod, If not set, the current primary or leader of the cluster will be used.") cmd.Flags().BoolVar(&o.AutoApprove, "auto-approve", false, "Skip interactive approval before promote the instance") + _ = cmd.MarkFlagRequired("candidate") o.addCommonFlags(cmd, f) return cmd } @@ -1069,8 +1080,23 @@ func buildCustomOpsExamples(t unstructured.Unstructured) string { return templates.Examples(baseCommand) } +// getTempFactory get a new factory when given factory isn't a TestFactory. +func getTempFactory(f cmdutil.Factory) cmdutil.Factory { + if factory, ok := f.(*testing.TestFactory); ok { + return factory + } else { + kubeConfigFlags := genericclioptions.NewConfigFlags(true) + matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags) + newFactory := cmdutil.NewFactory(matchVersionKubeConfigFlags) + return newFactory + } +} + func buildCustomOpsCmds(option *OperationsOptions) []*cobra.Command { - dynamic, _ := option.Factory.DynamicClient() + dynamic, _ := getTempFactory(option.Factory).DynamicClient() + if dynamic == nil { + return nil + } opsDefs, _ := dynamic.Resource(types.OpsDefinitionGVR()).List(context.TODO(), metav1.ListOptions{}) if opsDefs == nil { return nil @@ -1202,7 +1228,7 @@ func (o *CustomOperations) completeCustomSpec(cmd *cobra.Command) error { } } // validate if flags values are legal. - data, err := common.CoverStringToInterfaceBySchemaType(o.SchemaProperties, paramMap) + data, err := common.ConvertStringToInterfaceBySchemaType(o.SchemaProperties, paramMap) if err != nil { return err } @@ -1300,10 +1326,11 @@ func NewRebuildInstanceCmd(f cmdutil.Factory, streams genericiooptions.IOStreams ComponentOps: opsv1alpha1.ComponentOps{ ComponentName: compName, }, - Instances: instances, - InPlace: o.Inplace, - BackupName: o.BackupName, - RestoreEnv: envVars, + Instances: instances, + InPlace: o.Inplace, + BackupName: o.BackupName, + RestoreEnv: envVars, + SourceBackupTargetName: o.SourceBackupTargetName, }, } return nil @@ -1328,6 +1355,7 @@ func NewRebuildInstanceCmd(f cmdutil.Factory, streams genericiooptions.IOStreams cmd.Flags().StringVar(&o.BackupName, "backup", "", "instances will be rebuild by the specified backup.") cmd.Flags().StringSliceVar(&o.InstanceNames, "instances", nil, "instances which need to rebuild.") util.CheckErr(flags.CompletedInstanceFlag(cmd, f, "instances")) + cmd.Flags().StringVar(&o.SourceBackupTargetName, "source-backup-target", "", "To rebuild a sharding component instance from a backup, you can specify the name of the source backup target") cmd.Flags().StringSliceVar(&o.Nodes, "node", nil, `specified the target node which rebuilds the instance on the node otherwise will rebuild on a random node. format: insName1=nodeName,insName2=nodeName`) cmd.Flags().StringArrayVar(&o.Env, "restore-env", []string{}, "provide the necessary env for the 'Restore' operation from the backup. format: key1=value, key2=value") return cmd diff --git a/pkg/cmd/cluster/operations_test.go b/pkg/cmd/cluster/operations_test.go index 9f420c993..bab77bb53 100644 --- a/pkg/cmd/cluster/operations_test.go +++ b/pkg/cmd/cluster/operations_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -351,27 +351,23 @@ var _ = Describe("operations", func() { Expect(o.CompleteComponentsFlag()).Should(Succeed()) Expect(o.ComponentNames).Should(BeEmpty()) - By("validate failed because there are multi-components in cluster and not specify the component") - Expect(o.CompleteComponentsFlag()).Should(Succeed()) - Expect(o.CompleteSwitchoverOps()).ShouldNot(Succeed()) - Expect(testing.ContainExpectStrings(o.CompleteSwitchoverOps().Error(), "there are multiple components in cluster, please use --component to specify the component for promote")).Should(BeTrue()) - By("validate failed because o.Instance is illegal ") o.Name = clusterName1 o.Component = testing.ComponentName - o.Instance = fmt.Sprintf("%s-%s-%d", clusterName1, testing.ComponentName, 5) + o.Candidate = fmt.Sprintf("%s-%s-%d", clusterName1, testing.ComponentName, 5) Expect(o.Validate()).ShouldNot(Succeed()) Expect(testing.ContainExpectStrings(o.Validate().Error(), "not found")).Should(BeTrue()) By("validate failed because o.Instance is already leader and cannot be promoted") - o.Instance = fmt.Sprintf("%s-%s-%d", clusterName1, testing.ComponentName, 0) + o.Candidate = fmt.Sprintf("%s-%s-%d", clusterName1, testing.ComponentName, 0) Expect(o.Validate()).ShouldNot(Succeed()) Expect(testing.ContainExpectStrings(o.Validate().Error(), "cannot be promoted because it is already the targetRole")).Should(BeTrue()) By("validate failed because o.Instance does not belong to the current component") - o.Instance = fmt.Sprintf("%s-%s-%d", clusterName, testing.ComponentName, 1) + o.Candidate = fmt.Sprintf("%s-%s-%d", clusterName, testing.ComponentName, 1) + o.Name = clusterName1 Expect(o.Validate()).ShouldNot(Succeed()) - Expect(testing.ContainExpectStrings(o.Validate().Error(), "does not belong to the current component")).Should(BeTrue()) + Expect(testing.ContainExpectStrings(o.Validate().Error(), "does not belong to the current cluster")).Should(BeTrue()) }) It("Switchover ops base on component definition", func() { @@ -386,27 +382,23 @@ var _ = Describe("operations", func() { Expect(o.CompleteComponentsFlag()).Should(Succeed()) Expect(o.ComponentNames).Should(BeEmpty()) - By("validate failed because there are multi-components in cluster and not specify the component") - Expect(o.CompleteComponentsFlag()).Should(Succeed()) - Expect(o.CompleteSwitchoverOps()).ShouldNot(Succeed()) - Expect(testing.ContainExpectStrings(o.CompleteSwitchoverOps().Error(), "there are multiple components in cluster, please use --component to specify the component for promote")).Should(BeTrue()) - By("validate failed because o.Instance is illegal ") o.Name = clusterNameWithCompDef - o.Instance = fmt.Sprintf("%s-%s-%d", clusterNameWithCompDef, testing.ComponentName, 5) + o.Candidate = fmt.Sprintf("%s-%s-%d", clusterNameWithCompDef, testing.ComponentName, 5) Expect(o.Validate()).ShouldNot(Succeed()) Expect(testing.ContainExpectStrings(o.Validate().Error(), "not found")).Should(BeTrue()) By("validate failed because o.Instance is already leader and cannot be promoted") - o.Instance = fmt.Sprintf("%s-%s-%d", clusterNameWithCompDef, testing.ComponentName, 0) + o.Candidate = fmt.Sprintf("%s-%s-%d", clusterNameWithCompDef, testing.ComponentName, 0) Expect(o.Validate()).ShouldNot(Succeed()) Expect(testing.ContainExpectStrings(o.Validate().Error(), "cannot be promoted because it is already the targetRole")).Should(BeTrue()) - By("validate failed because o.Instance does not belong to the current component") - o.Instance = fmt.Sprintf("%s-%s-%d", clusterName1, testing.ComponentName, 1) + By("validate failed because o.Instance does not belong to the current cluster") + o.Candidate = fmt.Sprintf("%s-%s-%d", clusterName1, testing.ComponentName, 1) o.Component = testing.ComponentName + o.Name = clusterName Expect(o.Validate()).ShouldNot(Succeed()) - Expect(testing.ContainExpectStrings(o.Validate().Error(), "does not belong to the current component")).Should(BeTrue()) + Expect(testing.ContainExpectStrings(o.Validate().Error(), "does not belong to the current cluster")).Should(BeTrue()) }) diff --git a/pkg/cmd/cluster/register.go b/pkg/cmd/cluster/register.go index 1d605324b..b2cfa29b6 100644 --- a/pkg/cmd/cluster/register.go +++ b/pkg/cmd/cluster/register.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -241,5 +241,5 @@ Use "kbcli cluster create {{ .ClusterType }}" to create a {{ .ClusterType }} clu _ = util.PrintGoTemplate(&builder, exampleTpl, map[string]interface{}{ "ClusterType": t.String(), }) - return templates.Examples(builder.String()) + return templates.Examples(builder.String()) + "\n" } diff --git a/pkg/cmd/cluster/register_test.go b/pkg/cmd/cluster/register_test.go index d3fdd51eb..73ab29c87 100644 --- a/pkg/cmd/cluster/register_test.go +++ b/pkg/cmd/cluster/register_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -98,11 +98,7 @@ var _ = Describe("cluster register", func() { ) AfterEach(func() { - cluster.ClearCharts(cluster.ClusterType(engine)) - }) - - It("test register chart by source", func() { - Expect(RegisterClusterChart(tf, streams, source, engine, "", "")).Should(Succeed()) + os.Remove(filepath.Join(cluster.CliChartsCacheDir, filepath.Base(source))) }) It("test register chart by engine, version and default repo", func() { diff --git a/pkg/cmd/cluster/suite_test.go b/pkg/cmd/cluster/suite_test.go index 0a6e291ff..4b8e74d29 100644 --- a/pkg/cmd/cluster/suite_test.go +++ b/pkg/cmd/cluster/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/cluster/update.go b/pkg/cmd/cluster/update.go index a2c8d6fd4..e2b7261f8 100644 --- a/pkg/cmd/cluster/update.go +++ b/pkg/cmd/cluster/update.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -20,23 +20,16 @@ along with this program. If not, see . package cluster import ( - "bytes" "context" "encoding/csv" - "encoding/json" "fmt" "strconv" "strings" - "text/template" kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - "github.com/google/uuid" - "github.com/pkg/errors" "github.com/robfig/cron/v3" "github.com/spf13/cobra" "github.com/spf13/pflag" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -45,15 +38,10 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" - cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/configuration" "github.com/apecloud/kubeblocks/pkg/dataprotection/utils" - "github.com/apecloud/kubeblocks/pkg/gotemplate" "github.com/apecloud/kbcli/pkg/action" "github.com/apecloud/kbcli/pkg/cluster" @@ -68,9 +56,6 @@ var clusterUpdateExample = templates.Examples(` # enable cluster monitor kbcli cluster update mycluster --monitor=true - # enable all logs - kbcli cluster update mycluster --enable-all-logs=true - # update cluster tolerations kbcli cluster update mycluster --tolerations='"key=engineType,value=mongo,operator=Equal,effect=NoSchedule","key=diskType,value=ssd,operator=Equal,effect=NoSchedule"' @@ -82,7 +67,7 @@ var clusterUpdateExample = templates.Examples(` # enable cluster auto backup kbcli cluster update mycluster --backup-enabled=true - + # update cluster backup retention period kbcli cluster update mycluster --backup-retention-period=1d @@ -109,7 +94,6 @@ type UpdatableFlags struct { // Add-on switches for cluster observability DisableExporter bool `json:"monitor"` - EnableAllLogs bool `json:"enableAllLogs"` // Configuration and options for cluster affinity and tolerations PodAntiAffinity string `json:"podAntiAffinity"` @@ -174,8 +158,7 @@ func NewUpdateCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra. func (f *UpdatableFlags) addFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&f.DisableExporter, "disable-exporter", true, "Enable or disable monitoring") - cmd.Flags().BoolVar(&f.EnableAllLogs, "enable-all-logs", false, "Enable advanced application all log extraction, set to true will ignore enabledLogs of component level, default is false") - cmd.Flags().StringVar(&f.TerminationPolicy, "termination-policy", "Delete", "Termination policy, one of: (DoNotTerminate, Halt, Delete, WipeOut)") + cmd.Flags().StringVar(&f.TerminationPolicy, "termination-policy", "Delete", "Termination policy, one of: (DoNotTerminate, Delete, WipeOut)") cmd.Flags().StringSliceVar(&f.TolerationsRaw, "tolerations", nil, `Tolerations for cluster, such as "key=value:effect, key:effect", for example '"engineType=mongo:NoSchedule", "diskType:NoSchedule"'`) cmd.Flags().BoolVar(&f.BackupEnabled, "backup-enabled", false, "Specify whether enabled automated backup") cmd.Flags().StringVar(&f.BackupRetentionPeriod, "backup-retention-period", "1d", "a time string ending with the 'd'|'D'|'h'|'H' character to describe how long the Backup should be retained") @@ -189,10 +172,9 @@ func (f *UpdatableFlags) addFlags(cmd *cobra.Command) { "termination-policy", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{ - "DoNotTerminate\tblock delete operation", - "Halt\tdelete workload resources such as statefulset, deployment workloads but keep PVCs", - "Delete\tbased on Halt and deletes PVCs", - "WipeOut\tbased on Delete and wipe out all volume snapshots and snapshot data from backup storage location", + "DoNotTerminate\tprevents deletion of the Cluster", + "Delete\tdeletes all runtime resources belong to the Cluster.", + "WipeOut\tdeletes all Cluster resources, including volume snapshots and backups in external storage.", }, cobra.ShellCompDirectiveNoFileComp })) } @@ -333,7 +315,6 @@ func (o *UpdateOptions) buildPatch() error { // monitor and logs "disable-exporter": {field: "disableExporter", obj: nil, fn: buildComps}, - "enable-all-logs": {field: "enable-all-logs", obj: nil, fn: buildComps}, // backup config "backup-enabled": {field: "enabled", obj: nil, fn: buildBackup}, @@ -425,8 +406,6 @@ func (o *UpdateOptions) buildComponents(field string, val string) error { switch field { case "disableExporter": return o.updateMonitor(val) - case "enable-all-logs": - return o.updateEnabledLog(val) default: return nil } @@ -464,200 +443,6 @@ func (o *UpdateOptions) buildBackup(field string, val string) error { } } -func (o *UpdateOptions) updateEnabledLog(val string) error { - boolVal, err := strconv.ParseBool(val) - if err != nil { - return err - } - - // update --enabled-all-logs=false for all components - if !boolVal { - // TODO: replace with new api - /* for index := range o.cluster.Spec.ComponentSpecs { - o.cluster.Spec.ComponentSpecs[index].EnabledLogs = nil - }*/ - return nil - } - - // update --enabled-all-logs=true for all components - cd, err := cluster.GetClusterDefByName(o.dynamic, o.cluster.Spec.ClusterDef) - if err != nil { - return err - } - // TODO: replace with new api - // set --enabled-all-logs at cluster components - // setEnableAllLogs(o.cluster, cd) - if err = o.reconfigureLogVariables(o.cluster, cd); err != nil { - return errors.Wrap(err, "failed to reconfigure log variables of target cluster") - } - return nil -} - -const logsBlockName = "logsBlock" -const logsTemplateName = "template-logs-block" -const topTPLLogsObject = "component" -const defaultSectionName = "default" - -// reconfigureLogVariables reconfigures the log variables of cluster -func (o *UpdateOptions) reconfigureLogVariables(c *kbappsv1.Cluster, cd *kbappsv1.ClusterDefinition) error { - var ( - err error - configSpec *appsv1alpha1.ComponentConfigSpec - logValue *gotemplate.TplValues - ) - - createReconfigureOps := func(compSpec kbappsv1.ClusterComponentSpec, configSpec *appsv1alpha1.ComponentConfigSpec, logValue *gotemplate.TplValues) error { - var ( - buf bytes.Buffer - keyName string - configTemplate *corev1.ConfigMap - formatter *appsv1beta1.FileFormatConfig - logTPL *template.Template - logVariables map[string]string - unstructuredObj *unstructured.Unstructured - ) - - if configTemplate, formatter, err = findConfigTemplateInfo(o.dynamic, configSpec); err != nil { - return err - } - if keyName, logTPL, err = findLogsBlockTPL(configTemplate.Data); err != nil { - return err - } - if logTPL == nil { - return nil - } - if err = logTPL.Execute(&buf, logValue); err != nil { - return err - } - // TODO: very hack logic for ini config file - formatter.FormatterAction = appsv1beta1.FormatterAction{IniConfig: &appsv1beta1.IniConfig{SectionName: defaultSectionName}} - if logVariables, err = cfgcore.TransformConfigFileToKeyValueMap(keyName, formatter, buf.Bytes()); err != nil { - return err - } - // build OpsRequest and apply this OpsRequest - opsRequest := buildLogsReconfiguringOps(c.Name, c.Namespace, compSpec.Name, configSpec.Name, keyName, logVariables) - if unstructuredObj, err = util.ConvertObjToUnstructured(opsRequest); err != nil { - return err - } - return util.CreateResourceIfAbsent(o.dynamic, types.OpsGVR(), c.Namespace, unstructuredObj) - } - - for _, compSpec := range c.Spec.ComponentSpecs { - if configSpec, err = findFirstConfigSpec(o.dynamic, compSpec.Name, compSpec.ComponentDef); err != nil { - return err - } - if logValue, err = buildLogsTPLValues(&compSpec); err != nil { - return err - } - if err = createReconfigureOps(compSpec, configSpec, logValue); err != nil { - return err - } - } - return nil -} - -func findFirstConfigSpec( - cli dynamic.Interface, - compName string, - compDefName string) (*appsv1alpha1.ComponentConfigSpec, error) { - compDef, err := util.GetComponentDefByName(cli, compDefName) - if err != nil { - return nil, err - } - configSpecs, err := util.GetValidConfigSpecs(true, util.ToV1ComponentConfigSpecs(compDef.Spec.Configs)) - if err != nil { - return nil, err - } - if len(configSpecs) == 0 { - return nil, errors.Errorf("no config templates for component %s", compName) - } - return &configSpecs[0], nil -} - -func findConfigTemplateInfo(dynamic dynamic.Interface, configSpec *appsv1alpha1.ComponentConfigSpec) (*corev1.ConfigMap, *appsv1beta1.FileFormatConfig, error) { - if configSpec == nil { - return nil, nil, errors.New("configTemplateSpec is nil") - } - configTemplate, err := cluster.GetConfigMapByName(dynamic, configSpec.Namespace, configSpec.TemplateRef) - if err != nil { - return nil, nil, err - } - configConstraint, err := cluster.GetConfigConstraintByName(dynamic, configSpec.ConfigConstraintRef) - if err != nil { - return nil, nil, err - } - return configTemplate, configConstraint.Spec.FileFormatConfig, nil -} - -func newConfigTemplateEngine() *template.Template { - customizedFuncMap := configuration.BuiltInCustomFunctions(nil, nil, nil) - engine := gotemplate.NewTplEngine(nil, customizedFuncMap, logsTemplateName, nil, context.TODO()) - return engine.GetTplEngine() -} - -func findLogsBlockTPL(confData map[string]string) (string, *template.Template, error) { - engine := newConfigTemplateEngine() - for key, value := range confData { - if !strings.Contains(value, logsBlockName) { - continue - } - tpl, err := engine.Parse(value) - if err != nil { - return key, nil, err - } - logTPL := tpl.Lookup(logsBlockName) - // find target logs template - if logTPL != nil { - return key, logTPL, nil - } - return "", nil, errors.New("no logs config template found") - } - return "", nil, nil -} - -func buildLogsTPLValues(compSpec *kbappsv1.ClusterComponentSpec) (*gotemplate.TplValues, error) { - compMap := map[string]interface{}{} - bytesData, err := json.Marshal(compSpec) - if err != nil { - return nil, err - } - err = json.Unmarshal(bytesData, &compMap) - if err != nil { - return nil, err - } - value := gotemplate.TplValues{ - topTPLLogsObject: compMap, - } - return &value, nil -} - -func buildLogsReconfiguringOps(clusterName, namespace, compName, configName, keyName string, variables map[string]string) *opsv1alpha1.OpsRequest { - opsName := fmt.Sprintf("%s-%s", "logs-reconfigure", uuid.NewString()) - opsRequest := util.NewOpsRequestForReconfiguring(opsName, namespace, clusterName) - parameterPairs := make([]opsv1alpha1.ParameterPair, 0, len(variables)) - for key, value := range variables { - v := value - parameterPairs = append(parameterPairs, opsv1alpha1.ParameterPair{ - Key: key, - Value: &v, - }) - } - var keys []opsv1alpha1.ParameterConfig - keys = append(keys, opsv1alpha1.ParameterConfig{ - Key: keyName, - Parameters: parameterPairs, - }) - var configurations []opsv1alpha1.ConfigurationItem - configurations = append(configurations, opsv1alpha1.ConfigurationItem{ - Keys: keys, - Name: configName, - }) - reconfigure := opsRequest.Spec.Reconfigures[0] - reconfigure.ComponentName = compName - reconfigure.Configurations = append(reconfigure.Configurations, configurations...) - return opsRequest -} - func (o *UpdateOptions) updateMonitor(val string) error { disableExporter, err := strconv.ParseBool(val) if err != nil { diff --git a/pkg/cmd/cluster/update_test.go b/pkg/cmd/cluster/update_test.go index 4c0ad4984..9ee72605d 100644 --- a/pkg/cmd/cluster/update_test.go +++ b/pkg/cmd/cluster/update_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -92,14 +92,6 @@ var _ = Describe("cluster update", func() { Expect(o.Complete()).Should(Succeed()) Expect(o.Patch).Should(ContainSubstring("\"disableExporter\":false")) }) - - It("set enable-all-logs", func() { - fakeCluster := testing.FakeCluster("c1", "default") - tf.FakeDynamicClient = testing.FakeDynamicClient(fakeCluster) - Expect(cmd.Flags().Set("enable-all-logs", "false")).Should(Succeed()) - Expect(o.CmdComplete(cmd, args)).Should(Succeed()) - Expect(o.Complete()).Should(Succeed()) - }) }) /* Context("logs variables reconfiguring tests", func() { diff --git a/pkg/cmd/cluster/upgrade_to_1.0.go b/pkg/cmd/cluster/upgrade_to_1.0.go new file mode 100644 index 000000000..f68a3dbf8 --- /dev/null +++ b/pkg/cmd/cluster/upgrade_to_1.0.go @@ -0,0 +1,773 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +this file is part of kubeblocks project + +this program is free software: you can redistribute it and/or modify +it under the terms of the gnu affero general public license as published by +the free software foundation, either version 3 of the license, or +(at your option) any later version. + +this program is distributed in the hope that it will be useful +but without any warranty; without even the implied warranty of +merchantability or fitness for a particular purpose. see the +gnu affero general public license for more details. + +you should have received a copy of the gnu affero general public license +along with this program. if not, see . +*/ + +package cluster + +import ( + "context" + "encoding/json" + "fmt" + "slices" + "strings" + + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + kbappsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/charmbracelet/lipgloss" + "github.com/fatih/color" + "github.com/sergi/go-diff/diffmatchpatch" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + apiruntime "k8s.io/apimachinery/pkg/runtime" + apitypes "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/client-go/dynamic" + clientset "k8s.io/client-go/kubernetes" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/templates" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + "github.com/apecloud/kbcli/pkg/printer" + "github.com/apecloud/kbcli/pkg/types" + "github.com/apecloud/kbcli/pkg/util" + "github.com/apecloud/kbcli/pkg/util/prompt" +) + +const ( + kbIncrementConverterAK = "kb-increment-converter" + + escape = "\x1b" +) + +type componentDefRefConvert struct { + cmpdPrefix string + serviceVersion string +} + +type serviceVersionConvert struct { + fromServiceVersion string + toServiceVersion string +} + +var ( + clusterVersionConvert = map[string]map[string]componentDefRefConvert{} + // 0.9 cmpd name => 1.0 cmpd prefix + componentDefWithChartVersion = []string{ + "clickhouse-24", + "apecloud-mysql", + "elasticsearch-8", + "elasticsearch-7", + "kafka-combine", "kafka-controller", "kafka-exporter", "kafka-broker", + "loki-backend", "loki-gateway", "loki-write", "loki-read", + "milvus-datanode", "milvus-indexnode", "milvus-minio", + "milvus-mixcoord", "milvus-proxy", "milvus-querynode", "milvus-standalone", + "minio", + "mongodb", + "mysql-8.0", "mysql-8.4", "mysql-5.7", + "ob-ce", + "orchestrator-raft", "orchestrator-shareend", + "postgresql-12", "postgresql-14", "postgresql-15", "postgresql-16", + "qdrant", + "rabbitmq", + "redis-7", "redis-cluster-7", "redis-sentinel-7", "redis-twemproxy-0.5", + "pulsar-bookkeeper", "pulsar-broker-3", "pulsar-proxy-3", "pulsar-zookeeper-3", + "starrocks-ce-be", "starrocks-ce-fe", + "vanilla-postgresql-12", "vanilla-postgresql-14", "vanilla-postgresql-15", "vanilla-postgresql-supabase15", + "vm-insert", "vm-select", "vm-storage", + "weaviate", + "zookeeper", + } + + // 0.9 cmpd prefix => 1.0 cmpd prefix + componentDefPrefixConvert = map[string]string{ + "etcd-": "etcd-3", + "ch-keeper-24": "clickhouse-keeper-24", + "pulsar-bkrecovery": "pulsar-bookies-recovery-3", + "tidb-pd": "tidb-pd", + "tidb-7": "tidb", + "tidb-8": "tidb", + "tikv": "tikv", + } + componentServiceVersionMapping = map[string][]serviceVersionConvert{ + "minio": { + { + fromServiceVersion: "0.9.0", + toServiceVersion: "2024.6.29", + }, + }, + } +) + +func init() { + registerCVConvert := func(cvName string, cvConvert map[string]componentDefRefConvert) { + clusterVersionConvert[cvName] = cvConvert + } + // 1. apecloud-mysql + apeMysqlCompDefConvert := componentDefRefConvert{cmpdPrefix: "apecloud-mysql", serviceVersion: "8.0.30"} + apeMysqlCVConvert := map[string]componentDefRefConvert{ + "mysql": apeMysqlCompDefConvert, + "vtcontroller": apeMysqlCompDefConvert, + "vtgate": apeMysqlCompDefConvert, + } + registerCVConvert("ac-mysql-8.0.30", apeMysqlCVConvert) + registerCVConvert("ac-mysql-8.0.30-1", apeMysqlCVConvert) + + registerCVConvert("clickhouse-24.8.3", map[string]componentDefRefConvert{ + "clickhouse": {cmpdPrefix: "clickhouse-24", serviceVersion: "24.8.3"}, + "ch-keeper": {cmpdPrefix: "clickhouse-keeper-24", serviceVersion: "24.8.3"}, + // Warning: 3.8.0->3.8.4 + "zookeeper": {cmpdPrefix: "zookeeper", serviceVersion: "3.8.4"}, + }) + + registerCVConvert("elasticsearch-8.8.2", map[string]componentDefRefConvert{ + "elasticsearch": {cmpdPrefix: "elasticsearch-8", serviceVersion: "8.8.2"}, + }) + + registerCVConvert("etcd-v3.5.6", map[string]componentDefRefConvert{ + "etcd": {cmpdPrefix: "etcd-3", serviceVersion: "3.5.6"}, + }) + + registerCVConvert("greptimedb-0.3.2", map[string]componentDefRefConvert{ + "datanode": {cmpdPrefix: "greptimedb-datanode", serviceVersion: "0.3.2"}, + // Warning: 3.5.5->3.5.6 + "etcd": {cmpdPrefix: "etcd-3", serviceVersion: "3.5.6"}, + "meta": {cmpdPrefix: "greptimedb-meta", serviceVersion: "0.3.2"}, + "frontend": {cmpdPrefix: "greptimedb-frontend", serviceVersion: "0.3.2"}, + }) + registerCVConvert("influxdb-2.7.4", map[string]componentDefRefConvert{ + "influxdb": {cmpdPrefix: "influxdb", serviceVersion: "2.7.4"}, + }) + registerCVConvert("kafka-3.3.2", map[string]componentDefRefConvert{ + "kafka-server": {cmpdPrefix: "kafka-combine", serviceVersion: "3.3.2"}, + "kafka-broker": {cmpdPrefix: "kafka-broker", serviceVersion: "3.3.2"}, + "controller": {cmpdPrefix: "kafka-controller", serviceVersion: "3.3.2"}, + "kafka-exporter": {cmpdPrefix: "kafka-exporter", serviceVersion: "3.3.2"}, + }) + registerCVConvert("mariadb-10.6.15", map[string]componentDefRefConvert{ + "mariadb-compdef": {cmpdPrefix: "mariadb", serviceVersion: "10.6.15"}, + }) + registerCVConvert("mogdb-5.0.5", map[string]componentDefRefConvert{ + "mogdb": {cmpdPrefix: "mogdb", serviceVersion: "5.0.5"}, + }) + // mongodb + registerCVConvert("mongodb-5.0", map[string]componentDefRefConvert{ + "mongodb": {cmpdPrefix: "mongodb", serviceVersion: "5.0.28"}, + }) + registerCVConvert("mongodb-6.0", map[string]componentDefRefConvert{ + "mongodb": {cmpdPrefix: "mongodb", serviceVersion: "6.0.16"}, + }) + registerCVConvert("mongodb-4.4", map[string]componentDefRefConvert{ + "mongodb": {cmpdPrefix: "mongodb", serviceVersion: "4.4.29"}, + }) + registerCVConvert("mongodb-4.2", map[string]componentDefRefConvert{ + "mongodb": {cmpdPrefix: "mongodb", serviceVersion: "4.2.24"}, + }) + + registerCVConvert("mysql-8.0.33", map[string]componentDefRefConvert{ + "mysql": {cmpdPrefix: "mysql-8.0", serviceVersion: "8.0.33"}, + }) + registerCVConvert("mysql-5.7.44", map[string]componentDefRefConvert{ + "mysql": {cmpdPrefix: "mysql-5.7", serviceVersion: "5.7.44"}, + }) + registerCVConvert("mysql-8.4.2", map[string]componentDefRefConvert{ + "mysql": {cmpdPrefix: "mysql-8.4", serviceVersion: "8.4.2"}, + }) + registerCVConvert("nebula-v3.5.0", map[string]componentDefRefConvert{ + "nebula-console": {cmpdPrefix: "nebula-console", serviceVersion: "3.5.0"}, + "nebula-graphd": {cmpdPrefix: "nebula-graphd", serviceVersion: "3.5.0"}, + "nebula-metad": {cmpdPrefix: "nebula-metad", serviceVersion: "3.5.0"}, + "nebula-storaged": {cmpdPrefix: "nebula-storaged", serviceVersion: "3.5.0"}, + }) + registerCVConvert("neon-pg14-1.0.0", map[string]componentDefRefConvert{ + "neon-compute": {cmpdPrefix: "nneon-compute", serviceVersion: "1.0.0"}, + "neon-storagebroker": {cmpdPrefix: "neon-storagebroker", serviceVersion: "1.0.0"}, + "neon-safekeeper": {cmpdPrefix: "neon-safekeeper", serviceVersion: "1.0.0"}, + "neon-pageserver": {cmpdPrefix: "neon-pageserver", serviceVersion: "1.0.0"}, + }) + registerCVConvert("ob-ce-4.3.0.1-100000242024032211", map[string]componentDefRefConvert{ + "ob-ce": {cmpdPrefix: "oceanbase-ce", serviceVersion: "4.3.0.1"}, + }) + registerCVConvert("opensearch-2.7.0", map[string]componentDefRefConvert{ + "opensearch": {cmpdPrefix: "opensearch", serviceVersion: "2.7.0"}, + }) + registerCVConvert("orioledb-beta1", map[string]componentDefRefConvert{ + "orioledb": {cmpdPrefix: "orioledb", serviceVersion: "14.7.2"}, + }) + registerCVConvert("polardbx-v2.3", map[string]componentDefRefConvert{ + "gms": {cmpdPrefix: "polardbx-gms", serviceVersion: "2.3.0"}, + "dn": {cmpdPrefix: "polardbx-dn", serviceVersion: "2.3.0"}, + "cn": {cmpdPrefix: "polardbx-cn", serviceVersion: "2.3.0"}, + "cdc": {cmpdPrefix: "polardbx-cdc", serviceVersion: "2.3.0"}, + }) + // postgresql + registerCVConvert("postgresql-14.7.2", map[string]componentDefRefConvert{ + "postgresql": {cmpdPrefix: "postgresql-14", serviceVersion: "14.7.2"}, + }) + registerCVConvert("postgresql-12.14.1", map[string]componentDefRefConvert{ + "postgresql": {cmpdPrefix: "postgresql-12", serviceVersion: "12.14.1"}, + }) + registerCVConvert("postgresql-12.15.0", map[string]componentDefRefConvert{ + "postgresql": {cmpdPrefix: "postgresql-12", serviceVersion: "12.15.0"}, + }) + registerCVConvert("postgresql-14.8.0", map[string]componentDefRefConvert{ + "postgresql": {cmpdPrefix: "postgresql-14", serviceVersion: "14.8.0"}, + }) + registerCVConvert("postgresql-12.14.0", map[string]componentDefRefConvert{ + "postgresql": {cmpdPrefix: "postgresql-12", serviceVersion: "12.14.0"}, + }) + registerCVConvert("postgresql-15.7.0", map[string]componentDefRefConvert{ + "postgresql": {cmpdPrefix: "postgresql-15", serviceVersion: "15.7.0"}, + }) + registerCVConvert("postgresql-16.4.0", map[string]componentDefRefConvert{ + "postgresql": {cmpdPrefix: "postgresql-16", serviceVersion: "16.4.0"}, + }) + // pulsar + registerCVConvert("pulsar-2.11.2", map[string]componentDefRefConvert{ + "bookies": {cmpdPrefix: "pulsar-bookkeeper-2", serviceVersion: "2.11.2"}, + "bookies-recovery": {cmpdPrefix: "pulsar-bookies-recovery-2", serviceVersion: "2.11.2"}, + "pulsar-broker": {cmpdPrefix: "pulsar-broker-2", serviceVersion: "2.11.2"}, + "zookeeper": {cmpdPrefix: "pulsar-zookeeper-2", serviceVersion: "2.11.2"}, + "pulsar-proxy": {cmpdPrefix: "pulsar-proxy-2", serviceVersion: "2.11.2"}, + }) + registerCVConvert("pulsar-3.0.2", map[string]componentDefRefConvert{ + "bookies": {cmpdPrefix: "pulsar-bookkeeper-3", serviceVersion: "3.0.2"}, + "bookies-recovery": {cmpdPrefix: "pulsar-bookies-recovery-3", serviceVersion: "3.0.2"}, + "pulsar-broker": {cmpdPrefix: "pulsar-broker-3", serviceVersion: "3.0.2"}, + "zookeeper": {cmpdPrefix: "pulsar-zookeeper-3", serviceVersion: "3.0.2"}, + "pulsar-proxy": {cmpdPrefix: "pulsar-proxy-3", serviceVersion: "3.0.2"}, + }) + // qdrant + registerCVConvert("qdrant-1.5.0", map[string]componentDefRefConvert{ + "qdrant": {cmpdPrefix: "qdrant", serviceVersion: "1.5.0"}, + }) + registerCVConvert("qdrant-1.7.3", map[string]componentDefRefConvert{ + "qdrant": {cmpdPrefix: "qdrant", serviceVersion: "1.7.3"}, + }) + registerCVConvert("qdrant-1.8.1", map[string]componentDefRefConvert{ + "qdrant": {cmpdPrefix: "qdrant", serviceVersion: "1.8.1"}, + }) + registerCVConvert("qdrant-1.8.4", map[string]componentDefRefConvert{ + "qdrant": {cmpdPrefix: "qdrant", serviceVersion: "1.8.4"}, + }) + registerCVConvert("qdrant-1.10.0", map[string]componentDefRefConvert{ + "qdrant": {cmpdPrefix: "qdrant", serviceVersion: "1.10.0"}, + }) + // redis + registerCVConvert("redis-7.2.4", map[string]componentDefRefConvert{ + "redis": {cmpdPrefix: "redis-7", serviceVersion: "7.2.4"}, + }) + registerCVConvert("redis-7.0.6", map[string]componentDefRefConvert{ + "redis": {cmpdPrefix: "redis-7", serviceVersion: "7.0.6"}, + }) + registerCVConvert("risingwave-v1.0.0", map[string]componentDefRefConvert{ + "meta": {cmpdPrefix: "risingwave-meta", serviceVersion: "v1.0.0"}, + "frontend": {cmpdPrefix: "risingwave-frontend", serviceVersion: "v1.0.0"}, + "compute": {cmpdPrefix: "risingwave-compute", serviceVersion: "v1.0.0"}, + "compactor": {cmpdPrefix: "risingwave-compactor", serviceVersion: "v1.0.0"}, + "connector": {cmpdPrefix: "risingwave-connector", serviceVersion: "v1.0.0"}, + }) + registerCVConvert("tdengine-3.0.5.0", map[string]componentDefRefConvert{ + "tdengine": {cmpdPrefix: "tdengine", serviceVersion: "3.0.5"}, + }) + registerCVConvert("weaviate-1.18.0", map[string]componentDefRefConvert{ + "weaviate": {cmpdPrefix: "weaviate", serviceVersion: "1.19.6"}, + }) + registerCVConvert("yashandb-personal-23.1.1.100", map[string]componentDefRefConvert{ + "yashandb-compdef": {cmpdPrefix: "yashandb", serviceVersion: "23.1.1-100"}, + }) +} + +type UpgradeToV1Options struct { + Cmd *cobra.Command `json:"-"` + + Client clientset.Interface + f cmdutil.Factory + Dynamic dynamic.Interface + DryRun bool + NoDiff bool + Name string + Namespace string + genericiooptions.IOStreams + compDefList *unstructured.UnstructuredList +} + +func NewUpgradeToV1Option(f cmdutil.Factory, streams genericiooptions.IOStreams) *UpgradeToV1Options { + return &UpgradeToV1Options{ + f: f, + IOStreams: streams, + } +} + +var convertExample = templates.Examples(` + # upgrade a v1alpha1 cluster to v1 cluster + kbcli cluster upgrade-to-v1 mycluster + + # upgrade a v1alpha1 cluster with --dry-run + kbcli cluster upgrade-to-v1 mycluster --dry-run +`) + +func NewUpgradeToV1Cmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := NewUpgradeToV1Option(f, streams) + cmd := &cobra.Command{ + Use: "upgrade-to-v1 [NAME]", + Short: "upgrade cluster to v1 api version.", + Example: convertExample, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.ClusterGVR()), + Run: func(cmd *cobra.Command, args []string) { + cmdutil.BehaviorOnFatal(printer.FatalWithRedColor) + cmdutil.CheckErr(o.complete(args)) + cmdutil.CheckErr(o.Run()) + }, + } + cmd.Flags().BoolVar(&o.DryRun, "dry-run", false, "dry run mode") + cmd.Flags().BoolVar(&o.NoDiff, "no-diff", false, "only print the new cluster yaml") + return cmd +} + +func (o *UpgradeToV1Options) complete(args []string) error { + + if len(args) == 0 { + return fmt.Errorf("must specify cluster name") + } + o.Name = args[0] + o.Namespace, _, _ = o.f.ToRawKubeConfigLoader().Namespace() + o.Dynamic, _ = o.f.DynamicClient() + o.Client, _ = o.f.KubernetesClientSet() + return nil +} + +func (o *UpgradeToV1Options) GetConvertedCluster() (*kbappsv1.Cluster, *kbappsv1alpha1.Cluster, bool, error) { + cluster := &kbappsv1.Cluster{} + err := util.GetK8SClientObject(o.Dynamic, cluster, types.ClusterGVR(), o.Namespace, o.Name) + if err != nil { + return nil, nil, false, err + } + if cluster.Annotations[constant.CRDAPIVersionAnnotationKey] == kbappsv1.GroupVersion.String() { + return nil, nil, false, fmt.Errorf("cluster %s is already v1", o.Name) + } + // 1. get cluster v1alpha1 spec + clusterV1alpha1 := &kbappsv1alpha1.Cluster{} + err = util.GetK8SClientObject(o.Dynamic, clusterV1alpha1, types.ClusterV1alphaGVR(), o.Namespace, o.Name) + if err != nil { + return nil, nil, false, err + } + o.compDefList, err = o.Dynamic.Resource(types.CompDefGVR()).Namespace("").List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, nil, false, err + } + + var existUnsupportedSpec bool + if len(clusterV1alpha1.Spec.ClusterVersionRef) > 0 { + if err := o.ConvertForClusterVersion(cluster, clusterV1alpha1.Spec, &existUnsupportedSpec); err != nil { + return nil, nil, false, err + } + } else { + if err := o.Convert09ComponentDef(cluster, clusterV1alpha1.Spec, &existUnsupportedSpec); err != nil { + return nil, nil, false, err + } + } + delete(cluster.Annotations, kbIncrementConverterAK) + cluster.Annotations[constant.CRDAPIVersionAnnotationKey] = kbappsv1.GroupVersion.String() + return cluster, clusterV1alpha1, existUnsupportedSpec, nil +} + +func (o *UpgradeToV1Options) Run() error { + cluster, clusterV1alpha1, existUnsupportedSpec, err := o.GetConvertedCluster() + if err != nil { + return err + } + o.printDiff(clusterV1alpha1.DeepCopy(), cluster.DeepCopy()) + if existUnsupportedSpec { + return fmt.Errorf(`cluster "%s" has unknown clusterVersion or componentDefinition, you can replace with accorrding ComponentDefinition with 1.0 api`, o.Name) + } + if o.DryRun { + return nil + } + fmt.Println(printer.BoldYellow(fmt.Sprintf("Cluster %s will be converted to v1 with output as yaml.", o.Name))) + if err = prompt.Confirm(nil, o.In, "", "Please type 'Yes/yes' to confirm your operation:"); err != nil { + return err + } + if len(clusterV1alpha1.Spec.ClusterVersionRef) > 0 { + if err = o.convertCredential(clusterV1alpha1.Spec.ClusterDefRef); err != nil { + return err + } + } + if err = o.convertAccounts(); err != nil { + return err + } + if err = o.ConvertServices(); err != nil { + return err + } + + // convert to v1 + clusterObj, err := apiruntime.DefaultUnstructuredConverter.ToUnstructured(cluster) + if err != nil { + return err + } + _, err = o.Dynamic.Resource(types.ClusterGVR()).Namespace(o.Namespace).Update(context.TODO(), &unstructured.Unstructured{Object: clusterObj}, metav1.UpdateOptions{}) + if err != nil { + return err + } + output := fmt.Sprintf("Cluster %s has converted successfully, you can view the spec:", o.Name) + printer.PrintLine(output) + nextLine := fmt.Sprintf("\tkubectl get clusters.apps.kubeblocks.io %s -n %s -oyaml", o.Name, o.Namespace) + printer.PrintLine(nextLine) + if err = o.deleteConfiguration(); err != nil { + return err + } + return o.normalizeConfigMaps() +} + +func (o *UpgradeToV1Options) convertCredential(cdName string) error { + oldSecret := &corev1.Secret{} + err := util.GetK8SClientObject(o.Dynamic, oldSecret, types.SecretGVR(), o.Namespace, fmt.Sprintf("%s-conn-credential", o.Name)) + if err != nil { + return err + } + var ( + compName string + accountName string + ) + // TODO: support all cluster definition + switch cdName { + case "postgresql": + compName = "postgresql" + accountName = "postgres" + case "mysql": + compName = "mysql" + accountName = "root" + case "apecloud-mysql": + compName = "mysql" + accountName = "root" + case "etcd": + compName = "etcd" + accountName = "root" + case "weaviate": + compName = "weaviate" + accountName = "root" + case "redis": + compName = "redis" + accountName = "default" + case "qdrant": + compName = "qdrant" + accountName = "root" + case "polardbx": + compName = "gms" + accountName = "polardbx_root" + case "orioledb": + compName = "orioledb" + accountName = "postgres" + case "neon": + compName = "neon-compute" + accountName = "cloud_admin" + case "mongodb": + compName = "mongodb" + accountName = "root" + default: + return fmt.Errorf("unknown cluster definition %s", cdName) + } + newSecret := &corev1.Secret{} + newSecret.Name = constant.GenerateAccountSecretName(o.Name, compName, accountName) + newSecret.Namespace = oldSecret.Namespace + newSecret.Labels = constant.GetCompLabels(o.Name, compName) + newSecret.Labels["apps.kubeblocks.io/system-account"] = accountName + newSecret.Data = map[string][]byte{ + "username": oldSecret.Data["username"], + "password": oldSecret.Data["password"], + } + if _, err := o.Client.CoreV1().Secrets(oldSecret.Namespace).Create(context.TODO(), newSecret, metav1.CreateOptions{}); err != nil { + return client.IgnoreAlreadyExists(err) + } + return nil +} + +func (o *UpgradeToV1Options) convertAccounts() error { + secretList, err := o.Dynamic.Resource(types.SecretGVR()).Namespace(o.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", constant.AppInstanceLabelKey, o.Name), + }) + if err != nil { + return err + } + for i := range secretList.Items { + secret := secretList.Items[i] + labels := secret.GetLabels() + account, ok := labels["account.kubeblocks.io/name"] + if !ok { + continue + } + labels["apps.kubeblocks.io/system-account"] = account + secret.SetLabels(labels) + if _, err = o.Dynamic.Resource(types.SecretGVR()).Namespace(o.Namespace).Update(context.TODO(), &secret, metav1.UpdateOptions{}); err != nil { + return err + } + } + return nil +} + +func (o *UpgradeToV1Options) normalizeConfigMaps() error { + cmList, err := o.Dynamic.Resource(types.ConfigmapGVR()).Namespace(o.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", constant.AppInstanceLabelKey, o.Name), + }) + if err != nil { + return err + } + patch := func(cm unstructured.Unstructured) error { + // in-place upgrade + newData, err := json.Marshal(cm) + if err != nil { + return err + } + if _, err = o.Dynamic.Resource(types.ConfigmapGVR()).Namespace(o.Namespace).Patch(context.TODO(), cm.GetName(), apitypes.MergePatchType, newData, metav1.PatchOptions{}); err != nil { + return client.IgnoreNotFound(err) + } + return nil + } + for i := range cmList.Items { + cm := cmList.Items[i] + labels := cm.GetLabels() + if _, ok := labels[constant.CMConfigurationSpecProviderLabelKey]; !ok { + // add file-template label for scripts + if _, isScripts := labels[constant.CMTemplateNameLabelKey]; isScripts { + if err = o.Dynamic.Resource(types.ConfigmapGVR()).Namespace(cm.GetNamespace()).Delete(context.TODO(), cm.GetName(), metav1.DeleteOptions{}); client.IgnoreNotFound(err) != nil { + return err + } + } + continue + } + if _, ok := labels[constant.CMConfigurationSpecProviderLabelKey]; !ok { + continue + } + cm.SetOwnerReferences(nil) + if err = patch(cm); err != nil { + return err + } + } + return nil +} + +func (o *UpgradeToV1Options) getLatestComponentDef(componentDefPrefix string) (string, error) { + var matchedCompDefs []string + for _, v := range o.compDefList.Items { + if v.GetAnnotations()[constant.CRDAPIVersionAnnotationKey] != kbappsv1.GroupVersion.String() { + continue + } + if strings.HasPrefix(v.GetName(), componentDefPrefix) { + matchedCompDefs = append(matchedCompDefs, v.GetName()) + } + } + if len(matchedCompDefs) == 0 { + return "", fmt.Errorf("no matched componentDefinition for componentDef %s", componentDefPrefix) + } + // TODO: sort with semantic version? + slices.Sort(matchedCompDefs) + return matchedCompDefs[len(matchedCompDefs)-1], nil +} + +func (o *UpgradeToV1Options) deleteConfiguration() error { + configList, err := o.Dynamic.Resource(types.ConfigurationGVR()).Namespace(o.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", constant.AppInstanceLabelKey, o.Name), + }) + if err != nil { + return err + } + for i := range configList.Items { + if err = o.Dynamic.Resource(types.ConfigurationGVR()).Namespace(o.Namespace).Delete(context.TODO(), configList.Items[i].GetName(), metav1.DeleteOptions{}); err != nil { + return err + } + } + return nil +} + +func (o *UpgradeToV1Options) ConvertForClusterVersion(cluster *kbappsv1.Cluster, + clusterV1alpha1Spec kbappsv1alpha1.ClusterSpec, + existUnsupportedSpec *bool) error { + convert, ok := clusterVersionConvert[clusterV1alpha1Spec.ClusterVersionRef] + if !ok { + *existUnsupportedSpec = true + return nil + } + for i := range clusterV1alpha1Spec.ComponentSpecs { + compDefRef := clusterV1alpha1Spec.ComponentSpecs[i].ComponentDefRef + compDefConVert, ok := convert[compDefRef] + if !ok { + *existUnsupportedSpec = true + cluster.Spec.ComponentSpecs[i].ComponentDef = "" + cluster.Spec.ComponentSpecs[i].ServiceVersion = "" + continue + } + compDef, err := o.getLatestComponentDef(compDefConVert.cmpdPrefix) + if err != nil { + return err + } + cluster.Spec.ComponentSpecs[i].ComponentDef = compDef + cluster.Spec.ComponentSpecs[i].ServiceVersion = compDefConVert.serviceVersion + } + // remove deprecated v1alpha1 + cluster.Spec.ClusterDef = "" + return nil +} + +func (o *UpgradeToV1Options) Convert09ComponentDef(cluster *kbappsv1.Cluster, + clusterV1alpha1Spec kbappsv1alpha1.ClusterSpec, + existUnsupportedSpec *bool) error { + componentDefWithChartVersionSet := sets.New(componentDefWithChartVersion...) + convertCompDef := func(compDef string) (string, error) { + if componentDefWithChartVersionSet.Has(compDef) { + return o.getLatestComponentDef(compDef) + } + for k, cmpdDefPrefix := range componentDefPrefixConvert { + if strings.HasPrefix(compDef, k) { + return o.getLatestComponentDef(cmpdDefPrefix) + } + } + *existUnsupportedSpec = true + return "", nil + } + for i := range clusterV1alpha1Spec.ComponentSpecs { + compDef09 := clusterV1alpha1Spec.ComponentSpecs[i].ComponentDef + compDef, err := convertCompDef(compDef09) + if err != nil { + return err + } + cluster.Spec.ComponentSpecs[i].ComponentDef = compDef + // reset service account name + if cluster.Spec.ComponentSpecs[i].ServiceAccountName == fmt.Sprintf("kb-%s", cluster.Name) { + cluster.Spec.ComponentSpecs[i].ServiceAccountName = "" + } + toServiceVersion := o.ConvertServiceVersion(compDef09, clusterV1alpha1Spec.ComponentSpecs[i].ServiceVersion) + if len(toServiceVersion) != 0 { + cluster.Spec.ComponentSpecs[i].ServiceVersion = toServiceVersion + } + } + for i := range clusterV1alpha1Spec.ShardingSpecs { + compDef09 := clusterV1alpha1Spec.ShardingSpecs[i].Template.ComponentDef + compDef, err := convertCompDef(compDef09) + if err != nil { + return err + } + cluster.Spec.Shardings[i].Template.ComponentDef = compDef + // reset service account name + if cluster.Spec.Shardings[i].Template.ServiceAccountName == fmt.Sprintf("kb-%s", cluster.Name) { + cluster.Spec.Shardings[i].Template.ServiceAccountName = "" + } + toServiceVersion := o.ConvertServiceVersion(compDef09, clusterV1alpha1Spec.ShardingSpecs[i].Template.ServiceVersion) + if len(toServiceVersion) != 0 { + cluster.Spec.Shardings[i].Template.ServiceVersion = toServiceVersion + } + } + delete(cluster.Annotations, kbIncrementConverterAK) + return nil +} + +func (o *UpgradeToV1Options) ConvertServiceVersion(compDef09 string, fromServiceVersion string) string { + var toServiceVersion string + if converts, ok := componentServiceVersionMapping[compDef09]; ok { + for _, s := range converts { + if s.fromServiceVersion == fromServiceVersion { + toServiceVersion = s.toServiceVersion + break + } + } + } + return toServiceVersion +} + +func (o *UpgradeToV1Options) convertService(svc unstructured.Unstructured) error { + selector, _, _ := unstructured.NestedStringMap(svc.Object, "spec", "selector") + if _, ok := selector[constant.AppNameLabelKey]; !ok { + return nil + } + delete(selector, constant.AppNameLabelKey) + _ = unstructured.SetNestedStringMap(svc.Object, selector, "spec", "selector") + if _, err := o.Dynamic.Resource(types.ServiceGVR()).Namespace(o.Namespace).Update(context.TODO(), &svc, metav1.UpdateOptions{}); err != nil { + return err + } + selector1, _, _ := unstructured.NestedStringMap(svc.Object, "spec", "selector") + fmt.Println(selector1) + return nil +} + +func (o *UpgradeToV1Options) ConvertServices() error { + svcList, err := o.Dynamic.Resource(types.ServiceGVR()).Namespace(o.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s,app.kubernetes.io/managed-by=kubeblocks", constant.AppInstanceLabelKey, o.Name), + }) + if err != nil { + return err + } + for i := range svcList.Items { + svc := svcList.Items[i] + fmt.Println(svc.GetName()) + if err = o.convertService(svc); err != nil { + return err + } + } + return nil +} + +func (o *UpgradeToV1Options) printDiff(clusterV1Alpha1 *kbappsv1alpha1.Cluster, clusterV1 *kbappsv1.Cluster) { + delete(clusterV1Alpha1.Annotations, corev1.LastAppliedConfigAnnotation) + delete(clusterV1.Annotations, corev1.LastAppliedConfigAnnotation) + clusterV1Alpha1.Status = kbappsv1alpha1.ClusterStatus{} + clusterV1Alpha1.ObjectMeta.ManagedFields = nil + clusterV1.Status = kbappsv1.ClusterStatus{} + clusterV1.ObjectMeta.ManagedFields = nil + clusterV1Alpha1Srr, _ := yaml.Marshal(clusterV1Alpha1) + clusterV1Str, _ := yaml.Marshal(clusterV1) + if o.NoDiff { + fmt.Println(string(clusterV1Str)) + return + } + getDiffContext := func(isV1Cluster bool) string { + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(string(clusterV1Alpha1Srr), string(clusterV1Str), false) + var diffStr string + for _, d := range diffs { + switch d.Type { + case diffmatchpatch.DiffInsert: + if isV1Cluster { + diffStr += o.colorGreen(d.Text) + } + case diffmatchpatch.DiffDelete: + if !isV1Cluster { + diffStr += o.colorRed(d.Text) + } + case diffmatchpatch.DiffEqual: + diffStr += d.Text + } + } + return diffStr + } + // Add a purple, rectangular border + var style = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("63")) + fmt.Println(lipgloss.JoinHorizontal(lipgloss.Left, style.Render(getDiffContext(false)), " ", style.Render(getDiffContext(true)))) +} + +func (o *UpgradeToV1Options) colorRed(str string) string { + return strings.ReplaceAll(color.RedString(str), "\n", fmt.Sprintf("%s[0m\n%s[31m", escape, escape)) +} + +func (o *UpgradeToV1Options) colorGreen(str string) string { + return strings.ReplaceAll(color.GreenString(str), "\n", fmt.Sprintf("%s[0m\n%s[32m", escape, escape)) +} diff --git a/pkg/cmd/clusterdefinition/clusterdefinition.go b/pkg/cmd/clusterdefinition/clusterdefinition.go index 971777993..6dcb34a24 100644 --- a/pkg/cmd/clusterdefinition/clusterdefinition.go +++ b/pkg/cmd/clusterdefinition/clusterdefinition.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/clusterdefinition/clusterdefinition_test.go b/pkg/cmd/clusterdefinition/clusterdefinition_test.go index 1fbf8107a..e2d8a0c52 100644 --- a/pkg/cmd/clusterdefinition/clusterdefinition_test.go +++ b/pkg/cmd/clusterdefinition/clusterdefinition_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/clusterdefinition/describe.go b/pkg/cmd/clusterdefinition/describe.go index abb045df3..3ae6968da 100644 --- a/pkg/cmd/clusterdefinition/describe.go +++ b/pkg/cmd/clusterdefinition/describe.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/cmd/clusterdefinition/suite_test.go b/pkg/cmd/clusterdefinition/suite_test.go index 973e4b24b..84ee45545 100644 --- a/pkg/cmd/clusterdefinition/suite_test.go +++ b/pkg/cmd/clusterdefinition/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/componentdefinition/componentdefinition_test.go b/pkg/cmd/componentdefinition/componentdefinition_test.go index 834d865af..01869acfd 100644 --- a/pkg/cmd/componentdefinition/componentdefinition_test.go +++ b/pkg/cmd/componentdefinition/componentdefinition_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/componentdefinition/componnetdefinition.go b/pkg/cmd/componentdefinition/componnetdefinition.go index da333b850..b8b75cd69 100644 --- a/pkg/cmd/componentdefinition/componnetdefinition.go +++ b/pkg/cmd/componentdefinition/componnetdefinition.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/componentdefinition/describe.go b/pkg/cmd/componentdefinition/describe.go index f2e46f87c..6be92d52e 100644 --- a/pkg/cmd/componentdefinition/describe.go +++ b/pkg/cmd/componentdefinition/describe.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -198,9 +198,9 @@ func showRoles(roles []kbappsv1.ReplicaRole, out io.Writer) { } fmt.Fprintf(out, "Roles:\n") tbl := printer.NewTablePrinter(out) - tbl.SetHeader("\tNAME", "SERVICE-ABLE", "WRITE-ABLE", "VOTE-ABLE") + tbl.SetHeader("\tNAME", "WITH-QUORUM", "UPDATE-PRIORITY") for _, role := range roles { - tbl.AddRow("\t"+role.Name, role.Serviceable, role.Writable, role.Votable) + tbl.AddRow("\t"+role.Name, role.ParticipatesInQuorum, role.UpdatePriority) } tbl.Print() fmt.Fprint(out, "\n") @@ -228,10 +228,9 @@ func showSystemAccounts(systemAccounts []kbappsv1.SystemAccount, out io.Writer) } fmt.Fprintf(out, "System Accounts:\n") tbl := printer.NewTablePrinter(out) - tbl.SetHeader("\tNAME", "INIT-ACCOUNT", "HAS-SECRET-REF") + tbl.SetHeader("\tNAME", "INIT-ACCOUNT") for _, sa := range systemAccounts { - hasSecretRef := sa.SecretRef != nil - tbl.AddRow("\t"+sa.Name, sa.InitAccount, hasSecretRef) + tbl.AddRow("\t"+sa.Name, sa.InitAccount) } tbl.Print() fmt.Fprint(out, "\n") diff --git a/pkg/cmd/componentdefinition/suite_test.go b/pkg/cmd/componentdefinition/suite_test.go index 5da701c98..43bf2d344 100644 --- a/pkg/cmd/componentdefinition/suite_test.go +++ b/pkg/cmd/componentdefinition/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/componentversion/componentversion.go b/pkg/cmd/componentversion/componentversion.go index a63eaf07f..30599077c 100644 --- a/pkg/cmd/componentversion/componentversion.go +++ b/pkg/cmd/componentversion/componentversion.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/componentversion/componentversion_test.go b/pkg/cmd/componentversion/componentversion_test.go index 6e2a95146..47ff4c932 100644 --- a/pkg/cmd/componentversion/componentversion_test.go +++ b/pkg/cmd/componentversion/componentversion_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/componentversion/describe.go b/pkg/cmd/componentversion/describe.go index a38c639a6..4c58c3889 100644 --- a/pkg/cmd/componentversion/describe.go +++ b/pkg/cmd/componentversion/describe.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/cmd/componentversion/suite_test.go b/pkg/cmd/componentversion/suite_test.go index 83b4b5f35..7f1730309 100644 --- a/pkg/cmd/componentversion/suite_test.go +++ b/pkg/cmd/componentversion/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/dashboard/dashboard.go b/pkg/cmd/dashboard/dashboard.go deleted file mode 100644 index 5f3fcdd94..000000000 --- a/pkg/cmd/dashboard/dashboard.go +++ /dev/null @@ -1,427 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package dashboard - -import ( - "context" - "fmt" - "io" - "net/http" - "net/url" - "strings" - "time" - - "github.com/spf13/cobra" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/genericiooptions" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/portforward" - "k8s.io/client-go/transport/spdy" - cmdpf "k8s.io/kubectl/pkg/cmd/portforward" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/templates" - "k8s.io/utils/pointer" - - "github.com/apecloud/kbcli/pkg/printer" - "github.com/apecloud/kbcli/pkg/util" -) - -// kb support dashboard name -const ( - grafanaAddonName = "kubeblocks-grafana" - bytebaseAddonName = "bytebase" - nyancatAddonName = "kubeblocks-nyancat" - prometheusAlertManager = "kubeblocks-prometheus-alertmanager" - prometheusServer = "kubeblocks-prometheus-server" - pyroscopeServer = "kubeblocks-pyroscope-server" - jupyterHubAddon = "jupyter-hub" - jupyterNoteBookAddon = "jupyter-notebook" - minio = "minio" -) - -const ( - podRunningTimeoutFlag = "pod-running-timeout" - defaultPodExecTimeout = 60 * time.Second - - lokiAddonName = "kubeblocks-logs" - lokiGrafanaDirect = "container-logs" - localAdd = "127.0.0.1" -) - -type dashboard struct { - Name string - AddonName string - Port string - TargetPort string - Namespace string - CreationTime string - - // Label used to get the service - Label string -} - -var ( - listExample = templates.Examples(` - # List all dashboards - kbcli dashboard list - `) - - openExample = templates.Examples(` - # Open a dashboard, such as kube-grafana - kbcli dashboard open kubeblocks-grafana - - # Open a dashboard with a specific local port - kbcli dashboard open kubeblocks-grafana --port 8080 - - # for dashboard kubeblocks-grafana, support to direct the specified dashboard type - # now we support mysql,mongodb,postgresql,redis,weaviate,kafka,cadvisor,jmx and node - kbcli dashboard open kubeblocks-grafana mysql - `) - - // we do not use the default port to port-forward to avoid conflict with other services - dashboards = [...]*dashboard{ - { - Name: grafanaAddonName, - AddonName: "kb-addon-grafana", - Label: "app.kubernetes.io/instance=kb-addon-grafana,app.kubernetes.io/name=grafana", - TargetPort: "13000", - }, - { - Name: prometheusAlertManager, - AddonName: "kb-addon-prometheus-alertmanager", - Label: "app=prometheus,component=alertmanager,release=kb-addon-prometheus", - TargetPort: "19093", - }, - { - Name: prometheusServer, - AddonName: "kb-addon-prometheus-server", - Label: "app=prometheus,component=server,release=kb-addon-prometheus", - TargetPort: "19090", - }, - { - Name: nyancatAddonName, - AddonName: "kb-addon-nyancat", - Label: "app.kubernetes.io/instance=kb-addon-nyancat", - TargetPort: "8087", - }, - { - Name: lokiAddonName, - AddonName: "kb-addon-loki", - Label: "app.kubernetes.io/instance=kb-addon-loki", - TargetPort: "13100", - }, - { - Name: pyroscopeServer, - AddonName: "kb-addon-pyroscope-server", - Label: "app.kubernetes.io/instance=kb-addon-pyroscope-server,app.kubernetes.io/name=pyroscope", - TargetPort: "14040", - }, { - Name: bytebaseAddonName, - AddonName: "bytebase-entrypoint", - Label: "app=bytebase", - TargetPort: "18080", - }, - { - Name: jupyterHubAddon, - AddonName: "proxy-public", - Label: "app=jupyterhub", - TargetPort: "18081", - }, - { - Name: jupyterNoteBookAddon, - AddonName: "jupyter-notebook", - Label: " app.kubernetes.io/instance=kb-addon-jupyter-notebook", - TargetPort: "18888", - }, - { - Name: minio, - AddonName: "kb-addon-minio", - Label: "app.kubernetes.io/instance=kb-addon-minio", - TargetPort: "9001", - Port: "9001", - }, - } -) - -type listOptions struct { - genericiooptions.IOStreams - factory cmdutil.Factory - client *kubernetes.Clientset -} - -func newListOptions(f cmdutil.Factory, streams genericiooptions.IOStreams) *listOptions { - return &listOptions{ - factory: f, - IOStreams: streams, - } -} - -// NewDashboardCmd creates the dashboard command -func NewDashboardCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { - cmd := &cobra.Command{ - Use: "dashboard", - Short: "List and open the KubeBlocks dashboards.", - } - - // add subcommands - cmd.AddCommand( - newListCmd(f, streams), - newOpenCmd(f, streams), - ) - - return cmd -} - -func newListCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { - o := newListOptions(f, streams) - cmd := &cobra.Command{ - Use: "list", - Short: "List all dashboards.", - Example: listExample, - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - util.CheckErr(o.complete()) - util.CheckErr(o.run()) - }, - } - return cmd -} - -func (o *listOptions) complete() error { - var err error - o.client, err = o.factory.KubernetesClientSet() - return err -} - -// get all dashboard service and print -func (o *listOptions) run() error { - if err := getDashboardInfo(o.client); err != nil { - return err - } - - return printTable(o.Out) -} - -func printTable(out io.Writer) error { - tbl := printer.NewTablePrinter(out) - tbl.SetHeader("NAME", "NAMESPACE", "PORT", "CREATED-TIME") - for _, d := range dashboards { - if d.Namespace == "" { - continue - } - tbl.AddRow(d.Name, d.Namespace, d.TargetPort, d.CreationTime) - } - tbl.Print() - return nil -} - -type openOptions struct { - factory cmdutil.Factory - genericiooptions.IOStreams - portForwardOptions *cmdpf.PortForwardOptions - - name string - localPort string -} - -func newOpenOptions(f cmdutil.Factory, streams genericiooptions.IOStreams) *openOptions { - return &openOptions{ - factory: f, - IOStreams: streams, - portForwardOptions: &cmdpf.PortForwardOptions{ - PortForwarder: &defaultPortForwarder{streams}, - }, - } -} - -func newOpenCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { - o := newOpenOptions(f, streams) - cmd := &cobra.Command{ - Use: "open NAME [DASHBOARD-TYPE] [--port PORT]", - Short: "Open one dashboard.", - Example: openExample, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) == 1 && args[0] == supportDirectDashboard { - var name []string - for i := range availableTypes { - if strings.HasPrefix(availableTypes[i], toComplete) { - name = append(name, availableTypes[i]) - } - } - return name, cobra.ShellCompDirectiveNoFileComp - } - var names []string - for _, d := range dashboards { - names = append(names, d.Name) - } - return names, cobra.ShellCompDirectiveNoFileComp - }, - Run: func(cmd *cobra.Command, args []string) { - util.CheckErr(o.complete(cmd, args)) - util.CheckErr(o.run()) - }, - } - - cmd.Flags().StringVar(&o.localPort, "port", "", "dashboard local port") - cmd.Flags().Duration(podRunningTimeoutFlag, defaultPodExecTimeout, - "The time (like 5s, 2m, or 3h, higher than zero) to wait for at least one pod is running") - return cmd -} - -func (o *openOptions) complete(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return fmt.Errorf("missing dashborad name") - } - - o.name = args[0] - client, err := o.factory.KubernetesClientSet() - if err != nil { - return err - } - - if err = getDashboardInfo(client); err != nil { - return err - } - dashName := o.name - // opening loki dashboard redirects to grafana dashboard - if o.name == lokiAddonName { - dashName = grafanaAddonName - } - dash := getDashboardByName(dashName) - if dash == nil { - return fmt.Errorf("failed to find dashboard \"%s\", run \"kbcli dashboard list\" to list all dashboards", o.name) - } - if dash.Name == supportDirectDashboard && len(args) > 1 { - clusterType = args[1] - } - if o.localPort == "" { - if o.name == lokiAddonName { - // revert the target port for loki dashboard - o.localPort = getDashboardByName(lokiAddonName).TargetPort - } else { - o.localPort = dash.TargetPort - } - } - pfArgs := []string{fmt.Sprintf("svc/%s", dash.AddonName), fmt.Sprintf("%s:%s", o.localPort, dash.Port)} - o.portForwardOptions.Namespace = dash.Namespace - o.portForwardOptions.Address = []string{localAdd} - return o.portForwardOptions.Complete(newFactory(dash.Namespace), cmd, pfArgs) -} - -func (o *openOptions) run() error { - url := fmt.Sprintf("http://%s:%s", localAdd, o.localPort) - if o.name == "kubeblocks-grafana" { - err := buildGrafanaDirectURL(&url, clusterType) - if err != nil { - return err - } - } - // customized by loki - if o.name == lokiAddonName { - err := buildGrafanaDirectURL(&url, lokiGrafanaDirect) - if err != nil { - return err - } - } - go func() { - <-o.portForwardOptions.ReadyChannel - fmt.Fprintf(o.Out, "Forward successfully! Opening browser ...\n") - if err := util.OpenBrowser(url); err != nil { - fmt.Fprintf(o.ErrOut, "Failed to open browser: %v", err) - } - }() - return o.portForwardOptions.RunPortForward() -} - -func getDashboardByName(name string) *dashboard { - for i, d := range dashboards { - if d.Name == name { - return dashboards[i] - } - } - return nil -} - -func getDashboardInfo(client *kubernetes.Clientset) error { - getSvcs := func(client *kubernetes.Clientset, label string) (*corev1.ServiceList, error) { - return client.CoreV1().Services(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{ - LabelSelector: label, - }) - } - - for _, d := range dashboards { - var svc *corev1.Service - - // get all services that match the label - svcs, err := getSvcs(client, d.Label) - if err != nil { - return err - } - - // find the dashboard service - for i, s := range svcs.Items { - if s.Name == d.AddonName { - svc = &svcs.Items[i] - break - } - } - - if svc == nil { - continue - } - - // fill dashboard information - d.Namespace = svc.Namespace - d.CreationTime = util.TimeFormat(&svc.CreationTimestamp) - // if port is not specified, use the first port of the service - if len(svc.Spec.Ports) > 0 && d.Port == "" { - d.Port = fmt.Sprintf("%d", svc.Spec.Ports[0].Port) - if d.TargetPort == "" { - d.TargetPort = svc.Spec.Ports[0].TargetPort.String() - } - } - } - return nil -} - -func newFactory(namespace string) cmdutil.Factory { - cf := util.NewConfigFlagNoWarnings() - cf.Namespace = pointer.String(namespace) - return cmdutil.NewFactory(cf) -} - -type defaultPortForwarder struct { - genericiooptions.IOStreams -} - -func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts cmdpf.PortForwardOptions) error { - transport, upgrader, err := spdy.RoundTripperFor(opts.Config) - if err != nil { - return err - } - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url) - pf, err := portforward.NewOnAddresses(dialer, opts.Address, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.Out, f.ErrOut) - if err != nil { - return err - } - return pf.ForwardPorts() -} diff --git a/pkg/cmd/dashboard/dashboard_test.go b/pkg/cmd/dashboard/dashboard_test.go deleted file mode 100644 index 739f85c66..000000000 --- a/pkg/cmd/dashboard/dashboard_test.go +++ /dev/null @@ -1,111 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package dashboard - -import ( - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - corev1 "k8s.io/api/core/v1" - "k8s.io/cli-runtime/pkg/genericiooptions" - "k8s.io/cli-runtime/pkg/resource" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest/fake" - cmdtesting "k8s.io/kubectl/pkg/cmd/testing" -) - -const namespace = "test" - -var _ = Describe("kubeblocks", func() { - var streams genericiooptions.IOStreams - var tf *cmdtesting.TestFactory - - fakeSvcs := func() *corev1.ServiceList { - svcs := &corev1.ServiceList{} - svc := corev1.Service{} - svc.SetName("kubeblocks-grafana") - svc.SetNamespace(namespace) - svc.SetLabels(map[string]string{ - "app.kubernetes.io/instance": "kubeblocks", - "app.kubernetes.io/name": "grafana", - }) - svcs.Items = append(svcs.Items, svc) - return svcs - } - - BeforeEach(func() { - streams, _, _, _ = genericiooptions.NewTestIOStreams() - tf = cmdtesting.NewTestFactory().WithNamespace(namespace) - codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) - tf.UnstructuredClient = &fake.RESTClient{ - NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == "/api/v1/services" && m == "GET": - return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, fakeSvcs())}, nil - default: - return nil, nil - } - }), - } - tf.Client = tf.UnstructuredClient - }) - - AfterEach(func() { - tf.Cleanup() - }) - - It("dashboard cmd", func() { - cmd := NewDashboardCmd(tf, streams) - Expect(cmd).ShouldNot(BeNil()) - Expect(cmd.HasSubCommands()).Should(BeTrue()) - }) - - It("list", func() { - cmd := newListCmd(tf, streams) - Expect(cmd).ShouldNot(BeNil()) - - By("list options") - - o := newListOptions(tf, streams) - Expect(o.complete()).Should(Succeed()) - Expect(o.run()).Should(Succeed()) - }) - - It("open", func() { - cmd := newOpenCmd(tf, streams) - Expect(cmd).ShouldNot(BeNil()) - - By("open options") - o := newOpenOptions(tf, streams) - Expect(cmd.Flags().Set(podRunningTimeoutFlag, time.Second.String())).Should(Succeed()) - Expect(o).ShouldNot(BeNil()) - Expect(o.complete(cmd, []string{})).Should(HaveOccurred()) - Expect(o.complete(cmd, []string{"kubeblocks-grafana"})).Should(HaveOccurred()) - - clientSet, err := tf.KubernetesClientSet() - Expect(err).Should(Succeed()) - o.portForwardOptions.PodClient = clientSet.CoreV1() - Expect(o.run()).Should(HaveOccurred()) - }) -}) diff --git a/pkg/cmd/dashboard/grafana_direct.go b/pkg/cmd/dashboard/grafana_direct.go deleted file mode 100644 index 1f9167a4b..000000000 --- a/pkg/cmd/dashboard/grafana_direct.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package dashboard - -import ( - "fmt" - "strings" -) - -var ( - clusterType string - // Todo: availableTypes is hard code, better to do a dynamic query but where the source from? - availableTypes = []string{ - "mysql", - "cadvisor", - "jmx", - "kafka", - "mongodb", - "node", - "postgresql", - "redis", - "weaviate", - lokiGrafanaDirect, - } -) - -const supportDirectDashboard = "kubeblocks-grafana" - -func buildGrafanaDirectURL(url *string, targetType string) error { - if targetType == "" { - return nil - } - for i := range availableTypes { - if targetType == availableTypes[i] { - *url += "/d/" + availableTypes[i] - return nil - } - } - return fmt.Errorf("cluster type is invalid, support %s", strings.Join(availableTypes, ",")) -} diff --git a/pkg/cmd/dashboard/grafana_direct_test.go b/pkg/cmd/dashboard/grafana_direct_test.go deleted file mode 100644 index 8d69c6526..000000000 --- a/pkg/cmd/dashboard/grafana_direct_test.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package dashboard - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("grafana open flag", func() { - const baseURL = "http://127.0.0.1:8080" - var testClusterType string - var testURL string - expectURL := map[string]string{ - "mysql": "http://127.0.0.1:8080/d/mysql", - "": "http://127.0.0.1:8080", - } - - It("build grafana direct url", func() { - testURL = baseURL - testClusterType = "invalid" - Expect(buildGrafanaDirectURL(&testURL, testClusterType)).Should(HaveOccurred()) - - testURL = baseURL - testClusterType = "mysql" - Expect(buildGrafanaDirectURL(&testURL, testClusterType)).Should(Succeed()) - Expect(testURL).Should(Equal(expectURL[testClusterType])) - - testURL = baseURL - testClusterType = "" - Expect(buildGrafanaDirectURL(&testURL, testClusterType)).Should(Succeed()) - Expect(testURL).Should(Equal(expectURL[testClusterType])) - }) -}) diff --git a/pkg/cmd/dataprotection/actionset.go b/pkg/cmd/dataprotection/actionset.go index 2823c207b..6f6e787da 100644 --- a/pkg/cmd/dataprotection/actionset.go +++ b/pkg/cmd/dataprotection/actionset.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -43,7 +43,7 @@ func newListActionSetCmd(f cmdutil.Factory, streams genericclioptions.IOStreams) o := action.NewListOptions(f, streams, types.ActionSetGVR()) headers := []any{"NAME", "BACKUP-TYPE", "STATUS", "CREATED-TIME"} cmd := &cobra.Command{ - Use: "list-action-set", + Use: "list-action-sets", Short: "List actionsets", Aliases: []string{"list-as"}, Example: listActionSetExample, diff --git a/pkg/cmd/dataprotection/backup.go b/pkg/cmd/dataprotection/backup.go index 487046455..5293e31bf 100644 --- a/pkg/cmd/dataprotection/backup.go +++ b/pkg/cmd/dataprotection/backup.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -55,7 +55,7 @@ var ( # create a backup with a specified method, run "kbcli cluster desc-backup-policy mycluster" to show supported backup methods kbcli dp backup mybackup --cluster mycluster --method mymethod - # create a backup with specified backup policy, run "kbcli cluster list-backup-policy mycluster" to show the cluster supported backup policies + # create a backup with specified backup policy, run "kbcli cluster list-backup-policies mycluster" to show the cluster supported backup policies kbcli dp backup mybackup --cluster mycluster --policy mypolicy # create a backup from a parent backup @@ -74,10 +74,10 @@ var ( listBackupExample = templates.Examples(` # list all backups - kbcli dp list-backup + kbcli dp list-backups # list all backups of specified cluster - kbcli dp list-backup --cluster mycluster + kbcli dp list-backups --cluster mycluster `) ) @@ -247,7 +247,7 @@ func newBackupCommand(f cmdutil.Factory, streams genericiooptions.IOStreams) *co customOutPut := func(opt *action.CreateOptions) { output := fmt.Sprintf("Backup %s created successfully, you can view the progress:", opt.Name) printer.PrintLine(output) - nextLine := fmt.Sprintf("\tkbcli dp list-backup %s -n %s", opt.Name, opt.Namespace) + nextLine := fmt.Sprintf("\tkbcli dp list-backups %s -n %s", opt.Name, opt.Namespace) printer.PrintLine(nextLine) } @@ -375,7 +375,7 @@ func newListBackupCommand(f cmdutil.Factory, streams genericiooptions.IOStreams) o := action.NewListOptions(f, streams, types.BackupGVR()) clusterName := "" cmd := &cobra.Command{ - Use: "list-backup", + Use: "list-backups", Short: "List backups.", Aliases: []string{"ls-backups"}, Example: listBackupExample, diff --git a/pkg/cmd/dataprotection/backuppolicy.go b/pkg/cmd/dataprotection/backuppolicy.go index a2276743b..cc7938a07 100644 --- a/pkg/cmd/dataprotection/backuppolicy.go +++ b/pkg/cmd/dataprotection/backuppolicy.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -50,7 +50,7 @@ import ( var ( listBackupPolicyExample = templates.Examples(` # list all backup policies - kbcli dp list-backup-policy + kbcli dp list-backup-policies # using short cmd to list backup policy of the specified cluster kbcli dp list-bp mycluster @@ -143,7 +143,7 @@ func newListBackupPolicyCmd(f cmdutil.Factory, streams genericclioptions.IOStrea o := action.NewListOptions(f, streams, types.BackupPolicyGVR()) clusterName := "" cmd := &cobra.Command{ - Use: "list-backup-policy", + Use: "list-backup-policies", Short: "List backup policies", Aliases: []string{"list-bp"}, Example: listBackupPolicyExample, diff --git a/pkg/cmd/dataprotection/backuppolicytemplate.go b/pkg/cmd/dataprotection/backuppolicytemplate.go index c4bd59c53..dfccad4ca 100644 --- a/pkg/cmd/dataprotection/backuppolicytemplate.go +++ b/pkg/cmd/dataprotection/backuppolicytemplate.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -43,8 +43,8 @@ func newListBackupPolicyTemplateCmd(f cmdutil.Factory, streams genericclioptions o := action.NewListOptions(f, streams, types.BackupPolicyTemplateGVR()) headers := []any{"NAME", "SERVICE-KIND", "STATUS", "CREATED-TIME"} cmd := &cobra.Command{ - Use: "list-backup-policy-template", - Short: "List backup policy template", + Use: "list-backup-policy-templates", + Short: "List backup policy templates", Aliases: []string{"list-bpt"}, Example: listBPTExample, ValidArgsFunction: util.ResourceNameCompletionFunc(f, o.GVR), diff --git a/pkg/cmd/dataprotection/dataprotection.go b/pkg/cmd/dataprotection/dataprotection.go index bd024dd4e..6a438b55b 100644 --- a/pkg/cmd/dataprotection/dataprotection.go +++ b/pkg/cmd/dataprotection/dataprotection.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/dataprotection/datatprotection_test.go b/pkg/cmd/dataprotection/datatprotection_test.go index 7a6079eb9..bf7275d2b 100644 --- a/pkg/cmd/dataprotection/datatprotection_test.go +++ b/pkg/cmd/dataprotection/datatprotection_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -100,7 +100,7 @@ var _ = Describe("DataProtection", func() { tf.FakeDynamicClient = testing.FakeDynamicClient(objects...) } - It("list-action-set", func() { + It("list-action-sets", func() { By("fake client") initClient() @@ -124,7 +124,7 @@ var _ = Describe("DataProtection", func() { Expect(len(strings.Split(strings.Trim(out.String(), "\n"), "\n"))).Should(Equal(2)) }) - It("list-backup-policy", func() { + It("list-backup-policies", func() { By("fake client") defaultBackupPolicy := testing.FakeBackupPolicy(policyName, testing.ClusterName) policy2 := testing.FakeBackupPolicy("policy1", testing.ClusterName) @@ -132,7 +132,7 @@ var _ = Describe("DataProtection", func() { policy3.Namespace = "policy" initClient(defaultBackupPolicy, policy2, policy3) - By("test list-backup-policy cmd") + By("test list-backup-policies cmd") cmd := newListBackupPolicyCmd(tf, streams) Expect(cmd).ShouldNot(BeNil()) cmd.Run(cmd, nil) @@ -261,16 +261,16 @@ var _ = Describe("DataProtection", func() { Expect(completeForDeleteBackup(o, "")).Should(HaveOccurred()) }) - It("list-backup", func() { + It("list-backups", func() { cmd := newListBackupCommand(tf, streams) Expect(cmd).ShouldNot(BeNil()) - By("test list-backup cmd with no backup") + By("test list-backups cmd with no backup") tf.FakeDynamicClient = testing.FakeDynamicClient() o := action.NewListOptions(tf, streams, types.BackupGVR()) Expect(PrintBackupList(o)).Should(Succeed()) Expect(o.ErrOut.(*bytes.Buffer).String()).Should(ContainSubstring("No backups found")) - By("test list-backup") + By("test list-backups") backup1 := testing.FakeBackup("test1") backup1.Labels = map[string]string{ constant.AppInstanceLabelKey: "apecloud-mysql", @@ -338,7 +338,7 @@ var _ = Describe("DataProtection", func() { Expect(capturedOutput).Should(ContainSubstring(testing.BackupName)) }) - It("list-restore", func() { + It("list-restores", func() { By("fake client") initClient(testing.FakeRestore(testing.BackupName)) diff --git a/pkg/cmd/dataprotection/restore.go b/pkg/cmd/dataprotection/restore.go index c46198d7b..3785a67b9 100644 --- a/pkg/cmd/dataprotection/restore.go +++ b/pkg/cmd/dataprotection/restore.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -23,19 +23,21 @@ import ( "fmt" "strconv" - dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/constant" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/duration" "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes/scheme" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" + dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" + opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kbcli/pkg/action" "github.com/apecloud/kbcli/pkg/cluster" "github.com/apecloud/kbcli/pkg/printer" @@ -54,7 +56,7 @@ var ( listRestoreExample = templates.Examples(` # list all restores - kbcli dp list-restore`) + kbcli dp list-restores`) ) type CreateRestoreOptions struct { @@ -71,6 +73,10 @@ func (o *CreateRestoreOptions) Validate() error { if o.RestoreSpec.BackupName == "" { return fmt.Errorf("must be specified one of the --backup ") } + backup, err := GetBackupByName(o.Dynamic, o.RestoreSpec.BackupName, o.Namespace) + if backup == nil || err != nil { + return fmt.Errorf("failed to find the backup, please confirm the specified name and namespace of backup. %s", err) + } if o.Name == "" { name, err := cluster.GenerateClusterName(o.Dynamic, o.Namespace) @@ -155,7 +161,7 @@ func newListRestoreCommand(f cmdutil.Factory, streams genericiooptions.IOStreams o := action.NewListOptions(f, streams, types.RestoreGVR()) clusterName := "" cmd := &cobra.Command{ - Use: "list-restore", + Use: "list-restores", Short: "List restores.", Aliases: []string{"ls-restores"}, Example: listRestoreExample, @@ -283,3 +289,11 @@ func PrintRestoreDescribe(o *DescribeDPOptions, obj *dpv1alpha1.Restore) error { printer.PrintAllWarningEvents(events, o.Out) return nil } + +func GetBackupByName(dynamic dynamic.Interface, name string, namespace string) (*dpv1alpha1.Backup, error) { + backup := &dpv1alpha1.Backup{} + if err := util.GetK8SClientObject(dynamic, backup, types.BackupGVR(), namespace, name); err != nil { + return nil, err + } + return backup, nil +} diff --git a/pkg/cmd/dataprotection/suite_test.go b/pkg/cmd/dataprotection/suite_test.go index 24df844b3..f0cab3f9c 100644 --- a/pkg/cmd/dataprotection/suite_test.go +++ b/pkg/cmd/dataprotection/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/dataprotection/util.go b/pkg/cmd/dataprotection/util.go index 2f5722bc0..2eb60c184 100644 --- a/pkg/cmd/dataprotection/util.go +++ b/pkg/cmd/dataprotection/util.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/compare.go b/pkg/cmd/kubeblocks/compare.go index 275e0bb4b..cee309277 100644 --- a/pkg/cmd/kubeblocks/compare.go +++ b/pkg/cmd/kubeblocks/compare.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/compare_test.go b/pkg/cmd/kubeblocks/compare_test.go index 2361e2c9b..128b698e0 100644 --- a/pkg/cmd/kubeblocks/compare_test.go +++ b/pkg/cmd/kubeblocks/compare_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/config.go b/pkg/cmd/kubeblocks/config.go index 56e49ce6f..eef297235 100644 --- a/pkg/cmd/kubeblocks/config.go +++ b/pkg/cmd/kubeblocks/config.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/config_test.go b/pkg/cmd/kubeblocks/config_test.go index c2bcf1ba7..f337288dc 100644 --- a/pkg/cmd/kubeblocks/config_test.go +++ b/pkg/cmd/kubeblocks/config_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/install.go b/pkg/cmd/kubeblocks/install.go index c201b8e79..be7c03875 100644 --- a/pkg/cmd/kubeblocks/install.go +++ b/pkg/cmd/kubeblocks/install.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -534,12 +534,17 @@ func (o *InstallOptions) checkNamespace() error { // target namespace is not specified, use default namespace if o.HelmCfg.Namespace() == "" { o.HelmCfg.SetNamespace(o.Namespace) + } + if o.Namespace == types.DefaultNamespace { o.CreateNamespace = true } fmt.Fprintf(o.Out, "KubeBlocks will be installed to namespace \"%s\"\n", o.HelmCfg.Namespace()) // check if namespace exists if !o.CreateNamespace { _, err := o.Client.CoreV1().Namespaces().Get(context.TODO(), o.Namespace, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return fmt.Errorf("namespace %s not found, please use --create-namespace to create it", o.Namespace) + } return err } return nil diff --git a/pkg/cmd/kubeblocks/install_1.0.go b/pkg/cmd/kubeblocks/install_1.0.go index 3f837dfe9..b3a5aa4ee 100644 --- a/pkg/cmd/kubeblocks/install_1.0.go +++ b/pkg/cmd/kubeblocks/install_1.0.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -29,8 +29,9 @@ import ( "golang.org/x/mod/semver" "helm.sh/helm/v3/pkg/cli/values" appsv1 "k8s.io/api/apps/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" k8stypes "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" @@ -85,7 +86,7 @@ func (o *InstallOptions) preInstallWhenUpgradeFrom09() error { kbVersion := deploy.Labels[constant.AppVersionLabelKey] o.kb09Namespace = deploy.Namespace s := spinner.New(o.Out, spinnerMsg(fmt.Sprintf("Stop %s %s", msgKey, kbVersion))) - if err = o.stopDeployment(s, deploy); err != nil { + if err = o.stopDeploymentObject(s, deploy); err != nil { return err } } @@ -105,7 +106,10 @@ func (o *InstallOptions) preInstallWhenUpgradeFrom09() error { return err } // 4. Set global resources helm owner to 1.0 KB - return o.setGlobalResourcesHelmOwner() + if err := o.setGlobalResourcesHelmOwner(); err != nil { + return err + } + return o.setCRDAPIVersion() } func (o *InstallOptions) configKB09() error { @@ -130,10 +134,10 @@ func (o *InstallOptions) configKB09() error { return configKBRelease(configOpt) } -func (o *InstallOptions) stopDeployment(s spinner.Interface, deploy *appsv1.Deployment) error { +func (o *InstallOptions) stopDeploymentObject(s spinner.Interface, deploy *appsv1.Deployment) error { defer s.Fail() ctx := context.TODO() - if *deploy.Spec.Replicas != 0 { + if deploy.Spec.Replicas != nil && *deploy.Spec.Replicas != 0 { patch := []byte(fmt.Sprintf(`{"spec":{"replicas":%d}}`, 0)) _, err := o.Client.AppsV1().Deployments(deploy.Namespace).Patch( ctx, @@ -167,20 +171,9 @@ func (o *InstallOptions) stopDeployment(s spinner.Interface, deploy *appsv1.Depl func (o *InstallOptions) setGlobalResourcesHelmOwner() error { fmt.Fprintf(o.Out, "Change the release owner for the global resources\n") - setHelmOwner := func(gvr schema.GroupVersionResource, names []string) error { - patchOP := fmt.Sprintf(`[{"op": "replace", "path": "/metadata/annotations/meta.helm.sh~1release-name", "value": "%s"}`+ - `,{"op": "replace", "path": "/metadata/annotations/meta.helm.sh~1release-namespace", "value": "%s"}]`, types.KubeBlocksChartName, o.HelmCfg.Namespace()) - for _, name := range names { - if _, err := o.Dynamic.Resource(gvr).Namespace("").Patch(context.TODO(), name, - k8stypes.JSONPatchType, []byte(patchOP), metav1.PatchOptions{}); client.IgnoreNotFound(err) != nil { - return err - } - } - return nil - } // update ClusterRoles - if err := setHelmOwner(types.ClusterRoleGVR(), []string{ + if err := util.SetHelmOwner(o.Dynamic, types.ClusterRoleGVR(), types.KubeBlocksChartName, o.HelmCfg.Namespace(), []string{ "kubeblocks-cluster-pod-role", types.KubeBlocksChartName, fmt.Sprintf("%s-cluster-editor-role", types.KubeBlocksChartName), @@ -205,7 +198,7 @@ func (o *InstallOptions) setGlobalResourcesHelmOwner() error { return err } // update Addons - if err := setHelmOwner(types.AddonGVR(), []string{ + if err := util.SetHelmOwner(o.Dynamic, types.AddonGVR(), types.KubeBlocksChartName, o.HelmCfg.Namespace(), []string{ "apecloud-mysql", "etcd", "kafka", "llm", "mongodb", "mysql", "postgresql", "pulsar", "qdrant", "redis", "alertmanager-webhook-adaptor", @@ -216,12 +209,48 @@ func (o *InstallOptions) setGlobalResourcesHelmOwner() error { return err } // update StorageProviders - if err := setHelmOwner(types.StorageProviderGVR(), []string{ + if err := util.SetHelmOwner(o.Dynamic, types.StorageProviderGVR(), types.KubeBlocksChartName, o.HelmCfg.Namespace(), []string{ "cos", "ftp", "gcs-s3comp", "minio", "nfs", "obs", "oss", "pvc", "s3", }); err != nil { return err } + _, err := o.Dynamic.Resource(types.StorageClassGVR()).Namespace("").Get(context.TODO(), "kb-default-sc", metav1.GetOptions{}) + if err != nil { + if !apierrors.IsNotFound(err) { + return err + } + } else { + if err = util.SetHelmOwner(o.Dynamic, types.StorageClassGVR(), types.KubeBlocksChartName, o.HelmCfg.Namespace(), []string{ + "kb-default-sc", + }); err != nil { + return err + } + } // update BackupRepo - return setHelmOwner(types.BackupRepoGVR(), []string{fmt.Sprintf("%s-backuprepo", types.KubeBlocksChartName)}) + return util.SetHelmOwner(o.Dynamic, types.BackupRepoGVR(), types.KubeBlocksChartName, o.HelmCfg.Namespace(), []string{fmt.Sprintf("%s-backuprepo", types.KubeBlocksChartName)}) +} + +func (o *InstallOptions) setCRDAPIVersion() error { + setCRDAPIVersionAnnotation := func(list *unstructured.UnstructuredList, version string) error { + patchOP := fmt.Sprintf(`[{"op": "replace", "path": "/metadata/annotations/kubeblocks.io~1crd-api-version", "value": "apps.kubeblocks.io/%s"}]`, version) + for _, v := range list.Items { + if v.GetLabels()[constant.CRDAPIVersionAnnotationKey] != "" { + continue + } + if _, err := o.Dynamic.Resource(types.CompDefAlpha1GVR()).Namespace("").Patch(context.TODO(), v.GetName(), + k8stypes.JSONPatchType, []byte(patchOP), metav1.PatchOptions{}); client.IgnoreNotFound(err) != nil { + return err + } + } + return nil + } + compDefs, err := o.Dynamic.Resource(types.CompDefAlpha1GVR()).Namespace("").List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + if err = setCRDAPIVersionAnnotation(compDefs, types.AppsAPIVersion); err != nil { + return err + } + return nil } diff --git a/pkg/cmd/kubeblocks/install_1.0_test.go b/pkg/cmd/kubeblocks/install_1.0_test.go index e9ab7161a..b76dde7f9 100644 --- a/pkg/cmd/kubeblocks/install_1.0_test.go +++ b/pkg/cmd/kubeblocks/install_1.0_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -120,7 +120,7 @@ func TestStopDeployment(t *testing.T) { } s := spinner.New(o.Out, spinnerMsg("Stop KubeBlocks Deployment")) - err := o.stopDeployment(s, deploy) + err := o.stopDeploymentObject(s, deploy) assert.NoError(t, err) updatedDeploy, err := client.AppsV1().Deployments(namespace).Get(context.TODO(), "test-deploy", metav1.GetOptions{}) diff --git a/pkg/cmd/kubeblocks/install_test.go b/pkg/cmd/kubeblocks/install_test.go index c0634f914..2c8346b8a 100644 --- a/pkg/cmd/kubeblocks/install_test.go +++ b/pkg/cmd/kubeblocks/install_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/kubeblocks.go b/pkg/cmd/kubeblocks/kubeblocks.go index 7c1899c1f..33bb2972a 100644 --- a/pkg/cmd/kubeblocks/kubeblocks.go +++ b/pkg/cmd/kubeblocks/kubeblocks.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/kubeblocks_objects.go b/pkg/cmd/kubeblocks/kubeblocks_objects.go index b1f70b252..d46c3db35 100644 --- a/pkg/cmd/kubeblocks/kubeblocks_objects.go +++ b/pkg/cmd/kubeblocks/kubeblocks_objects.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -22,6 +22,7 @@ package kubeblocks import ( "context" "encoding/json" + "fmt" "strings" corev1 "k8s.io/api/core/v1" @@ -82,7 +83,9 @@ func getKBObjects(dynamic dynamic.Interface, namespace string, addons []*extensi ctx := context.TODO() // get CRDs - crds, err := dynamic.Resource(types.CRDGVR()).List(ctx, metav1.ListOptions{}) + crds, err := dynamic.Resource(types.CRDGVR()).List(ctx, metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", constant.AppNameLabelKey, types.KubeBlocksChartName), + }) appendErr(err) kbObjs[types.CRDGVR()] = &unstructured.UnstructuredList{} diff --git a/pkg/cmd/kubeblocks/kubeblocks_objects_test.go b/pkg/cmd/kubeblocks/kubeblocks_objects_test.go index b5c6624b3..56dfcfff7 100644 --- a/pkg/cmd/kubeblocks/kubeblocks_objects_test.go +++ b/pkg/cmd/kubeblocks/kubeblocks_objects_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -155,13 +155,15 @@ func mockName() string { } func mockCRD() []runtime.Object { + label := map[string]string{constant.AppNameLabelKey: types.KubeBlocksChartName} clusterCRD := v1.CustomResourceDefinition{ TypeMeta: metav1.TypeMeta{ Kind: "CustomResourceDefinition", APIVersion: "apiextensions.k8s.io/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: "clusters.apps.kubeblocks.io", + Name: "clusters.apps.kubeblocks.io", + Labels: label, }, Spec: v1.CustomResourceDefinitionSpec{ Group: types.AppsAPIGroup, @@ -174,7 +176,8 @@ func mockCRD() []runtime.Object { APIVersion: "apiextensions.k8s.io/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: "clusterdefinitions.apps.kubeblocks.io", + Name: "clusterdefinitions.apps.kubeblocks.io", + Labels: label, }, Spec: v1.CustomResourceDefinitionSpec{ Group: types.AppsAPIGroup, @@ -188,7 +191,8 @@ func mockCRD() []runtime.Object { APIVersion: "apiextensions.k8s.io/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: "actionsets.dataprotection.kubeblocks.io", + Name: "actionsets.dataprotection.kubeblocks.io", + Labels: label, }, Spec: v1.CustomResourceDefinitionSpec{ Group: types.DPAPIGroup, diff --git a/pkg/cmd/kubeblocks/kubeblocks_test.go b/pkg/cmd/kubeblocks/kubeblocks_test.go index 315c69548..c373946dd 100644 --- a/pkg/cmd/kubeblocks/kubeblocks_test.go +++ b/pkg/cmd/kubeblocks/kubeblocks_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/list_versions.go b/pkg/cmd/kubeblocks/list_versions.go index 65c98adb4..c7c6ce481 100644 --- a/pkg/cmd/kubeblocks/list_versions.go +++ b/pkg/cmd/kubeblocks/list_versions.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/list_versions_test.go b/pkg/cmd/kubeblocks/list_versions_test.go index 4111cafff..b577cad64 100644 --- a/pkg/cmd/kubeblocks/list_versions_test.go +++ b/pkg/cmd/kubeblocks/list_versions_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/preflight.go b/pkg/cmd/kubeblocks/preflight.go index d7b1aa3ce..955b1cf9d 100644 --- a/pkg/cmd/kubeblocks/preflight.go +++ b/pkg/cmd/kubeblocks/preflight.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/preflight_test.go b/pkg/cmd/kubeblocks/preflight_test.go index 61d411b0d..664afeda3 100644 --- a/pkg/cmd/kubeblocks/preflight_test.go +++ b/pkg/cmd/kubeblocks/preflight_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/status.go b/pkg/cmd/kubeblocks/status.go index 16e9b3dc6..65fba6c52 100644 --- a/pkg/cmd/kubeblocks/status.go +++ b/pkg/cmd/kubeblocks/status.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -401,7 +401,7 @@ func (o *statusOptions) showWorkloads(ctx context.Context, allErrs *[]error) { tblPrinter.SetHeader("NAMESPACE", "KIND", "NAME", "READY PODS", "CPU(cores)", "MEMORY(bytes)", "CREATED-AT") - unstructuredList := util.ListResourceByGVR(ctx, o.dynamic, o.ns, kubeBlocksWorkloads, o.selectorList, allErrs) + unstructuredList := util.ListResourceByGVR(ctx, o.dynamic, "", kubeBlocksWorkloads, o.selectorList, allErrs) cpuMap, memMap, readyMap := computeMetricByWorkloads(ctx, o.ns, unstructuredList, o.mc, allErrs) diff --git a/pkg/cmd/kubeblocks/status_test.go b/pkg/cmd/kubeblocks/status_test.go index dfbe3abc5..dfd5c2c5d 100644 --- a/pkg/cmd/kubeblocks/status_test.go +++ b/pkg/cmd/kubeblocks/status_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/suite_test.go b/pkg/cmd/kubeblocks/suite_test.go index 83b588016..707a7c2b8 100644 --- a/pkg/cmd/kubeblocks/suite_test.go +++ b/pkg/cmd/kubeblocks/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/uninstall.go b/pkg/cmd/kubeblocks/uninstall.go index 5f2cc5a45..d95c6aa67 100644 --- a/pkg/cmd/kubeblocks/uninstall.go +++ b/pkg/cmd/kubeblocks/uninstall.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -31,7 +31,6 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/exp/maps" - "helm.sh/helm/v3/pkg/repo" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -229,10 +228,6 @@ func (o *UninstallOptions) Uninstall() error { // remove KB release printSpinner(newSpinner("Uninstall helm release "+types.KubeBlocksReleaseName+" "+v.KubeBlocks), chart.Uninstall(o.HelmCfg)) - - // remove repo - printSpinner(newSpinner("Remove helm repo "+types.KubeBlocksChartName), - helm.RemoveRepo(&repo.Entry{Name: types.KubeBlocksChartName})) } // delete namespace if it is default namespace diff --git a/pkg/cmd/kubeblocks/uninstall_test.go b/pkg/cmd/kubeblocks/uninstall_test.go index a342ddfe8..2b6e8a4c8 100644 --- a/pkg/cmd/kubeblocks/uninstall_test.go +++ b/pkg/cmd/kubeblocks/uninstall_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/upgrade.go b/pkg/cmd/kubeblocks/upgrade.go index 773c5acc6..de2a060cd 100644 --- a/pkg/cmd/kubeblocks/upgrade.go +++ b/pkg/cmd/kubeblocks/upgrade.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -36,7 +36,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" apitypes "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" "k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" @@ -104,24 +103,12 @@ func (o *InstallOptions) getKBRelease() (*release.Release, error) { } o.HelmCfg.SetNamespace(ns) } - // check helm release status + // get helm release KBRelease, err := helm.GetHelmRelease(o.HelmCfg, types.KubeBlocksChartName) if err != nil { return nil, fmt.Errorf("failed to get Helm release: %v in namespace %s", err, o.Namespace) } - // intercept status of pending, unknown, uninstalling and uninstalled. - var status release.Status - if KBRelease != nil && KBRelease.Info != nil { - status = KBRelease.Info.Status - } else { - return nil, fmt.Errorf("failed to get Helm release status: release or release info is nil") - } - if status.IsPending() { - return nil, fmt.Errorf("helm release status is %s. Please wait until the release status changes to ‘deployed’ before upgrading KubeBlocks", status.String()) - } else if status != release.StatusDeployed && status != release.StatusFailed && status != release.StatusSuperseded { - return nil, fmt.Errorf("helm release status is %s. Please fix the release before upgrading KubeBlocks", status.String()) - } return KBRelease, nil } @@ -214,19 +201,15 @@ func (o *InstallOptions) Upgrade() error { // new version resources, which may be not compatible. helm will start the new version // KubeBlocks after upgrade. s = spinner.New(o.Out, spinnerMsg("Stop KubeBlocks "+kbVersion)) - defer s.Fail() - if err = o.deleteDeployment(util.GetKubeBlocksDeploy); err != nil { + if err = o.stopDeployment(s, util.GetKubeBlocksDeploy); err != nil { return err } - s.Success() // stop the data protection deployment s = spinner.New(o.Out, spinnerMsg("Stop DataProtection")) - defer s.Fail() - if err = o.deleteDeployment(util.GetDataProtectionDeploy); err != nil { + if err = o.stopDeployment(s, util.GetDataProtectionDeploy); err != nil { return err } - s.Success() msg = "to " + o.Version } @@ -308,7 +291,7 @@ func (o *InstallOptions) upgradeChart() error { } // deleteDeployment deletes deployment. -func (o *InstallOptions) deleteDeployment(getter deploymentGetter) error { +func (o *InstallOptions) stopDeployment(s spinner.Interface, getter deploymentGetter) error { deploy, err := getter(o.Client) if err != nil { if apierrors.IsNotFound(err) { @@ -335,31 +318,7 @@ func (o *InstallOptions) deleteDeployment(getter deploymentGetter) error { ------------------ Deployment %s end ----------------`, deploy.Name, string(bytes), deploy.Name) - if err = o.Client.AppsV1().Deployments(deploy.Namespace).Delete(context.TODO(), - deploy.Name, - metav1.DeleteOptions{ - GracePeriodSeconds: func() *int64 { - seconds := int64(0) - return &seconds - }(), - }); err != nil { - return err - } - - // wait for deployment to be deleted - return wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 1*time.Minute, true, - func(_ context.Context) (bool, error) { - deploy, err = getter(o.Client) - if err != nil { - if apierrors.IsNotFound(err) { - return true, nil - } - return false, err - } else if deploy == nil { - return true, nil - } - return false, err - }) + return o.stopDeploymentObject(s, deploy) } // keepAddons set the addons to keep when upgrade KubeBlocks avoid the addons been deleted diff --git a/pkg/cmd/kubeblocks/upgrade_test.go b/pkg/cmd/kubeblocks/upgrade_test.go index 9cb012830..c524aabbe 100644 --- a/pkg/cmd/kubeblocks/upgrade_test.go +++ b/pkg/cmd/kubeblocks/upgrade_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -22,11 +22,10 @@ package kubeblocks import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/spf13/cobra" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/release" - - "github.com/spf13/cobra" appsv1 "k8s.io/api/apps/v1" "k8s.io/cli-runtime/pkg/genericiooptions" clientfake "k8s.io/client-go/rest/fake" @@ -175,77 +174,4 @@ var _ = Describe("kubeblocks upgrade", func() { }) }) - Context("upgrade from different status", func() { - BeforeEach(func() { - streams, _, _, _ = genericiooptions.NewTestIOStreams() - tf = cmdtesting.NewTestFactory().WithNamespace(namespace) - tf.Client = &clientfake.RESTClient{} - cfg = helm.NewFakeConfig(namespace) - actionCfg, _ = helm.NewActionConfig(cfg) - }) - - AfterEach(func() { - helm.ResetFakeActionConfig() - tf.Cleanup() - }) - - mockKubeBlocksDeploy := func() *appsv1.Deployment { - deploy := &appsv1.Deployment{} - deploy.SetLabels(map[string]string{ - "app.kubernetes.io/component": "apps", - "app.kubernetes.io/name": types.KubeBlocksChartName, - "app.kubernetes.io/version": "0.3.0", - }) - return deploy - } - It("run upgrade", func() { - testCase := []struct { - status release.Status - checkResult bool - }{ - {release.StatusDeployed, true}, - {release.StatusSuperseded, true}, - {release.StatusFailed, true}, - {release.StatusUnknown, false}, - {release.StatusUninstalled, false}, - {release.StatusUninstalling, false}, - {release.StatusPendingInstall, false}, - {release.StatusPendingUpgrade, false}, - {release.StatusPendingRollback, false}, - } - - for i := range testCase { - actionCfg, _ = helm.NewActionConfig(cfg) - err := actionCfg.Releases.Create(&release.Release{ - Name: testing.KubeBlocksChartName, - Namespace: namespace, - Version: 1, - Info: &release.Info{ - Status: testCase[i].status, - }, - Chart: &chart.Chart{}, - }) - Expect(err).Should(BeNil()) - o := &InstallOptions{ - Options: Options{ - IOStreams: streams, - HelmCfg: cfg, - Namespace: "default", - Client: testing.FakeClientSet(mockKubeBlocksDeploy()), - Dynamic: testing.FakeDynamicClient(), - }, - Version: version.DefaultKubeBlocksVersion, - Check: false, - } - if testCase[i].checkResult { - Expect(o.Upgrade()).Should(Succeed()) - } else { - Expect(o.Upgrade()).Should(HaveOccurred()) - } - helm.ResetFakeActionConfig() - } - - }) - }) - }) diff --git a/pkg/cmd/kubeblocks/util.go b/pkg/cmd/kubeblocks/util.go index 481335dc7..7375cc155 100644 --- a/pkg/cmd/kubeblocks/util.go +++ b/pkg/cmd/kubeblocks/util.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/kubeblocks/util_test.go b/pkg/cmd/kubeblocks/util_test.go index 0daccd361..01d9483ad 100644 --- a/pkg/cmd/kubeblocks/util_test.go +++ b/pkg/cmd/kubeblocks/util_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/opsdefinition/describe.go b/pkg/cmd/opsdefinition/describe.go index 961308310..8e4f957c0 100644 --- a/pkg/cmd/opsdefinition/describe.go +++ b/pkg/cmd/opsdefinition/describe.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/cmd/opsdefinition/opsdefinition.go b/pkg/cmd/opsdefinition/opsdefinition.go index fcd4a464a..47254d07d 100644 --- a/pkg/cmd/opsdefinition/opsdefinition.go +++ b/pkg/cmd/opsdefinition/opsdefinition.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/opsdefinition/opsdefinition_test.go b/pkg/cmd/opsdefinition/opsdefinition_test.go index 777ee51f5..6480ba027 100644 --- a/pkg/cmd/opsdefinition/opsdefinition_test.go +++ b/pkg/cmd/opsdefinition/opsdefinition_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/opsdefinition/suite_test.go b/pkg/cmd/opsdefinition/suite_test.go index 9be2266ca..65d758e3b 100644 --- a/pkg/cmd/opsdefinition/suite_test.go +++ b/pkg/cmd/opsdefinition/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/options/options.go b/pkg/cmd/options/options.go index 007222768..3d713d773 100644 --- a/pkg/cmd/options/options.go +++ b/pkg/cmd/options/options.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/options/options_test.go b/pkg/cmd/options/options_test.go index 68bafce27..8776bed5c 100644 --- a/pkg/cmd/options/options_test.go +++ b/pkg/cmd/options/options_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/playground/base.go b/pkg/cmd/playground/base.go index ce2931a97..7f4de7b49 100644 --- a/pkg/cmd/playground/base.go +++ b/pkg/cmd/playground/base.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/playground/destroy.go b/pkg/cmd/playground/destroy.go index de5dda092..cbda832c8 100644 --- a/pkg/cmd/playground/destroy.go +++ b/pkg/cmd/playground/destroy.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/playground/destroy_test.go b/pkg/cmd/playground/destroy_test.go index 03e881560..5bf177a58 100644 --- a/pkg/cmd/playground/destroy_test.go +++ b/pkg/cmd/playground/destroy_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/playground/init.go b/pkg/cmd/playground/init.go index 1cd782a71..0698d937b 100644 --- a/pkg/cmd/playground/init.go +++ b/pkg/cmd/playground/init.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -23,6 +23,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strings" "time" @@ -30,12 +31,16 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/exp/slices" + "golang.org/x/net/context" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/rand" "k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/klog/v2" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" + "sigs.k8s.io/yaml" cp "github.com/apecloud/kbcli/pkg/cloudprovider" "github.com/apecloud/kbcli/pkg/cluster" @@ -72,7 +77,8 @@ on the created kubernetes cluster, and an apecloud-mysql cluster named mycluster kbcli cluster describe mycluster # connect to database - kbcli cluster connect mycluster + kbcli exec -it mycluster-mysql-0 bash + mysql -h 127.1 -u root -p$MYSQL_ROOT_PASSWORD # view the Grafana kbcli dashboard open kubeblocks-grafana @@ -97,9 +103,15 @@ type initOptions struct { autoApprove bool dockerVersion *gv.Version + k3dClusterOptions baseOptions } +type k3dClusterOptions struct { + k3sImage string + k3dProxyImage string +} + func newInitCmd(streams genericiooptions.IOStreams) *cobra.Command { o := &initOptions{ IOStreams: streams, @@ -123,6 +135,8 @@ func newInitCmd(streams genericiooptions.IOStreams) *cobra.Command { cmd.Flags().StringVar(&o.region, "region", "", "The region to create kubernetes cluster") cmd.Flags().DurationVar(&o.Timeout, "timeout", 600*time.Second, "Time to wait for init playground, such as --timeout=10m") cmd.Flags().BoolVar(&o.autoApprove, "auto-approve", false, "Skip interactive approval during the initialization of playground") + cmd.Flags().StringVar(&o.k3sImage, "k3s-image", cp.K3sImageDefault, "Specify k3s image that you want to use for the nodes if you want to init playground locally") + cmd.Flags().StringVar(&o.k3dProxyImage, "k3d-proxy-image", cp.K3dProxyImageDefault, "Specify k3d proxy image if you want to init playground locally") util.CheckErr(cmd.RegisterFlagCompletionFunc( "cloud-provider", @@ -192,9 +206,25 @@ func (o *initOptions) local() error { clusterInfo = &cp.K8sClusterInfo{ CloudProvider: provider.Name(), ClusterName: types.K3dClusterName, + K3dClusterInfo: cp.K3dClusterInfo{ + K3sImage: o.k3sImage, + K3dProxyImage: o.k3dProxyImage, + }, } } + if clusterInfo.K3sImage == "" { + if o.prevCluster != nil { + playgrouddir, err := initPlaygroundDir() + if err != nil { + return err + } + return fmt.Errorf(fmt.Sprintf("k3s image not specified, you can run `rm -rf %s ` and retry", playgrouddir)) + } + clusterInfo.K3sImage = cp.K3sImageDefault + clusterInfo.K3dProxyImage = cp.K3dProxyImageDefault + } + if err = writeClusterInfo(o.stateFilePath, clusterInfo); err != nil { return errors.Wrapf(err, "failed to write kubernetes cluster info to state file %s:\n %v", o.stateFilePath, clusterInfo) } @@ -398,6 +428,10 @@ func (o *initOptions) installKBAndCluster(info *cp.K8sClusterInfo) error { return errors.Wrap(err, "failed to install KubeBlocks") } klog.V(1).Info("KubeBlocks installed successfully") + if err = o.createSnapshotController(); err != nil { + return errors.Wrap(err, "failed to install snapshot controller") + } + klog.V(1).Info("create snapshot controller addon successfully") // install database cluster clusterInfo := "ClusterType: " + o.clusterType s := spinner.New(o.Out, spinnerMsg("Create cluster %s (%s)", kbClusterName, clusterInfo)) @@ -482,6 +516,46 @@ func (o *initOptions) installKubeBlocks(k8sClusterName string) error { return insOpts.Install() } +func (o *initOptions) createSnapshotController() error { + if o.cloudProvider != cp.Local { + return nil + } + cli, err := util.NewFactory().DynamicClient() + if err != nil { + return err + } + _, currentFile, _, _ := runtime.Caller(1) + baseDir := filepath.Dir(currentFile) + getUnstructured := func(fileName string) (*unstructured.Unstructured, error) { + cmBytes, err := os.ReadFile(fileName) + if err != nil { + return nil, err + } + cm := &unstructured.Unstructured{} + if err := yaml.Unmarshal(cmBytes, cm); err != nil { + return nil, err + } + return cm, err + } + snapshotControllerCM, err := getUnstructured(baseDir + "/snapshot-controller/snapshot-controller-cm.yaml") + if err != nil { + return err + } + snapshotControllerCM.SetNamespace(defaultNamespace) + if _, err = cli.Resource(types.ConfigmapGVR()).Namespace(defaultNamespace).Create(context.TODO(), snapshotControllerCM, metav1.CreateOptions{}); err != nil { + return err + } + + snapshotControllerAddon, err := getUnstructured(baseDir + "/snapshot-controller/snapshot-controller-addon.yaml") + if err != nil { + return err + } + if _, err = cli.Resource(types.AddonGVR()).Namespace("").Create(context.TODO(), snapshotControllerAddon, metav1.CreateOptions{}); err != nil { + return err + } + return nil +} + // createCluster constructs a cluster create options and run func (o *initOptions) createCluster() error { c, err := cmdcluster.NewSubCmdsOptions(&cmdcluster.NewCreateOptions(util.NewFactory(), genericiooptions.NewTestIOStreamsDiscard()).CreateOptions, cluster.ClusterType(o.clusterType)) diff --git a/pkg/cmd/playground/init_test.go b/pkg/cmd/playground/init_test.go index d94066fa5..ec5c1be79 100644 --- a/pkg/cmd/playground/init_test.go +++ b/pkg/cmd/playground/init_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/playground/kubeconfig.go b/pkg/cmd/playground/kubeconfig.go index b28cfadcc..b73d64817 100644 --- a/pkg/cmd/playground/kubeconfig.go +++ b/pkg/cmd/playground/kubeconfig.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/playground/kubeconfig_test.go b/pkg/cmd/playground/kubeconfig_test.go index 9f0cf158f..9c8afd666 100644 --- a/pkg/cmd/playground/kubeconfig_test.go +++ b/pkg/cmd/playground/kubeconfig_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/playground/palyground.go b/pkg/cmd/playground/palyground.go index 76d70190e..4d17f37c5 100644 --- a/pkg/cmd/playground/palyground.go +++ b/pkg/cmd/playground/palyground.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/playground/playground_test.go b/pkg/cmd/playground/playground_test.go index 35c4160aa..e4df1eac2 100644 --- a/pkg/cmd/playground/playground_test.go +++ b/pkg/cmd/playground/playground_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/playground/snapshot-controller/snapshot-controller-addon.yaml b/pkg/cmd/playground/snapshot-controller/snapshot-controller-addon.yaml new file mode 100644 index 000000000..b2e31c0d8 --- /dev/null +++ b/pkg/cmd/playground/snapshot-controller/snapshot-controller-addon.yaml @@ -0,0 +1,71 @@ +apiVersion: extensions.kubeblocks.io/v1alpha1 +kind: Addon +metadata: + name: snapshot-controller +spec: + defaultInstallValues: + - enabled: true + resources: {} + tolerations: '[{"effect":"NoSchedule","key":"kb-controller","operator":"Equal","value":"true"}]' + - resources: {} + selectors: + - key: KubeGitVersion + operator: Contains + values: + - eks + storageClass: ebs.csi.aws.com + tolerations: '[{"effect":"NoSchedule","key":"kb-controller","operator":"Equal","value":"true"}]' + - resources: {} + selectors: + - key: KubeGitVersion + operator: Contains + values: + - gke + storageClass: pd.csi.storage.gke.io + tolerations: '[{"effect":"NoSchedule","key":"kb-controller","operator":"Equal","value":"true"}]' + - resources: {} + selectors: + - key: KubeGitVersion + operator: Contains + values: + - aks + storageClass: disk.csi.azure.com + tolerations: '[{"effect":"NoSchedule","key":"kb-controller","operator":"Equal","value":"true"}]' + description: 'Deploys a Snapshot Controller in a cluster. Snapshot Controllers are + often bundled with the Kubernetes distribution, this chart is meant for cases + where it is not. ' + helm: + chartLocationURL: file:///snapshot-controller-1.7.2.tgz + chartsImage: apecloud-registry.cn-zhangjiakou.cr.aliyuncs.com/apecloud/kubeblocks-charts:0.9.3 + chartsPathInImage: /charts + installValues: + configMapRefs: + - key: values-kubeblocks-override.yaml + name: snapshot-controller-chart-kubeblocks-values + valuesMapping: + jsonMap: + tolerations: tolerations + resources: + cpu: + limits: resources.limits.cpu + requests: resources.requests.cpu + memory: + limits: resources.limits.memory + requests: resources.requests.memory + valueMap: + replicaCount: replicaCount + storageClass: volumeSnapshotClasses[0].driver + installable: + autoInstall: true + selectors: + - key: KubeGitVersion + operator: DoesNotContain + values: + - tke + - aliyun. + - key: KubeProvider + operator: DoesNotContain + values: + - huaweiCloud + - azure + type: Helm diff --git a/pkg/cmd/playground/snapshot-controller/snapshot-controller-cm.yaml b/pkg/cmd/playground/snapshot-controller/snapshot-controller-cm.yaml new file mode 100644 index 000000000..04aeeae42 --- /dev/null +++ b/pkg/cmd/playground/snapshot-controller/snapshot-controller-cm.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +data: + values-kubeblocks-override.yaml: |- + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: kb-controller + operator: In + values: + - "true" + weight: 100 + enabled: true + image: + repository: apecloud-registry.cn-zhangjiakou.cr.aliyuncs.com/apecloud/snapshot-controller + tag: v6.2.1 + replicaCount: 1 + tolerations: + - effect: NoSchedule + key: kb-controller + operator: Equal + value: "true" + volumeSnapshotClasses: + - deletionPolicy: Delete + driver: hostpath.csi.k8s.io + name: default-vsc +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/instance: kubeblocks + app.kubernetes.io/name: kubeblocks + name: snapshot-controller-chart-kubeblocks-values diff --git a/pkg/cmd/playground/suite_test.go b/pkg/cmd/playground/suite_test.go index 7e2dba31e..6442624d1 100644 --- a/pkg/cmd/playground/suite_test.go +++ b/pkg/cmd/playground/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -37,8 +37,8 @@ func TestPlayground(t *testing.T) { var _ = BeforeSuite(func() { // set fake image info - cp.K3sImage = "fake-k3s-image" - cp.K3dProxyImage = "fake-k3d-proxy-image" + cp.K3sImageDefault = "fake-k3s-image" + cp.K3dProxyImageDefault = "fake-k3d-proxy-image" // set default cluster name to test types.K3dClusterName = "kb-playground-test" diff --git a/pkg/cmd/playground/types.go b/pkg/cmd/playground/types.go index 13ede14ff..069e77aa7 100644 --- a/pkg/cmd/playground/types.go +++ b/pkg/cmd/playground/types.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -32,7 +32,7 @@ const ( const ( defaultCloudProvider = cloudprovider.Local - defaultClusterType = "apecloud-mysql" + defaultClusterType = "mysql" // defaultNamespace is the namespace of playground cluster defaultNamespace = "default" diff --git a/pkg/cmd/playground/util.go b/pkg/cmd/playground/util.go index a1186e15d..ec0be4f47 100644 --- a/pkg/cmd/playground/util.go +++ b/pkg/cmd/playground/util.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/playground/util_test.go b/pkg/cmd/playground/util_test.go index 6dd25bd51..66e260d54 100644 --- a/pkg/cmd/playground/util_test.go +++ b/pkg/cmd/playground/util_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/describe.go b/pkg/cmd/plugin/describe.go index 34b2aee64..723ecd2b1 100644 --- a/pkg/cmd/plugin/describe.go +++ b/pkg/cmd/plugin/describe.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/index.go b/pkg/cmd/plugin/index.go index 67a0f70b8..a52e722a6 100644 --- a/pkg/cmd/plugin/index.go +++ b/pkg/cmd/plugin/index.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/install.go b/pkg/cmd/plugin/install.go index 04ff75e56..5a3b409ae 100755 --- a/pkg/cmd/plugin/install.go +++ b/pkg/cmd/plugin/install.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/pathutil.go b/pkg/cmd/plugin/pathutil.go index b16c3ea0f..1b6814d40 100644 --- a/pkg/cmd/plugin/pathutil.go +++ b/pkg/cmd/plugin/pathutil.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/platform.go b/pkg/cmd/plugin/platform.go index 1083bf8c1..21e72e31a 100755 --- a/pkg/cmd/plugin/platform.go +++ b/pkg/cmd/plugin/platform.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/plugin.go b/pkg/cmd/plugin/plugin.go index 8e53fc200..be1aefdea 100644 --- a/pkg/cmd/plugin/plugin.go +++ b/pkg/cmd/plugin/plugin.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/plugin_test.go b/pkg/cmd/plugin/plugin_test.go index 81f25fae3..7753bf69c 100644 --- a/pkg/cmd/plugin/plugin_test.go +++ b/pkg/cmd/plugin/plugin_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/search.go b/pkg/cmd/plugin/search.go index 7b6275f22..c695ced7d 100644 --- a/pkg/cmd/plugin/search.go +++ b/pkg/cmd/plugin/search.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/search_test.go b/pkg/cmd/plugin/search_test.go index cf3a539f8..c5692f666 100644 --- a/pkg/cmd/plugin/search_test.go +++ b/pkg/cmd/plugin/search_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/types.go b/pkg/cmd/plugin/types.go index 5788b41e1..8775a1801 100644 --- a/pkg/cmd/plugin/types.go +++ b/pkg/cmd/plugin/types.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/uninstall.go b/pkg/cmd/plugin/uninstall.go index 8d5d605a2..7dbbc7422 100644 --- a/pkg/cmd/plugin/uninstall.go +++ b/pkg/cmd/plugin/uninstall.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/upgrade.go b/pkg/cmd/plugin/upgrade.go index 205a4d8cf..62e1cdb5f 100644 --- a/pkg/cmd/plugin/upgrade.go +++ b/pkg/cmd/plugin/upgrade.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/plugin/utils.go b/pkg/cmd/plugin/utils.go index cafeec8dd..c32770c95 100644 --- a/pkg/cmd/plugin/utils.go +++ b/pkg/cmd/plugin/utils.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/report/genericclient.go b/pkg/cmd/report/genericclient.go index b09de0e19..9432659b3 100644 --- a/pkg/cmd/report/genericclient.go +++ b/pkg/cmd/report/genericclient.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/report/maskprinter.go b/pkg/cmd/report/maskprinter.go index 53a8e300f..e927c935b 100644 --- a/pkg/cmd/report/maskprinter.go +++ b/pkg/cmd/report/maskprinter.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/report/report.go b/pkg/cmd/report/report.go index ff1263457..f8c104c10 100644 --- a/pkg/cmd/report/report.go +++ b/pkg/cmd/report/report.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/report/report_test.go b/pkg/cmd/report/report_test.go index 091612001..1e9cbbbb9 100644 --- a/pkg/cmd/report/report_test.go +++ b/pkg/cmd/report/report_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/report/suite_test.go b/pkg/cmd/report/suite_test.go index c201d2b8b..5b776b15c 100644 --- a/pkg/cmd/report/suite_test.go +++ b/pkg/cmd/report/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/report/zipwritter.go b/pkg/cmd/report/zipwritter.go index d2b404b48..c9f884497 100644 --- a/pkg/cmd/report/zipwritter.go +++ b/pkg/cmd/report/zipwritter.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/report/zipwritter_test.go b/pkg/cmd/report/zipwritter_test.go index 1238e3d8c..b9136ac5b 100644 --- a/pkg/cmd/report/zipwritter_test.go +++ b/pkg/cmd/report/zipwritter_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/trace/chart/objecttree/tree_chart.go b/pkg/cmd/trace/chart/objecttree/tree_chart.go new file mode 100644 index 000000000..db66cf0d2 --- /dev/null +++ b/pkg/cmd/trace/chart/objecttree/tree_chart.go @@ -0,0 +1,172 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package objecttree + +import ( + "fmt" + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/tree" + zone "github.com/lrstanley/bubblezone" + corev1 "k8s.io/api/core/v1" + + tracev1 "github.com/apecloud/kubeblocks/apis/trace/v1" +) + +var ( + enumeratorStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("63")). // purple + MarginRight(1) + + workloadStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("35")) + + noneWorkloadStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("212")) + + selectedBackgroundColor = lipgloss.Color("4") +) + +type Model struct { + tree *tree.Tree + zoneID string + zoneManager *zone.Manager + + data *tracev1.ObjectTreeNode + + selected *int +} + +func (m *Model) Init() tea.Cmd { + return nil +} + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + if mm, ok := msg.(tea.MouseMsg); ok && mm.Action == tea.MouseActionPress { + if m.zoneManager == nil { + return m, nil + } + if m.zoneManager.Get(m.zoneID).InBounds(mm) { + m.setSelectedObject(mm) + } + } + return m, nil +} + +func (m *Model) View() string { + m.tree = buildObjectTree(m.data) + if m.tree == nil { + return "" + } + + depthObjectMap := make(map[int]string) + buildDepth2ObjectMap(m.data, 0, depthObjectMap) + + setBackgroundColor := func(style lipgloss.Style, nodeValue string) lipgloss.Style { + if m.selected != nil { + if obj, ok := depthObjectMap[*m.selected]; ok && nodeValue == obj { + return style.Background(selectedBackgroundColor) + } + } + return style + } + + rootStyle := setBackgroundColor(workloadStyle, m.tree.Value()) + m.tree.RootStyle(rootStyle) + m.tree.EnumeratorStyle(enumeratorStyle) + + isWorkload := func(node string) bool { + if strings.Contains(node, "Cluster") || + strings.Contains(node, "Component") || + strings.Contains(node, "InstanceSet") || + strings.Contains(node, "Pod") { + return true + } + return false + } + + m.tree.ItemStyleFunc(func(children tree.Children, i int) lipgloss.Style { + style := noneWorkloadStyle + if isWorkload(children.At(i).Value()) { + style = workloadStyle + } + return setBackgroundColor(style, children.At(i).Value()) + }) + + return m.zoneManager.Mark(m.zoneID, m.tree.String()) +} + +func (m *Model) SetData(data *tracev1.ObjectTreeNode) { + m.data = data +} + +func (m *Model) GetSelected() int { + if m.selected == nil { + return -1 + } + return *m.selected +} + +func (m *Model) setSelectedObject(mm tea.MouseMsg) { + _, y := m.zoneManager.Get(m.zoneID).Pos(mm) + if y >= 0 { + m.selected = &y + } +} + +func buildDepth2ObjectMap(objectTree *tracev1.ObjectTreeNode, depth int, depthObjectMap map[int]string) int { + if objectTree == nil { + return depth + } + obj := formatNode(&objectTree.Primary) + depthObjectMap[depth] = obj + for _, secondary := range objectTree.Secondaries { + depth = buildDepth2ObjectMap(secondary, depth+1, depthObjectMap) + } + return depth +} + +func formatNode(reference *corev1.ObjectReference) string { + return fmt.Sprintf("%s/%s", reference.Kind, reference.Name) +} + +func buildObjectTree(objectTree *tracev1.ObjectTreeNode) *tree.Tree { + if objectTree == nil { + return nil + } + + treeNode := tree.New() + treeNode.Root(formatNode(&objectTree.Primary)) + for _, secondary := range objectTree.Secondaries { + child := buildObjectTree(secondary) + treeNode.Child(child) + } + return treeNode +} + +func NewTree(data *tracev1.ObjectTreeNode, manager *zone.Manager) *Model { + return &Model{ + data: data, + zoneManager: manager, + zoneID: manager.NewPrefix(), + } +} diff --git a/pkg/cmd/trace/chart/reconciliation_trace_chart.go b/pkg/cmd/trace/chart/reconciliation_trace_chart.go new file mode 100644 index 000000000..efd2a0f6e --- /dev/null +++ b/pkg/cmd/trace/chart/reconciliation_trace_chart.go @@ -0,0 +1,390 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package chart + +import ( + "fmt" + + "github.com/76creates/stickers/flexbox" + "github.com/NimbleMarkets/ntcharts/barchart" + "github.com/NimbleMarkets/ntcharts/canvas" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + zone "github.com/lrstanley/bubblezone" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + + tracev1 "github.com/apecloud/kubeblocks/apis/trace/v1" + + "github.com/apecloud/kbcli/pkg/cmd/trace/chart/objecttree" + "github.com/apecloud/kbcli/pkg/cmd/trace/chart/richviewport" + "github.com/apecloud/kbcli/pkg/cmd/trace/chart/summary" + "github.com/apecloud/kbcli/pkg/cmd/trace/chart/timeserieslinechart" +) + +var ( + axisStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("3")) // yellow + + labelStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("6")) // cyan + + totalBlockStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("6")) // cyan + + addedBlockStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("2")) // green + + updatedBlockStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("3")) // yellow + + deletedBlockStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("9")) // red + + eventBlockStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("6")) // cyan + + columnStyle = lipgloss.NewStyle(). + Border(lipgloss.NormalBorder(), true, true, false, true). + BorderForeground(lipgloss.Color("#2bcbba")) + + changesLineStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("4")) // blue + + summaryNLatestChangeSeparator = " " +) + +type TraceUpdateMsg struct { + Trace *tracev1.ReconciliationTrace +} + +// Model defines the BubbleTea Model of the ReconciliationTrace. +type Model struct { + // base framework + base *flexbox.HorizontalFlexBox + + // summary + summary *summary.Model + + // latest change + latestChange *richviewport.Model + + // current object tree + objectTree *objecttree.Model + + // changes + changes *timeserieslinechart.Model + + zoneManager *zone.Manager + + trace *tracev1.ReconciliationTrace + selectedChange *tracev1.ObjectChange +} + +func (m *Model) Init() tea.Cmd { + m.zoneManager = zone.New() + + m.base = flexbox.NewHorizontal(0, 0) + columns := []*flexbox.Column{ + m.base.NewColumn().AddCells( + flexbox.NewCell(1, 1).SetStyle(columnStyle), + flexbox.NewCell(1, 2).SetStyle(columnStyle), + ), + } + m.base.AddColumns(columns) + + m.objectTree = objecttree.NewTree(nil, m.zoneManager) + + return nil +} + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "q", "ctrl+c": + return m, tea.Quit + } + case tea.MouseMsg: + if msg.Action == tea.MouseActionPress { + if m.summary != nil { + m.summary.Update(msg) + } + if m.objectTree != nil { + m.objectTree.Update(msg) + } + if m.changes != nil { + if m.zoneManager.Get(m.changes.ZoneID()).InBounds(msg) { + m.setSelectedChange(msg) + } + } + } + case tea.WindowSizeMsg: + m.base.SetWidth(msg.Width) + m.base.SetHeight(msg.Height) + m.base.ForceRecalculate() + case TraceUpdateMsg: + m.trace = msg.Trace + } + return m, nil +} + +func (m *Model) View() string { + m.updateSummaryView() + m.updateLatestChangeView() + m.updateObjectTreeView() + m.updateChangesView() + + m.updateStatusBarView() + m.updateMainContentView() + + return m.zoneManager.Scan(m.base.Render()) +} + +func (m *Model) updateSummaryView() { + if m.trace == nil { + return + } + dataSet := buildSummaryDataSet(&m.trace.Status.CurrentState.Summary) + if len(dataSet) == 0 { + return + } + m.summary = summary.New(m.base.GetColumn(0).GetCell(0).GetHeight(), + dataSet, + barchart.WithZoneManager(m.zoneManager), + barchart.WithStyles(axisStyle, labelStyle)) +} + +func (m *Model) updateLatestChangeView() { + if m.trace == nil { + return + } + if m.summary == nil { + return + } + formatChange := func(change *tracev1.ObjectChange) string { + desc := change.Description + if change.LocalDescription != nil { + desc = *change.LocalDescription + } + name := types.NamespacedName{Namespace: change.ObjectReference.Namespace, Name: change.ObjectReference.Name}.String() + return fmt.Sprintf("GVK: %s/%s\nObject: %s\nDescription: %s", change.ObjectReference.GroupVersionKind().GroupVersion(), change.ObjectReference.Kind, name, desc) + } + changeText := "" + if l := len(m.trace.Status.CurrentState.Changes); l > 0 { + changeText = formatChange(&m.trace.Status.CurrentState.Changes[l-1]) + } + if m.selectedChange != nil { + changeText = formatChange(m.selectedChange) + } + w := lipgloss.Width(m.summary.View()) + baseBorder := 1 + m.latestChange = richviewport.NewViewPort( + m.base.GetWidth()-2*baseBorder-w-len(summaryNLatestChangeSeparator), + m.base.GetColumn(0).GetCell(0).GetHeight()-baseBorder, + "Latest Change", + changeText) +} + +func (m *Model) updateObjectTreeView() { + if m.trace == nil { + return + } + m.objectTree.SetData(m.trace.Status.CurrentState.ObjectTree) +} + +func (m *Model) updateChangesView() { + if m.trace == nil { + return + } + if m.objectTree == nil { + return + } + + depthMap := make(map[corev1.ObjectReference]float64) + depth := buildDepthMap(m.trace.Status.CurrentState.ObjectTree, 0, depthMap) + minYValue := 0.0 + maxYValue := float64(len(depthMap)) + w, h := lipgloss.Size(m.objectTree.View()) + changesChart := timeserieslinechart.New(m.base.GetWidth()-2-w, h+2, timeserieslinechart.WithZoneManager(m.zoneManager)) + changesChart.AxisStyle = axisStyle + changesChart.LabelStyle = labelStyle + changesChart.XLabelFormatter = timeserieslinechart.HourTimeLabelFormatter() + changesChart.YLabelFormatter = func(i int, f float64) string { + return "" + } + changesChart.UpdateHandler = timeserieslinechart.SecondUpdateHandler(1) + changesChart.SetYRange(minYValue, maxYValue) + changesChart.SetViewYRange(minYValue, maxYValue) + changesChart.SetStyle(changesLineStyle) + m.changes = &changesChart + if len(m.trace.Status.CurrentState.Changes) > 0 { + change := m.trace.Status.CurrentState.Changes[0] + minX := change.Timestamp.Time.Unix() + maxX := minX + 1 + m.changes.SetViewXRange(float64(minX), float64(maxX)) + m.changes.SetXRange(float64(minX), float64(maxX)) + } + for _, change := range m.trace.Status.CurrentState.Changes { + objRef := normalizeObjectRef(&change.ObjectReference) + m.changes.Push(timeserieslinechart.TimePoint{Time: change.Timestamp.Time, Value: depth - depthMap[*objRef] + 1}) + } + m.changes.SetDataSetStyleFunc(func(tp *timeserieslinechart.TimePoint) lipgloss.Style { + change := m.findSelectedChange(tp) + if change == nil { + return lipgloss.NewStyle() + } + switch change.ChangeType { + case tracev1.ObjectCreationType: + return addedBlockStyle + case tracev1.ObjectUpdateType: + return updatedBlockStyle + case tracev1.ObjectDeletionType: + return deletedBlockStyle + case tracev1.EventType: + return eventBlockStyle + } + return lipgloss.NewStyle() + }) + m.changes.DrawRect() + m.changes.HighlightLine(m.objectTree.GetSelected(), lipgloss.Color("4")) +} + +func (m *Model) updateStatusBarView() { + if m.trace == nil { + return + } + if m.summary == nil { + return + } + if m.latestChange == nil { + return + } + summary := m.summary.View() + latestChange := m.latestChange.View() + status := lipgloss.JoinHorizontal(lipgloss.Left, summary, summaryNLatestChangeSeparator, latestChange) + m.base.GetColumn(0).GetCell(0).SetContent(status) +} + +func (m *Model) updateMainContentView() { + if m.trace == nil { + return + } + if m.objectTree == nil { + return + } + if m.changes == nil { + return + } + objectTree := m.objectTree.View() + changes := m.changes.View() + mainContent := lipgloss.JoinHorizontal(lipgloss.Left, objectTree, changes) + m.base.GetColumn(0).GetCell(1).SetContent(mainContent) +} + +func (m *Model) setSelectedChange(msg tea.MouseMsg) { + m.selectedChange = nil + x, y := m.zoneManager.Get(m.changes.ZoneID()).Pos(msg) + point := m.changes.TimePointFromPoint(canvas.Point{X: x, Y: y}) + if point == nil { + return + } + change := m.findSelectedChange(point) + if change != nil { + m.selectedChange = change + } +} + +func (m *Model) findSelectedChange(point *timeserieslinechart.TimePoint) *tracev1.ObjectChange { + depthMap := make(map[corev1.ObjectReference]float64) + depth := buildDepthMap(m.trace.Status.CurrentState.ObjectTree, 0, depthMap) + for i := range m.trace.Status.CurrentState.Changes { + change := &m.trace.Status.CurrentState.Changes[i] + if change.Timestamp.Time.Unix() != point.Time.Unix() { + continue + } + objRef := normalizeObjectRef(&change.ObjectReference) + if depthMap[*objRef] == (depth + 1 - point.Value) { + return change + } + } + return nil +} + +func buildSummaryDataSet(summary *tracev1.ObjectTreeDiffSummary) []barchart.BarData { + var dataSet []barchart.BarData + for i := range summary.ObjectSummaries { + n := normalizeObjectSummary(&summary.ObjectSummaries[i]) + d := barchart.BarData{ + Label: n.ObjectType.Kind, + Values: []barchart.BarValue{ + {Name: "Total", Value: float64(n.Total), Style: totalBlockStyle}, + {Name: "Added", Value: float64(*n.ChangeSummary.Added), Style: addedBlockStyle}, + {Name: "Updated", Value: float64(*n.ChangeSummary.Updated), Style: updatedBlockStyle}, + {Name: "Deleted", Value: float64(*n.ChangeSummary.Deleted), Style: deletedBlockStyle}, + }, + } + dataSet = append(dataSet, d) + } + return dataSet +} + +func normalizeObjectRef(ref *corev1.ObjectReference) *corev1.ObjectReference { + objRef := *ref + objRef.UID = "" + objRef.ResourceVersion = "" + return &objRef +} + +func normalizeObjectSummary(s *tracev1.ObjectSummary) *tracev1.ObjectSummary { + if s == nil { + return nil + } + if s.ChangeSummary == nil { + s.ChangeSummary = &tracev1.ObjectChangeSummary{} + } + if s.ChangeSummary.Added == nil { + s.ChangeSummary.Added = pointer.Int32(0) + } + if s.ChangeSummary.Updated == nil { + s.ChangeSummary.Updated = pointer.Int32(0) + } + if s.ChangeSummary.Deleted == nil { + s.ChangeSummary.Deleted = pointer.Int32(0) + } + return s +} + +func buildDepthMap(objectTree *tracev1.ObjectTreeNode, depth float64, depthMap map[corev1.ObjectReference]float64) float64 { + if objectTree == nil { + return depth + } + objRef := normalizeObjectRef(&objectTree.Primary) + depthMap[*objRef] = depth + for _, secondary := range objectTree.Secondaries { + depth = buildDepthMap(secondary, depth+1, depthMap) + } + return depth +} + +func NewReconciliationTraceChart() *Model { + return &Model{} +} diff --git a/pkg/cmd/trace/chart/richviewport/viewport.go b/pkg/cmd/trace/chart/richviewport/viewport.go new file mode 100644 index 000000000..3b1b8d62a --- /dev/null +++ b/pkg/cmd/trace/chart/richviewport/viewport.go @@ -0,0 +1,119 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package richviewport + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +var ( + borderColor = lipgloss.Color("63") // purple + + borderStyle = lipgloss.NewStyle(). + Border(lipgloss.NormalBorder(), false, true). + BorderForeground(borderColor) + + lineStyle = lipgloss.NewStyle(). + Foreground(borderColor) + + titleStyle = func() lipgloss.Style { + b := lipgloss.NormalBorder() + b.Right = "├" + return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1). + BorderForeground(borderColor) + }() + + infoStyle = func() lipgloss.Style { + b := lipgloss.NormalBorder() + b.Left = "┤" + return titleStyle.BorderStyle(b). + BorderForeground(borderColor) + }() +) + +type Model struct { + header string + content string + width int + height int + viewport viewport.Model +} + +func (m *Model) Init() tea.Cmd { + return nil +} + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + m.viewport, cmd = m.viewport.Update(msg) + cmds = append(cmds, cmd) + + return m, tea.Batch(cmds...) +} + +func (m *Model) View() string { + return lipgloss.JoinVertical(lipgloss.Top, + m.headerView(), + borderStyle.Render(m.viewport.View()), + m.footerView()) +} + +func (m *Model) headerView() string { + title := titleStyle.Render(m.header) + line := strings.Repeat("─", max(0, m.width-lipgloss.Width(title)-1)) + return lipgloss.JoinHorizontal(lipgloss.Center, title, lineStyle.Render(line), lineStyle.Render(" \n┐\n│")) +} + +func (m *Model) footerView() string { + info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)) + line := strings.Repeat("─", max(0, m.width-lipgloss.Width(info)-1)) + return lipgloss.JoinHorizontal(lipgloss.Center, lineStyle.Render("│\n└\n "), lineStyle.Render(line), info) +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func NewViewPort(w, h int, header, content string) *Model { + m := &Model{ + header: header, + content: content, + width: w, + height: h, + } + headerHeight := lipgloss.Height(m.headerView()) + footerHeight := lipgloss.Height(m.footerView()) + verticalMarginHeight := headerHeight + footerHeight + m.viewport = viewport.New(m.width-2, m.height-verticalMarginHeight) + m.viewport.SetContent(m.content) + m.viewport.YPosition = headerHeight + 1 + return m +} diff --git a/pkg/cmd/trace/chart/summary/summary_chart.go b/pkg/cmd/trace/chart/summary/summary_chart.go new file mode 100644 index 000000000..50ad338ca --- /dev/null +++ b/pkg/cmd/trace/chart/summary/summary_chart.go @@ -0,0 +1,111 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package summary + +import ( + "fmt" + + "github.com/NimbleMarkets/ntcharts/barchart" + "github.com/NimbleMarkets/ntcharts/canvas" + "github.com/NimbleMarkets/ntcharts/canvas/runes" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +var borderStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("6")) // cyan + +var selectedBarData barchart.BarData + +type Model struct { + summary barchart.Model + data []barchart.BarData +} + +func (m *Model) Init() tea.Cmd { + return nil +} + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + if mm, ok := msg.(tea.MouseMsg); ok && mm.Action == tea.MouseActionPress { + if m.summary.ZoneManager() == nil { + return m, nil + } + if m.summary.ZoneManager().Get(m.summary.ZoneID()).InBounds(mm) { + m.setBarData(&m.summary, mm) + } + } + return m, nil +} + +func (m *Model) View() string { + m.summary.Draw() + return lipgloss.JoinHorizontal(lipgloss.Top, + m.summary.View(), + borderStyle.Render(totals(m.data)), + borderStyle.Render(legend(m.data[0])), + borderStyle.Render(selectedData()), + ) +} + +func (m *Model) setBarData(b *barchart.Model, msg tea.MouseMsg) { + x, y := m.summary.ZoneManager().Get(b.ZoneID()).Pos(msg) + selectedBarData = b.BarDataFromPoint(canvas.Point{X: x, Y: y}) +} + +func legend(bd barchart.BarData) (r string) { + r = "Legend\n" + for _, bv := range bd.Values { + r += "\n" + bv.Style.Render(fmt.Sprintf("%c %s", runes.FullBlock, bv.Name)) + } + return +} +func totals(lv []barchart.BarData) (r string) { + r = "Totals\n" + for _, bd := range lv { + sum := bd.Values[0].Value + r += "\n" + fmt.Sprintf("%s %d", bd.Label, int(sum)) + } + return +} + +func selectedData() (r string) { + r = "Selected\n" + if len(selectedBarData.Values) == 0 { + return + } + r += selectedBarData.Label + for _, bv := range selectedBarData.Values { + r += " " + bv.Style.Render(fmt.Sprintf("%d", int(bv.Value))) + } + return +} + +func New(h int, dataSet []barchart.BarData, opts ...barchart.Option) *Model { + gap := 1 + barWidth := 4 + opts = append(opts, barchart.WithDataSet(dataSet), barchart.WithBarWidth(barWidth), barchart.WithBarGap(gap)) + bar := barchart.New(len(dataSet)*(barWidth+gap), h-1, opts...) + return &Model{ + summary: bar, + data: dataSet, + } +} diff --git a/pkg/cmd/trace/chart/timeserieslinechart/options.go b/pkg/cmd/trace/chart/timeserieslinechart/options.go new file mode 100644 index 000000000..19bff4786 --- /dev/null +++ b/pkg/cmd/trace/chart/timeserieslinechart/options.go @@ -0,0 +1,157 @@ +// ntcharts - Copyright (c) 2024 Neomantra Corp. +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package timeserieslinechart + +// File contains options used by the timeserieslinechart during initialization with New(). + +import ( + "time" + + "github.com/NimbleMarkets/ntcharts/canvas/runes" + "github.com/NimbleMarkets/ntcharts/linechart" + + "github.com/charmbracelet/lipgloss" + zone "github.com/lrstanley/bubblezone" +) + +// Option is used to set options when initializing a timeserieslinechart. Example: +// +// tslc := New(width, height, minX, maxX, minY, maxY, WithStyles(someLineStyle, someLipglossStyle)) +type Option func(*Model) + +// WithLineChart sets internal linechart to given linechart. +func WithLineChart(lc *linechart.Model) Option { + return func(m *Model) { + m.Model = *lc + } +} + +// WithUpdateHandler sets the UpdateHandler used +// when processing bubbletea Msg events in Update(). +func WithUpdateHandler(h linechart.UpdateHandler) Option { + return func(m *Model) { + m.UpdateHandler = h + } +} + +// WithZoneManager sets the bubblezone Manager used +// when processing bubbletea Msg mouse events in Update(). +func WithZoneManager(zm *zone.Manager) Option { + return func(m *Model) { + m.SetZoneManager(zm) + } +} + +// WithXLabelFormatter sets the default X label formatter for displaying X values as strings. +func WithXLabelFormatter(fmter linechart.LabelFormatter) Option { + return func(m *Model) { + m.XLabelFormatter = fmter + } +} + +// WithYLabelFormatter sets the default Y label formatter for displaying Y values as strings. +func WithYLabelFormatter(fmter linechart.LabelFormatter) Option { + return func(m *Model) { + m.YLabelFormatter = fmter + } +} + +// WithAxesStyles sets the axes line and line label styles. +func WithAxesStyles(as lipgloss.Style, ls lipgloss.Style) Option { + return func(m *Model) { + m.AxisStyle = as + m.LabelStyle = ls + } +} + +// WithXYSteps sets the number of steps when drawing X and Y axes values. +// If X steps 0, then X axis will be hidden. +// If Y steps 0, then Y axis will be hidden. +func WithXYSteps(x, y int) Option { + return func(m *Model) { + m.SetXStep(x) + m.SetYStep(y) + } +} + +// WithYRange sets expected and displayed +// minimum and maximum Y value range. +func WithYRange(min, max float64) Option { + return func(m *Model) { + m.SetYRange(min, max) + m.SetViewYRange(min, max) + } +} + +// WithTimeRange sets expected and displayed minimun and maximum +// time values range on the X axis. +func WithTimeRange(min, max time.Time) Option { + return func(m *Model) { + m.SetTimeRange(min, max) + m.SetViewTimeRange(min, max) + } +} + +// WithLineStyle sets the default line style of data sets. +func WithLineStyle(ls runes.LineStyle) Option { + return func(m *Model) { + m.SetLineStyle(ls) + } +} + +// WithDataSetLineStyle sets the line style of the data set given by name. +func WithDataSetLineStyle(n string, ls runes.LineStyle) Option { + return func(m *Model) { + m.SetDataSetLineStyle(n, ls) + } +} + +// WithStyle sets the default lipgloss style of data sets. +func WithStyle(s lipgloss.Style) Option { + return func(m *Model) { + m.SetStyle(s) + } +} + +// WithDataSetStyle sets the lipgloss style of the data set given by name. +func WithDataSetStyle(n string, s lipgloss.Style) Option { + return func(m *Model) { + m.SetDataSetStyle(n, s) + } +} + +// WithTimeSeries adds []TimePoint values to the default data set. +func WithTimeSeries(p []TimePoint) Option { + return func(m *Model) { + for _, v := range p { + m.Push(v) + } + } +} + +// WithDataSetTimeSeries adds []TimePoint data points to the data set given by name. +func WithDataSetTimeSeries(n string, p []TimePoint) Option { + return func(m *Model) { + for _, v := range p { + m.PushDataSet(n, v) + } + } +} diff --git a/pkg/cmd/trace/chart/timeserieslinechart/timeserieslinechart.go b/pkg/cmd/trace/chart/timeserieslinechart/timeserieslinechart.go new file mode 100644 index 000000000..a50dca10e --- /dev/null +++ b/pkg/cmd/trace/chart/timeserieslinechart/timeserieslinechart.go @@ -0,0 +1,584 @@ +// ntcharts - Copyright (c) 2024 Neomantra Corp. +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +// Package timeserieslinechart implements a linechart that draws lines +// for time series data points +package timeserieslinechart + +// https://en.wikipedia.org/wiki/Moving_average + +import ( + "fmt" + "math" + "sort" + "time" + + "github.com/NimbleMarkets/ntcharts/canvas" + "github.com/NimbleMarkets/ntcharts/canvas/buffer" + "github.com/NimbleMarkets/ntcharts/canvas/graph" + "github.com/NimbleMarkets/ntcharts/canvas/runes" + "github.com/NimbleMarkets/ntcharts/linechart" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +const DefaultDataSetName = "default" + +type StyleFunc func(*TimePoint) lipgloss.Style + +func DateTimeLabelFormatter() linechart.LabelFormatter { + var yearLabel string + return func(i int, v float64) string { + if i == 0 { // reset year labeling if redisplaying values + yearLabel = "" + } + t := time.Unix(int64(v), 0).UTC() + monthDay := t.Format("01/02") + year := t.Format("'06") + if yearLabel != year { // apply year label if first time seeing year + yearLabel = year + return fmt.Sprintf("%s %s", yearLabel, monthDay) + } else { + return monthDay + } + } +} + +func HourTimeLabelFormatter() linechart.LabelFormatter { + return func(i int, v float64) string { + t := time.Unix(int64(v), 0).UTC() + return t.Format("15:04:05") + } +} + +type TimePoint struct { + Time time.Time + Value float64 +} + +// cAverage tracks cumulative average +type cAverage struct { + Avg float64 + Count float64 +} + +// Add adds a float64 to current cumulative average +func (a *cAverage) Add(f float64) float64 { + a.Count += 1 + a.Avg += (f - a.Avg) / a.Count + return a.Avg +} + +type dataSet struct { + LineStyle runes.LineStyle // type of line runes to draw + Style lipgloss.Style + + // stores TimePoints as FloatPoint64{X:time.Time, Y: value} + // time.Time will be converted to seconds since epoch. + // both time and value will be scaled to fit the graphing area + tBuf *buffer.Float64PointScaleBuffer +} + +// Model contains state of a timeserieslinechart with an embedded linechart.Model +// The X axis contains time.Time values and the Y axis contains float64 values. +// A data set consists of a sequence TimePoints in chronological order. +// If multiple TimePoints map to the same column, then average value the time points +// will be used as the Y value of the column. +// The X axis contains a time range and the Y axis contains a numeric value range. +// Uses linechart Model UpdateHandler() for processing keyboard and mouse messages. +type Model struct { + linechart.Model + dLineStyle runes.LineStyle // default data set LineStyletype + dStyle lipgloss.Style // default data set Style + dStyleFunc StyleFunc + dSets map[string]*dataSet // maps names to data sets + + pointToDataMap map[canvas.Point]*TimePoint // canvas point to time point map +} + +// New returns a timeserieslinechart Model initialized from +// width, height, Y value range and various options. +// By default, the chart will set time.Now() as the minimum time, +// enable auto set X and Y value ranges, +// and only allow moving viewport on X axis. +func New(w, h int, opts ...Option) Model { + m := Model{ + Model: linechart.New(w, h, 0.0, float64(time.Now().Unix()), 0, 1, + linechart.WithXYSteps(4, 2), + linechart.WithXLabelFormatter(DateTimeLabelFormatter()), + linechart.WithAutoXYRange(), // automatically adjust value ranges + linechart.WithUpdateHandler(DateUpdateHandler(1))), // only scroll on X axis, increments by 1 day + dLineStyle: runes.ArcLineStyle, + dStyle: lipgloss.NewStyle(), + dSets: make(map[string]*dataSet), + } + for _, opt := range opts { + opt(&m) + } + m.UpdateGraphSizes() + m.rescaleData() + if _, ok := m.dSets[DefaultDataSetName]; !ok { + m.dSets[DefaultDataSetName] = m.newDataSet() + } + return m +} + +// newDataSet returns a new initialize *dataSet. +func (m *Model) newDataSet() *dataSet { + xs := float64(m.GraphWidth()) / (m.ViewMaxX() - m.ViewMinX()) // x scale factor + ys := float64(m.Origin().Y) / (m.ViewMaxY() - m.ViewMinY()) // y scale factor + offset := canvas.Float64Point{X: m.ViewMinX(), Y: m.ViewMinY()} + scale := canvas.Float64Point{X: xs, Y: ys} + return &dataSet{ + LineStyle: m.dLineStyle, + Style: m.dStyle, + tBuf: buffer.NewFloat64PointScaleBuffer(offset, scale), + } +} + +// rescaleData will reinitialize time chunks and +// map time points into graph columns for display +func (m *Model) rescaleData() { + // rescale time points buffer + xs := float64(m.GraphWidth()) / (m.ViewMaxX() - m.ViewMinX()) // x scale factor + ys := float64(m.Origin().Y) / (m.ViewMaxY() - m.ViewMinY()) // y scale factor + offset := canvas.Float64Point{X: m.ViewMinX(), Y: m.ViewMinY()} + scale := canvas.Float64Point{X: xs, Y: ys} + for _, ds := range m.dSets { + if ds.tBuf.Offset() != offset { + ds.tBuf.SetOffset(offset) + } + if ds.tBuf.Scale() != scale { + ds.tBuf.SetScale(scale) + } + } +} + +// ClearAllData will reset stored data values in all data sets. +func (m *Model) ClearAllData() { + for _, ds := range m.dSets { + ds.tBuf.Clear() + } + m.dSets[DefaultDataSetName] = m.newDataSet() +} + +// ClearDataSet will erase stored data set given by name string. +func (m *Model) ClearDataSet(n string) { + if ds, ok := m.dSets[n]; ok { + ds.tBuf.Clear() + } +} + +// SetTimeRange updates the minimum and maximum expected time values. +// Existing data will be rescaled. +func (m *Model) SetTimeRange(min, max time.Time) { + m.Model.SetXRange(float64(min.Unix()), float64(max.Unix())) + m.rescaleData() +} + +// SetYRange updates the minimum and maximum expected Y values. +// Existing data will be rescaled. +func (m *Model) SetYRange(min, max float64) { + m.Model.SetYRange(min, max) + m.rescaleData() +} + +// SetViewTimeRange updates the displayed minimum and maximum time values. +// Existing data will be rescaled. +func (m *Model) SetViewTimeRange(min, max time.Time) { + m.Model.SetViewXRange(float64(min.Unix()), float64(max.Unix())) + m.rescaleData() +} + +// SetViewYRange updates the displayed minimum and maximum Y values. +// Existing data will be rescaled. +func (m *Model) SetViewYRange(min, max float64) { + m.Model.SetViewYRange(min, max) + m.rescaleData() +} + +// SetViewTimeAndYRange updates the displayed minimum and maximum time and Y values. +// Existing data will be rescaled. +func (m *Model) SetViewTimeAndYRange(minX, maxX time.Time, minY, maxY float64) { + m.Model.SetViewXRange(float64(minX.Unix()), float64(maxX.Unix())) + m.Model.SetViewYRange(minY, maxY) + m.rescaleData() +} + +// Resize will change timeserieslinechart display width and height. +// Existing data will be rescaled. +func (m *Model) Resize(w, h int) { + m.Model.Resize(w, h) + m.rescaleData() +} + +// SetLineStyle will set the default line styles of data sets. +func (m *Model) SetLineStyle(ls runes.LineStyle) { + m.dLineStyle = ls + m.SetDataSetLineStyle(DefaultDataSetName, ls) +} + +// SetStyle will set the default lipgloss styles of data sets. +func (m *Model) SetStyle(s lipgloss.Style) { + m.dStyle = s + m.SetDataSetStyle(DefaultDataSetName, s) +} + +// SetDataSetLineStyle will set the line style of the given data set by name string. +func (m *Model) SetDataSetLineStyle(n string, ls runes.LineStyle) { + if _, ok := m.dSets[n]; !ok { + m.dSets[n] = m.newDataSet() + } + ds := m.dSets[n] + ds.LineStyle = ls +} + +// SetDataSetStyle will set the lipgloss style of the given data set by name string. +func (m *Model) SetDataSetStyle(n string, s lipgloss.Style) { + if _, ok := m.dSets[n]; !ok { + m.dSets[n] = m.newDataSet() + } + ds := m.dSets[n] + ds.Style = s +} + +func (m *Model) SetDataSetStyleFunc(f StyleFunc) { + m.dStyleFunc = f +} + +// Push will push a TimePoint data value to the default data set +// to be displayed with Draw. +func (m *Model) Push(t TimePoint) { + m.PushDataSet(DefaultDataSetName, t) +} + +// PushDataSet will push a TimePoint data value to a data set +// to be displayed with Draw. Using given data set by name string. +func (m *Model) PushDataSet(n string, t TimePoint) { + f := canvas.Float64Point{X: float64(t.Time.Unix()), Y: t.Value} + // auto adjust x and y ranges if enabled + if m.AutoAdjustRange(f) { + m.UpdateGraphSizes() + m.rescaleData() + } + if _, ok := m.dSets[n]; !ok { + m.dSets[n] = m.newDataSet() + } + ds := m.dSets[n] + ds.tBuf.Push(f) +} + +// Draw will draw lines runes displayed from right to left +// of the graphing area of the canvas. Uses default data set. +func (m *Model) Draw() { + m.DrawDataSets([]string{DefaultDataSetName}) +} + +// DrawAll will draw lines runes for all data sets +// from right to left of the graphing area of the canvas. +func (m *Model) DrawAll() { + names := make([]string, 0, len(m.dSets)) + for n, ds := range m.dSets { + if ds.tBuf.Length() > 0 { + names = append(names, n) + } + } + sort.Strings(names) + m.DrawDataSets(names) +} + +// DrawDataSets will draw lines runes from right to left +// of the graphing area of the canvas for each data set given +// by name strings. +func (m *Model) DrawDataSets(names []string) { + if len(names) == 0 { + return + } + m.Clear() + m.DrawXYAxisAndLabel() + for _, n := range names { + if ds, ok := m.dSets[n]; ok { + dataPoints := ds.tBuf.ReadAll() + dataLen := len(dataPoints) + if dataLen == 0 { + return + } + // get sequence of line values for graphing + seqY := m.getLineSequence(dataPoints) + // convert to canvas coordinates and avoid drawing below X axis + yCoords := canvas.CanvasYCoordinates(m.Origin().Y, seqY) + if m.XStep() > 0 { + for i, v := range yCoords { + if v > m.Origin().Y { + yCoords[i] = m.Origin().Y + } + } + } + startX := m.Canvas.Width() - len(yCoords) + graph.DrawLineSequence(&m.Canvas, + (startX == m.Origin().X), + startX, + yCoords, + ds.LineStyle, + ds.Style) + } + } +} + +// DrawRect will draw rect dots. Uses default data set. +func (m *Model) DrawRect() { + m.DrawRectDataSets([]string{DefaultDataSetName}) +} + +// DrawRectAll will draw rect dots for all data sets. +func (m *Model) DrawRectAll() { + names := make([]string, 0, len(m.dSets)) + for n, ds := range m.dSets { + if ds.tBuf.Length() > 0 { + names = append(names, n) + } + } + sort.Strings(names) + m.DrawRectDataSets(names) +} + +// DrawRectDataSets will draw each point in data set as a rect. +func (m *Model) DrawRectDataSets(names []string) { + if len(names) == 0 { + return + } + m.Clear() + m.DrawXYAxisAndLabel() + m.pointToDataMap = make(map[canvas.Point]*TimePoint) + for _, n := range names { + if ds, ok := m.dSets[n]; ok { + dataPoints := ds.tBuf.ReadAll() + dataLen := len(dataPoints) + if dataLen == 0 { + return + } + for i := 0; i < dataLen; i++ { + fp := dataPoints[i] + p := canvas.CanvasPointFromFloat64Point(m.Origin(), fp) + // get all rune patterns for braille grid + // and draw them on to the canvas + startX := 0 + if m.YStep() > 0 { + startX = m.Origin().X + 1 + } + p.X += startX + style := m.dStyle + raw := ds.tBuf.AtRaw(i) + tp := &TimePoint{Time: time.Unix(int64(raw.X), 0), Value: raw.Y} + if m.dStyleFunc != nil { + style = m.dStyleFunc(tp) + } + m.Canvas.SetCell(p, canvas.NewCellWithStyle(runes.FullBlock, style)) + + m.pointToDataMap[p] = tp + } + } + } +} + +// DrawBraille will draw braille runes displayed from right to left +// of the graphing area of the canvas. Uses default data set. +func (m *Model) DrawBraille() { + m.DrawBrailleDataSets([]string{DefaultDataSetName}) +} + +// DrawBrailleAll will draw braille runes for all data sets +// from right to left of the graphing area of the canvas. +func (m *Model) DrawBrailleAll() { + names := make([]string, 0, len(m.dSets)) + for n, ds := range m.dSets { + if ds.tBuf.Length() > 0 { + names = append(names, n) + } + } + sort.Strings(names) + m.DrawBrailleDataSets(names) +} + +// DrawBrailleDataSets will draw braille runes from right to left +// of the graphing area of the canvas for each data set given +// by name strings. +func (m *Model) DrawBrailleDataSets(names []string) { + if len(names) == 0 { + return + } + m.Clear() + m.DrawXYAxisAndLabel() + m.pointToDataMap = make(map[canvas.Point]*TimePoint) + for _, n := range names { + if ds, ok := m.dSets[n]; ok { + dataPoints := ds.tBuf.ReadAll() + dataLen := len(dataPoints) + if dataLen == 0 { + return + } + // draw lines from each point to the next point + bGrid := graph.NewBrailleGrid(m.GraphWidth(), m.GraphHeight(), + 0, float64(m.GraphWidth()), // X values already scaled to graph + 0, float64(m.GraphHeight())) // Y values already scaled to graph + for i := 0; i < dataLen; i++ { + p := dataPoints[i] + // ignore points that will not be displayed + bothBeforeMin := p.X < 0 + bothAfterMax := p.X > float64(m.GraphWidth()) + if bothBeforeMin || bothAfterMax { + continue + } + // get braille grid points from two Float64Point data points + gp := bGrid.GridPoint(p) + bGrid.Set(gp) + + // cache the mapping relationship + pos := canvas.Point{X: gp.X / m.XStep(), Y: gp.Y / m.YStep()} + raw := ds.tBuf.AtRaw(i) + m.pointToDataMap[pos] = &TimePoint{Time: time.Unix(int64(raw.X), 0), Value: raw.Y} + } + + // get all rune patterns for braille grid + // and draw them on to the canvas + startX := 0 + if m.YStep() > 0 { + startX = m.Origin().X + 1 + } + patterns := bGrid.BraillePatterns() + for i, xb := range patterns { + for j, r := range xb { + if r != runes.BrailleBlockOffset { + patterns[i][j] = runes.FullBlock + } + } + } + DrawBraillePatterns(&m.Canvas, + canvas.Point{X: startX, Y: 0}, patterns, ds.Style) + } + } +} + +// DrawBrailleRune draws a braille rune on to the canvas at given (X,Y) coordinates with given style. +// The function checks for existing braille runes already on the canvas and +// will draw a new braille pattern with the dot patterns of both the existing and given runes. +// Does nothing if given rune is Null or is not a braille rune. +func DrawBrailleRune(m *canvas.Model, p canvas.Point, r rune, s lipgloss.Style) { + if r == runes.Null { + return + } + cr := m.Cell(p).Rune + if cr == 0 { // set rune if nothing exists on canvas + m.SetCell(p, canvas.NewCellWithStyle(r, s)) + return + } + m.SetCell(p, canvas.NewCellWithStyle(r, s)) +} + +// DrawBraillePatterns draws braille runes from a [][]rune representing a 2D grid of +// Braille Pattern runes. The runes will be drawn onto the canvas from starting from top +// left of the grid to the bottom right of the grid starting at the given canvas Point. +// Given style will be applied to all runes drawn. +// This function can be used with the output [][]rune from PatternDotsGrid.BraillePatterns(). +func DrawBraillePatterns(m *canvas.Model, p canvas.Point, b [][]rune, s lipgloss.Style) { + for y, row := range b { + for x, r := range row { + if r != runes.BrailleBlockOffset { + DrawBrailleRune(m, p.Add(canvas.Point{X: x, Y: y}), r, s) + } + } + } +} + +// getLineSequence returns a sequence of Y values +// to draw line runes from a given set of scaled []FloatPoint64. +func (m *Model) getLineSequence(points []canvas.Float64Point) []int { + width := m.Width() - m.Origin().X // line runes can draw on axes + if width <= 0 { + return []int{} + } + dataLen := len(points) + // each index of the bucket corresponds to a graph column. + // each index value is the average of data point values + // that is mapped to that graph column. + buckets := make([]cAverage, width) + for i := 0; i < dataLen; i++ { + j := i + 1 + if j >= dataLen { + j = i + } + p1 := canvas.NewPointFromFloat64Point(points[i]) + p2 := canvas.NewPointFromFloat64Point(points[j]) + // ignore points that will not be displayed on the graph + bothBeforeMin := (p1.X < 0 && p2.X < 0) + bothAfterMax := (p1.X > m.GraphWidth() && p2.X > m.GraphWidth()) + if bothBeforeMin || bothAfterMax { + continue + } + // place all points between two points + // that approximates a line into buckets + points := graph.GetLinePoints(p1, p2) + for _, p := range points { + if (p.X >= 0) && (p.X) < width { + buckets[p.X].Add(float64(p.Y)) + } + } + } + // populate sequence of Y values to for drawing + r := make([]int, width) + for i, v := range buckets { + r[i] = int(math.Round(v.Avg)) + } + return r +} + +// Update processes bubbletea Msg to by invoking +// UpdateHandlerFunc callback if linechart is focused. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + if !m.Focused() { + return m, nil + } + m.UpdateHandler(&m.Model, msg) + m.rescaleData() + return m, nil +} + +func (m *Model) TimePointFromPoint(point canvas.Point) *TimePoint { + if m.pointToDataMap == nil { + return nil + } + return m.pointToDataMap[point] +} + +func (m *Model) HighlightLine(y int, color lipgloss.Color) { + if y < 0 || y >= m.Origin().Y { + return + } + startX := 0 + if m.YStep() > 0 { + startX = m.Origin().X + 1 + } + for i := 0; i < m.GraphWidth(); i++ { + p := canvas.Point{X: startX + i, Y: y} + cell := m.Canvas.Cell(p) + m.Canvas.SetCell(p, canvas.NewCellWithStyle(cell.Rune, cell.Style.Background(color))) + } +} diff --git a/pkg/cmd/trace/chart/timeserieslinechart/updatehandler.go b/pkg/cmd/trace/chart/timeserieslinechart/updatehandler.go new file mode 100644 index 000000000..59f9a5f6e --- /dev/null +++ b/pkg/cmd/trace/chart/timeserieslinechart/updatehandler.go @@ -0,0 +1,95 @@ +// ntcharts - Copyright (c) 2024 Neomantra Corp. +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package timeserieslinechart + +// File contains methods and objects used during +// timeserieslinechart Model Update() to modify internal state. +// timeserieslinechart is able to zoom in and out of the graph, +// and increase and decrease the X values to simulating moving +// the viewport of the linechart + +import ( + "github.com/NimbleMarkets/ntcharts/linechart" +) + +// DateUpdateHandler is used by timeserieslinechart to enable +// zooming in and out with the mouse wheel or page up and page down, +// moving the viewing window by holding down mouse button and moving, +// and moving the viewing window with the arrow keys. +// There is only movement along the X axis by day increments. +// Uses linechart Canvas Keymap for keyboard messages. +func DateUpdateHandler(i int) linechart.UpdateHandler { + daySeconds := 86400 * i // number of seconds in a day + return linechart.XAxisUpdateHandler(float64(daySeconds)) +} + +// DateNoZoomUpdateHandler is used by timeserieslinechart to enable +// moving the viewing window by using the mouse scroll wheel, +// holding down mouse button and moving, +// and moving the viewing window with the arrow keys. +// There is only movement along the X axis by day increments. +// Uses linechart Canvas Keymap for keyboard messages. +func DateNoZoomUpdateHandler(i int) linechart.UpdateHandler { + daySeconds := 86400 * i // number of seconds in a day + return linechart.XAxisNoZoomUpdateHandler(float64(daySeconds)) +} + +// HourUpdateHandler is used by timeserieslinechart to enable +// zooming in and out with the mouse wheel or page up and page down, +// moving the viewing window by holding down mouse button and moving, +// and moving the viewing window with the arrow keys. +// There is only movement along the X axis by hour increments. +// Uses linechart Canvas Keymap for keyboard messages. +func HourUpdateHandler(i int) linechart.UpdateHandler { + hourSeconds := 3600 * i // number of seconds in a hour + return linechart.XAxisUpdateHandler(float64(hourSeconds)) +} + +// HourNoZoomUpdateHandler is used by timeserieslinechart to enable +// moving the viewing window by using the mouse scroll wheel, +// holding down mouse button and moving, +// and moving the viewing window with the arrow keys. +// There is only movement along the X axis by hour increments. +// Uses linechart Canvas Keymap for keyboard messages. +func HourNoZoomUpdateHandler(i int) linechart.UpdateHandler { + hourSeconds := 3600 * i // number of seconds in a hour + return linechart.XAxisNoZoomUpdateHandler(float64(hourSeconds)) +} + +// SecondUpdateHandler is used by timeserieslinechart to enable +// zooming in and out with the mouse wheel or page up and page down, +// moving the viewing window by holding down mouse button and moving, +// and moving the viewing window with the arrow keys. +// There is only movement along the X axis by second increments. +// Uses linechart Canvas Keymap for keyboard messages. +func SecondUpdateHandler(i int) linechart.UpdateHandler { + return linechart.XAxisUpdateHandler(float64(i)) +} + +// SecondNoZoomUpdateHandler is used by timeserieslinechart to enable +// moving the viewing window by using the mouse scroll wheel, +// holding down mouse button and moving, +// and moving the viewing window with the arrow keys. +// There is only movement along the X axis by second increments. +// Uses linechart Canvas Keymap for keyboard messages. +func SecondNoZoomUpdateHandler(i int) linechart.UpdateHandler { + return linechart.XAxisNoZoomUpdateHandler(float64(i)) +} diff --git a/pkg/cmd/trace/create.go b/pkg/cmd/trace/create.go new file mode 100644 index 000000000..dcd6100d3 --- /dev/null +++ b/pkg/cmd/trace/create.go @@ -0,0 +1,94 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package trace + +import ( + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericiooptions" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/apecloud/kbcli/pkg/action" + "github.com/apecloud/kbcli/pkg/types" + "github.com/apecloud/kbcli/pkg/util" +) + +const ( + CueTemplateName = "trace_template.cue" +) + +var ( + createExamples = templates.Examples(` + # create a trace for cluster has the same name 'pg-cluster' + kbcli trace create pg-cluster + + # create a trace for cluster has the name of 'pg-cluster' + kbcli trace create pg-cluster-trace --cluster-name pg-cluster + + # create a trace with custom locale, stateEvaluationExpression + kbcli trace create pg-cluster-trace --locale zh_cn --cel-state-evaluation-expression "has(object.status.phase) && object.status.phase == \"Running\""`) +) + +type CreateOptions struct { + action.CreateOptions `json:"-"` + ClusterName string `json:"clusterName,omitempty"` + Locale string `json:"locale,omitempty"` + Depth int64 `json:"depth,omitempty"` + CelStateEvaluationExpression string `json:"celStateEvaluationExpression,omitempty"` +} + +func (o *CreateOptions) Complete() error { + o.CreateOptions.Options = o + return o.CreateOptions.Complete() +} + +func (o *CreateOptions) Run() error { + return o.CreateOptions.Run() +} + +func newCreateCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := &CreateOptions{ + CreateOptions: action.CreateOptions{ + Factory: f, + IOStreams: streams, + CueTemplateName: CueTemplateName, + GVR: types.TraceGVR(), + }, + } + cmd := &cobra.Command{ + Use: "create trace-name", + Short: "create a trace.", + Example: createExamples, + Aliases: []string{"c"}, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.TraceGVR()), + Run: func(cmd *cobra.Command, args []string) { + o.CreateOptions.Args = args + util.CheckErr(o.Complete()) + util.CheckErr(o.Run()) + }, + } + + cmd.Flags().StringVar(&o.ClusterName, "cluster-name", "", "Specify target cluster name.") + cmd.Flags().StringVar(&o.Locale, "locale", "", "Specify locale.") + cmd.Flags().Int64Var(&o.Depth, "depth", 0, "Specify object tree depth to display.") + cmd.Flags().StringVar(&o.CelStateEvaluationExpression, "cel-state-evaluation-expression", "", "Specify CEL state evaluation expression.") + + return cmd +} diff --git a/pkg/cmd/trace/delete.go b/pkg/cmd/trace/delete.go new file mode 100644 index 000000000..322465054 --- /dev/null +++ b/pkg/cmd/trace/delete.go @@ -0,0 +1,65 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package trace + +import ( + "fmt" + + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericiooptions" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/apecloud/kbcli/pkg/action" + "github.com/apecloud/kbcli/pkg/types" + "github.com/apecloud/kbcli/pkg/util" +) + +var ( + deleteExamples = templates.Examples(` + # Delete a trace + kbcli trace delete pg-cluster`) +) + +func newDeleteCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := action.NewDeleteOptions(f, streams, types.TraceGVR()) + cmd := &cobra.Command{ + Use: "delete trace-name", + Short: "Delete a trace.", + Example: deleteExamples, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.TraceGVR()), + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(complete(o, args)) + cmdutil.CheckErr(o.Run()) + }, + } + return cmd +} + +func complete(o *action.DeleteOptions, args []string) error { + if len(args) == 0 { + return fmt.Errorf("missing trace name") + } + if len(args) > 1 { + return fmt.Errorf("can't delete multiple views at once") + } + o.Names = args + return nil +} diff --git a/pkg/cmd/trace/list.go b/pkg/cmd/trace/list.go new file mode 100644 index 000000000..8882a4b51 --- /dev/null +++ b/pkg/cmd/trace/list.go @@ -0,0 +1,54 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package trace + +import ( + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericiooptions" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/apecloud/kbcli/pkg/action" + "github.com/apecloud/kbcli/pkg/types" + "github.com/apecloud/kbcli/pkg/util" +) + +var ( + listExamples = templates.Examples(` + # list all traces + kbcli trace list`) +) + +func newListCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := action.NewListOptions(f, streams, types.TraceGVR()) + cmd := &cobra.Command{ + Use: "list", + Short: "list all traces.", + Example: listExamples, + Aliases: []string{"ls"}, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, o.GVR), + Run: func(cmd *cobra.Command, args []string) { + _, err := o.Run() + util.CheckErr(err) + }, + } + o.AddFlags(cmd) + return cmd +} diff --git a/pkg/cmd/trace/update.go b/pkg/cmd/trace/update.go new file mode 100644 index 000000000..4c35045fd --- /dev/null +++ b/pkg/cmd/trace/update.go @@ -0,0 +1,102 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package trace + +import ( + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/cli-runtime/pkg/genericiooptions" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/apecloud/kbcli/pkg/action" + "github.com/apecloud/kbcli/pkg/types" + "github.com/apecloud/kbcli/pkg/util" +) + +var ( + updateExamples = templates.Examples(` + # update a trace with custom locale, stateEvaluationExpression + kbcli trace update pg-cluster-trace --locale zh_cn --cel-state-evaluation-expression "has(object.status.phase) && object.status.phase == \"Running\""`) +) + +type UpdateOptions struct { + *action.PatchOptions + Locale string `json:"locale,omitempty"` + Depth int64 `json:"depth,omitempty"` + CelStateEvaluationExpression string `json:"celStateEvaluationExpression,omitempty"` +} + +func (o *UpdateOptions) CmdComplete(cmd *cobra.Command) error { + if err := o.PatchOptions.CmdComplete(cmd); err != nil { + return err + } + return o.buildPatch() +} + +func (o *UpdateOptions) buildPatch() error { + spec := make(map[string]interface{}) + if o.Depth >= 0 { + spec["depth"] = o.Depth + } + if o.Locale != "" { + spec["locale"] = o.Locale + } + if o.CelStateEvaluationExpression != "" { + spec["celStateEvaluationExpression"] = o.CelStateEvaluationExpression + } + obj := unstructured.Unstructured{ + Object: map[string]interface{}{ + "spec": spec, + }, + } + bytes, err := obj.MarshalJSON() + if err != nil { + return err + } + o.PatchOptions.Patch = string(bytes) + return nil +} + +func newUpdateCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + o := &UpdateOptions{ + PatchOptions: action.NewPatchOptions(f, streams, types.TraceGVR()), + } + cmd := &cobra.Command{ + Use: "update trace-name", + Short: "update a trace.", + Example: updateExamples, + Aliases: []string{"u"}, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.TraceGVR()), + Run: func(cmd *cobra.Command, args []string) { + o.PatchOptions.Names = args + util.CheckErr(o.CmdComplete(cmd)) + util.CheckErr(o.PatchOptions.Run()) + }, + } + + cmd.Flags().StringVar(&o.Locale, "locale", "", "Specify locale.") + cmd.Flags().Int64Var(&o.Depth, "depth", -1, "Specify object tree depth to display.") + cmd.Flags().StringVar(&o.CelStateEvaluationExpression, "cel-state-evaluation-expression", "", "Specify CEL state evaluation expression.") + + o.PatchOptions.AddFlags(cmd) + + return cmd +} diff --git a/pkg/cmd/dashboard/suite_test.go b/pkg/cmd/trace/view.go similarity index 56% rename from pkg/cmd/dashboard/suite_test.go rename to pkg/cmd/trace/view.go index 5f8bf9a73..c811f4918 100644 --- a/pkg/cmd/dashboard/suite_test.go +++ b/pkg/cmd/trace/view.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -17,16 +17,26 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package dashboard +package trace import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericiooptions" + cmdutil "k8s.io/kubectl/pkg/cmd/util" ) -func TestDashboard(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Dashboard Suite") +func NewTraceCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "trace", + Short: "trace management command", + Aliases: []string{"v"}, + } + cmd.AddCommand( + newListCmd(f, streams), + newWatchCmd(f, streams), + newCreateCmd(f, streams), + newUpdateCmd(f, streams), + newDeleteCmd(f, streams), + ) + return cmd } diff --git a/pkg/cmd/trace/watch.go b/pkg/cmd/trace/watch.go new file mode 100644 index 000000000..d3cf318a0 --- /dev/null +++ b/pkg/cmd/trace/watch.go @@ -0,0 +1,134 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package trace + +import ( + "context" + "fmt" + + tea "github.com/charmbracelet/bubbletea" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/client-go/dynamic" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/templates" + + tracev1 "github.com/apecloud/kubeblocks/apis/trace/v1" + + "github.com/apecloud/kbcli/pkg/cmd/trace/chart" + "github.com/apecloud/kbcli/pkg/types" + "github.com/apecloud/kbcli/pkg/util" +) + +var ( + watchExamples = templates.Examples(` + # watch a trace + kbcli trace watch pg-cluster-trace`) + + program *tea.Program +) + +func newWatchCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "watch trace-name", + Short: "watch a trace.", + Example: watchExamples, + Aliases: []string{"w"}, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.TraceGVR()), + Run: func(cmd *cobra.Command, args []string) { + util.CheckErr(watch(f, streams, args)) + }, + } + return cmd +} + +func watch(f cmdutil.Factory, streams genericiooptions.IOStreams, args []string) error { + go doWatch(f, streams, args) + return renderTrace() +} + +func doWatch(f cmdutil.Factory, streams genericiooptions.IOStreams, args []string) { + o := &watchOptions{factory: f, streams: streams, gvr: types.TraceGVR()} + if err := o.complete(args); err != nil { + klog.Fatal("failed to init clientset", err) + } + ctx := context.TODO() + watcher, err := o.dynamic.Resource(o.gvr).Namespace(o.namespace).Watch(ctx, metav1.ListOptions{}) + if err != nil { + klog.Fatal("failed to watch trace", err) + } + for event := range watcher.ResultChan() { + obj, ok := event.Object.(*unstructured.Unstructured) + if !ok { + continue + } + trace := &tracev1.ReconciliationTrace{} + if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, trace); err != nil { + klog.Fatal("failed to convert trace object", err) + } + if trace.Name == args[0] && program != nil { + program.Send(chart.TraceUpdateMsg{Trace: trace}) + } + } +} + +func renderTrace() error { + m := chart.NewReconciliationTraceChart() + program = tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion()) + _, err := program.Run() + return err +} + +type watchOptions struct { + factory cmdutil.Factory + client clientset.Interface + dynamic dynamic.Interface + namespace string + + gvr schema.GroupVersionResource + name string + + streams genericiooptions.IOStreams +} + +func (o *watchOptions) complete(args []string) error { + var err error + + if len(args) == 0 { + return fmt.Errorf("a trace name is required") + } + o.name = args[0] + if o.client, err = o.factory.KubernetesClientSet(); err != nil { + return err + } + if o.dynamic, err = o.factory.DynamicClient(); err != nil { + return err + } + if o.namespace, _, err = o.factory.ToRawKubeConfigLoader().Namespace(); err != nil { + return err + } + return nil +} diff --git a/pkg/cmd/version/suite_test.go b/pkg/cmd/version/suite_test.go index 146ace3f8..37e2f7bfa 100644 --- a/pkg/cmd/version/suite_test.go +++ b/pkg/cmd/version/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/version/version.go b/pkg/cmd/version/version.go index 8e38842fc..a88e27ece 100644 --- a/pkg/cmd/version/version.go +++ b/pkg/cmd/version/version.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/cmd/version/version_test.go b/pkg/cmd/version/version_test.go index 693b5e7e8..96e85d2e8 100644 --- a/pkg/cmd/version/version_test.go +++ b/pkg/cmd/version/version_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyze.go b/pkg/preflight/analyze.go index 2de6fc9bc..d1c31efda 100644 --- a/pkg/preflight/analyze.go +++ b/pkg/preflight/analyze.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyze_test.go b/pkg/preflight/analyze_test.go index 47e932323..02fea6720 100644 --- a/pkg/preflight/analyze_test.go +++ b/pkg/preflight/analyze_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/access.go b/pkg/preflight/analyzer/access.go index 8088232ad..2a580e723 100644 --- a/pkg/preflight/analyzer/access.go +++ b/pkg/preflight/analyzer/access.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/access_test.go b/pkg/preflight/analyzer/access_test.go index 2cbb9787d..748c7b4cd 100644 --- a/pkg/preflight/analyzer/access_test.go +++ b/pkg/preflight/analyzer/access_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/analyze_result.go b/pkg/preflight/analyzer/analyze_result.go index ac404d8a2..7e68dbe74 100644 --- a/pkg/preflight/analyzer/analyze_result.go +++ b/pkg/preflight/analyzer/analyze_result.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/analyze_result_test.go b/pkg/preflight/analyzer/analyze_result_test.go index 899b649df..6aaba7a47 100644 --- a/pkg/preflight/analyzer/analyze_result_test.go +++ b/pkg/preflight/analyzer/analyze_result_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/analyzer.go b/pkg/preflight/analyzer/analyzer.go index 03d395e80..b70ac3a64 100644 --- a/pkg/preflight/analyzer/analyzer.go +++ b/pkg/preflight/analyzer/analyzer.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/anzlyzer_test.go b/pkg/preflight/analyzer/anzlyzer_test.go index 044e8ee7c..7edf4e040 100644 --- a/pkg/preflight/analyzer/anzlyzer_test.go +++ b/pkg/preflight/analyzer/anzlyzer_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/host_analyzer.go b/pkg/preflight/analyzer/host_analyzer.go index 2cbc2fbee..e6e43dc97 100644 --- a/pkg/preflight/analyzer/host_analyzer.go +++ b/pkg/preflight/analyzer/host_analyzer.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/host_analyzer_test.go b/pkg/preflight/analyzer/host_analyzer_test.go index 6f053c9bc..9e7df7567 100644 --- a/pkg/preflight/analyzer/host_analyzer_test.go +++ b/pkg/preflight/analyzer/host_analyzer_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/host_region.go b/pkg/preflight/analyzer/host_region.go index 19ad237e8..5b2101b7c 100644 --- a/pkg/preflight/analyzer/host_region.go +++ b/pkg/preflight/analyzer/host_region.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/host_region_test.go b/pkg/preflight/analyzer/host_region_test.go index 655fc90a2..c082239aa 100644 --- a/pkg/preflight/analyzer/host_region_test.go +++ b/pkg/preflight/analyzer/host_region_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/host_utility.go b/pkg/preflight/analyzer/host_utility.go index 8693884ca..799f6098d 100644 --- a/pkg/preflight/analyzer/host_utility.go +++ b/pkg/preflight/analyzer/host_utility.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/host_utility_test.go b/pkg/preflight/analyzer/host_utility_test.go index 688aac8d8..fc324409a 100644 --- a/pkg/preflight/analyzer/host_utility_test.go +++ b/pkg/preflight/analyzer/host_utility_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/kb_storage_class.go b/pkg/preflight/analyzer/kb_storage_class.go index b5d9dcd84..6b3c35fcf 100644 --- a/pkg/preflight/analyzer/kb_storage_class.go +++ b/pkg/preflight/analyzer/kb_storage_class.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/kb_storage_class_test.go b/pkg/preflight/analyzer/kb_storage_class_test.go index 56310498c..865b0e3a4 100644 --- a/pkg/preflight/analyzer/kb_storage_class_test.go +++ b/pkg/preflight/analyzer/kb_storage_class_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/kb_taint.go b/pkg/preflight/analyzer/kb_taint.go index a24c6738c..8bf68385b 100644 --- a/pkg/preflight/analyzer/kb_taint.go +++ b/pkg/preflight/analyzer/kb_taint.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/kb_taint_test.go b/pkg/preflight/analyzer/kb_taint_test.go index 57f765b6c..06d523af3 100644 --- a/pkg/preflight/analyzer/kb_taint_test.go +++ b/pkg/preflight/analyzer/kb_taint_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/analyzer/suite_test.go b/pkg/preflight/analyzer/suite_test.go index fe0874a2c..3e4edbc00 100644 --- a/pkg/preflight/analyzer/suite_test.go +++ b/pkg/preflight/analyzer/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/collect.go b/pkg/preflight/collect.go index d93f10083..9205c2080 100644 --- a/pkg/preflight/collect.go +++ b/pkg/preflight/collect.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/collect_test.go b/pkg/preflight/collect_test.go index 810f856fd..83c7cd20e 100644 --- a/pkg/preflight/collect_test.go +++ b/pkg/preflight/collect_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/collector/host_collector.go b/pkg/preflight/collector/host_collector.go index 31f5933ae..1786fc8d6 100644 --- a/pkg/preflight/collector/host_collector.go +++ b/pkg/preflight/collector/host_collector.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/collector/host_collector_test.go b/pkg/preflight/collector/host_collector_test.go index 34aed2ddc..b43fc26cc 100644 --- a/pkg/preflight/collector/host_collector_test.go +++ b/pkg/preflight/collector/host_collector_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/collector/host_region.go b/pkg/preflight/collector/host_region.go index 467b433c8..c9d0c20dc 100644 --- a/pkg/preflight/collector/host_region.go +++ b/pkg/preflight/collector/host_region.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/collector/host_region_test.go b/pkg/preflight/collector/host_region_test.go index ad373c2a8..c31dd9975 100644 --- a/pkg/preflight/collector/host_region_test.go +++ b/pkg/preflight/collector/host_region_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/collector/host_utility.go b/pkg/preflight/collector/host_utility.go index 8632e0503..c2dbbe737 100644 --- a/pkg/preflight/collector/host_utility.go +++ b/pkg/preflight/collector/host_utility.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/collector/host_utility_test.go b/pkg/preflight/collector/host_utility_test.go index 554b93362..79525aafb 100644 --- a/pkg/preflight/collector/host_utility_test.go +++ b/pkg/preflight/collector/host_utility_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/collector/suite_test.go b/pkg/preflight/collector/suite_test.go index feb65c497..7d9d2e52a 100644 --- a/pkg/preflight/collector/suite_test.go +++ b/pkg/preflight/collector/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/concat_spec.go b/pkg/preflight/concat_spec.go index 07cb4f095..d6d7f7237 100644 --- a/pkg/preflight/concat_spec.go +++ b/pkg/preflight/concat_spec.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/concat_spec_test.go b/pkg/preflight/concat_spec_test.go index 274e21453..10fa44460 100644 --- a/pkg/preflight/concat_spec_test.go +++ b/pkg/preflight/concat_spec_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/interactive/interactive.go b/pkg/preflight/interactive/interactive.go index e5c59578f..036abac95 100644 --- a/pkg/preflight/interactive/interactive.go +++ b/pkg/preflight/interactive/interactive.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/load_spec.go b/pkg/preflight/load_spec.go index 93d04f256..c08e07e0a 100644 --- a/pkg/preflight/load_spec.go +++ b/pkg/preflight/load_spec.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/load_spec_test.go b/pkg/preflight/load_spec_test.go index 91bc6b7f2..890afc248 100644 --- a/pkg/preflight/load_spec_test.go +++ b/pkg/preflight/load_spec_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/suite_test.go b/pkg/preflight/suite_test.go index ebf6c227f..3954c3022 100644 --- a/pkg/preflight/suite_test.go +++ b/pkg/preflight/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/testing/fake.go b/pkg/preflight/testing/fake.go index 7915cbed3..fd649120c 100644 --- a/pkg/preflight/testing/fake.go +++ b/pkg/preflight/testing/fake.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/testing/fake_test.go b/pkg/preflight/testing/fake_test.go index 9d4df45f4..5afbfc17a 100644 --- a/pkg/preflight/testing/fake_test.go +++ b/pkg/preflight/testing/fake_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/testing/suite_test.go b/pkg/preflight/testing/suite_test.go index e46223ae1..1a68eeebe 100644 --- a/pkg/preflight/testing/suite_test.go +++ b/pkg/preflight/testing/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/text_results.go b/pkg/preflight/text_results.go index 66ed95715..952c76950 100644 --- a/pkg/preflight/text_results.go +++ b/pkg/preflight/text_results.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/text_results_test.go b/pkg/preflight/text_results_test.go index c3eb3700d..f1f62886b 100644 --- a/pkg/preflight/text_results_test.go +++ b/pkg/preflight/text_results_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/util/schema.go b/pkg/preflight/util/schema.go index f98830689..8ccadcc37 100644 --- a/pkg/preflight/util/schema.go +++ b/pkg/preflight/util/schema.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/util/suite_test.go b/pkg/preflight/util/suite_test.go index 570ed180a..d04e91d21 100644 --- a/pkg/preflight/util/suite_test.go +++ b/pkg/preflight/util/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/util/util.go b/pkg/preflight/util/util.go index 99329502a..d3c29e5f9 100644 --- a/pkg/preflight/util/util.go +++ b/pkg/preflight/util/util.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/preflight/util/util_test.go b/pkg/preflight/util/util_test.go index 53a58f28c..f2565e3a5 100644 --- a/pkg/preflight/util/util_test.go +++ b/pkg/preflight/util/util_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/printer/describe.go b/pkg/printer/describe.go index c4c375f61..0545eb4cc 100644 --- a/pkg/printer/describe.go +++ b/pkg/printer/describe.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -30,9 +30,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - - "github.com/apecloud/kbcli/pkg/types" "github.com/apecloud/kbcli/pkg/util" ) @@ -71,36 +68,6 @@ func PrintConditions(conditions []metav1.Condition, out io.Writer) { tbl.Print() } -// PrintComponentConfigMeta prints the conditions of resource. -func PrintComponentConfigMeta(tplInfos []types.ConfigTemplateInfo, clusterName, componentName string, out io.Writer) { - if len(tplInfos) == 0 { - return - } - tbl := NewTablePrinter(out) - PrintTitle("ConfigSpecs Meta") - enableReconfiguring := func(tpl appsv1alpha1.ComponentConfigSpec, configFileKey string) string { - if len(tpl.ConfigConstraintRef) > 0 && util.IsSupportConfigFileReconfigure(tpl, configFileKey) { - return "true" - } - return "false" - } - tbl.SetHeader("CONFIG-SPEC-NAME", "FILE", "ENABLED", "TEMPLATE", "CONSTRAINT", "RENDERED", "COMPONENT", "CLUSTER") - for _, info := range tplInfos { - for configFileKey := range info.CMObj.Data { - tbl.AddRow( - BoldYellow(info.Name), - configFileKey, - BoldYellow(enableReconfiguring(info.TPL, configFileKey)), - info.TPL.TemplateRef, - info.TPL.ConfigConstraintRef, - info.CMObj.Name, - componentName, - clusterName) - } - } - tbl.Print() -} - // PrintHelmValues prints the helm values file of the release in specified format, supports JSON、YAML and Table func PrintHelmValues(configs map[string]interface{}, format Format, out io.Writer) { inTable := func() { diff --git a/pkg/printer/describe_test.go b/pkg/printer/describe_test.go index e0fa70720..bb022171d 100644 --- a/pkg/printer/describe_test.go +++ b/pkg/printer/describe_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/printer/format.go b/pkg/printer/format.go index 1352e772a..2e3f0549e 100755 --- a/pkg/printer/format.go +++ b/pkg/printer/format.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/printer/format_test.go b/pkg/printer/format_test.go index db0ac754d..8d8a7501b 100644 --- a/pkg/printer/format_test.go +++ b/pkg/printer/format_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/printer/helper.go b/pkg/printer/helper.go index f7b2800aa..ddbd995f8 100644 --- a/pkg/printer/helper.go +++ b/pkg/printer/helper.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index 2ed31640d..9d336c309 100644 --- a/pkg/printer/printer.go +++ b/pkg/printer/printer.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/printer/printer_test.go b/pkg/printer/printer_test.go index af8ed3cd8..ecda656ba 100644 --- a/pkg/printer/printer_test.go +++ b/pkg/printer/printer_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/scheme/install.go b/pkg/scheme/install.go index b04afbb51..386f3f254 100644 --- a/pkg/scheme/install.go +++ b/pkg/scheme/install.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/scheme/scheme.go b/pkg/scheme/scheme.go index 305c9ae9c..d32702abb 100644 --- a/pkg/scheme/scheme.go +++ b/pkg/scheme/scheme.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/spinner/spinner.go b/pkg/spinner/spinner.go index e5ccd1ed7..0c6afb82b 100644 --- a/pkg/spinner/spinner.go +++ b/pkg/spinner/spinner.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/spinner/spinner_test.go b/pkg/spinner/spinner_test.go index 73c3f081c..cd4dc44ed 100644 --- a/pkg/spinner/spinner_test.go +++ b/pkg/spinner/spinner_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/spinner/windows_spinner.go b/pkg/spinner/windows_spinner.go index aa94b597e..85b162479 100644 --- a/pkg/spinner/windows_spinner.go +++ b/pkg/spinner/windows_spinner.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/testing/client.go b/pkg/testing/client.go index 6dbe69303..086ff4990 100644 --- a/pkg/testing/client.go +++ b/pkg/testing/client.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/testing/factory.go b/pkg/testing/factory.go index 1ea41e5c1..f51600a68 100644 --- a/pkg/testing/factory.go +++ b/pkg/testing/factory.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/testing/factory_test.go b/pkg/testing/factory_test.go index 76a254cad..2ab88e615 100644 --- a/pkg/testing/factory_test.go +++ b/pkg/testing/factory_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/testing/fake.go b/pkg/testing/fake.go index f28770ada..706415805 100644 --- a/pkg/testing/fake.go +++ b/pkg/testing/fake.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -24,6 +24,7 @@ import ( "time" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/controller/instanceset" chaosmeshv1alpha1 "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" @@ -74,6 +75,9 @@ const ( accountName = "root" + fakeConfigTemplateName = "mysql-config" + FakeMysqlTemplateName = "mysql-config-tpl" + IsDefault = true ) @@ -314,22 +318,19 @@ func FakeCompDef() *kbappsv1.ComponentDefinition { }, Roles: []kbappsv1.ReplicaRole{ { - Name: "leader", - Serviceable: true, - Writable: true, - Votable: true, + Name: "leader", + ParticipatesInQuorum: true, + UpdatePriority: 2, }, { - Name: "follower", - Serviceable: true, - Writable: false, - Votable: true, + Name: "follower", + ParticipatesInQuorum: true, + UpdatePriority: 1, }, { - Name: "learner", - Serviceable: false, - Writable: false, - Votable: false, + Name: "learner", + ParticipatesInQuorum: true, + UpdatePriority: 0, }, }, SystemAccounts: []kbappsv1.SystemAccount{ @@ -355,6 +356,14 @@ func FakeCompDef() *kbappsv1.ComponentDefinition { Reconfigure: &defaultAction, AccountProvision: &defaultAction, }, + Configs: []kbappsv1.ComponentFileTemplate{ + { + Name: fakeConfigTemplateName, + Template: FakeMysqlTemplateName, + Namespace: "default", + VolumeName: "for_test", + }, + }, } return compDef } @@ -365,6 +374,49 @@ func FakeActionSet() *dpv1alpha1.ActionSet { return as } +func FakeParameterDefinition() *parametersv1alpha1.ParametersDefinition { + pd := ¶metersv1alpha1.ParametersDefinition{ + TypeMeta: metav1.TypeMeta{ + APIVersion: fmt.Sprintf("%s/%s", types.ParametersAPIGroup, types.ParametersAPIVersion), + Kind: types.KindParametersDef, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pd", + }, + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + FileName: "my.cnf", + }, + } + return pd +} + +func FakeParameterConfigRenderer() *parametersv1alpha1.ParamConfigRenderer { + pcr := ¶metersv1alpha1.ParamConfigRenderer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: fmt.Sprintf("%s/%s", types.ParametersAPIGroup, types.ParametersAPIVersion), + Kind: types.KindParameterConfigRender, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pcr", + Namespace: Namespace, + }, + Spec: parametersv1alpha1.ParamConfigRendererSpec{ + ComponentDef: CompDefName, + ParametersDefs: []string{"test-pd"}, + Configs: []parametersv1alpha1.ComponentConfigDescription{ + { + Name: "my.cnf", + TemplateName: fakeConfigTemplateName, + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + }, + }, + }, + }, + } + return pcr +} + func FakeBackupPolicy(backupPolicyName, clusterName string) *dpv1alpha1.BackupPolicy { template := &dpv1alpha1.BackupPolicy{ TypeMeta: metav1.TypeMeta{ diff --git a/pkg/testing/fake_test.go b/pkg/testing/fake_test.go index 4d0ee56c6..835433523 100644 --- a/pkg/testing/fake_test.go +++ b/pkg/testing/fake_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/testing/printer.go b/pkg/testing/printer.go index 2981fad64..127b0a9f9 100644 --- a/pkg/testing/printer.go +++ b/pkg/testing/printer.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/testing/suite_test.go b/pkg/testing/suite_test.go index 68fad137c..46853e5c6 100644 --- a/pkg/testing/suite_test.go +++ b/pkg/testing/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/types/config.go b/pkg/types/config.go index 048275db7..0ccbca596 100644 --- a/pkg/types/config.go +++ b/pkg/types/config.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/types/types.go b/pkg/types/types.go index 8fad4a9be..76fa959cb 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -118,6 +118,7 @@ const ( ResourceConfigurationVersions = "configurations" KindCluster = "Cluster" KindClusterDef = "ClusterDefinition" + KindComponentVersion = "ComponentVersion" KindConfigConstraint = "ConfigConstraint" KindConfiguration = "Configuration" KindBackup = "Backup" @@ -130,6 +131,8 @@ const ( KindDeployment = "Deployment" KindConfigMap = "ConfigMap" KindCronJob = "CronJob" + KindParametersDef = "ParametersDefinition" + KindParameterConfigRender = "ParameterConfigRender" ) // K8S rbac API group @@ -175,6 +178,8 @@ const ( ResourceBackupRepos = "backuprepos" ResourceBackupSchedules = "backupschedules" ResourceBackupTemplates = "backuppolicytemplates" + ParametersAPIGroup = "parameters.kubeblocks.io" + ParametersAPIVersion = "v1alpha1" ) // Extensions API group @@ -216,6 +221,13 @@ const ( ResourceRSM = "replicatedstatemachines" ) +// trace API group +const ( + TraceAPIGroup = "trace.kubeblocks.io" + TraceAPIVersion = "v1" + ResourceTrace = "reconciliationtraces" +) + const ( None = "" @@ -289,6 +301,10 @@ func ClusterGVR() schema.GroupVersionResource { return schema.GroupVersionResource{Group: AppsAPIGroup, Version: AppsV1APIVersion, Resource: ResourceClusters} } +func ClusterV1alphaGVR() schema.GroupVersionResource { + return schema.GroupVersionResource{Group: AppsAPIGroup, Version: AppsAPIVersion, Resource: ResourceClusters} +} + func ClusterDefGVR() schema.GroupVersionResource { return schema.GroupVersionResource{Group: AppsAPIGroup, Version: AppsV1APIVersion, Resource: ResourceClusterDefs} } @@ -297,6 +313,10 @@ func CompDefGVR() schema.GroupVersionResource { return schema.GroupVersionResource{Group: AppsAPIGroup, Version: AppsV1APIVersion, Resource: ResourceComponentDefs} } +func CompDefAlpha1GVR() schema.GroupVersionResource { + return schema.GroupVersionResource{Group: AppsAPIGroup, Version: AppsAPIVersion, Resource: ResourceComponentDefs} +} + func ComponentGVR() schema.GroupVersionResource { return schema.GroupVersionResource{Group: AppsAPIGroup, Version: AppsV1APIVersion, Resource: ResourceComponents} } @@ -474,3 +494,7 @@ func JobGVR() schema.GroupVersionResource { func CronJobGVR() schema.GroupVersionResource { return schema.GroupVersionResource{Group: K8SBatchAPIGroup, Version: K8sBatchAPIVersion, Resource: ResourceCronJobs} } + +func TraceGVR() schema.GroupVersionResource { + return schema.GroupVersionResource{Group: TraceAPIGroup, Version: TraceAPIVersion, Resource: ResourceTrace} +} diff --git a/pkg/util/completion.go b/pkg/util/completion.go index dde8c150f..b8f1520db 100644 --- a/pkg/util/completion.go +++ b/pkg/util/completion.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/completion_test.go b/pkg/util/completion_test.go index 7d51948a0..2aea64b2f 100644 --- a/pkg/util/completion_test.go +++ b/pkg/util/completion_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/conversion/conversion.go b/pkg/util/conversion/conversion.go index d9bb4ac68..0b5093341 100644 --- a/pkg/util/conversion/conversion.go +++ b/pkg/util/conversion/conversion.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/util/conversion/types.go b/pkg/util/conversion/types.go index e6f3216dc..882ac22ca 100644 --- a/pkg/util/conversion/types.go +++ b/pkg/util/conversion/types.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/util/conversion/utils.go b/pkg/util/conversion/utils.go index 3fbee5f92..1c1dada20 100644 --- a/pkg/util/conversion/utils.go +++ b/pkg/util/conversion/utils.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/util/error.go b/pkg/util/error.go index a349e50cf..6ca1591d2 100644 --- a/pkg/util/error.go +++ b/pkg/util/error.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/error_test.go b/pkg/util/error_test.go index bf3243009..7c8f7d15b 100644 --- a/pkg/util/error_test.go +++ b/pkg/util/error_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/fileutils.go b/pkg/util/fileutils.go index 798a05c38..24933e737 100644 --- a/pkg/util/fileutils.go +++ b/pkg/util/fileutils.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/flags/flags.go b/pkg/util/flags/flags.go index 78a687275..0b878d1b8 100644 --- a/pkg/util/flags/flags.go +++ b/pkg/util/flags/flags.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -50,6 +50,7 @@ const ( ) const ( + typeNull = "null" typeString = "string" typeNumber = "number" typeInteger = "integer" @@ -104,6 +105,9 @@ func castOrZero[T any](v any) T { func buildOneFlag(cmd *cobra.Command, k string, s *spec.Schema, isArray bool) error { name := strcase.KebabCase(k) + if cmd.Flag(name) != nil { + return nil + } tpe := typeString if len(s.Type) > 0 { tpe = s.Type[0] @@ -132,6 +136,8 @@ func buildOneFlag(cmd *cobra.Command, k string, s *spec.Schema, isArray bool) er } case typeArray: return fmt.Errorf("unsupported build flags for object with array nested within an array") + case typeNull: + return nil default: return fmt.Errorf("unsupported json schema type %s", s.Type) } @@ -157,6 +163,8 @@ func buildOneFlag(cmd *cobra.Command, k string, s *spec.Schema, isArray bool) er if err := buildOneFlag(cmd, name, s.Items.Schema, true); err != nil { return err } + case typeNull: + return nil default: return fmt.Errorf("unsupported json schema type %s", s.Type) } diff --git a/pkg/util/flags/flags_test.go b/pkg/util/flags/flags_test.go index b8d9706f2..2a08b5bc2 100644 --- a/pkg/util/flags/flags_test.go +++ b/pkg/util/flags/flags_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/flags/schema_to_flags.go b/pkg/util/flags/schema_to_flags.go index 7fd8e4704..ba3016194 100644 --- a/pkg/util/flags/schema_to_flags.go +++ b/pkg/util/flags/schema_to_flags.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/flags/suite_test.go b/pkg/util/flags/suite_test.go index 5d954b41b..410b9fd16 100644 --- a/pkg/util/flags/suite_test.go +++ b/pkg/util/flags/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/git.go b/pkg/util/git.go index 7e26a9c0d..f311987e2 100644 --- a/pkg/util/git.go +++ b/pkg/util/git.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/helm/config.go b/pkg/util/helm/config.go index 91b263761..b20f7de52 100644 --- a/pkg/util/helm/config.go +++ b/pkg/util/helm/config.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/helm/diff.go b/pkg/util/helm/diff.go index 5a8f8100b..7fa4cee86 100644 --- a/pkg/util/helm/diff.go +++ b/pkg/util/helm/diff.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/helm/diff_test.go b/pkg/util/helm/diff_test.go index 200ec6b6c..e8ce25fd2 100644 --- a/pkg/util/helm/diff_test.go +++ b/pkg/util/helm/diff_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/helm/downloader.go b/pkg/util/helm/downloader.go index 1e9c451d6..0be86be3b 100644 --- a/pkg/util/helm/downloader.go +++ b/pkg/util/helm/downloader.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/helm/errors.go b/pkg/util/helm/errors.go index 1da1965ea..f04eda746 100644 --- a/pkg/util/helm/errors.go +++ b/pkg/util/helm/errors.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -33,6 +33,7 @@ import ( // Implementing errors should be more friendly to downstream handlers var ErrReleaseNotDeployed = fmt.Errorf("release: not in deployed status") +var ErrReleaseNotReadyForUpgrade = fmt.Errorf("release: not in deployed, failed or superseded status") func ReleaseNotFound(err error) bool { if err == nil { diff --git a/pkg/util/helm/helm.go b/pkg/util/helm/helm.go index e9b0bbbeb..f41d79815 100644 --- a/pkg/util/helm/helm.go +++ b/pkg/util/helm/helm.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -154,6 +154,30 @@ func RemoveRepo(r *repo.Entry) error { return nil } +// GetInstalledForUpgrade gets helm package release info and check the status. +func (i *InstallOpts) GetInstalledForUpgrade(cfg *action.Configuration) (*release.Release, error) { + res, err := action.NewGet(cfg).Run(i.Name) + if err != nil { + return nil, err + } + if res == nil { + return nil, driver.ErrReleaseNotFound + } + // intercept status of pending, unknown, uninstalling and uninstalled. + var status release.Status + if res.Info != nil { + status = res.Info.Status + } else { + return nil, fmt.Errorf("failed to get Helm release status: release or release info is nil") + } + if status.IsPending() { + return nil, errors.Wrapf(ErrReleaseNotReadyForUpgrade, "helm release status is %s. Please wait until the release status changes to ‘deployed’ before upgrading KubeBlocks", status.String()) + } else if status != release.StatusDeployed && status != release.StatusFailed && status != release.StatusSuperseded { + return nil, errors.Wrapf(ErrReleaseNotReadyForUpgrade, "helm release status is %s. Please fix the release before upgrading KubeBlocks,uninstall and install kubeblocks could be a way to fix error", status.String()) + } + return res, nil +} + // GetInstalled gets helm package release info if installed. func (i *InstallOpts) GetInstalled(cfg *action.Configuration) (*release.Release, error) { res, err := action.NewGet(cfg).Run(i.Name) @@ -525,7 +549,7 @@ func (i *InstallOpts) Upgrade(cfg *Config) error { } func (i *InstallOpts) tryUpgrade(cfg *action.Configuration) (*release.Release, error) { - installed, err := i.GetInstalled(cfg) + installed, err := i.GetInstalledForUpgrade(cfg) if err != nil { return nil, err } diff --git a/pkg/util/helm/helm_test.go b/pkg/util/helm/helm_test.go index 42651dbe9..fc1b0f263 100644 --- a/pkg/util/helm/helm_test.go +++ b/pkg/util/helm/helm_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -132,34 +132,42 @@ var _ = Describe("helm util", func() { Expect(o.Uninstall(cfg)).Should(HaveOccurred()) // release not found }) - It("should fail at fetching charts when release is already deployed", func() { - err := actionCfg.Releases.Create(&release.Release{ - Name: o.Name, - Version: 1, - Info: &release.Info{ - Status: release.StatusDeployed, - }, - Chart: &chart.Chart{}, - }) - Expect(err).Should(BeNil()) - _, err = o.tryUpgrade(actionCfg) - Expect(err).Should(HaveOccurred()) // failed at fetching charts - Expect(o.tryUninstall(actionCfg)).Should(BeNil()) // release exists - }) + It("should fail when status is not one of deployed, failed and superseded.", func() { + testCase := []struct { + status release.Status + checkResult bool + }{ + // deployed, failed and superseded + {release.StatusDeployed, true}, + {release.StatusSuperseded, true}, + {release.StatusFailed, true}, + // others + {release.StatusUnknown, false}, + {release.StatusUninstalled, false}, + {release.StatusUninstalling, false}, + {release.StatusPendingInstall, false}, + {release.StatusPendingUpgrade, false}, + {release.StatusPendingRollback, false}, + } - It("should fail when chart is already deployed", func() { - err := actionCfg.Releases.Create(&release.Release{ - Name: o.Name, - Version: 1, - Info: &release.Info{ - Status: release.StatusFailed, - }, - Chart: &chart.Chart{}, - }) - Expect(err).Should(BeNil()) - _, err = o.tryUpgrade(actionCfg) - Expect(errors.Is(err, ErrReleaseNotDeployed)).Should(BeTrue()) - Expect(o.tryUninstall(actionCfg)).Should(BeNil()) // release exists + for i := range testCase { + err := actionCfg.Releases.Create(&release.Release{ + Name: o.Name, + Version: 1, + Info: &release.Info{ + Status: testCase[i].status, + }, + Chart: &chart.Chart{}, + }) + Expect(err).Should(BeNil()) + _, err = o.tryUpgrade(actionCfg) + if testCase[i].checkResult { + Expect(errors.Is(err, ErrReleaseNotReadyForUpgrade)).Should(BeFalse()) + } else { + Expect(errors.Is(err, ErrReleaseNotReadyForUpgrade)).Should(BeTrue()) + } + Expect(o.tryUninstall(actionCfg)).Should(BeNil()) // release exists + } }) }) diff --git a/pkg/util/helm/suite_test.go b/pkg/util/helm/suite_test.go index 785414235..4b630c1c8 100644 --- a/pkg/util/helm/suite_test.go +++ b/pkg/util/helm/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/log.go b/pkg/util/log.go index f86b28d15..32d05f2ce 100644 --- a/pkg/util/log.go +++ b/pkg/util/log.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/prompt/prompt.go b/pkg/util/prompt/prompt.go index 32a77cff4..7f641f713 100644 --- a/pkg/util/prompt/prompt.go +++ b/pkg/util/prompt/prompt.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/prompt/prompt_test.go b/pkg/util/prompt/prompt_test.go index edf21bf49..27e7da2cb 100644 --- a/pkg/util/prompt/prompt_test.go +++ b/pkg/util/prompt/prompt_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/provider.go b/pkg/util/provider.go index 163a68a96..485c1abb6 100644 --- a/pkg/util/provider.go +++ b/pkg/util/provider.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/provider_test.go b/pkg/util/provider_test.go index ae9bc733a..e6084407f 100644 --- a/pkg/util/provider_test.go +++ b/pkg/util/provider_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/suite_test.go b/pkg/util/suite_test.go index 570ed180a..d04e91d21 100644 --- a/pkg/util/suite_test.go +++ b/pkg/util/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/util.go b/pkg/util/util.go index 4ad27ea90..60c8a0d12 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project @@ -44,7 +44,9 @@ import ( kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + "github.com/apecloud/kubeblocks/pkg/generics" "github.com/fatih/color" "github.com/go-logr/logr" "github.com/pkg/errors" @@ -460,34 +462,6 @@ func GetEventObject(e *corev1.Event) string { return fmt.Sprintf("%s/%s", kind, e.InvolvedObject.Name) } -// ComponentConfigSpecs returns configSpecs used by the component. -func ComponentConfigSpecs(clusterName string, namespace string, cli dynamic.Interface, componentName string, reloadTpl bool) ([]kbappsv1alpha1.ComponentConfigSpec, error) { - var ( - clusterObj = kbappsv1.Cluster{} - clusterDefObj = kbappsv1.ClusterDefinition{} - ) - - clusterKey := client.ObjectKey{ - Namespace: namespace, - Name: clusterName, - } - if err := GetResourceObjectFromGVR(types.ClusterGVR(), clusterKey, cli, &clusterObj); err != nil { - return nil, err - } - clusterDefKey := client.ObjectKey{ - Namespace: "", - Name: clusterObj.Spec.ClusterDef, - } - if err := GetResourceObjectFromGVR(types.ClusterDefGVR(), clusterDefKey, cli, &clusterDefObj); err != nil { - return nil, err - } - compDef, err := GetComponentDefByCompName(cli, &clusterObj, componentName) - if err != nil { - return nil, err - } - return GetValidConfigSpecs(reloadTpl, ToV1ComponentConfigSpecs(compDef.Spec.Configs)) -} - func GetComponentDefByName(dynamic dynamic.Interface, name string) (*kbappsv1.ComponentDefinition, error) { componentDef := &kbappsv1.ComponentDefinition{} if err := GetK8SClientObject(dynamic, componentDef, types.CompDefGVR(), "", name); err != nil { @@ -511,70 +485,6 @@ func GetComponentDefByCompName(cli dynamic.Interface, clusterObj *kbappsv1.Clust return GetComponentDefByName(cli, compDefName) } -func GetValidConfigSpecs(reloadTpl bool, configSpecs []kbappsv1alpha1.ComponentConfigSpec) ([]kbappsv1alpha1.ComponentConfigSpec, error) { - if !reloadTpl || len(configSpecs) == 1 { - return configSpecs, nil - } - - validConfigSpecs := make([]kbappsv1alpha1.ComponentConfigSpec, 0, len(configSpecs)) - for _, configSpec := range configSpecs { - if configSpec.ConfigConstraintRef != "" && configSpec.TemplateRef != "" { - validConfigSpecs = append(validConfigSpecs, configSpec) - } - } - return validConfigSpecs, nil -} - -func GetConfigSpecsFromComponentName(cli dynamic.Interface, namespace, clusterName, componentName string, reloadTpl bool) ([]kbappsv1alpha1.ComponentConfigSpec, error) { - configKey := client.ObjectKey{ - Namespace: namespace, - Name: core.GenerateComponentConfigurationName(clusterName, componentName), - } - config := kbappsv1alpha1.Configuration{} - if err := GetResourceObjectFromGVR(types.ConfigurationGVR(), configKey, cli, &config); err != nil { - return nil, err - } - if len(config.Spec.ConfigItemDetails) == 0 { - return nil, nil - } - - configSpecs := make([]kbappsv1alpha1.ComponentConfigSpec, 0, len(config.Spec.ConfigItemDetails)) - for _, item := range config.Spec.ConfigItemDetails { - if item.ConfigSpec != nil { - configSpecs = append(configSpecs, *item.ConfigSpec) - } - } - return GetValidConfigSpecs(reloadTpl, configSpecs) -} - -func ToV1ComponentConfigSpec(configSpec kbappsv1.ComponentConfigSpec) kbappsv1alpha1.ComponentConfigSpec { - config := kbappsv1alpha1.ComponentConfigSpec{ - ComponentTemplateSpec: kbappsv1alpha1.ComponentTemplateSpec{ - Name: configSpec.Name, - TemplateRef: configSpec.TemplateRef, - Namespace: configSpec.Namespace, - VolumeName: configSpec.VolumeName, - DefaultMode: configSpec.DefaultMode, - }, - Keys: configSpec.Keys, - ConfigConstraintRef: configSpec.ConfigConstraintRef, - InjectEnvTo: configSpec.InjectEnvTo, - AsSecret: configSpec.AsSecret, - } - for i := range configSpec.ReRenderResourceTypes { - config.ReRenderResourceTypes = append(config.ReRenderResourceTypes, kbappsv1alpha1.RerenderResourceType(configSpec.ReRenderResourceTypes[i])) - } - return config -} - -func ToV1ComponentConfigSpecs(configSpecs []kbappsv1.ComponentConfigSpec) []kbappsv1alpha1.ComponentConfigSpec { - var configs []kbappsv1alpha1.ComponentConfigSpec - for i := range configSpecs { - configs = append(configs, ToV1ComponentConfigSpec(configSpecs[i])) - } - return configs -} - // GetK8SClientObject gets the client object of k8s, // obj must be a struct pointer so that obj can be updated with the response. func GetK8SClientObject(dynamic dynamic.Interface, @@ -601,132 +511,6 @@ func GetResourceObjectFromGVR(gvr schema.GroupVersionResource, key client.Object return apiruntime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.Object, k8sObj) } -func GetDefaultRoleSelector(cli dynamic.Interface, - cluster *kbappsv1.Cluster, - compDefName string, - clusterCompDefRefName string) (string, error) { - if len(compDefName) > 0 { - compDef, err := GetCompDefByName(cli, compDefName) - if err != nil { - return "", err - } - if len(compDef.Spec.Roles) == 0 { - return "", nil - } - for _, role := range compDef.Spec.Roles { - if role.Writable && role.Serviceable { - return role.Name, nil - } - } - return "", nil - } - /*if cluster.Spec.ClusterDef != "" && clusterCompDefRefName != "" { - clusterDef, err := GetClusterDefByName(cli, cluster.Spec.ClusterDef) - if err != nil { - return "", err - } - clusterCompDef := clusterDef.GetComponentDefByName(clusterCompDefRefName) - if clusterCompDef == nil { - return "", fmt.Errorf("referenced cluster component definition is not defined: %s", clusterCompDefRefName) - } - switch clusterCompDef.WorkloadType { - case kbappsv1alpha1.Replication: - return constant.Primary, nil - case kbappsv1alpha1.Consensus: - if clusterCompDef.ConsensusSpec != nil { - return clusterCompDef.ConsensusSpec.Leader.Name, nil - } - return constant.Leader, nil - case kbappsv1alpha1.Stateful: - if clusterCompDef.RSMSpec != nil { - for _, role := range clusterCompDef.RSMSpec.Roles { - if role.IsLeader { - return role.Name, nil - } - } - } - } - }*/ - return "", nil -} - -// GetCompDefByName gets the ComponentDefinition object by the name. -func GetCompDefByName(cli dynamic.Interface, compDefName string) (*kbappsv1alpha1.ComponentDefinition, error) { - compDef := &kbappsv1alpha1.ComponentDefinition{} - compDefKey := client.ObjectKey{ - Namespace: "", - Name: compDefName, - } - if err := GetResourceObjectFromGVR(types.CompDefGVR(), compDefKey, cli, &compDef); err != nil { - return nil, err - } - return compDef, nil -} - -// GetClusterDefByName gets the ClusterDefinition object by the name. -func GetClusterDefByName(cli dynamic.Interface, clusterDefName string) (*kbappsv1alpha1.ClusterDefinition, error) { - clusterDef := &kbappsv1alpha1.ClusterDefinition{} - clusterDefKey := client.ObjectKey{ - Namespace: "", - Name: clusterDefName, - } - if err := GetResourceObjectFromGVR(types.ClusterDefGVR(), clusterDefKey, cli, &clusterDef); err != nil { - return nil, err - } - return clusterDef, nil -} - -// GetComponentsFromResource returns name of component. -func GetComponentsFromResource(namespace, clusterName string, componentSpecs []kbappsv1.ClusterComponentSpec, cli dynamic.Interface) ([]string, error) { - componentNames := make([]string, 0, len(componentSpecs)) - for _, component := range componentSpecs { - configKey := client.ObjectKey{ - Namespace: namespace, - Name: core.GenerateComponentConfigurationName(clusterName, component.Name), - } - config := kbappsv1alpha1.Configuration{} - if err := GetResourceObjectFromGVR(types.ConfigurationGVR(), configKey, cli, &config); err != nil { - return nil, err - } - if len(config.Spec.ConfigItemDetails) == 0 { - continue - } - - if enableReconfiguring(&config.Spec) { - componentNames = append(componentNames, component.Name) - } - } - return componentNames, nil -} - -func IsSupportConfigFileReconfigure(configTemplateSpec kbappsv1alpha1.ComponentConfigSpec, configFileKey string) bool { - if len(configTemplateSpec.Keys) == 0 { - return true - } - for _, keySelector := range configTemplateSpec.Keys { - if keySelector == configFileKey { - return true - } - } - return false -} - -func enableReconfiguring(component *kbappsv1alpha1.ConfigurationSpec) bool { - if component == nil { - return false - } - for _, item := range component.ConfigItemDetails { - if item.ConfigSpec == nil { - continue - } - tpl := item.ConfigSpec - if len(tpl.ConfigConstraintRef) > 0 && len(tpl.TemplateRef) > 0 { - return true - } - } - return false -} - // IsSupportReconfigureParams checks whether all updated parameters belong to config template parameters. func IsSupportReconfigureParams(tpl kbappsv1alpha1.ComponentConfigSpec, values map[string]*string, cli dynamic.Interface) (bool, error) { var ( @@ -765,24 +549,47 @@ func IsSupportReconfigureParams(tpl kbappsv1alpha1.ComponentConfigSpec, values m return true, nil } -func ValidateParametersModified(tpl *kbappsv1alpha1.ComponentConfigSpec, parameters sets.Set[string], cli dynamic.Interface) (err error) { - cc := kbappsv1beta1.ConfigConstraint{} - ccKey := client.ObjectKey{ - Namespace: "", - Name: tpl.ConfigConstraintRef, +func ValidateParametersModified(classifyParameters map[string]map[string]*parametersv1alpha1.ParametersInFile, pds []*parametersv1alpha1.ParametersDefinition) (err error) { + validator := func(index int, parameters sets.Set[string]) error { + if index < 0 || len(pds[index].Spec.ImmutableParameters) == 0 { + return nil + } + immuSet := sets.New(pds[index].Spec.ImmutableParameters...) + uniqueParameters := immuSet.Intersection(parameters) + if uniqueParameters.Len() == 0 { + return nil + } + return core.MakeError("parameter[%v] is immutable, cannot be modified!", cfgutil.ToSet(uniqueParameters).AsSlice()) } - if err = GetResourceObjectFromGVR(types.ConfigConstraintGVR(), ccKey, cli, &cc); err != nil { - return + + for _, tplParams := range classifyParameters { + for file, params := range tplParams { + match := func(pd *parametersv1alpha1.ParametersDefinition) bool { + return pd.Spec.FileName == file + } + index := generics.FindFirstFunc(pds, match) + if err := validator(index, sets.KeySet(params.Parameters)); err != nil { + return err + } + } } - return ValidateParametersModified2(parameters, cc.Spec) + return nil } -func ValidateParametersModified2(parameters sets.Set[string], cc kbappsv1beta1.ConfigConstraintSpec) error { - if len(cc.ImmutableParameters) == 0 { +func ValidateParametersModified2(parameters sets.Set[string], pds []*parametersv1alpha1.ParametersDefinition, file string) error { + var ret *parametersv1alpha1.ParametersDefinition + for _, pd := range pds { + if pd.Spec.FileName == file { + ret = pd + break + } + } + + if ret == nil || len(ret.Spec.ImmutableParameters) == 0 { return nil } - immutableParameters := sets.New(cc.ImmutableParameters...) + immutableParameters := sets.New(ret.Spec.ImmutableParameters...) uniqueParameters := immutableParameters.Intersection(parameters) if uniqueParameters.Len() == 0 { return nil @@ -1350,3 +1157,50 @@ func GetClusterNameFromArgsOrFlag(cmd *cobra.Command, args []string) string { } return "" } + +func SetHelmOwner(dynamicClient dynamic.Interface, gvr schema.GroupVersionResource, releaseName, namespace string, names []string) error { + patchOP := fmt.Sprintf(`[{"op": "replace", "path": "/metadata/annotations/meta.helm.sh~1release-name", "value": "%s"}`+ + `,{"op": "replace", "path": "/metadata/annotations/meta.helm.sh~1release-namespace", "value": "%s"}]`, releaseName, namespace) + for _, name := range names { + if _, err := dynamicClient.Resource(gvr).Namespace("").Patch(context.TODO(), name, + k8sapitypes.JSONPatchType, []byte(patchOP), metav1.PatchOptions{}); client.IgnoreNotFound(err) != nil { + return err + } + } + return nil +} + +// AddAnnotationToComponentOrShard adds a specific annotation to a component. +func AddAnnotationToComponentOrShard(dynamicClient dynamic.Interface, componentName, namespace, annotationKey, annotationValue string) error { + gvr := types.ComponentGVR() + resourceClient := dynamicClient.Resource(gvr).Namespace(namespace) + componentObj, err := resourceClient.Get(context.Background(), componentName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get component %s: %v", componentName, err) + } + + annotations := componentObj.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + annotations[annotationKey] = annotationValue + componentObj.SetAnnotations(annotations) + + _, err = resourceClient.Update(context.Background(), componentObj, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to update component %s with annotation %s: %v", componentName, annotationKey, err) + } + + return nil +} + +func GetComponentsOrShards(cluster *kbappsv1.Cluster) []string { + var components []string + for _, component := range cluster.Spec.ComponentSpecs { + components = append(components, component.Name) + } + for _, sharding := range cluster.Spec.Shardings { + components = append(components, sharding.Name) + } + return components +} diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go index a165fb3b1..13bf8fce6 100644 --- a/pkg/util/util_test.go +++ b/pkg/util/util_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/version.go b/pkg/util/version.go index ab068985e..b12d22746 100644 --- a/pkg/util/version.go +++ b/pkg/util/version.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/pkg/util/version_test.go b/pkg/util/version_test.go index 3f9ead212..5461c2d9b 100644 --- a/pkg/util/version_test.go +++ b/pkg/util/version_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/version/version.go b/version/version.go index 3c96a4f86..865834487 100644 --- a/version/version.go +++ b/version/version.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project diff --git a/version/version_test.go b/version/version_test.go index 11e60f22f..a2ac30895 100644 --- a/version/version_test.go +++ b/version/version_test.go @@ -1,5 +1,5 @@ /* -Copyright (C) 2022-2024 ApeCloud Co., Ltd +Copyright (C) 2022-2025 ApeCloud Co., Ltd This file is part of KubeBlocks project