From 48ea19d623abf944251a1191c778d5b7ece0870b Mon Sep 17 00:00:00 2001 From: Ahmad Ibrahim Date: Tue, 4 Jun 2024 14:48:04 -0700 Subject: [PATCH] feat: configure cli commands --- .gitignore | 45 ++ Makefile | 263 +++++++ cmd/root.go | 119 ++++ cmd/validator.go | 159 +++++ cmd/version.go | 19 + go.mod | 97 +++ go.sum | 358 ++++++++++ pkg/cmd/common/common.go | 24 + pkg/cmd/validator/validator.go | 644 ++++++++++++++++++ pkg/components/validator.go | 558 +++++++++++++++ pkg/config/config.go | 154 +++++ pkg/config/constants.go | 529 ++++++++++++++ pkg/config/manager/config_manager.go | 27 + pkg/logging/logger.go | 159 +++++ pkg/repo/repo.go | 617 +++++++++++++++++ pkg/services/env_service.go | 394 +++++++++++ pkg/services/k8s_service.go | 213 ++++++ pkg/services/validator/validator_service.go | 401 +++++++++++ pkg/utils/aws/aws.go | 45 ++ pkg/utils/cmd/cmd.go | 13 + pkg/utils/crypto/crypto.go | 114 ++++ pkg/utils/embed/bin/.gitkeep | 0 pkg/utils/embed/embed.go | 163 +++++ ...validator-iam-role-spectro-cloud-base.tmpl | 288 ++++++++ ...svalidator-iam-role-spectro-cloud-eks.tmpl | 135 ++++ ...am-role-spectro-cloud-minimal-dynamic.tmpl | 120 ++++ ...iam-role-spectro-cloud-minimal-static.tmpl | 94 +++ .../validator/validation-result.tmpl | 4 + .../validator/validator-base-values.tmpl | 173 +++++ .../validator-plugin-aws-values.tmpl | 60 ++ .../validator-plugin-azure-values.tmpl | 52 ++ .../validator-plugin-network-values.tmpl | 54 ++ .../validator-plugin-oci-values.tmpl | 52 ++ .../validator-plugin-vsphere-values.tmpl | 54 ++ .../validator/validator-rules-aws.tmpl | 7 + .../validator/validator-rules-azure.tmpl | 7 + .../validator/validator-rules-network.tmpl | 7 + .../validator/validator-rules-oci.tmpl | 7 + .../validator/validator-rules-vsphere.tmpl | 7 + .../vsphere-root-level-privileges-7.0.yaml | 108 +++ .../vsphere-root-level-privileges-8.0.yaml | 108 +++ .../vsphere-root-level-privileges-all.yaml | 431 ++++++++++++ .../validator/vsphere-spectro-cloud-tags.yaml | 15 + .../vsphere-spectro-entity-privileges.yaml | 8 + pkg/utils/exec/exec.go | 154 +++++ pkg/utils/extra/utils.go | 39 ++ pkg/utils/file/file.go | 178 +++++ pkg/utils/kind/kind.go | 175 +++++ pkg/utils/kube/kube.go | 227 ++++++ pkg/utils/ptr/ptr.go | 160 +++++ pkg/utils/string/string.go | 87 +++ validator.go | 9 + 52 files changed, 7935 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 cmd/root.go create mode 100644 cmd/validator.go create mode 100644 cmd/version.go create mode 100644 go.sum create mode 100644 pkg/cmd/common/common.go create mode 100644 pkg/cmd/validator/validator.go create mode 100644 pkg/components/validator.go create mode 100644 pkg/config/config.go create mode 100644 pkg/config/constants.go create mode 100644 pkg/config/manager/config_manager.go create mode 100644 pkg/logging/logger.go create mode 100644 pkg/repo/repo.go create mode 100644 pkg/services/env_service.go create mode 100644 pkg/services/k8s_service.go create mode 100644 pkg/services/validator/validator_service.go create mode 100644 pkg/utils/aws/aws.go create mode 100644 pkg/utils/cmd/cmd.go create mode 100644 pkg/utils/crypto/crypto.go create mode 100644 pkg/utils/embed/bin/.gitkeep create mode 100644 pkg/utils/embed/embed.go create mode 100644 pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-base.tmpl create mode 100644 pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-eks.tmpl create mode 100644 pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-minimal-dynamic.tmpl create mode 100644 pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-minimal-static.tmpl create mode 100644 pkg/utils/embed/resources/validator/validation-result.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-base-values.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-plugin-aws-values.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-plugin-azure-values.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-plugin-network-values.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-plugin-oci-values.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-plugin-vsphere-values.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-rules-aws.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-rules-azure.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-rules-network.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-rules-oci.tmpl create mode 100644 pkg/utils/embed/resources/validator/validator-rules-vsphere.tmpl create mode 100644 pkg/utils/embed/resources/validator/vsphere-root-level-privileges-7.0.yaml create mode 100644 pkg/utils/embed/resources/validator/vsphere-root-level-privileges-8.0.yaml create mode 100644 pkg/utils/embed/resources/validator/vsphere-root-level-privileges-all.yaml create mode 100644 pkg/utils/embed/resources/validator/vsphere-spectro-cloud-tags.yaml create mode 100644 pkg/utils/embed/resources/validator/vsphere-spectro-entity-privileges.yaml create mode 100644 pkg/utils/exec/exec.go create mode 100644 pkg/utils/extra/utils.go create mode 100644 pkg/utils/file/file.go create mode 100644 pkg/utils/kind/kind.go create mode 100644 pkg/utils/kube/kube.go create mode 100644 pkg/utils/ptr/ptr.go create mode 100644 pkg/utils/string/string.go create mode 100644 validator.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..5b37a581 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test +*cert.pem +*key.pem + +# Coverage files +_build/ +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# IDE +.idea/installer.iml +.idea/modules.xml +.idea/workspace.xml +.vscode +*__debug_bin* + +# Binaries +bin/ +!bin/.gitkeep + +# Embedded Binaries +pkg/utils/embed/bin/* + +# Creds +.netrc + +# hack +.validator + +# test +tests/unit-test-data/*.tmp +.idea + +# os generated files +.DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..fc47de82 --- /dev/null +++ b/Makefile @@ -0,0 +1,263 @@ +# If you update this file, please follow: +# https://www.thapaliya.com/en/writings/well-documented-makefiles/ + +# Meta +.PHONY: docker kind kubectl helm build test vmtoolsd-shim # TODO: check if i need vmtoolsd-shim +.DEFAULT_GOAL:=help + +# Images +IMAGE_TAG ?= latest + +# TODO: update this image location +CLI_IMG ?= "gcr.io/spectro-common-dev/${USER}/validator:$(IMAGE_TAG)" + +# Dependency Versions +BUILDER_GOLANG_VERSION ?= 1.22 +DOCKER_VERSION ?= 24.0.6 +HELM_VERSION ?= 3.14.0 +ENVTEST_VERSION ?= 1.27.1 +GOLANGCI_VERSION ?= 1.54.2 +KIND_VERSION ?= 0.20.0 +KUBECTL_VERSION ?= 1.24.10 + +# Product Version +VERSION_SUFFIX ?= -dev +VERSION ?= 0.0.1${VERSION_SUFFIX} + +# Common vars +MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIR := $(dir $(MAKEFILE_PATH)) +TEMPLATE_DIR := $(CURRENT_DIR)/server/internal/templates +EMBED_BIN := ./pkg/utils/embed/bin +BIN_DIR ?= ./bin + +# Go env vars +GOOS ?= $(shell go env GOOS) +GOARCH ?= $(shell go env GOARCH) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +PLATFORM=$(GOOS) +ifeq ("$(GOOS)", "darwin") +PLATFORM=mac +else ifeq ("$(GOOS)", "windows") +PLATFORM=win +endif +TARGETARCH ?= amd64 + +# Test vars +COVER_DIR=_build/cov +COVER_PKGS=$(shell go list ./... | grep -v /tests/) # omit integration tests + +# Swagger vars +MAKE_COMMAND=make -f +INSTALLER_MAKE_PATH := $(CURRENT_DIR)server/internal/spec/Makefile + +# Integrated Images List +IMAGE_LIST=_build/images/images.list + +##@ Help Targets +help: ## Display this help + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[0m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Build Targets +build: binaries ## Build CLI + @echo "Building CLI binary..." + CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) GO111MODULE=on go build -ldflags " \ + -X github.com/validator-labs/validatorctl/cmd.Version=$(VERSION)" \ + -a -o bin/validator validator.go + +build-dev: binaries build-cli ## Build CLI & copy validator binary into your PATH + sudo cp bin/validator /usr/local/bin + +get-version: ## Get the product version + @echo "$(VERSION)" + +##@ Static Analysis Targets +fmt: ## Run go fmt + go fmt ./... + +lint: golangci-lint ## Run golangci-lint + $(GOLANGCI_LINT) run + +vet: binaries ## Run go vet + go vet ./... + +##@ Test Targets +test-unit: ## Run unit tests + @mkdir -p $(COVER_DIR) + rm -rf $(COVER_DIR)/* + IS_TEST=true CLI_VERSION=$(VERSION) go test -v -race -parallel 6 -timeout 20m \ + -covermode=atomic -coverprofile=$(COVER_DIR)/unit.out $(COVER_PKGS) + +# For now we can't enable -race for integration tests +# due to https://github.com/spf13/viper/issues/174 +test-integration: binaries init-kubebuilder ## Run integration tests + @mkdir -p $(COVER_DIR) + KUBEBUILDER_ASSETS=${KUBEBUILDER_ASSETS} IS_TEST=true CLI_VERSION=$(VERSION) KUBECONFIG= DISABLE_KIND_CLUSTER_CHECK=true \ + go test -v -parallel 6 -timeout 30m \ + -covermode=atomic -coverpkg=./... -coverprofile=$(COVER_DIR)/integration.out ./tests/... + +.PHONY: test +test: gocovmerge test-unit test-integration ## Run unit tests, integration test + $(GOCOVMERGE) $(COVER_DIR)/*.out > $(COVER_DIR)/coverage.out.tmp + # Omit models and test code from coverage report + cat $(COVER_DIR)/coverage.out.tmp | grep -vE 'models|tests' > $(COVER_DIR)/coverage.out + go tool cover -func=$(COVER_DIR)/coverage.out -o $(COVER_DIR)/cover.func + go tool cover -html=$(COVER_DIR)/coverage.out -o $(COVER_DIR)/cover.html + go tool cover -func $(COVER_DIR)/coverage.out | grep total + +coverage: ## Show global test coverage + go tool cover -func $(COVER_DIR)/coverage.out + +coverage-html: ## Open global test coverage report in your browser + go tool cover -html $(COVER_DIR)/coverage.out + +coverage-unit: ## Show unit test coverage + go tool cover -func $(COVER_DIR)/unit.out + +coverage-unit-html: ## Open unit test coverage report in your browser + go tool cover -html $(COVER_DIR)/unit.out + +coverage-integration: ## Show integration test coverage + go tool cover -func $(COVER_DIR)/integration.out + +coverage-integration-html: ## Open integration test coverage report in your browser + go tool cover -html $(COVER_DIR)/integration.out + +##@ Image Targets + +BUILD_ARGS = --build-arg CLI_VERSION=${VERSION} --build-arg BUILDER_GOLANG_VERSION=${BUILDER_GOLANG_VERSION} + +docker-all: docker-cli docker-push ## Builds & pushes Docker images to container registry + +docker-cli: binaries + docker buildx build ${BUILD_ARGS} --platform linux/${TARGETARCH} --load -f build/docker/cli.Dockerfile . -t ${CLI_IMG} + +docker-compose: binaries ## Rebuild images and restart docker-compose + docker compose build + docker compose up + +docker-push: ## Pushes Docker images to container registry + docker push ${CLI_IMG} + echo cli,core,${CLI_IMG} >> ${IMAGE_LIST} + +docker-rmi: ## Remove Docker images from local Docker engine + docker rmi -f ${CLI_IMG} + +create-images-list: ## Create the image list for CICD + mkdir -p _build/images + touch $(IMAGE_LIST) + + +##@ Tools Targets +binaries: docker helm kind kubectl vmtoolsd-shim ## Build embedded binaries + +clean-binaries: ## Clean embedded binaries + @echo "Cleaning embedded binaries..." + rm -rf $(EMBED_BIN)/docker + rm -rf $(EMBED_BIN)/helm + rm -rf $(EMBED_BIN)/kind + rm -rf $(EMBED_BIN)/kubectl + rm -rf $(EMBED_BIN)/vmtoolsd + +truncate-binaries: + @echo "Truncating embedded binaries..." + : > $(EMBED_BIN)/docker + : > $(EMBED_BIN)/helm + : > $(EMBED_BIN)/kind + : > $(EMBED_BIN)/kubectl + : > $(EMBED_BIN)/vmtoolsd + +docker: +ifeq ("$(wildcard $(EMBED_BIN)/docker)", "") + if [[ "$(GOOS)" == "windows" ]]; then \ + curl -L https://download.docker.com/$(PLATFORM)/static/stable/x86_64/docker-$(DOCKER_VERSION).zip -o docker.zip; \ + unzip docker.zip; \ + rm -f docker.zip; \ + mv docker/docker.exe $(EMBED_BIN)/docker; \ + else \ + curl -L https://download.docker.com/$(PLATFORM)/static/stable/x86_64/docker-$(DOCKER_VERSION).tgz | tar xz docker/docker; \ + mv docker/docker $(EMBED_BIN)/docker; \ + fi + chmod +x $(EMBED_BIN)/docker + rm -rf ./docker +endif + +kind: +ifeq ("$(wildcard $(EMBED_BIN)/kind)", "") + curl -Lo $(EMBED_BIN)/kind https://github.com/kubernetes-sigs/kind/releases/download/v$(KIND_VERSION)/kind-$(GOOS)-$(GOARCH) + chmod +x $(EMBED_BIN)/kind +endif + +kubectl: +ifeq ("$(wildcard $(EMBED_BIN)/kubectl)", "") + if [[ "$(GOOS)" == "windows" ]]; then \ + curl -Lo $(EMBED_BIN)/kubectl https://dl.k8s.io/release/v$(KUBECTL_VERSION)/bin/$(GOOS)/$(GOARCH)/kubectl.exe; \ + else \ + curl -Lo $(EMBED_BIN)/kubectl https://dl.k8s.io/release/v$(KUBECTL_VERSION)/bin/$(GOOS)/$(GOARCH)/kubectl; \ + fi + chmod +x $(EMBED_BIN)/kubectl +endif + +helm: +ifeq ("$(wildcard $(EMBED_BIN)/helm)", "") + if [[ "$(GOOS)" == "windows" ]]; then \ + curl -L https://get.helm.sh/helm-v$(HELM_VERSION)-$(GOOS)-$(GOARCH).zip -o helm.zip; \ + unzip helm.zip; \ + rm -f helm.zip; \ + mv windows-amd64/helm.exe $(EMBED_BIN)/helm; \ + rm -rf ./windows-amd64; \ + else \ + curl -L https://get.helm.sh/helm-v$(HELM_VERSION)-$(GOOS)-$(GOARCH).tar.gz | tar xz; \ + mv $(GOOS)-$(GOARCH)/helm $(EMBED_BIN)/helm; \ + rm -rf ./$(GOOS)-$(GOARCH); \ + fi + chmod +x $(EMBED_BIN)/helm +endif + +vmtoolsd-shim: ## Build VMware Tools shim +ifeq ("$(wildcard $(EMBED_BIN)/vmtoolsd)", "") + CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) GO111MODULE=on go build -o $(EMBED_BIN)/vmtoolsd vmtoolsd-shim/main.go +endif + +golangci-lint: + if ! test -f $(BIN_DIR)/golangci-lint-linux-amd64; then \ + curl -LOs https://github.com/golangci/golangci-lint/releases/download/v$(GOLANGCI_VERSION)/golangci-lint-$(GOLANGCI_VERSION)-linux-amd64.tar.gz; \ + tar -zxf golangci-lint-$(GOLANGCI_VERSION)-linux-amd64.tar.gz; \ + mv golangci-lint-$(GOLANGCI_VERSION)-*/golangci-lint $(BIN_DIR)/golangci-lint-linux-amd64; \ + chmod +x $(BIN_DIR)/golangci-lint-linux-amd64; \ + rm -rf ./golangci-lint-$(GOLANGCI_VERSION)-linux-amd64*; \ + fi + if ! test -f $(BIN_DIR)/golangci-lint-$(GOOS)-$(GOARCH); then \ + curl -LOs https://github.com/golangci/golangci-lint/releases/download/v$(GOLANGCI_VERSION)/golangci-lint-$(GOLANGCI_VERSION)-$(GOOS)-$(GOARCH).tar.gz; \ + tar -zxf golangci-lint-$(GOLANGCI_VERSION)-$(GOOS)-$(GOARCH).tar.gz; \ + mv golangci-lint-$(GOLANGCI_VERSION)-*/golangci-lint $(BIN_DIR)/golangci-lint-$(GOOS)-$(GOARCH); \ + chmod +x $(BIN_DIR)/golangci-lint-$(GOOS)-$(GOARCH); \ + rm -rf ./golangci-lint-$(GOLANGCI_VERSION)-$(GOOS)-$(GOARCH)*; \ + fi +GOLANGCI_LINT=$(BIN_DIR)/golangci-lint-$(GOOS)-$(GOARCH) + +gocovmerge: +ifeq (, $(shell which gocovmerge)) + go version + go install github.com/wadey/gocovmerge@latest + go mod tidy +GOCOVMERGE=$(GOBIN)/gocovmerge +else +GOCOVMERGE=$(shell which gocovmerge) +endif + +init-kubebuilder: setup-envtest + $(BIN_DIR)/setup-envtest use --bin-dir $(BIN_DIR) $(ENVTEST_VERSION) +KUBEBUILDER_ASSETS = $(shell pwd)/$(shell $(BIN_DIR)/setup-envtest use -p path --bin-dir $(BIN_DIR) $(ENVTEST_VERSION)) + +setup-envtest: +ifeq ("$(wildcard $(BIN_DIR)/setup-envtest)", "") + GOBIN=$(shell pwd)/bin go install sigs.k8s.io/controller-runtime/tools/setup-envtest +endif +SETUP_ENVTEST=$(BIN_DIR)/setup-envtest + diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 00000000..47716731 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,119 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/fsnotify/fsnotify" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + cfg "github.com/validator-labs/validatorctl/pkg/config" + cfgmanager "github.com/validator-labs/validatorctl/pkg/config/manager" + log "github.com/validator-labs/validatorctl/pkg/logging" +) + +var ( + cfgFile string + logLevel string + workspaceLoc string + + rootCmd *cobra.Command + + // set at compile time via -ldflags; see release targets in Makefile + Subcommands string + Version string +) + +func init() { + InitRootCmd() + cobra.OnInitialize(InitConfig) +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + exit(err) + } +} + +// InitRootCmd initializes the root command and adds all enabled subcommands +func InitRootCmd() *cobra.Command { + // rootCmd represents the base command when called without any subcommands + rootCmd = &cobra.Command{ + Use: "validator", + Short: "Welcome to the Validator CLI", + Long: `Welcome to the Validator CLI. + Install validator & configure validator plugins. + Use 'validator help ' to explore all of the functionality the Validator CLI has to offer.`, + SilenceUsage: false, + } + + globalFlags := rootCmd.PersistentFlags() + globalFlags.StringVarP(&cfgFile, "config", "c", "", "Validator CLI config file location") + globalFlags.StringVarP(&logLevel, "log-level", "l", "info", "Log level. One of: [panic fatal error warn info debug trace]") + globalFlags.StringVarP(&workspaceLoc, "workspace", "w", "", `Workspace location for staging runtime configurations and logs (default "$HOME/.validator")`) + + if err := viper.BindPFlag("logLevel", globalFlags.Lookup("log-level")); err != nil { + exit(err) + } + + // add base commands + rootCmd.AddCommand(NewVersionCmd()) + rootCmd.AddCommand(NewDeployValidatorCmd()) + rootCmd.AddCommand(NewUpgradeValidatorCmd()) + rootCmd.AddCommand(NewUndeployValidatorCmd()) + rootCmd.AddCommand(NewDescribeValidationResultsCmd()) + + return rootCmd +} + +// InitConfig reads in config file and ENV variables if set +func InitConfig() { + log.SetLevel(viper.GetString("logLevel")) + + if cfgFile != "" { + // Use config file from the --config flag + viper.SetConfigFile(cfgFile) + } else { + // Find home directory + cfgPath, err := cfg.DefaultWorkspaceLoc() + cobra.CheckErr(err) + + // Search for config under home directory + viper.AddConfigPath(cfgPath) + viper.SetConfigType("yaml") + viper.SetConfigName(cfg.ConfigFile) + } + viper.SetEnvPrefix("VALIDATOR_CTL") + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it + if err := viper.ReadInConfig(); err == nil { + viper.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("Config file changed:", e.Name) + // This is actually a noop - the updated config will be + // written to disk separately, but still nice to notify + // the user that something changed! + }) + viper.WatchConfig() + } else { + switch err.(type) { + case viper.ConfigFileNotFoundError: + log.InfoCLI("No validator cli config file detected. One will be created.") + default: + log.FatalCLI("Failed to initialize Validator CLI config", "error", err) + } + } + + // Instantiate config + if err := cfgmanager.Init(); err != nil { + exit(err) + } +} + +func exit(err error) { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/cmd/validator.go b/cmd/validator.go new file mode 100644 index 00000000..7eede126 --- /dev/null +++ b/cmd/validator.go @@ -0,0 +1,159 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/validator-labs/validatorctl/pkg/cmd/common" + "github.com/validator-labs/validatorctl/pkg/cmd/validator" + cfg "github.com/validator-labs/validatorctl/pkg/config" + cfgmanager "github.com/validator-labs/validatorctl/pkg/config/manager" + cmdutils "github.com/validator-labs/validatorctl/pkg/utils/cmd" +) + +func NewDeployValidatorCmd() *cobra.Command { + c := cfgmanager.Config() + var configFile string + var configOnly, updatePasswords, reconfigure bool + + cmd := &cobra.Command{ + Use: "install", + Short: "Install validator & configure validator plugin(s)", + Long: `Install validator & configure validator plugin(s). + +For more information about validator, see: https://github.com/validator-labs/validator. +`, + Args: cobra.NoArgs, + SilenceUsage: false, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return common.InitWorkspace(c, cfg.Validator, cfg.ValidatorSubdirs, true) + }, + RunE: func(cmd *cobra.Command, args []string) error { + taskConfig := cfg.NewTaskConfig( + Version, configFile, configOnly, false, updatePasswords, false, false, + ) + if err := c.Save(""); err != nil { + return err + } + + if err := validator.DeployValidatorCommand(c, taskConfig, reconfigure); err != nil { + return fmt.Errorf("failed to install validator: %v", err) + } + return nil + }, + } + + flags := cmd.Flags() + flags.StringVarP(&configFile, "config-file", "f", "", "Install using a configuration file (optional)") + flags.BoolVarP(&configOnly, "config-only", "o", false, "Generate configuration file only. Do not proceed with installation. Default: false.") + flags.BoolVarP(&updatePasswords, "update-passwords", "p", false, "Update passwords only. Do not proceed with installation. The --config-file flag must be provided. Default: false.") + flags.BoolVarP(&reconfigure, "reconfigure", "r", false, "Re-configure validator and plugin(s) prior to installation. The --config-file flag must be provided. Default: false.") + + cmd.MarkFlagsMutuallyExclusive("update-passwords", "reconfigure") + + return cmd +} + +func NewUpgradeValidatorCmd() *cobra.Command { + c := cfgmanager.Config() + var configFile string + + cmd := &cobra.Command{ + Use: "upgrade", + Short: "Upgrade validator & re-configure validator plugin(s)", + Long: `Upgrade validator & re-configure validator plugin(s). + +For more information about validator, see: https://github.com/validator-labs/validator. +`, + Args: cobra.NoArgs, + SilenceUsage: false, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return common.InitWorkspace(c, cfg.Validator, cfg.ValidatorSubdirs, true) + }, + RunE: func(cmd *cobra.Command, args []string) error { + taskConfig := cfg.NewTaskConfig( + Version, configFile, false, false, false, false, false, + ) + if err := validator.UpgradeValidatorCommand(c, taskConfig); err != nil { + return fmt.Errorf("failed to upgrade validator: %v", err) + } + return nil + }, + } + + flags := cmd.Flags() + flags.StringVarP(&configFile, "config-file", "f", "", "Upgrade using a configuration file") + + cmdutils.MarkFlagRequired(cmd, "config-file") + + return cmd +} + +func NewUndeployValidatorCmd() *cobra.Command { + c := cfgmanager.Config() + var configFile string + var deleteCluster bool + + cmd := &cobra.Command{ + Use: "uninstall", + Short: "Uninstall validator & all validator plugin(s)", + Long: "Uninstall validator & all validator plugin(s)", + Args: cobra.NoArgs, + SilenceUsage: false, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return common.InitWorkspace(c, cfg.Validator, cfg.ValidatorSubdirs, true) + }, + RunE: func(cmd *cobra.Command, args []string) error { + taskConfig := cfg.NewTaskConfig( + Version, configFile, false, false, false, false, false, + ) + if err := validator.UndeployValidatorCommand(taskConfig, deleteCluster); err != nil { + return fmt.Errorf("failed to uninstall validator: %v", err) + } + return nil + }, + } + + flags := cmd.Flags() + flags.StringVarP(&configFile, "config-file", "f", "", "Validator configuration file (required)") + flags.BoolVarP(&deleteCluster, "delete-cluster", "d", true, "Delete the validator kind cluster. Does not apply if using a preexisting K8s cluster. Default: true.") + + cmdutils.MarkFlagRequired(cmd, "config-file") + + return cmd +} + +func NewDescribeValidationResultsCmd() *cobra.Command { + c := cfgmanager.Config() + var configFile string + + cmd := &cobra.Command{ + Use: "describe", + Short: "Describe all validation results in a Kubernetes cluster", + Long: `Describe all validation results in a Kubernetes cluster + +Validation results in the cluster specified by the KUBECONFIG environment variable will be described. +If the --config-file flag is specified, the KUBECONFIG specified in the validator configuration file will be used instead. +`, + Args: cobra.NoArgs, + SilenceUsage: false, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return common.InitWorkspace(c, cfg.Validator, cfg.ValidatorSubdirs, true) + }, + RunE: func(cmd *cobra.Command, args []string) error { + taskConfig := cfg.NewTaskConfig( + Version, configFile, false, false, false, false, false, + ) + if err := validator.DescribeValidationResultsCommand(taskConfig); err != nil { + return fmt.Errorf("failed to describe validation results: %v", err) + } + return nil + }, + } + + flags := cmd.Flags() + flags.StringVarP(&configFile, "config-file", "f", "", "Validator configuration file to read kubeconfig from (optional)") + + return cmd +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 00000000..ae23ab6d --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// NewVersionCmd returns the cobra command that outputs the Validator CLI version +func NewVersionCmd() *cobra.Command { + return &cobra.Command{ + Use: "version", + Args: cobra.NoArgs, + Short: "Prints the Validator CLI version", + Run: func(cobraCmd *cobra.Command, args []string) { + fmt.Printf("Validator CLI version: %s\n", Version) + }, + } +} diff --git a/go.mod b/go.mod index 7e9bc68b..bf983ab7 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,100 @@ module github.com/validator-labs/validatorctl go 1.22.3 + +require ( + emperror.dev/errors v0.8.1 + github.com/Masterminds/sprig/v3 v3.2.3 + github.com/aws/aws-sdk-go v1.53.16 + github.com/fsnotify/fsnotify v1.7.0 + github.com/pkg/errors v0.9.1 + github.com/pterm/pterm v0.12.79 + github.com/sirupsen/logrus v1.9.3 + github.com/spectrocloud-labs/prompts-tui v0.0.0-20240530192817-99e039fe7e8b + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.19.0 + github.com/validator-labs/validator v0.0.41 + github.com/validator-labs/validator-plugin-aws v0.0.26 + github.com/validator-labs/validator-plugin-azure v0.0.11 + github.com/validator-labs/validator-plugin-network v0.0.16 + github.com/validator-labs/validator-plugin-oci v0.0.10 + github.com/validator-labs/validator-plugin-vsphere v0.0.22 + golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 + gopkg.in/yaml.v2 v2.4.0 + k8s.io/api v0.30.1 + k8s.io/apimachinery v0.30.1 + k8s.io/client-go v0.30.1 +) + +require ( + atomicgo.dev/cursor v0.2.0 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect + atomicgo.dev/schedule v0.1.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/swag v0.22.9 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gookit/color v1.5.4 // indirect + github.com/hashicorp/hcl v1.0.1-vault-5 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.30.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect + sigs.k8s.io/cluster-api v1.7.2 // indirect + sigs.k8s.io/controller-runtime v0.18.3 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..49d2b3e6 --- /dev/null +++ b/go.sum @@ -0,0 +1,358 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= +emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0= +emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/aws/aws-sdk-go v1.53.16 h1:8oZjKQO/ml1WLUZw5hvF7pvYjPf8o9f57Wldoy/q9Qc= +github.com/aws/aws-sdk-go v1.53.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= +github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= +github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= +github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= +github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.79 h1:lH3yrYMhdpeqX9y5Ep1u7DejyHy7NSQg9qrBjF9dFT4= +github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +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/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +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/spectrocloud-labs/prompts-tui v0.0.0-20240530192817-99e039fe7e8b h1:Y4JyFXAvR9UH0UMWKDGNBrJp9Qv3HIWXbWdyQSfN+Ng= +github.com/spectrocloud-labs/prompts-tui v0.0.0-20240530192817-99e039fe7e8b/go.mod h1:XCvyEc3OLxKVXNLbOGZJOR6PiktfWqjYdrwU+ymCmLQ= +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 v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/validator-labs/validator v0.0.41 h1:8k37U5LvNXbE2/hw/v/LfOruUquG24s7nc0COMkqhQ0= +github.com/validator-labs/validator v0.0.41/go.mod h1:jROnkakouWYaPxvS3zjPEbyCrhhlbN5g29jWuG9TmYM= +github.com/validator-labs/validator-plugin-aws v0.0.26 h1:9Hy5Mf7tmIKfoYBC7J2a9b2LvJyjDKSwBGYG0O4bBik= +github.com/validator-labs/validator-plugin-aws v0.0.26/go.mod h1:UwA3e1lKOY58bsSy/W57aRw6e+AKmULWtDS5KnfjPZU= +github.com/validator-labs/validator-plugin-azure v0.0.11 h1:Yl1y0N1rVKPcMHkhdhNPPoRe4sARYNVdOWh7av0lcRY= +github.com/validator-labs/validator-plugin-azure v0.0.11/go.mod h1:0AxIXWZF+8SHHefkeb8nyA96OXcW+kYvK9kaGRQ1CY8= +github.com/validator-labs/validator-plugin-network v0.0.16 h1:W+ZKmAXAR4BmAv7KoRH8tUXNbpEWMh+BIw+LRdr8V1o= +github.com/validator-labs/validator-plugin-network v0.0.16/go.mod h1:LEd+5Pfdh8uCXi2N8L2vOdVtxxm2rtHriB1egwYFmHg= +github.com/validator-labs/validator-plugin-oci v0.0.10 h1:Qd6zgS9gN/RjZ+EV3z8LhCBUinhe02w5G1IuHR4u6f8= +github.com/validator-labs/validator-plugin-oci v0.0.10/go.mod h1:QAFcEmXDGKId3gsbC3C2Kg4qWW71FLimi9VeGQtEp3Y= +github.com/validator-labs/validator-plugin-vsphere v0.0.22 h1:W6sXfe+kT3/YgES+xwZgdmuo4BQZnGc4ls/XXm9ggjs= +github.com/validator-labs/validator-plugin-vsphere v0.0.22/go.mod h1:aB7cpQZFQdjrKpk8LrZQckv1gO81xUqyMsDpebBA3yg= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= +k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= +k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= +k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= +k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= +k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/cluster-api v1.7.2 h1:bRE8zoao7ajuLC0HijqfZVcubKQCPlZ04HMgcA53FGE= +sigs.k8s.io/cluster-api v1.7.2/go.mod h1:V9ZhKLvQtsDODwjXOKgbitjyCmC71yMBwDcMyNNIov0= +sigs.k8s.io/controller-runtime v0.18.3 h1:B5Wmmo8WMWK7izei+2LlXLVDGzMwAHBNLX68lwtlSR4= +sigs.k8s.io/controller-runtime v0.18.3/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/cmd/common/common.go b/pkg/cmd/common/common.go new file mode 100644 index 00000000..29a84b98 --- /dev/null +++ b/pkg/cmd/common/common.go @@ -0,0 +1,24 @@ +package common + +import ( + "fmt" + + cfg "github.com/validator-labs/validatorctl/pkg/config" + log "github.com/validator-labs/validatorctl/pkg/logging" + embed_utils "github.com/validator-labs/validatorctl/pkg/utils/embed" +) + +func InitWorkspace(c *cfg.Config, workspaceDir string, subdirs []string, timestamped bool) error { + // Create workspace + if err := c.CreateWorkspace(workspaceDir, subdirs, timestamped); err != nil { + return fmt.Errorf("failed to initialize workspace: %v", err) + } + + // Unpack binaries + embed_utils.InitBinaries(c) + + // Initialize logger + log.SetOutput(c.RunLoc) + + return nil +} diff --git a/pkg/cmd/validator/validator.go b/pkg/cmd/validator/validator.go new file mode 100644 index 00000000..32aaeccc --- /dev/null +++ b/pkg/cmd/validator/validator.go @@ -0,0 +1,644 @@ +package validator + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/pkg/errors" + "gopkg.in/yaml.v2" + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + vapi "github.com/validator-labs/validator/api/v1alpha1" + "github.com/validator-labs/validator/pkg/helm" + + //"github.com/spectrocloud/palette-cli/models" + "github.com/validator-labs/validatorctl/pkg/components" + //"github.com/spectrocloud/palette-cli/pkg/components/cluster" + //"github.com/spectrocloud/palette-cli/pkg/config" + cfg "github.com/validator-labs/validatorctl/pkg/config" + log "github.com/validator-labs/validatorctl/pkg/logging" + //"github.com/spectrocloud/palette-cli/pkg/repo" + //"github.com/spectrocloud/palette-cli/pkg/services/clouds" + //"github.com/validator-labs/validatorctl/pkg/services/validator" + embed "github.com/validator-labs/validatorctl/pkg/utils/embed" + "github.com/validator-labs/validatorctl/pkg/utils/kind" + "github.com/validator-labs/validatorctl/pkg/utils/kube" + //net_utils "github.com/spectrocloud/palette-cli/pkg/utils/network" + //"github.com/spectrocloud/palette/api/v1alpha1" +) + +var ( + ErrNilValidationResult = errors.New("validation result is nil") +) + +func DeployValidatorCommand(c *cfg.Config, tc *cfg.TaskConfig, reconfigure bool) error { + /* + var vc *components.ValidatorConfig + var err error + var saveConfig bool + + if tc.ConfigFile == "" && reconfigure { + log.FatalCLI("Cannot reconfigure validator without providing a configuration file.") + } + + if tc.ConfigFile != "" && !reconfigure { + // Silent Mode + vc, err = components.NewValidatorFromConfig(tc) + if err != nil { + return errors.Wrap(err, "failed to load validator configuration file") + } + if tc.UpdatePasswords { + log.Header("Updating credentials in validator configuration file") + if err := validator.UpdateValidatorCredentials(vc); err != nil { + return err + } + saveConfig = true + } + if vc.Kubeconfig == "" { + vc.Kubeconfig = filepath.Join(c.RunLoc, "kind-cluster.kubeconfig") + saveConfig = true + } + } else { + // Interactive mode + if reconfigure { + vc, err = components.NewValidatorFromConfig(tc) + if err != nil { + return errors.Wrap(err, "failed to load validator configuration file") + } + } else { + vc = components.NewValidatorConfig() + } + + // for dev build versions, we allow selection of specific validator and plugin versions + // for all other builds, we set a fixed version for the validator and plugins + vc.UseFixedVersions = !strings.HasSuffix(tc.CliVersion, "-dev") + if err := validator.ReadValidatorConfig(c, tc, vc); err != nil { + return errors.Wrap(err, "failed to create new validator configuration") + } + tc.ConfigFile = filepath.Join(c.RunLoc, cfg.ValidatorConfigFile) + saveConfig = true + } + + // save / print validator config file + if saveConfig { + if err := components.SaveValidatorConfig(vc, tc); err != nil { + return err + } + } else { + log.InfoCLI("validator configuration file: %s", tc.ConfigFile) + } + + if tc.CreateConfigOnly || tc.UpdatePasswords { + return nil + } + + if vc.UseKindCluster { + if err := createKindCluster(c, vc); err != nil { + return err + } + } + + return applyValidatorAndPlugins(c, vc) + */ + return nil +} + +func UpgradeValidatorCommand(c *cfg.Config, taskConfig *cfg.TaskConfig) error { + /* + vc, err := components.NewValidatorFromConfig(taskConfig) + if err != nil { + return errors.Wrap(err, "failed to load validator configuration file") + } + if vc.Kubeconfig == "" { + return errors.New("invalid validator configuration: kubeconfig is required") + } + return applyValidatorAndPlugins(c, vc) + */ + return nil +} + +func UndeployValidatorCommand(taskConfig *cfg.TaskConfig, deleteCluster bool) error { + /* + vc, err := components.NewValidatorFromConfig(taskConfig) + if err != nil { + return errors.Wrap(err, "failed to load validator configuration file") + } + + log.Header("Uninstalling validator") + helmClient, err := getHelmClient(vc) + if err != nil { + return err + } + if err := helmClient.Delete(cfg.Validator, cfg.Validator); err != nil { + return errors.Wrap(err, "failed to delete validator Helm release") + } + log.InfoCLI("\nUninstalled validator and validator plugin(s) successfully") + + if vc.UseKindCluster && deleteCluster { + return kind.DeleteCluster(cfg.ValidatorKindClusterName) + } + + return nil + */ + return nil +} + +/* +func loadVspherePrivileges(version string) ([]string, error) { + var rolePrivilegeVersion string + switch { + case strings.HasPrefix(version, "6.7"): + rolePrivilegeVersion = cfg.SpectroRootLevelPrivilegesV6_7 + case strings.HasPrefix(version, "7.0"): + rolePrivilegeVersion = cfg.SpectroRootLevelPrivilegesV7_0 + case strings.HasPrefix(version, "8.0"): + rolePrivilegeVersion = cfg.SpectroRootLevelPrivilegesV8_0 + default: + return nil, errors.Errorf("Unsupported vSphere version %s", version) + } + + return validator.LoadPrivileges(cfg.ValidatorPluginVsphereRolePrivilegeFiles[rolePrivilegeVersion]) +} +*/ + +func DescribeValidationResultsCommand(taskConfig *cfg.TaskConfig) error { + /* + kClient, err := getValidationResultsCRDClient(taskConfig) + if err != nil { + return errors.Wrap(err, "failed to get validation result client") + } + + vrs, err := kClient.List(context.Background(), metav1.ListOptions{}) + if err != nil { + return errors.Wrap(err, "failed to list validation results") + } + + if err := printValidationResults(vrs.Items); err != nil { + return err + } + + return nil + */ + return nil +} + +func buildValidationResultString(vrObj unstructured.Unstructured) (string, error) { + vr := &vapi.ValidationResult{} + bytes, err := vrObj.MarshalJSON() + if err != nil { + return "", err + } + if err := json.Unmarshal(bytes, vr); err != nil { + return "", err + } + + sb := &strings.Builder{} + sb.WriteString("\n=================\nValidation Result\n=================\n") + keys := []string{"Plugin", "Name", "Namespace", "State"} + vals := []string{vr.Spec.Plugin, vr.Name, vr.Namespace, string(vr.Status.State)} + + for _, c := range vr.Status.Conditions { + if c.Type == vapi.SinkEmission { + keys = append(keys, "Sink State") + vals = append(vals, string(c.Reason)) + break + } + } + + args := map[string]interface{}{ + "Keys": keys, + "Values": vals, + } + + if err := embed.PrintTableTemplate(sb, args, cfg.Validator, "validation-result.tmpl"); err != nil { + return "", err + } + + sb.WriteString("\n------------\nRule Results\n------------\n") + for _, c := range vr.Status.ValidationConditions { + args := map[string]interface{}{ + "Keys": []string{"Validation Rule", "Validation Type", "Status", "Last Validated", "Message"}, + "Values": []string{c.ValidationRule, c.ValidationType, string(c.Status), c.LastValidationTime.Format(time.RFC3339), strings.TrimSpace(c.Message)}, + } + + if err := embed.PrintTableTemplate(sb, args, cfg.Validator, "validation-result.tmpl"); err != nil { + return "", err + } + + for i, d := range c.Details { + if i == 0 { + sb.WriteString("\n-------\nDetails\n-------\n") + } + sb.WriteString(fmt.Sprintf("- %s\n", d)) + } + for i, f := range c.Failures { + if i == 0 { + sb.WriteString("\n--------\nFailures\n--------\n") + } + sb.WriteString(fmt.Sprintf("- %s\n", f)) + } + } + return sb.String(), nil +} + +// applyValidatorAndPlugins installs/upgrades validator + plugin(s), then applies/updates validator CRs for each plugin +func applyValidatorAndPlugins(c *cfg.Config, vc *components.ValidatorConfig) error { + log.Header("Installing/Upgrading validator and validator plugin(s)") + + if err := applyValidator(c, vc); err != nil { + return err + } + log.InfoCLI("\nvalidator and validator plugin(s) installed successfully") + + if err := applyPlugins(c, vc); err != nil { + return err + } + log.Header("validator plugin(s) installed successfully") + log.InfoCLI("\nPlugins will now execute validation checks.") + + log.InfoCLI("\nYou can list validation results via the following command:") + log.InfoCLI("kubectl -n validator get validationresults --kubeconfig %s", vc.Kubeconfig) + + log.InfoCLI("\nAnd you can view all validation result details via the following command:") + log.InfoCLI("kubectl -n validator describe validationresults --kubeconfig %s", vc.Kubeconfig) + return nil +} + +func createReleaseSecretCmd(secret *components.Secret) []string { + args := []string{ + "create", "secret", "generic", secret.Name, "-n", "validator", + // include empty username/password, even if unset, to avoid error in validator + fmt.Sprintf("--from-literal=username=%s", secret.Username), + fmt.Sprintf("--from-literal=password=%s", secret.Password), + } + if secret.CaCertFile != "" { + args = append(args, fmt.Sprintf("--from-file=caCert=%s", secret.CaCertFile)) + } + return args +} + +func applyValidator(c *cfg.Config, vc *components.ValidatorConfig) error { + kubecommands, kubecommandsPre := [][]string{}, [][]string{} + kClient, err := kube.GetKubeClientset(vc.Kubeconfig) + if err != nil { + return err + } + + // build validator plugin spec + validatorSpec := vapi.ValidatorConfigSpec{ + Plugins: make([]vapi.HelmRelease, 0), + } + + if vc.AWSPlugin.Enabled { + args := map[string]interface{}{ + "Config": vc.AWSPlugin, + "ImageRegistry": vc.ImageRegistry, + } + values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-aws-values.tmpl") + if err != nil { + return errors.Wrap(err, "failed to render validator plugin aws values.yaml") + } + validatorSpec.Plugins = append(validatorSpec.Plugins, vapi.HelmRelease{ + Chart: vc.AWSPlugin.Release.Chart, + Values: string(values), + }) + if vc.AWSPlugin.ReleaseSecret != nil && vc.AWSPlugin.ReleaseSecret.ShouldCreate() { + kubecommandsPre = append(kubecommandsPre, createReleaseSecretCmd(vc.AWSPlugin.ReleaseSecret)) + } + kubecommands = append(kubecommands, cfg.ValidatorPluginAwsWaitCmd) + } + + if vc.AzurePlugin.Enabled { + args := map[string]interface{}{ + "Config": vc.AzurePlugin, + "ImageRegistry": vc.ImageRegistry, + } + values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-azure-values.tmpl") + if err != nil { + return errors.Wrap(err, "failed to render validator plugin azure values.yaml") + } + validatorSpec.Plugins = append(validatorSpec.Plugins, vapi.HelmRelease{ + Chart: vc.AzurePlugin.Release.Chart, + Values: string(values), + }) + if vc.AzurePlugin.ReleaseSecret != nil && vc.AzurePlugin.ReleaseSecret.ShouldCreate() { + kubecommandsPre = append(kubecommandsPre, createReleaseSecretCmd(vc.AzurePlugin.ReleaseSecret)) + } + kubecommands = append(kubecommands, cfg.ValidatorPluginAzureWaitCmd) + } + + if vc.NetworkPlugin.Enabled { + args := map[string]interface{}{ + "Tag": vc.NetworkPlugin.Release.Chart.Version, + "ImageRegistry": vc.ImageRegistry, + } + values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-network-values.tmpl") + if err != nil { + return errors.Wrap(err, "failed to render validator plugin network values.yaml") + } + validatorSpec.Plugins = append(validatorSpec.Plugins, vapi.HelmRelease{ + Chart: vc.NetworkPlugin.Release.Chart, + Values: string(values), + }) + if vc.NetworkPlugin.ReleaseSecret != nil && vc.NetworkPlugin.ReleaseSecret.ShouldCreate() { + kubecommandsPre = append(kubecommandsPre, createReleaseSecretCmd(vc.NetworkPlugin.ReleaseSecret)) + } + kubecommands = append(kubecommands, cfg.ValidatorPluginNetworkWaitCmd) + } + + if vc.OCIPlugin.Enabled { + args := map[string]interface{}{ + "Config": vc.OCIPlugin, + "ImageRegistry": vc.ImageRegistry, + } + values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-oci-values.tmpl") + if err != nil { + return errors.Wrap(err, "failed to render validator plugin oci values.yaml") + } + validatorSpec.Plugins = append(validatorSpec.Plugins, vapi.HelmRelease{ + Chart: vc.OCIPlugin.Release.Chart, + Values: string(values), + }) + if vc.OCIPlugin.ReleaseSecret != nil && vc.OCIPlugin.ReleaseSecret.ShouldCreate() { + kubecommandsPre = append(kubecommandsPre, createReleaseSecretCmd(vc.OCIPlugin.ReleaseSecret)) + } + kubecommands = append(kubecommands, cfg.ValidatorPluginOciWaitCmd) + } + + if vc.VspherePlugin.Enabled { + args := map[string]interface{}{ + "Config": vc.VspherePlugin, + "ImageRegistry": vc.ImageRegistry, + } + values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-vsphere-values.tmpl") + if err != nil { + return errors.Wrap(err, "failed to render validator plugin vsphere values.yaml") + } + validatorSpec.Plugins = append(validatorSpec.Plugins, vapi.HelmRelease{ + Chart: vc.VspherePlugin.Release.Chart, + Values: string(values), + }) + if vc.VspherePlugin.ReleaseSecret != nil && vc.VspherePlugin.ReleaseSecret.ShouldCreate() { + kubecommandsPre = append(kubecommandsPre, createReleaseSecretCmd(vc.VspherePlugin.ReleaseSecret)) + } + kubecommands = append(kubecommands, cfg.ValidatorPluginVsphereWaitCmd) + } + + if !vc.AnyPluginEnabled() { + log.FatalCLI("Invalid validator config: at least one plugin must be enabled!") + } + + // concatenate base validator values w/ plugin values + args := map[string]interface{}{ + "ImageRegistry": vc.ImageRegistry, + "Tag": vc.Release.Chart.Version, + "ProxyConfig": vc.ProxyConfig, + "SinkConfig": vc.SinkConfig, + "AWSPlugin": vc.AWSPlugin, + "VspherePlugin": vc.VspherePlugin, + "OCIPlugin": vc.OCIPlugin, + "AzurePlugin": vc.AzurePlugin, + } + if vc.ProxyConfig.Enabled { + args["ProxyCaCertData"] = strings.Split(vc.ProxyConfig.Env.ProxyCaCertData, "\n") + } + values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-base-values.tmpl") + if err != nil { + return errors.Wrap(err, "failed to render validator base values.yaml") + } + pluginValues, err := yaml.Marshal(validatorSpec) + if err != nil { + return errors.Wrap(err, "failed to marshal validator plugin YAML") + } + pluginValues = bytes.ReplaceAll(pluginValues, []byte("sink: null"), nil) + values = append(values, pluginValues...) + finalValues := string(values) + log.Debug("applying validator helm chart with values:") + log.Debug(finalValues) + + // install validator helm chart + + if len(kubecommandsPre) > 0 { + _, err := kClient.CoreV1().Namespaces().Get(context.Background(), cfg.Validator, metav1.GetOptions{}) + if err != nil && apierrs.IsNotFound(err) { + kubecommandsPre = append([][]string{{"create", "namespace", cfg.Validator}}, kubecommandsPre...) + } + for _, c := range kubecommandsPre { + if _, stderr, err := kube.KubectlCommand(c, vc.Kubeconfig); err != nil { + // ignore already exists errors when creating release secrets + if !strings.HasSuffix(strings.TrimSpace(stderr), "already exists") { + return errors.Wrap(err, stderr) + } + log.Debug(stderr) + } + } + } + + helmClient, err := getHelmClient(vc) + if err != nil { + return err + } + opts := helm.Options{ + Chart: vc.Release.Chart.Name, + Repo: vc.Release.Chart.Repository, + CaFile: vc.Release.Chart.CaFile, + InsecureSkipTlsVerify: vc.Release.Chart.InsecureSkipTlsVerify, + Version: vc.Release.Chart.Version, + Values: finalValues, + CreateNamespace: true, + } + if vc.ReleaseSecret != nil { + opts.Username = vc.ReleaseSecret.Username + opts.Password = vc.ReleaseSecret.Password + } + + var cleanupLocalChart bool + if strings.HasPrefix(opts.Repo, "oci://") { + log.InfoCLI("\n==== Pulling validator Helm chart from OCI repository %s ====", opts.Repo) + + opts.Untar = true + opts.UntarDir = c.RunLoc + opts.Version = strings.TrimPrefix(opts.Version, "v") + + if err := helmClient.Pull(opts); err != nil { + return err + } + log.InfoCLI("Pulled plugin Helm chart %s from OCI repository", opts.Chart) + + opts.Path = fmt.Sprintf("%s/%s", c.RunLoc, opts.Chart) + opts.Chart = "" + cleanupLocalChart = true + log.InfoCLI("Reconfigured Helm options to deploy local chart") + } + + log.InfoCLI("\n==== Installing/upgrading validator Helm chart ====") + if err := helmClient.Upgrade(cfg.Validator, cfg.Validator, opts); err != nil { + return errors.Wrap(err, "failed to install validator helm chart") + } + if cleanupLocalChart { + if err := os.RemoveAll(opts.Path); err != nil { + return errors.Wrap(err, "failed to remove local chart directory") + } + log.InfoCLI("Cleaned up local chart directory: %s", opts.Path) + } + + // wait for validator to be ready + if _, stderr, err := kube.KubectlCommand(cfg.ValidatorWaitCmd, vc.Kubeconfig); err != nil { + return errors.Wrap(err, stderr) + } + log.InfoCLI("Pausing for 20s for validator to establish a lease & begin plugin installation") + time.Sleep(20 * time.Second) + + // wait for validator plugin(s) to be ready + for _, c := range kubecommands { + if _, stderr, err := kube.KubectlCommand(c, vc.Kubeconfig); err != nil { + return errors.Wrap(err, stderr) + } + } + + return nil +} + +// getHelmClient gets a helm client w/ a monkey-patched path to the embedded kind binary +func getHelmClient(vc *components.ValidatorConfig) (helm.HelmClient, error) { + apiCfg, err := kube.GetAPIConfig(vc.Kubeconfig) + if err != nil { + return nil, errors.Wrap(err, "failed to get API config from kubeconfig") + } + helm.CommandPath = embed.Helm + helmClient := helm.NewHelmClient(apiCfg) + return helmClient, nil +} + +func applyPlugins(c *cfg.Config, vc *components.ValidatorConfig) error { + if vc.AWSPlugin.Enabled { + log.InfoCLI("\n==== Applying AWS plugin validator(s) ====") + + // render & apply any AWS validator rules other than IAM + if vc.AWSPlugin.Validator.ResultCount() > 0 { + if err := createValidator( + vc.Kubeconfig, c.RunLoc, "rules", cfg.ValidatorPluginAwsTemplate, *vc.AWSPlugin.Validator, + ); err != nil { + return err + } + } + + // render & apply AWS IAM validator + if vc.AWSPlugin.IamCheck.Enabled { + template := cfg.ValidatorPluginAwsIamMap[vc.AWSPlugin.IamCheck.Type] + outputPath := strings.ReplaceAll(filepath.Join(c.RunLoc, "manifests", template), ".tmpl", ".yaml") + auth, err := yaml.Marshal(vc.AWSPlugin.Validator.Auth) + if err != nil { + return errors.Wrap(err, "failed to marshal AWS plugin auth") + } + args := map[string]interface{}{ + "Auth": indent(auth, 4), + "IamRoleName": vc.AWSPlugin.IamCheck.IamRoleName, + } + if err := embed.RenderTemplate(args, cfg.Validator, template, outputPath); err != nil { + return err + } + if err := applyValidatorManifest(vc.Kubeconfig, cfg.ValidatorPluginAws, outputPath); err != nil { + return err + } + } + } + + if vc.VspherePlugin.Enabled { + log.InfoCLI("\n==== Applying vSphere plugin validator(s) ====") + if err := createValidator( + vc.Kubeconfig, c.RunLoc, "rules", cfg.ValidatorPluginVsphereTemplate, *vc.VspherePlugin.Validator, + ); err != nil { + return err + } + } + + if vc.NetworkPlugin.Enabled { + log.InfoCLI("\n==== Applying Network plugin validator(s) ====") + if err := createValidator( + vc.Kubeconfig, c.RunLoc, "rules", cfg.ValidatorPluginNetworkTemplate, *vc.NetworkPlugin.Validator, + ); err != nil { + return err + } + } + + if vc.OCIPlugin.Enabled { + log.InfoCLI("\n==== Applying OCI plugin validator(s) ====") + if err := createValidator( + vc.Kubeconfig, c.RunLoc, "rules", cfg.ValidatorPluginOciTemplate, *vc.OCIPlugin.Validator, + ); err != nil { + return err + } + } + + if vc.AzurePlugin.Enabled { + log.InfoCLI("\n==== Applying Azure plugin validator(s) ====") + if err := createValidator( + vc.Kubeconfig, c.RunLoc, "rules", cfg.ValidatorPluginAzureTemplate, *vc.AzurePlugin.Validator, + ); err != nil { + return err + } + } + + return nil +} + +func createValidator(kubeconfig, runLoc, name, template string, validator interface{}) error { + spec, err := yaml.Marshal(validator) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("failed to marshal %s validator", name)) + } + args := map[string]interface{}{ + "Name": name, + "Namespace": cfg.Validator, + "Spec": indent(spec, 2), + } + path := filepath.Join(runLoc, "manifests", fmt.Sprintf("%s.yaml", name)) + if err := embed.RenderTemplate(args, cfg.Validator, template, path); err != nil { + return errors.Wrap(err, fmt.Sprintf("failed to render %s validator manifest", name)) + } + return applyValidatorManifest(kubeconfig, name, path) +} + +func indent(bs []byte, indent int) string { + b := bytes.Buffer{} + for _, l := range bytes.Split(bs, []byte("\n")) { + for i := 0; i < indent; i++ { + b.Write([]byte(" ")) + } + l = append(l, []byte("\n")...) + b.Write(l) + } + return b.String() +} + +func applyValidatorManifest(kubeconfig, name, path string) error { + cmd := []string{"apply", "-f", path} + if _, stderr, err := kube.KubectlCommand(cmd, kubeconfig); err != nil { + return errors.Wrap(err, fmt.Sprintf("failed to apply %s validator: %s", name, stderr)) + } + return nil +} + +func createKindCluster(c *cfg.Config, vc *components.ValidatorConfig) error { + clusterConfig := filepath.Join(c.RunLoc, "kind-cluster-config.yaml") + if err := kind.AdvancedConfig(vc.ProxyConfig.Env, clusterConfig); err != nil { + return err + } + if err := kind.StartCluster(cfg.ValidatorKindClusterName, clusterConfig, vc.Kubeconfig); err != nil { + return errors.Wrap(err, "failed to start validator kind cluster") + } + if err := os.Setenv("KUBECONFIG", vc.Kubeconfig); err != nil { + return errors.Wrap(err, "failed to set KUBECONFIG env var") + } + log.InfoCLI("\nCreated kind cluster. kubeconfig: %s", vc.Kubeconfig) + return nil +} diff --git a/pkg/components/validator.go b/pkg/components/validator.go new file mode 100644 index 00000000..6ba8565e --- /dev/null +++ b/pkg/components/validator.go @@ -0,0 +1,558 @@ +package components + +import ( + "os" + + "emperror.dev/errors" + "gopkg.in/yaml.v2" + + aws "github.com/validator-labs/validator-plugin-aws/api/v1alpha1" + azure "github.com/validator-labs/validator-plugin-azure/api/v1alpha1" + network "github.com/validator-labs/validator-plugin-network/api/v1alpha1" + oci "github.com/validator-labs/validator-plugin-oci/api/v1alpha1" + vsphere "github.com/validator-labs/validator-plugin-vsphere/api/v1alpha1" + validator "github.com/validator-labs/validator/api/v1alpha1" + + //"github.com/spectrocloud/palette-cli/models" + cfg "github.com/validator-labs/validatorctl/pkg/config" + log "github.com/validator-labs/validatorctl/pkg/logging" + "github.com/validator-labs/validatorctl/pkg/repo" + "github.com/validator-labs/validatorctl/pkg/utils/crypto" + models "github.com/validator-labs/validatorctl/pkg/utils/extra" + "github.com/validator-labs/validatorctl/pkg/utils/ptr" +) + +type ValidatorConfig struct { + Release *validator.HelmRelease `yaml:"helmRelease"` + ReleaseSecret *Secret `yaml:"helmReleaseSecret"` + UseKindCluster bool `yaml:"useKindCluster"` + Kubeconfig string `yaml:"kubeconfig"` + SinkConfig *SinkConfig `yaml:"sinkConfig"` + ScarProps *repo.ScarProps `yaml:"scarProps"` + ProxyConfig *ProxyConfig `yaml:"proxyConfig"` + ImageRegistry string `yaml:"imageRegistry"` + UseFixedVersions bool `yaml:"useFixedVersions"` + + AWSPlugin *AWSPluginConfig `yaml:"awsPlugin,omitempty"` + NetworkPlugin *NetworkPluginConfig `yaml:"networkPlugin,omitempty"` + OCIPlugin *OCIPluginConfig `yaml:"ociPlugin,omitempty"` + VspherePlugin *VspherePluginConfig `yaml:"vspherePlugin,omitempty"` + AzurePlugin *AzurePluginConfig `yaml:"azurePlugin,omitempty"` +} + +func NewValidatorConfig() *ValidatorConfig { + return &ValidatorConfig{ + // Base config + Release: &validator.HelmRelease{}, + ReleaseSecret: &Secret{}, + SinkConfig: &SinkConfig{}, + ProxyConfig: &ProxyConfig{ + Env: &models.V1Env{}, + }, + ScarProps: &repo.ScarProps{}, + // Plugin config + AWSPlugin: &AWSPluginConfig{ + Release: &validator.HelmRelease{}, + ReleaseSecret: &Secret{}, + IamCheck: &IamCheck{}, + Validator: &aws.AwsValidatorSpec{}, + }, + AzurePlugin: &AzurePluginConfig{ + Release: &validator.HelmRelease{}, + ReleaseSecret: &Secret{}, + Validator: &azure.AzureValidatorSpec{}, + RuleTypes: make(map[int]string), + PlacementTypes: make(map[int]string), + StaticDeploymentTypes: make(map[int]string), + StaticDeploymentValues: make(map[int]*AzureStaticDeploymentValues), + }, + NetworkPlugin: &NetworkPluginConfig{ + Release: &validator.HelmRelease{}, + ReleaseSecret: &Secret{}, + Validator: &network.NetworkValidatorSpec{}, + }, + OCIPlugin: &OCIPluginConfig{ + Release: &validator.HelmRelease{}, + ReleaseSecret: &Secret{}, + Validator: &oci.OciValidatorSpec{}, + CaCertPaths: make(map[int]string), + }, + VspherePlugin: &VspherePluginConfig{ + Release: &validator.HelmRelease{}, + ReleaseSecret: &Secret{}, + Validator: &vsphere.VsphereValidatorSpec{}, + Account: &models.V1VsphereCloudAccount{}, + }, + } +} + +func (c *ValidatorConfig) AnyPluginEnabled() bool { + return c.AWSPlugin.Enabled || c.NetworkPlugin.Enabled || c.VspherePlugin.Enabled || c.OCIPlugin.Enabled || c.AzurePlugin.Enabled +} + +func (c *ValidatorConfig) decrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt release secret configuration") + } + } + if err := c.SinkConfig.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt Sink configuration") + } + if err := c.ScarProps.Decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt SCAR properties") + } + + if c.AWSPlugin != nil { + if err := c.AWSPlugin.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt AWS plugin configuration") + } + } + if c.AzurePlugin != nil { + if err := c.AzurePlugin.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt Azure plugin configuration") + } + } + if c.NetworkPlugin != nil { + if err := c.NetworkPlugin.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt Network plugin configuration") + } + } + if c.OCIPlugin != nil { + if err := c.OCIPlugin.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt OCI plugin configuration") + } + } + if c.VspherePlugin != nil { + if err := c.VspherePlugin.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt vSphere plugin configuration") + } + } + + return nil +} + +func (c *ValidatorConfig) encrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt release secret configuration") + } + } + if err := c.SinkConfig.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt Sink configuration") + } + if err := c.ScarProps.Encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt SCAR properties") + } + + if c.AWSPlugin != nil { + if err := c.AWSPlugin.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt AWS plugin configuration") + } + } + if c.AzurePlugin != nil { + if err := c.AzurePlugin.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt Azure plugin configuration") + } + } + if c.NetworkPlugin != nil { + if err := c.NetworkPlugin.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt Network plugin configuration") + } + } + if c.OCIPlugin != nil { + if err := c.OCIPlugin.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt OCI plugin configuration") + } + } + if c.VspherePlugin != nil { + if err := c.VspherePlugin.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt vSphere plugin configuration") + } + } + + return nil +} + +type ProxyConfig struct { + Enabled bool `yaml:"enabled"` + Env *models.V1Env `yaml:"env"` +} + +type SinkConfig struct { + Enabled bool `yaml:"enabled"` + CreateSecret bool `yaml:"createSecret"` + SecretName string `yaml:"secretName"` + Type string `yaml:"type"` + Values map[string]string `yaml:"values"` +} + +func (c *SinkConfig) encrypt() error { + if c.Values == nil { + return nil + } + for k, v := range c.Values { + if v == "" { + continue + } + value, err := crypto.EncryptB64([]byte(v)) + if err != nil { + return errors.Wrapf(err, "failed to encrypt SinkConfig key %s", k) + } + c.Values[k] = value + } + return nil +} + +func (c *SinkConfig) decrypt() error { + if c.Values == nil { + return nil + } + for k := range c.Values { + if c.Values[k] == "" { + continue + } + bytes, err := crypto.DecryptB64(c.Values[k]) + if err != nil { + return errors.Wrapf(err, "failed to decrypt SinkConfig key %s", k) + } + c.Values[k] = string(*bytes) + } + return nil +} + +type IamCheck struct { + Enabled bool `yaml:"enabled"` + IamRoleName string `yaml:"iamRoleName"` + Type cfg.IamCheckType `yaml:"type"` +} + +type AWSPluginConfig struct { + Enabled bool `yaml:"enabled"` + Release *validator.HelmRelease `yaml:"helmRelease"` + ReleaseSecret *Secret `yaml:"helmReleaseSecret"` + AccessKeyId string `yaml:"accessKeyId,omitempty"` + SecretAccessKey string `yaml:"secretAccessKey,omitempty"` + SessionToken string `yaml:"sessionToken,omitempty"` + ServiceAccountName string `yaml:"serviceAccountName,omitempty"` + IamCheck *IamCheck `yaml:"iamCheck"` + Validator *aws.AwsValidatorSpec `yaml:"validator"` +} + +func (c *AWSPluginConfig) encrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt release secret configuration") + } + } + + accessKey, err := crypto.EncryptB64([]byte(c.AccessKeyId)) + if err != nil { + return errors.Wrap(err, "failed to encrypt access key id") + } + c.AccessKeyId = accessKey + + secretKey, err := crypto.EncryptB64([]byte(c.SecretAccessKey)) + if err != nil { + return errors.Wrap(err, "failed to encrypt secret access key") + } + c.SecretAccessKey = secretKey + + sessionToken, err := crypto.EncryptB64([]byte(c.SessionToken)) + if err != nil { + return errors.Wrap(err, "failed to encrypt session token") + } + c.SessionToken = sessionToken + + return nil +} + +func (c *AWSPluginConfig) decrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt release secret configuration") + } + } + + bytes, err := crypto.DecryptB64(c.AccessKeyId) + if err != nil { + return errors.Wrap(err, "failed to decrypt access key id") + } + c.AccessKeyId = string(*bytes) + + bytes, err = crypto.DecryptB64(c.SecretAccessKey) + if err != nil { + return errors.Wrap(err, "failed to decrypt secret access key") + } + c.SecretAccessKey = string(*bytes) + + bytes, err = crypto.DecryptB64(c.SessionToken) + if err != nil { + return errors.Wrap(err, "failed to decrypt session token") + } + c.SessionToken = string(*bytes) + + return nil +} + +type AzurePluginConfig struct { + Enabled bool `yaml:"enabled"` + Release *validator.HelmRelease `yaml:"helmRelease"` + ReleaseSecret *Secret `yaml:"helmReleaseSecret"` + ServiceAccountName string `yaml:"serviceAccountName,omitempty"` + TenantID string `yaml:"tenantId"` + ClientID string `yaml:"clientId"` + ClientSecret string `yaml:"clientSecret"` + RuleTypes map[int]string `yaml:"ruleTypes"` + PlacementTypes map[int]string `yaml:"placementTypes"` + StaticDeploymentTypes map[int]string `yaml:"staticDeploymentTypes"` + StaticDeploymentValues map[int]*AzureStaticDeploymentValues `yaml:"staticDeploymentValues"` + Validator *azure.AzureValidatorSpec `yaml:"validator"` +} + +func (c *AzurePluginConfig) encrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt release secret configuration") + } + } + + clientSecret, err := crypto.EncryptB64([]byte(c.ClientSecret)) + if err != nil { + return errors.Wrap(err, "failed to encrypt Azure Client Secret") + } + c.ClientSecret = clientSecret + + return nil +} + +func (c *AzurePluginConfig) decrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt release secret configuration") + } + } + + bytes, err := crypto.DecryptB64(c.ClientSecret) + if err != nil { + return errors.Wrap(err, "failed to decrypt Azure Client Secret") + } + c.ClientSecret = string(*bytes) + + return nil +} + +type AzureStaticDeploymentValues struct { + Subscription string `yaml:"subscriptionUuid"` + ResourceGroup string `yaml:"resourceGroupUuid"` + VirtualNetwork string `yaml:"virtualNetworkUuid"` + Subnet string `yaml:"subnetUuid"` + ComputeGallery string `yaml:"computeGalleryUuid"` +} + +type NetworkPluginConfig struct { + Enabled bool `yaml:"enabled"` + Release *validator.HelmRelease `yaml:"helmRelease"` + ReleaseSecret *Secret `yaml:"helmReleaseSecret"` + Validator *network.NetworkValidatorSpec `yaml:"validator"` +} + +func (c *NetworkPluginConfig) encrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt release secret configuration") + } + } + return nil +} + +func (c *NetworkPluginConfig) decrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt release secret configuration") + } + } + return nil +} + +type OCIPluginConfig struct { + Enabled bool `yaml:"enabled"` + Release *validator.HelmRelease `yaml:"helmRelease"` + ReleaseSecret *Secret `yaml:"helmReleaseSecret"` + Secrets []*Secret `yaml:"secrets,omitempty"` + PublicKeySecrets []*PublicKeySecret `yaml:"publicKeySecrets,omitempty"` + CaCertPaths map[int]string `yaml:"caCertPaths,omitempty"` + Validator *oci.OciValidatorSpec `yaml:"validator"` +} + +func (c *OCIPluginConfig) encrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt release secret configuration") + } + } + for _, s := range c.Secrets { + if s != nil { + if err := s.encrypt(); err != nil { + return err + } + } + } + return nil +} + +func (c *OCIPluginConfig) decrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt release secret configuration") + } + } + for _, s := range c.Secrets { + if s != nil { + if err := s.decrypt(); err != nil { + return err + } + } + } + return nil +} + +type VspherePluginConfig struct { + Enabled bool `yaml:"enabled"` + Release *validator.HelmRelease `yaml:"helmRelease"` + ReleaseSecret *Secret `yaml:"helmReleaseSecret"` + Account *models.V1VsphereCloudAccount `yaml:"account"` // TODO: add this + Validator *vsphere.VsphereValidatorSpec `yaml:"validator"` + VsphereEntityPrivilegeRules []VsphereEntityPrivilegeRule `yaml:"vsphereEntityPrivilegeRules"` + VsphereRolePrivilegeRules []VsphereRolePrivilegeRule `yaml:"vsphereRolePrivilegeRules"` + VsphereTagRules []VsphereTagRule `yaml:"vsphereTagRules"` +} + +func (c *VspherePluginConfig) encrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt release secret configuration") + } + } + if c.Account != nil && c.Account.Password != nil { + password, err := crypto.EncryptB64([]byte(*c.Account.Password)) + if err != nil { + return errors.Wrap(err, "failed to encrypt password") + } + c.Account.Password = &password + } + return nil +} + +func (c *VspherePluginConfig) decrypt() error { + if c.ReleaseSecret != nil { + if err := c.ReleaseSecret.decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt release secret configuration") + } + } + if c.Account != nil && c.Account.Password != nil { + bytes, err := crypto.DecryptB64(*c.Account.Password) + if err != nil { + return errors.Wrap(err, "failed to decrypt password") + } + c.Account.Password = ptr.StringPtr(string(*bytes)) + } + return nil +} + +type VsphereEntityPrivilegeRule struct { + vsphere.EntityPrivilegeValidationRule `yaml:",inline"` + ClusterScoped bool `yaml:"clusterScoped"` + RuleType string `yaml:"ruleType"` +} + +type VsphereRolePrivilegeRule struct { + vsphere.GenericRolePrivilegeValidationRule `yaml:",inline"` + Name string `yaml:"name"` +} + +type VsphereTagRule struct { + vsphere.TagValidationRule `yaml:",inline"` + RuleType string `yaml:"ruleType"` +} + +type PublicKeySecret struct { + Name string `yaml:"name"` + Keys []string `yaml:"keys"` +} + +type Secret struct { + Name string `yaml:"name"` + Username string `yaml:"username"` + Password string `yaml:"password"` + CaCertFile string `yaml:"caCertFile"` + Exists bool `yaml:"exists"` +} + +func (s *Secret) ShouldCreate() bool { + return !s.Exists && (s.Username != "" || s.Password != "" || s.CaCertFile != "") +} + +func (s *Secret) encrypt() error { + password, err := crypto.EncryptB64([]byte(s.Password)) + if err != nil { + return errors.Wrap(err, "failed to encrypt password") + } + s.Password = password + + return nil +} + +func (s *Secret) decrypt() error { + bytes, err := crypto.DecryptB64(s.Password) + if err != nil { + return errors.Wrap(err, "failed to decrypt password") + } + s.Password = string(*bytes) + + return nil +} + +// NewValidatorFromConfig loads a validator configuration file from disk and decrypts it +func NewValidatorFromConfig(taskConfig *cfg.TaskConfig) (*ValidatorConfig, error) { + c, err := LoadValidatorConfig(taskConfig) + if err != nil { + return nil, err + } + if err := c.decrypt(); err != nil { + return nil, err + } + return c, nil +} + +// LoadValidatorConfig loads a validator configuration file from disk +func LoadValidatorConfig(taskConfig *cfg.TaskConfig) (*ValidatorConfig, error) { + bytes, err := os.ReadFile(taskConfig.ConfigFile) + if err != nil { + return nil, errors.Wrap(err, "failed to read validator config file") + } + c := &ValidatorConfig{} + if err = yaml.Unmarshal(bytes, c); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal validator config") + } + return c, nil +} + +// SaveValidatorConfig saves a validator configuration file to disk +func SaveValidatorConfig(c *ValidatorConfig, taskConfig *cfg.TaskConfig) error { + if err := c.encrypt(); err != nil { + return err + } + b, err := yaml.Marshal(c) + if err != nil { + return errors.Wrap(err, "failed to marshal validator config") + } + if err := c.decrypt(); err != nil { + return err + } + if err = os.WriteFile(taskConfig.ConfigFile, b, 0600); err != nil { + return errors.Wrap(err, "failed to create validator config file") + } + log.InfoCLI("validator configuration file saved: %s", taskConfig.ConfigFile) + return nil +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 00000000..cb965906 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,154 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "emperror.dev/errors" + "github.com/spf13/viper" + "gopkg.in/yaml.v2" + //prompt_utils "github.com/spectrocloud-labs/prompts-tui/prompts" + //"github.com/validator-labs/validatorctl/pkg/utils/ptr" + //"github.com/spectrocloud/palette-cli/models" + //log "github.com/validator-labs/validatorctl/pkg/logging" + //"github.com/validator-labs/validatorctl/pkg/utils/crypto" + //string_utils "github.com/validator-labs/validatorctl/pkg/utils/string" +) + +func NewConfig() *Config { + return &Config{ + WorkspaceLoc: "", + } +} + +type Config struct { + RunLoc string `yaml:"runLoc"` + WorkspaceLoc string `yaml:"workspaceLoc"` +} + +type ImageRegistryType string + +const ( + ImageRegistryTypeDefault = "Default" + ImageRegistryTypeCustom = "Custom" +) + +type TaskConfig struct { + CliVersion string + ConfigFile string + CreateConfigOnly bool + Silent bool + UpdatePasswords bool + UpdateTokens bool + SkipTeardown bool +} + +func NewTaskConfig(cliVersion, configFile string, configOnly, silent, updatePasswords, updateTokens, skipTeardown bool) *TaskConfig { + return &TaskConfig{ + CliVersion: cliVersion, + ConfigFile: configFile, + CreateConfigOnly: configOnly, + Silent: silent, + UpdatePasswords: updatePasswords, + UpdateTokens: updateTokens, + SkipTeardown: skipTeardown, + } +} + +func DefaultWorkspaceLoc() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, WorkspaceLoc), nil +} + +func (c *Config) initWorkspaceLoc() (err error) { + if c.WorkspaceLoc == "" { + c.WorkspaceLoc, err = DefaultWorkspaceLoc() + } + if viper.ConfigFileUsed() == "" { + viper.SetConfigFile(filepath.Join(c.WorkspaceLoc, ConfigFile)) + } + return +} + +func (c *Config) CreateWorkspace(folder string, subdirs []string, timestamped bool) error { + // Derive base dir + if err := c.initWorkspaceLoc(); err != nil { + return err + } + c.RunLoc = c.WorkspaceLoc + + if timestamped { + t := time.Now() + c.RunLoc = filepath.Join(c.WorkspaceLoc, fmt.Sprintf("%s-%s", folder, t.Format(TimeFormat))) + } + + // Create subdirs + for _, s := range subdirs { + d := filepath.Join(c.RunLoc, s) + if _, err := os.Stat(d); os.IsNotExist(err) { + if err = os.MkdirAll(d, 0700); err != nil { + return err + } + } + } + + return nil +} + +// restoreGlobalDefaults resets ephemeral values in the global config before updating it on disk +func (c *Config) restoreGlobalDefaults() (err error) { + c.WorkspaceLoc, err = DefaultWorkspaceLoc() + return +} + +// TODO: check if these Encrypt and Decrypt functions are needed +func (c *Config) Decrypt() error { + return nil +} + +func (c *Config) Encrypt() error { + return nil +} + +func (c *Config) Save(path string) error { + if err := c.restoreGlobalDefaults(); err != nil { + return err + } + if err := c.Encrypt(); err != nil { + return errors.Wrap(err, "failed to encrypt config") + } + b, err := yaml.Marshal(c) + if err != nil { + return errors.Wrap(err, "failed to marshal config") + } + if err := c.Decrypt(); err != nil { + return errors.Wrap(err, "failed to decrypt config") + } + if path == "" { + path = viper.ConfigFileUsed() + } + if err = os.WriteFile(path, b, 0600); err != nil { + return errors.Wrap(err, "failed to create config file") + } + return nil +} + +func (c *Config) Load() error { + if err := viper.Unmarshal(&c); err != nil { + return err + } + return c.Decrypt() +} + +func (c *Config) Kubeconfig() string { + return filepath.Join(c.RunLoc, "kubeconfig") +} + +func (c *Config) ManifestDir() string { + return filepath.Join(c.RunLoc, "manifests") +} diff --git a/pkg/config/constants.go b/pkg/config/constants.go new file mode 100644 index 00000000..732fde2d --- /dev/null +++ b/pkg/config/constants.go @@ -0,0 +1,529 @@ +package config + +import ( + "os" + "slices" + + "github.com/spectrocloud-labs/prompts-tui/prompts" + + vtypes "github.com/validator-labs/validator/pkg/types" +) + +type MachineOptions struct { + CPUOpts []string + MemoryOpts []string + DiskOpts []string + Options []string + MongoPvc []string + MongoCpu []string + MongoMem []string + MinMongoPvc int + MinMongoCpu int + MinMongoMem int + MinCPU int + MinMemory int + MinDisk int +} + +type IamCheckType string + +const ( + ConfigFile = "validator.yaml" + TimeFormat = "20060102150405" + WorkspaceLoc = ".validator" + + ClusterConfigTemplate = "cluster-configuration.tmpl" + SSHKeyPrompt = "# Specify one or more SSH Public Keys below. Keys must be newline-separated. Type :wq to save and exit (if using vi).\n" + KindImage = "kindest/node" + KindImageInternalRepo = "spectro-images-public/kindest/node" + KindImageTag = "v1.27.11" + /* + CertManagerManifest = "cert-manager-v1.14.4.yaml" + CertManagerManifestTemplate = "cert-manager-v1.14.4.tmpl" + ReachManifestTemplate = "reach-v4.4.0.tmpl" + ReachManifest = "reach-v4.4.0.yaml" + */ + NoProxyPrompt = "# Default NO_PROXY values are on the lines below.\n# Edit as you see fit (comments are ignored). The file should contain a list of NO_PROXY values, newline separated.\n# Type :wq to save and exit (if using vi).\n\n" + /* + DefaultNoProxy = "127.0.0.1,192.168.0.0/16,10.0.0.0/16,10.96.0.0/12,169.254.169.254,169.254.0.0/24,localhost,kubernetes,kubernetes.default,kubernetes.default.svc,kubernetes.default.svc.cluster,kubernetes.default.svc.cluster.local,.svc,.svc.cluster,.svc.cluster.local,.svc.cluster.local,.company.local" + ProxyClusterPodPresetTemplate = "proxy-cluster-pod-preset.tmpl" + */ + RegistryMirrorPrompt = "# This editor has Registry Mirror configs auto-generated by the CLI for the provided endpoint & base content path.\n" + + "# This config will be used by Containerd for pulling images from the custom registry. You may replace or make additional changes to the auto-generated config.\n" + + "# Type :wq to save and exit (if using vi)\n" + RegistryMirrorSeparator = "::" + + // Validator constants + ValidatorConfigFile = "validator.yaml" + ValidatorKindClusterName = "validator-kind-cluster" + ValidatorHelmRepository = "https://validator-labs.github.io" + ValidatorImageRegistry = "quay.io/validator-labs" + + ValidatorPluginAws = "validator-plugin-aws" + ValidatorPluginAzure = "validator-plugin-azure" + ValidatorPluginNetwork = "validator-plugin-network" + ValidatorPluginOci = "validator-plugin-oci" + ValidatorPluginVsphere = "validator-plugin-vsphere" + + ValidatorPluginAwsTemplate = "validator-rules-aws.tmpl" + ValidatorPluginAzureTemplate = "validator-rules-azure.tmpl" + ValidatorPluginNetworkTemplate = "validator-rules-network.tmpl" + ValidatorPluginOciTemplate = "validator-rules-oci.tmpl" + ValidatorPluginVsphereTemplate = "validator-rules-vsphere.tmpl" + + IamCheckTypeBase IamCheckType = "Base" + IamCheckTypeEks IamCheckType = "EKS" + IamCheckTypeMinimalDynamic IamCheckType = "Minimal-Dynamic" + IamCheckTypeMinimalStatic IamCheckType = "Minimal-Static" + + ValidatorVsphereEntityDatacenter = "Datacenter" + ValidatorVsphereEntityCluster = "Cluster" + ValidatorVsphereEntityFolder = "Folder" + ValidatorVsphereEntityResourcePool = "Resource Pool" + ValidatorVsphereEntityHost = "ESXi Host" + ValidatorVsphereEntityVirtualMachine = "Virtual Machine" + ValidatorVsphereEntityVirtualApp = "Virtual App" + ValidatorVsphereVersionConstraint = ">= 6.0, < 9.0" + SpectroRootLevelPrivilegesV6_7 = "Spectro Root-level Role Privileges vSphere 6.7" + SpectroRootLevelPrivilegesV7_0 = "Spectro Root-level Role Privileges vSphere 7.0" + SpectroRootLevelPrivilegesV8_0 = "Spectro Root-level Role Privileges vSphere 8.0" + CustomPrivileges = "Custom Root-level Role Privileges vSphere" + SpectroEntityPrivileges = "Spectro Entity Privileges" + CustomEntityPrivileges = "Custom Entity Privileges" + SpectroCloudTags = "Spectro Cloud Tags" + CustomVsphereTags = "Custom vSphere Tags" + SpectroCloudTagsFile = "vsphere-spectro-cloud-tags.yaml" + SpectroEntityPrivilegesFile = "vsphere-spectro-entity-privileges.yaml" + + DefaultStorageClassAnnotation string = "storageclass.kubernetes.io/is-default-class" + + // Embed dirs + Kind string = "kind" + Validator string = "validator" + + // regex + DomainRegex = "([a-zA-Z0-9]{1,63}|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.[a-zA-Z0-9]{1,63}|\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]){0,10}\\.([a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,30}[a-zA-Z0-9]\\.[a-zA-Z]{2,})" + UsernameRegex = "[a-zA-Z0-9]+(?:\\.[a-zA-Z0-9]+)*(?:-[a-zA-Z0-9]+)*(?:_[a-zA-Z0-9]+)*" + VSphereUsernameRegex = "^" + UsernameRegex + "@" + DomainRegex + "$" + //PaletteResourceNameRegex = "[a-z][a-z0-9-]{1,31}[a-z0-9]" + CDIImageRegistryRegex = "^(docker|oci-archive):\\/\\/([a-z0-9.\\-\\/]+)([:]{0})$" + CPUReqRegex = "(^\\d+\\.?\\d*[M,G]Hz)" + MemoryReqRegex = "(^\\d+\\.?\\d*[M,G,T]i)" + DiskReqRegex = "(^\\d+\\.?\\d*[M,G,T]i)" + MaasApiRegex = `^https?://.*\/MAAS$` +) + +var ( + // Misc. + DefaultPodCIDR = "192.168.0.0/16" + DefaultServiceIPRange = "10.96.0.0/12" + HTTPSchemes = []string{"https://", "http://"} + + /* + // EC / PCG + AirGappedKeywords = []string{DefaultScarKeyword, DefaultHarborPsswdKeyword, DefaultHostNameKeyword} + ECMachineOptions = MachineOptions{ + CPUOpts: []string{"8", "16", "32", "Custom"}, + MemoryOpts: []string{"16384", "32768", "65536", "Custom"}, + DiskOpts: []string{"60", "100", "120", "Custom"}, + MongoPvc: []string{"20", "60", "80"}, + MongoCpu: []string{"2", "4", "6"}, + MongoMem: []string{"4", "8", "12"}, + MinCPU: 8, + MinMemory: 16384, + MinDisk: 60, + MinMongoPvc: 20, + MinMongoCpu: 2, + MinMongoMem: 4, + Options: []string{ + "S: 8 CPU, 16 GB memory, 60 GB storage, 20 GB database with 2 CPU limit and 4 GB memory limit", + "M: 16 CPU, 32 GB memory, 100 GB storage, 60 GB database with 4 CPU limit and 8 GB memory limit", + "L: 32 CPU, 64 GB memory, 120 GB storage, 80 GB database with 8 CPU limit and 16 GB memory limit", + "Custom", + }, + } + ECVertexMachineOptions = MachineOptions{ + CPUOpts: []string{"16", "32", "64", "Custom"}, + MemoryOpts: []string{"32768", "65536", "131072", "Custom"}, + DiskOpts: []string{"60", "100", "120", "Custom"}, + MongoPvc: []string{"20", "60", "80"}, + MongoCpu: []string{"4", "6", "8"}, + MongoMem: []string{"8", "12", "16"}, + MinCPU: 16, + MinMemory: 32768, + MinDisk: 60, + MinMongoPvc: 20, + MinMongoCpu: 4, + MinMongoMem: 8, + Options: []string{ + "S: 16 CPU, 32 GB memory, 60 GB storage, 20 GB database with 4 CPU limit and 8 GB memory limit", + "M: 32 CPU, 64 GB memory, 100 GB storage, 60 GB database with 6 CPU limit and 12 GB memory limit", + "L: 64 CPU, 128 GB memory, 120 GB storage, 80 GB database with 8 CPU limit and 16 GB memory limit", + "Custom", + }, + } + PCGMachineOptions = MachineOptions{ + CPUOpts: []string{"4", "8", "16", "Custom"}, + MemoryOpts: []string{"4096", "8192", "16384", "Custom"}, + DiskOpts: []string{"60", "100", "200", "Custom"}, + MinCPU: 4, + MinMemory: 4096, + MinDisk: 60, + Options: []string{ + "S: 4 CPU, 4 GB memory, 60 GB storage", + "M: 8 CPU, 8 GB memory, 100 GB storage", + "L: 16 CPU, 16 GB memory, 120 GB storage", + "Custom", + }, + } + */ + RegistryMirrors = []string{"docker.io", "gcr.io", "ghcr.io", "k8s.gcr.io", "registry.k8s.io", "quay.io", "*"} + /* + CertManagerWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/cert-manager", "-n", "cert-manager"} + CertManagerCaInjectorWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/cert-manager-cainjector", "-n", "cert-manager"} + CertManagerWebhookWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/cert-manager-webhook", "-n", "cert-manager"} + ReachWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/reach-controller-manager", "-n", "reach-system"} + + */ + // Command dirs + BaseDirs = []string{"bin", "logs"} + /* + InstallSubdirs = []string{"logs", "manifests", "pivot-manifests", "status"} + PDESubdirs = []string{"logs", "app-profiles"} + */ + ValidatorSubdirs = []string{"logs", "manifests"} + /* + VMSubdirs = []string{"logs", "vms", "migrations"} + + // PDE + AppDeploymentTypes = []string{ClusterGroup, VirtualCluster} + OperatorServiceTypes = []string{DatabaseService, MessagingService, ObjectStorageService, SecurityService} + OperatorServiceMap = map[string]string{ + DatabaseService: "db", + MessagingService: "message-broker", + ObjectStorageService: "storage", + SecurityService: "security", + } + TierTypes = []string{ + ContainerTier, + HelmTier, + ManifestTier, + OperatorTier, + } + + // Forklift + ForkliftManifestFiles = []string{ForkliftVcenterSourceManifest, ForkliftHostsManifest, + ForkliftNetworkmapManifest, ForkliftStoragemapManifest, + ForkliftMigrationPlanManifest, ForkliftMigrationManifest, + } + ForkliftInstallationManifestsOrder = []string{CertManagerManifest, ForkliftOLMCRDsManifest, ForkliftOLMManifest, + ForkliftOperatorManifest, ForkliftControllerManifest} + ForkliftReadinessChecksMapping = map[string][]string{ + CertManagerManifest: {"wait", "--for=condition=available", "--timeout=600s", "deployment/cert-manager", "-n", "cert-manager"}, + ForkliftOLMCRDsManifest: nil, + ForkliftOLMManifest: {"wait", "--for=condition=available", "--timeout=600s", "deployment/olm-operator", "-n", "olm"}, + ForkliftOperatorManifest: {"wait", "--for=condition=available", "--timeout=600s", "deployment/forklift-operator", "-n", "konveyor-forklift"}, + ForkliftControllerManifest: {"wait", "--for=condition=available", "--timeout=600s", "deployment/forklift-controller", "-n", "konveyor-forklift"}, + } + ForkliftReadinessInitialWaitSeconds = map[string]int{ + CertManagerManifest: 15, + ForkliftOLMCRDsManifest: 0, + ForkliftOLMManifest: 30, + ForkliftOperatorManifest: 90, + ForkliftControllerManifest: 60, + } + */ + + // Validator + PlacementTypeStatic = "Static" + PlacementTypeDynamic = "Dynamic" + PlacementTypes = []string{PlacementTypeStatic, PlacementTypeDynamic} + + ValidatorChartVersions = map[string]string{ + Validator: "v0.0.41", + ValidatorPluginAws: "v0.0.26", + ValidatorPluginAzure: "v0.0.11", + ValidatorPluginNetwork: "v0.0.16", + ValidatorPluginVsphere: "v0.0.22", + ValidatorPluginOci: "v0.0.10", + } + + ValidatorWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/validator-controller-manager", "-n", "validator"} + ValidatorPluginAwsWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/validator-plugin-aws-controller-manager", "-n", "validator"} + ValidatorPluginVsphereWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/validator-plugin-vsphere-controller-manager", "-n", "validator"} + ValidatorPluginNetworkWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/validator-plugin-network-controller-manager", "-n", "validator"} + ValidatorPluginOciWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/validator-plugin-oci-controller-manager", "-n", "validator"} + ValidatorPluginAzureWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/validator-plugin-azure-controller-manager", "-n", "validator"} + + ValidatorBasicAuthKeys = []string{"username", "password"} + ValidatorSinkKeys = map[vtypes.SinkType][]string{ + vtypes.SinkTypeAlertmanager: {"endpoint", "insecureSkipVerify", "username", "password", "caCert"}, + vtypes.SinkTypeSlack: {"apiToken", "channelId"}, + } + ValidatorPluginAwsKeys = []string{"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"} + ValidatorPluginAzureKeys = []string{"AZURE_TENANT_ID", "AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET"} + ValidatorPluginVsphereKeys = []string{"username", "password", "vcenterServer", "insecureSkipVerify"} + ValidatorPluginOciSigVerificationKeysRegex = ".pub$" + + ValidatorPluginAwsIamMap = map[IamCheckType]string{ + IamCheckTypeBase: "awsvalidator-iam-role-spectro-cloud-base.tmpl", + IamCheckTypeEks: "awsvalidator-iam-role-spectro-cloud-eks.tmpl", + IamCheckTypeMinimalDynamic: "awsvalidator-iam-role-spectro-cloud-minimal-dynamic.tmpl", + IamCheckTypeMinimalStatic: "awsvalidator-iam-role-spectro-cloud-minimal-static.tmpl", + } + ValidatorPluginAwsServiceQuotas = []prompts.ChoiceItem{ + { + ID: "ec2", + Name: "EC2-VPC Elastic IPs", + }, + { + ID: "ec2", + Name: "Public AMIs", + }, + { + ID: "elasticfilesystem", + Name: "File systems per account", + }, + { + ID: "elasticloadbalancing", + Name: "Application Load Balancers per Region", + }, + { + ID: "elasticloadbalancing", + Name: "Classic Load Balancers per Region", + }, + { + ID: "elasticloadbalancing", + Name: "Network Load Balancers per Region", + }, + { + ID: "vpc", + Name: "Internet gateways per Region", + }, + { + ID: "vpc", + Name: "Network interfaces per Region", + }, + { + ID: "vpc", + Name: "VPCs per Region", + }, + { + ID: "vpc", + Name: "Subnets per VPC", + }, + { + ID: "vpc", + Name: "NAT gateways per Availability Zone", + }, + } + + ValidatorPluginNetworkProxyProtocols = []prompts.ChoiceItem{ + { + ID: "4|1080", + Name: "SOCKS v.4", + }, + { + ID: "5|1080", + Name: "SOCKS v.5", + }, + { + ID: "connect|3128", + Name: "HTTPS proxy", + }, + } + + ValidatorPluginVsphereRolePrivilegeChoices = []string{ + SpectroRootLevelPrivilegesV6_7, + SpectroRootLevelPrivilegesV7_0, + SpectroRootLevelPrivilegesV8_0, + CustomPrivileges, + } + ValidatorPluginVsphereRolePrivilegeFiles = map[string]string{ + SpectroRootLevelPrivilegesV6_7: "vsphere-root-level-privileges-7.0.yaml", + SpectroRootLevelPrivilegesV7_0: "vsphere-root-level-privileges-7.0.yaml", + SpectroRootLevelPrivilegesV8_0: "vsphere-root-level-privileges-8.0.yaml", + CustomPrivileges: "vsphere-root-level-privileges-all.yaml", + } + ValidatorPluginVsphereEntityPrivilegeChoices = []string{ + SpectroEntityPrivileges, + CustomEntityPrivileges, + } + ValidatorPluginVsphereEntities = []string{ + ValidatorVsphereEntityCluster, + ValidatorVsphereEntityDatacenter, + ValidatorVsphereEntityHost, + ValidatorVsphereEntityFolder, + ValidatorVsphereEntityResourcePool, + ValidatorVsphereEntityVirtualApp, + ValidatorVsphereEntityVirtualMachine, + } + ValidatorPluginVsphereEntityMap = map[string]string{ + ValidatorVsphereEntityCluster: "cluster", + ValidatorVsphereEntityDatacenter: "datacenter", + ValidatorVsphereEntityHost: "host", + ValidatorVsphereEntityFolder: "folder", + ValidatorVsphereEntityResourcePool: "resourcepool", + ValidatorVsphereEntityVirtualApp: "vapp", + ValidatorVsphereEntityVirtualMachine: "vm", + } + ValidatorPluginVsphereDeploymentDestination = []string{ + ValidatorVsphereEntityCluster, + ValidatorVsphereEntityHost, + ValidatorVsphereEntityResourcePool, + } + ValidatorPluginVsphereTagChoices = []string{SpectroCloudTags, CustomVsphereTags} + + ValidatorAzurePluginStaticPlacementResourceGroupLevelActions = []string{ + "Microsoft.Compute/disks/delete", + "Microsoft.Compute/disks/read", + "Microsoft.Compute/disks/write", + "Microsoft.Compute/virtualMachines/delete", + "Microsoft.Compute/virtualMachines/extensions/delete", + "Microsoft.Compute/virtualMachines/extensions/read", + "Microsoft.Compute/virtualMachines/extensions/write", + "Microsoft.Compute/virtualMachines/read", + "Microsoft.Compute/virtualMachines/write", + "Microsoft.Network/loadBalancers/backendAddressPools/join/action", + "Microsoft.Network/loadBalancers/delete", + "Microsoft.Network/loadBalancers/inboundNatRules/delete", + "Microsoft.Network/loadBalancers/inboundNatRules/join/action", + "Microsoft.Network/loadBalancers/inboundNatRules/read", + "Microsoft.Network/loadBalancers/inboundNatRules/write", + "Microsoft.Network/loadBalancers/read", + "Microsoft.Network/loadBalancers/write", + "Microsoft.Network/networkInterfaces/delete", + "Microsoft.Network/networkInterfaces/join/action", + "Microsoft.Network/networkInterfaces/read", + "Microsoft.Network/networkInterfaces/write", + "Microsoft.Network/networkSecurityGroups/read", + "Microsoft.Network/networkSecurityGroups/securityRules/delete", + "Microsoft.Network/networkSecurityGroups/securityRules/read", + "Microsoft.Network/networkSecurityGroups/securityRules/write", + "Microsoft.Network/privateDnsZones/A/delete", + "Microsoft.Network/privateDnsZones/A/read", + "Microsoft.Network/privateDnsZones/A/write", + "Microsoft.Network/privateDnsZones/delete", + "Microsoft.Network/privateDnsZones/read", + "Microsoft.Network/privateDnsZones/virtualNetworkLinks/delete", + "Microsoft.Network/privateDnsZones/virtualNetworkLinks/read", + "Microsoft.Network/privateDnsZones/virtualNetworkLinks/write", + "Microsoft.Network/privateDnsZones/write", + "Microsoft.Network/publicIPAddresses/delete", + "Microsoft.Network/publicIPAddresses/join/action", + "Microsoft.Network/publicIPAddresses/read", + "Microsoft.Network/publicIPAddresses/write", + "Microsoft.Network/routeTables/delete", + "Microsoft.Network/routeTables/read", + "Microsoft.Network/routeTables/write", + "Microsoft.Network/virtualNetworks/join/action", + "Microsoft.Resources/subscriptions/resourceGroups/read", + } + ValidatorAzurePluginStaticPlacementVirtualNetworkLevelActions = []string{ + "Microsoft.Network/virtualNetworks/read", + } + ValidatorAzurePluginStaticPlacementSubnetLevelActions = []string{ + "Microsoft.Network/virtualNetworks/subnets/join/action", + "Microsoft.Network/virtualNetworks/subnets/read", + } + ValidatorAzurePluginStaticPlacementComputeGalleryLevelActions = []string{ + "Microsoft.Compute/galleries/images/read", + "Microsoft.Compute/galleries/images/versions/read", + } + ValidatorAzurePluginDynamicPlacementActions = []string{ + "Microsoft.Compute/disks/delete", + "Microsoft.Compute/disks/read", + "Microsoft.Compute/disks/write", + "Microsoft.Compute/virtualMachines/delete", + "Microsoft.Compute/virtualMachines/extensions/delete", + "Microsoft.Compute/virtualMachines/extensions/read", + "Microsoft.Compute/virtualMachines/extensions/write", + "Microsoft.Compute/virtualMachines/read", + "Microsoft.Compute/virtualMachines/write", + "Microsoft.Network/loadBalancers/backendAddressPools/join/action", + "Microsoft.Network/loadBalancers/delete", + "Microsoft.Network/loadBalancers/inboundNatRules/delete", + "Microsoft.Network/loadBalancers/inboundNatRules/join/action", + "Microsoft.Network/loadBalancers/inboundNatRules/read", + "Microsoft.Network/loadBalancers/inboundNatRules/write", + "Microsoft.Network/loadBalancers/read", + "Microsoft.Network/loadBalancers/write", + "Microsoft.Network/networkInterfaces/delete", + "Microsoft.Network/networkInterfaces/join/action", + "Microsoft.Network/networkInterfaces/read", + "Microsoft.Network/networkInterfaces/write", + "Microsoft.Network/networkSecurityGroups/read", + "Microsoft.Network/networkSecurityGroups/securityRules/delete", + "Microsoft.Network/networkSecurityGroups/securityRules/read", + "Microsoft.Network/networkSecurityGroups/securityRules/write", + "Microsoft.Network/publicIPAddresses/delete", + "Microsoft.Network/publicIPAddresses/join/action", + "Microsoft.Network/publicIPAddresses/read", + "Microsoft.Network/publicIPAddresses/write", + "Microsoft.Network/routeTables/delete", + "Microsoft.Network/routeTables/read", + "Microsoft.Network/routeTables/write", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Network/privateDnsZones/read", + "Microsoft.Network/privateDnsZones/write", + "Microsoft.Network/privateDnsZones/delete", + "Microsoft.Network/privateDnsZones/virtualNetworkLinks/read", + "Microsoft.Network/privateDnsZones/virtualNetworkLinks/write", + "Microsoft.Network/privateDnsZones/virtualNetworkLinks/delete", + "Microsoft.Network/virtualNetworks/join/action", + "Microsoft.Network/privateDnsZones/A/write", + "Microsoft.Network/privateDnsZones/A/read", + "Microsoft.Network/privateDnsZones/A/delete", + "Microsoft.Storage/storageAccounts/blobServices/containers/write", + "Microsoft.Storage/storageAccounts/blobServices/containers/read", + "Microsoft.Storage/storageAccounts/write", + "Microsoft.Storage/storageAccounts/read", + "Microsoft.Storage/storageAccounts/blobServices/listKeys/action", + "Microsoft.Network/virtualNetworks/write", + "Microsoft.Network/virtualNetworks/read", + "Microsoft.Network/virtualNetworks/delete", + "Microsoft.Network/virtualNetworks/virtualMachines/read", + "Microsoft.Network/virtualNetworks/virtualNetworkPeerings/read", + "Microsoft.Network/virtualNetworks/virtualNetworkPeerings/write", + "Microsoft.Network/virtualNetworks/virtualNetworkPeerings/delete", + "Microsoft.Network/virtualNetworks/peer/action", + "Microsoft.Network/virtualNetworks/join/action", + "Microsoft.Network/virtualNetworks/joinLoadBalancer/action", + "Microsoft.Network/virtualNetworks/subnets/write", + "Microsoft.Network/virtualNetworks/subnets/read", + "Microsoft.Network/virtualNetworks/subnets/delete", + "Microsoft.Network/virtualNetworks/subnets/virtualMachines/read", + "Microsoft.Network/virtualNetworks/subnets/join/action", + "Microsoft.Network/virtualNetworks/subnets/joinLoadBalancer/action", + "Microsoft.Compute/images/write", + "Microsoft.Compute/images/read", + "Microsoft.Compute/galleries/write", + "Microsoft.Compute/galleries/read", + "Microsoft.Compute/galleries/images/write", + "Microsoft.Compute/galleries/images/read", + "Microsoft.Compute/galleries/images/versions/read", + "Microsoft.Compute/galleries/images/versions/write", + } +) + +func ValidatorIamCheckTypes() []string { + checkTypes := make([]string, 0) + for k := range ValidatorPluginAwsIamMap { + checkTypes = append(checkTypes, string(k)) + } + slices.Sort(checkTypes) + return checkTypes +} + +// Dynamic CLI Defaults + +var GatewayIPDefault, NameServerDefault, NTPServerDefault, SearchDomainDefault string + +func init() { + if os.Getenv("SPECTRO_DEFAULTS") != "" { + GatewayIPDefault = "10.10.128.1" + NameServerDefault = "10.10.128.8" + SearchDomainDefault = "spectrocloud.dev" + NTPServerDefault = "8.8.8.8" + } +} diff --git a/pkg/config/manager/config_manager.go b/pkg/config/manager/config_manager.go new file mode 100644 index 00000000..6e03c07a --- /dev/null +++ b/pkg/config/manager/config_manager.go @@ -0,0 +1,27 @@ +package manager + +import ( + "github.com/validator-labs/validatorctl/pkg/cmd/common" + cfg "github.com/validator-labs/validatorctl/pkg/config" +) + +var c *cfg.Config + +func init() { + Reset() +} + +func Init() error { + if err := c.Load(); err != nil { + return err + } + return common.InitWorkspace(c, "", cfg.BaseDirs, false) +} + +func Config() *cfg.Config { + return c +} + +func Reset() { + c = cfg.NewConfig() +} diff --git a/pkg/logging/logger.go b/pkg/logging/logger.go new file mode 100644 index 00000000..038c2e1d --- /dev/null +++ b/pkg/logging/logger.go @@ -0,0 +1,159 @@ +package logging + +import ( + "fmt" + "io" + logging "log" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/pterm/pterm" + "github.com/sirupsen/logrus" +) + +// The global logger's standard methods (i.e., log.Infof, log.Debugf, etc.) +// write log entries to disk. +// +// file location: ~/.validator/logs/validator.log +// +// The log.InfoCLI method logs entries to the console. It is used to guide users +// through an interactive TUI experience. + +var ( + log *logrus.Logger + cliLog = pterm.DefaultLogger + logFile, statusFile string + Newline = true +) + +func init() { + log = &logrus.Logger{ + Out: io.Discard, + Formatter: &logrus.TextFormatter{ + FullTimestamp: true, + }, + } +} + +func SetLevel(logLevel string) { + level, err := logrus.ParseLevel(logLevel) + if err != nil { + logging.Fatalf("error setting log level: %v", err) + } + log.SetLevel(level) +} + +func GetLevel() logrus.Level { + return log.Level +} + +func SetOutput(runLoc string) { + logFile = filepath.Join(runLoc, "logs", "validator.log") + statusFile = filepath.Join(runLoc, "status", "status") + + f, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) //#nosec + if err != nil { + logging.Fatalf("error opening file: %v", err) + } + log.SetOutput(f) +} + +// logContext recovers the original caller context of each log message +func logContext() *logrus.Entry { + if pc, file, line, ok := runtime.Caller(2); ok { + file = file[strings.LastIndex(file, "/")+1:] + funcFull := runtime.FuncForPC(pc).Name() + funcName := funcFull[strings.LastIndex(funcFull, ".")+1:] + entry := log.WithField("src", fmt.Sprintf("%s:%s:%d", file, funcName, line)) + return entry + } + return nil +} + +// Debug ... +func Debug(format string, v ...interface{}) { + entry := logContext() + entry.Debugf(format, v...) +} + +// Info ... +func Info(format string, v ...interface{}) { + entry := logContext() + entry.Infof(format, v...) +} + +// Warn ... +func Warn(format string, v ...interface{}) { + entry := logContext() + entry.Warnf(format, v...) +} + +// Error ... +func Error(format string, v ...interface{}) { + entry := logContext() + entry.Errorf(format, v...) +} + +// FatalCLI prints a message to the terminal & exits +func FatalCLI(msg string, args ...any) { + entry := logContext() + ptermLog(cliLog.Fatal, entry, msg, args...) + entry.Fatal(msg, args) +} + +// InfoCLI prints an info message to the terminal & creates a log entry +func InfoCLI(format string, v ...interface{}) { + printToConsole(format, v...) + + entry := logContext() + entry.Infof(format, v...) +} + +// ErrorCLI prints an error message to the terminal & creates a log entry +func ErrorCLI(msg string, args ...any) { + entry := logContext() + ptermLog(cliLog.Error, entry, msg, args...) + entry.Info(msg, args) +} + +func ptermLog(f func(string, ...[]pterm.LoggerArgument), entry *logrus.Entry, msg string, args ...any) { + // pterm.Logger does not support an odd number of arguments + numArgs := len(args) + if numArgs%2 == 0 { + f(msg, cliLog.Args(args...)) + } else { + errMsg := "error: invalid (odd) number of arguments (%d) provided to ErrorCLI" + printToConsole(errMsg, numArgs) + entry.Fatalf(errMsg, numArgs) + } +} + +func printToConsole(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + fmt.Fprint(os.Stdout, strings.TrimSuffix(s, "\n")) + if Newline { + fmt.Fprintf(os.Stdout, "\n") + } +} + +func Header(s string) { + HeaderCustom(s, pterm.BgCyan, pterm.FgBlack) +} + +func HeaderCustom(s string, bgColor, textColor pterm.Color) { + fmt.Fprintf(os.Stdout, "\n") + pterm.DefaultHeader. + WithMargin(15). + WithBackgroundStyle(pterm.NewStyle(bgColor)). + WithTextStyle(pterm.NewStyle(textColor)). + WithFullWidth(true). + Println(s) + fmt.Fprintf(os.Stdout, "\n") +} + +// Out returns the io.Writer used to write messages to the console +func Out() *os.File { + return os.Stdout +} diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go new file mode 100644 index 00000000..f8b64307 --- /dev/null +++ b/pkg/repo/repo.go @@ -0,0 +1,617 @@ +package repo + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "strings" + + "github.com/spectrocloud-labs/prompts-tui/prompts" + + "emperror.dev/errors" + "github.com/pterm/pterm" + log "github.com/validator-labs/validatorctl/pkg/logging" + aws_utils "github.com/validator-labs/validatorctl/pkg/utils/aws" + "github.com/validator-labs/validatorctl/pkg/utils/crypto" + palette_utils "github.com/validator-labs/validatorctl/pkg/utils/extra" // TODO: fix this name +) + +// Scar Props +type ScarProps struct { + ImageRegistryType RegistryType `yaml:"imageRegistryType"` + PackRegistryType RegistryType `yaml:"packRegistryType"` + MongoDbProps MongoDbProps `yaml:"mongo"` + + // unmarshalled from /config/onprem-properties.yaml + Accounts Accounts `yaml:"accounts"` + Secrets Secrets `yaml:"secrets"` + OCIImageRegistry *OCIRegistry `yaml:"ociImageRegistry"` + OCIPackRegistry *OCIRegistry `yaml:"ociPackRegistry"` +} + +func (p *ScarProps) Decrypt() error { + if p == nil { + return nil + } + if p.OCIImageRegistry != nil { + if err := p.OCIImageRegistry.Decrypt(); err != nil { + return err + } + } + if p.OCIPackRegistry != nil { + if err := p.OCIPackRegistry.Decrypt(); err != nil { + return err + } + } + return nil +} + +func (p *ScarProps) Encrypt() error { + if p == nil { + return nil + } + if p.OCIImageRegistry != nil { + if err := p.OCIImageRegistry.Encrypt(); err != nil { + return err + } + } + if p.OCIPackRegistry != nil { + if err := p.OCIPackRegistry.Encrypt(); err != nil { + return err + } + } + return nil +} + +func (p *ScarProps) UpdateAuthTokens() error { + if p == nil { + return nil + } + if p.OCIPackRegistry != nil { + if err := p.OCIPackRegistry.UpdateAuthTokens(); err != nil { + return err + } + } + return nil +} + +func (p *ScarProps) UpdatePasswords() error { + if p == nil { + return nil + } + log.Header("OCI Configuration") + if p.OCIImageRegistry != nil { + if err := p.OCIImageRegistry.UpdatePasswords(); err != nil { + return err + } + } + if p.OCIPackRegistry != nil { + if err := p.OCIPackRegistry.UpdatePasswords(); err != nil { + return err + } + } + return nil +} + +func (p *ScarProps) DefaultRegistryMeta() (string, RegistryType, RegistryType) { + var endpoint string + imageRegistryType, packRegistryType := RegistryTypeSpectro, RegistryTypeSpectro + + if p.OCIPackRegistry != nil { + if p.OCIPackRegistry.OCIRegistryECR != nil && p.OCIPackRegistry.OCIRegistryECR.Endpoint != "" { + endpoint = p.OCIPackRegistry.OCIRegistryECR.Endpoint + packRegistryType = RegistryTypeOCIECR + } else if p.OCIPackRegistry.OCIRegistryBasic != nil && p.OCIPackRegistry.OCIRegistryBasic.Endpoint != "" { + endpoint = p.OCIPackRegistry.OCIRegistryBasic.Endpoint + packRegistryType = RegistryTypeOCI + } + } + if p.OCIImageRegistry != nil { + if p.OCIImageRegistry.OCIRegistryECR != nil && p.OCIImageRegistry.OCIRegistryECR.Endpoint != "" { + imageRegistryType = RegistryTypeOCIECR + } else if p.OCIPackRegistry.OCIRegistryBasic != nil && p.OCIImageRegistry.OCIRegistryBasic.Endpoint != "" { + imageRegistryType = RegistryTypeOCI + } + } + return endpoint, imageRegistryType, packRegistryType +} + +// UpdateAuthJson generates credentials for the packserver-credential secret +func (p *ScarProps) UpdateAuthJson() error { + if p.OCIImageRegistry != nil { + if err := p.OCIImageRegistry.UpdateAuthJsonByType(p.ImageRegistryType); err != nil { + return err + } + } + if p.OCIPackRegistry != nil { + if err := p.OCIPackRegistry.UpdateAuthJsonByType(p.PackRegistryType); err != nil { + return err + } + } + return nil +} + +func (p *ScarProps) ImageUrl(name, tag string) string { + url := fmt.Sprintf("%s/%s:%s", p.ociImageRegistryBasePath(), name, tag) + return strings.ReplaceAll(url, "//", "/") +} + +func (p *ScarProps) PackUrl(name, tag string) string { + url := fmt.Sprintf("%s/spectro-packs/archive/%s:%s", p.ociPackRegistryBasePath(), name, tag) + return strings.ReplaceAll(url, "//", "/") +} + +func (p *ScarProps) ociImageRegistryBasePath() string { + return fmt.Sprintf("%s/%s", + p.OCIImageRegistry.Endpoint(p.ImageRegistryType), + p.OCIImageRegistry.BaseContentPath(p.ImageRegistryType), + ) +} + +func (p *ScarProps) ociPackRegistryBasePath() string { + return fmt.Sprintf("%s/%s", + p.OCIPackRegistry.Endpoint(p.PackRegistryType), + p.OCIPackRegistry.BaseContentPath(p.PackRegistryType), + ) +} + +// Secrets +type Secrets struct { + ImagePull string `yaml:"imagePull"` +} + +// Registry +type Registry struct { + RegistryBase `yaml:",inline"` + Username string `yaml:"username"` + Password string `yaml:"password"` +} + +type RegistryType string + +const ( + RegistryTypeOCI RegistryType = "OCI" + RegistryTypeOCIECR RegistryType = "OCI ECR" + RegistryTypeSpectro RegistryType = "spectro" +) + +var OCIRegistryTypes = []string{ + string(RegistryTypeOCI), + string(RegistryTypeOCIECR), +} + +type OCIRegistry struct { + AuthJson string `yaml:"authJson"` + OCIRegistryBasic *OCIRegistryBasic `yaml:"ociRegistry,omitempty"` + OCIRegistryECR *OCIRegistryECR `yaml:"ociEcrRegistry,omitempty"` +} + +func (r *OCIRegistry) Decrypt() error { + bytes, err := crypto.DecryptB64(r.AuthJson) + if err != nil { + return err + } + r.AuthJson = string(*bytes) + + if r.OCIRegistryBasic != nil { + bytes, err := crypto.DecryptB64(r.OCIRegistryBasic.Password) + if err != nil { + return err + } + r.OCIRegistryBasic.Password = string(*bytes) + } + + if r.OCIRegistryECR != nil { + bytes, err := crypto.DecryptB64(r.OCIRegistryECR.AccessKey) + if err != nil { + return err + } + r.OCIRegistryECR.AccessKey = string(*bytes) + + bytes, err = crypto.DecryptB64(r.OCIRegistryECR.SecretKey) + if err != nil { + return err + } + r.OCIRegistryECR.SecretKey = string(*bytes) + } + + return nil +} + +func (r *OCIRegistry) Encrypt() error { + authJson, err := crypto.EncryptB64([]byte(r.AuthJson)) + if err != nil { + return err + } + r.AuthJson = authJson + + if r.OCIRegistryBasic != nil { + ociPassword, err := crypto.EncryptB64([]byte(r.OCIRegistryBasic.Password)) + if err != nil { + return err + } + r.OCIRegistryBasic.Password = ociPassword + } + + if r.OCIRegistryECR != nil { + accessKey, err := crypto.EncryptB64([]byte(r.OCIRegistryECR.AccessKey)) + if err != nil { + return err + } + r.OCIRegistryECR.AccessKey = accessKey + + secretKey, err := crypto.EncryptB64([]byte(r.OCIRegistryECR.SecretKey)) + if err != nil { + return err + } + r.OCIRegistryECR.SecretKey = secretKey + } + + return nil +} + +func (r *OCIRegistry) UpdateAuthJsonByType(registryType RegistryType) error { + switch registryType { + case RegistryTypeOCI: + auth, err := r.OCIRegistryBasic.Auth() + if err != nil { + return err + } + return r.UpdateAuthJson(r.OCIRegistryBasic.Endpoint, auth) + case RegistryTypeOCIECR: + auth, err := r.OCIRegistryECR.Auth() + if err != nil { + return err + } + return r.UpdateAuthJson(r.OCIRegistryECR.Endpoint, auth) + } + return nil +} + +func (r *OCIRegistry) UpdateAuthJson(endpoint string, auth *palette_utils.Auth) error { + registryCreds := palette_utils.RegistryAuthMap{endpoint: *auth} + registryCredsJson, err := json.Marshal(registryCreds) + if err != nil { + return err + } + r.AuthJson = base64.StdEncoding.EncodeToString(registryCredsJson) + return nil +} + +func (r *OCIRegistry) UpdateAuthTokens() error { + if r.OCIRegistryECR != nil { + auth, err := r.OCIRegistryECR.Auth() + if err != nil { + return err + } + if err := r.UpdateAuthJson(r.OCIRegistryECR.Endpoint, auth); err != nil { + return err + } + } + return nil +} + +func (r *OCIRegistry) UpdatePasswords() error { + auth, err := r.decodeAuth() + if err != nil { + return err + } + if r.OCIRegistryBasic != nil { + if err := r.OCIRegistryBasic.ReadCredentials(auth); err != nil { + return err + } + if err := r.UpdateAuthJson(r.OCIRegistryBasic.Endpoint, auth); err != nil { + return err + } + } else if r.OCIRegistryECR != nil { + if err := r.OCIRegistryECR.ReadCredentials(auth); err != nil { + return err + } + if err := r.UpdateAuthJson(r.OCIRegistryECR.Endpoint, auth); err != nil { + return err + } + } + return nil +} + +func (r *OCIRegistry) decodeAuth() (*palette_utils.Auth, error) { + authBytes, err := base64.StdEncoding.DecodeString(r.AuthJson) + if err != nil { + return nil, errors.Wrap(err, "failed to decode authJson") + } + authMap := &palette_utils.RegistryAuthMap{} + if err := json.Unmarshal(authBytes, authMap); err != nil { + return nil, err + } + var auth *palette_utils.Auth + for _, a := range *authMap { + a := a + auth = &a + break + } + return auth, nil +} + +func (r *OCIRegistry) Endpoint(registryType RegistryType) string { + var ep string + switch registryType { + case RegistryTypeOCI: + ep = r.OCIRegistryBasic.Endpoint + case RegistryTypeOCIECR: + ep = r.OCIRegistryECR.Endpoint + } + ep = strings.TrimSuffix(ep, "/") + ep = strings.TrimSuffix(ep, "/v2") + return ep +} + +func (r *OCIRegistry) BaseContentPath(registryType RegistryType) string { + var baseContentPath string + switch registryType { + case RegistryTypeOCI: + baseContentPath = r.OCIRegistryBasic.BaseContentPath + case RegistryTypeOCIECR: + baseContentPath = r.OCIRegistryECR.BaseContentPath + } + return strings.Trim(baseContentPath, "/") +} + +func (r *OCIRegistry) CACertData(registryType RegistryType) string { + switch registryType { + case RegistryTypeOCI: + return r.OCIRegistryBasic.CACertData + case RegistryTypeOCIECR: + return r.OCIRegistryECR.CACertData + default: + return "" + } +} + +func (r *OCIRegistry) CACertName(registryType RegistryType) string { + switch registryType { + case RegistryTypeOCI: + return r.OCIRegistryBasic.CACertName + case RegistryTypeOCIECR: + return r.OCIRegistryECR.CACertName + default: + return "" + } +} + +func (r *OCIRegistry) CACertPath(registryType RegistryType) string { + switch registryType { + case RegistryTypeOCI: + return r.OCIRegistryBasic.CACertPath + case RegistryTypeOCIECR: + return r.OCIRegistryECR.CACertPath + default: + return "" + } +} + +func (r *OCIRegistry) ReusedProxyCACert(registryType RegistryType) bool { + switch registryType { + case RegistryTypeOCI: + return r.OCIRegistryBasic.ReusedProxyCACert + case RegistryTypeOCIECR: + return r.OCIRegistryECR.ReusedProxyCACert + default: + return false + } +} + +func (r *OCIRegistry) InsecureSkipVerify(registryType RegistryType) bool { + switch registryType { + case RegistryTypeOCI: + return r.OCIRegistryBasic.InsecureSkipVerify + case RegistryTypeOCIECR: + return r.OCIRegistryECR.InsecureSkipVerify + default: + return false + } +} + +func (r *OCIRegistry) Username(registryType RegistryType) (string, error) { + switch registryType { + case RegistryTypeOCI: + return r.OCIRegistryBasic.Username, nil + case RegistryTypeOCIECR: + username, _, err := aws_utils.GetECRCredentials( + r.OCIRegistryECR.AccessKey, + r.OCIRegistryECR.SecretKey, + r.OCIRegistryECR.Region, + ) + return username, err + default: + return "", nil + } +} + +func (r *OCIRegistry) Password(registryType RegistryType) (string, error) { + switch registryType { + case RegistryTypeOCI: + return r.OCIRegistryBasic.Password, nil + case RegistryTypeOCIECR: + _, password, err := aws_utils.GetECRCredentials( + r.OCIRegistryECR.AccessKey, + r.OCIRegistryECR.SecretKey, + r.OCIRegistryECR.Region, + ) + return password, err + default: + return "", nil + } +} + +// OCI Registry - standard +type OCIRegistryBasic struct { + RegistryBase `yaml:",inline"` + Username string `yaml:"username"` + Password string `yaml:"password"` + BaseContentPath string `yaml:"baseContentPath"` + MirrorRegistries string `yaml:"mirrorRegistries"` +} + +func (r *OCIRegistryBasic) Auth() (*palette_utils.Auth, error) { + auth, err := initAuth(r.CACertData, r.InsecureSkipVerify) + if err != nil { + return nil, err + } + auth.Username = r.Username + auth.Password = r.Password + return auth, nil +} + +func (r *OCIRegistryBasic) ReadCredentials(auth *palette_utils.Auth) error { + var err error + r.Username, r.Password, err = prompts.ReadBasicCreds( + "Registry Username", "Registry Password", auth.Username, auth.Password, true, false, + ) + if err != nil { + return err + } + auth.Username = r.Username + auth.Password = r.Password + return nil +} + +// OCI Registry - ECR +type OCIRegistryECR struct { + RegistryBase `yaml:",inline"` + AccessKey string `yaml:"accessKey"` + SecretKey string `yaml:"secretKey"` + Region string `yaml:"region"` + BaseContentPath string `yaml:"baseContentPath"` + IsPrivate bool `yaml:"isPrivate"` +} + +func (r *OCIRegistryECR) Auth() (*palette_utils.Auth, error) { + auth, err := initAuth(r.CACertData, r.InsecureSkipVerify) + if err != nil { + return nil, err + } + auth.Username, auth.Password, err = aws_utils.GetECRCredentials( + r.AccessKey, r.SecretKey, r.Region, + ) + if err != nil { + return nil, err + } + return auth, nil +} + +func (r *OCIRegistryECR) ReadCredentials(auth *palette_utils.Auth) error { + var err error + r.AccessKey, r.SecretKey, err = prompts.ReadBasicCreds( + "Registry AccessKey", "Registry SecretKey", auth.Username, auth.Password, false, true, + ) + if err != nil { + return err + } + r.Region, err = prompts.ReadText("Registry Region", "", false, -1) + if err != nil { + return err + } + r.IsPrivate, err = prompts.ReadBool("ECR Registry is private", true) + if err != nil { + return err + } + auth.Username, auth.Password, err = aws_utils.GetECRCredentials( + r.AccessKey, r.SecretKey, r.Region, + ) + if err != nil { + pterm.DefaultLogger.Error("failed to get ECR credentials", pterm.DefaultLogger.Args("error", err)) + return r.ReadCredentials(auth) + } + return nil +} + +func initAuth(caCertBase64 string, insecure bool) (*palette_utils.Auth, error) { + caCert, err := base64.StdEncoding.DecodeString(caCertBase64) + if err != nil { + return nil, err + } + auth := &palette_utils.Auth{ + Tls: palette_utils.TlsConfig{ + Ca: string(caCert), + InsecureSkipVerify: insecure, + }, + } + return auth, nil +} + +type RegistryBase struct { + Name string `yaml:"name"` + Endpoint string `yaml:"endpoint"` + InsecureSkipVerify bool `yaml:"insecureSkipVerify"` + CACertData string `yaml:"caCert"` + CACertName string `yaml:"caCertName"` + CACertPath string `yaml:"caCertPath"` + ReusedProxyCACert bool `yaml:"reusedProxyCACert"` +} + +// MongoDB +type MongoDbProps struct { + Url string `yaml:"url"` + CpuLimit string `yaml:"cpuLimit"` + MemoryLimit string `yaml:"memoryLimit"` + PvcSize string `yaml:"pvcSize"` +} + +// Accounts +type Accounts struct { + DevOps DevOps `yaml:"devOps"` +} + +// DevOps +type DevOps struct { + Aws Aws `yaml:"aws"` + Azure Azure `yaml:"azure"` + Gcp Gcp `yaml:"gcp"` + Maas Maas `yaml:"maas"` + Openstack Openstack `yaml:"openstack"` + Vsphere Vsphere `yaml:"vsphere"` +} + +// Aws +type Aws struct { + AccessKey string `yaml:"accessKey"` + SecretKey string `yaml:"secretKey"` + GoldenImageRegion string `yaml:"goldenImageRegion"` +} + +// Azure +type Azure struct { + ClientId string `yaml:"clientId"` + ClientSecret string `yaml:"clientSecret"` + TenantId string `yaml:"tenantId"` + SubscriptionId string `yaml:"subscriptionId"` + Storage AzureStorage `yaml:"azureStorage"` +} + +type AzureStorage struct { + AccessKey string `yaml:"accessKey"` + StorageName string `yaml:"storageName"` + Container string `yaml:"container"` +} + +// Gcp +type Gcp struct { + JsonCredentials string `yaml:"jsonCredentials"` + ImageProject string `yaml:"imageProject"` +} + +// Maas +type Maas struct { + ImagesHostEndpoint string `yaml:"imagesHostEndpoint"` +} + +// Openstack +type Openstack struct { + ImagesHostEndpoint string `yaml:"imagesHostEndpoint"` +} + +// vSphere +type Vsphere struct { + ImagesHostEndpoint string `yaml:"imagesHostEndpoint"` + OverlordOvaLocation string `yaml:"overlordOvaLocation"` +} diff --git a/pkg/services/env_service.go b/pkg/services/env_service.go new file mode 100644 index 00000000..2cab3b71 --- /dev/null +++ b/pkg/services/env_service.go @@ -0,0 +1,394 @@ +package services + +import ( + "encoding/base64" + "fmt" + "net/url" + "os" + "os/exec" + "strings" + "time" + + "github.com/pkg/errors" + + "github.com/spectrocloud-labs/prompts-tui/prompts" + + cfg "github.com/validator-labs/validatorctl/pkg/config" + log "github.com/validator-labs/validatorctl/pkg/logging" + "github.com/validator-labs/validatorctl/pkg/repo" + crypto_utils "github.com/validator-labs/validatorctl/pkg/utils/crypto" + exec_utils "github.com/validator-labs/validatorctl/pkg/utils/exec" + models "github.com/validator-labs/validatorctl/pkg/utils/extra" + palette_utils "github.com/validator-labs/validatorctl/pkg/utils/extra" // TODO: rename this + "github.com/validator-labs/validatorctl/pkg/utils/file" + string_utils "github.com/validator-labs/validatorctl/pkg/utils/string" +) + +func ReadEnvProps(env *models.V1Env) error { + + log.Header("Enter Environment Configuration") + + // Proxy env vars & CA cert + if err := ReadProxyProps(env); err != nil { + return err + } + + // Pod CIDR + podCIDR, err := prompts.ReadCIDRs("Pod CIDR", *env.PodCIDR, "Invalid Pod CIDR", false, 1) + if err != nil { + return err + } + env.PodCIDR = &podCIDR + + // Service CIDR + serviceCIDR, err := prompts.ReadCIDRs("Service IP Range", *env.ServiceIPRange, "Invalid Service IP Range", false, 1) + if err != nil { + return err + } + env.ServiceIPRange = &serviceCIDR + + return nil +} + +func ReadProxyProps(e *models.V1Env) (err error) { + // https_proxy + e.HTTPSProxy, err = prompts.ReadURL("HTTPS Proxy", e.HTTPSProxy, "HTTPS Proxy should be a valid URL", true) + if err != nil { + return + } + + // http_proxy + e.HTTPProxy, err = prompts.ReadURL("HTTP Proxy", e.HTTPProxy, "HTTP Proxy should be a valid URL", true) + if err != nil { + return + } + + if e.HTTPProxy != "" || e.HTTPSProxy != "" { + // no_proxy + log.InfoCLI("Configure NO_PROXY") + time.Sleep(2 * time.Second) + e.NoProxy, err = file.EditFileValidated(cfg.NoProxyPrompt, e.NoProxy, ",", prompts.ValidateNoProxy, -1) + if err != nil { + return + } + + // Proxy CA certificate + caCertPath, caCertName, caCertData, err := crypto_utils.ReadCACert("Proxy CA certificate filepath", e.ProxyCaCertPath, "") + if err != nil { + return err + } + e.ProxyCaCertData = string(caCertData) + e.ProxyCaCertName = caCertName + e.ProxyCaCertPath = caCertPath + } + + return +} + +func ReadRegistryProps(e *models.V1Env, p *repo.ScarProps, tc *cfg.TaskConfig) error { + + log.Header("Enter Pack & Image Registry Configuration") + + defaultPackRegistry, defaultImageRegistryType, defaultPackRegistryType := p.DefaultRegistryMeta() + if defaultPackRegistry != "" { + prompt := fmt.Sprintf("Use default Pack Registry (%s)", defaultPackRegistry) + useDefaultPackRegistry, err := prompts.ReadBool(prompt, true) + if err != nil { + return err + } + if useDefaultPackRegistry { + p.ImageRegistryType = defaultImageRegistryType + p.PackRegistryType = defaultPackRegistryType + if err := p.UpdateAuthJson(); err != nil { + return err + } + return nil + } + } + + // OCI pack registry + var err error + var packRegistryType *repo.RegistryType + + packRegistryType, p.OCIPackRegistry, err = ReadOCIRegistry(e, "Pack", tc) + if err != nil { + return err + } + p.PackRegistryType = *packRegistryType + + // OCI image registry + log.InfoCLI("Enter 'Y' to pull images from public registries or 'N' to specify an OCI image registry") + usePublicImageRegistries, err := prompts.ReadBool("Pull images from public registries", true) + if err != nil { + return err + } + if !usePublicImageRegistries { + // For now, only OCI is supported for images - not OCI ECR + p.ImageRegistryType = repo.RegistryTypeOCI + + var sharedRegistry bool + if p.PackRegistryType == repo.RegistryTypeOCI { + sharedRegistry, err = prompts.ReadBool("Use the same OCI Registry for packs & images", true) + if err != nil { + return err + } + } + if !sharedRegistry { + // Either pack registry is OCI ECR, or user wants separate OCI registries for packs & images + _, p.OCIImageRegistry, err = ReadOCIRegistry(e, "Image", tc) + if err != nil { + return err + } + } else { + p.OCIImageRegistry = p.OCIPackRegistry + } + } else { + p.ImageRegistryType = repo.RegistryTypeSpectro + } + + return nil +} + +func ReadOCIRegistry(e *models.V1Env, registryType string, tc *cfg.TaskConfig) (*repo.RegistryType, *repo.OCIRegistry, error) { + log.InfoCLI("\n==== %s OCI Registry Configuration ====", registryType) + + /* + if tc.AirgapConfig != nil { + useAirgapReg, err = prompts.ReadBool(fmt.Sprintf("Use local, air-gapped %s Registry", registryType), true) + if err != nil { + return nil, nil, err + } + caPathOverride = tc.AirgapConfig.CaCertPath + } + */ + + var registryOciType repo.RegistryType + if registryType == "Pack" /*&& !useAirgapReg*/ { + ociType, err := prompts.Select("Registry Type", repo.OCIRegistryTypes) + if err != nil { + return nil, nil, err + } + registryOciType = repo.RegistryType(ociType) + } else { + registryOciType = repo.RegistryTypeOCI // OCI ECR not supported for images + } + + name, err := prompts.ReadText("Registry Name", "", false, -1) + if err != nil { + return nil, nil, err + } + + endpoint, err := prompts.ReadURL( + "Registry Endpoint", "", "Invalid Registry Endpoint. A scheme is required, e.g.: 'https://'.", false, + ) + if err != nil { + return nil, nil, err + } + endpoint = string_utils.MultiTrim(endpoint, cfg.HTTPSchemes, nil) + + baseContentPath, err := prompts.ReadText("Registry Base Content Path", "", true, -1) + if err != nil { + return nil, nil, err + } + insecure, err := prompts.ReadBool("Allow Insecure Connection (Bypass x509 Verification)", true) + if err != nil { + return nil, nil, err + } + + var caCertData []byte + var caCertName, caPath string + var reuseProxyCaCert bool + + if !insecure { + if e.ProxyCaCertPath != "" { + prompt := fmt.Sprintf("Reuse proxy CA cert for %s registry", registryType) + reuseProxyCaCert, err = prompts.ReadBool(prompt, true) + if err != nil { + return nil, nil, err + } + } + if reuseProxyCaCert { + caCertData = []byte(e.ProxyCaCertData) + caCertName = e.ProxyCaCertName + caPath = e.ProxyCaCertPath + } else { + caPath, caCertName, caCertData, err = crypto_utils.ReadCACert("Registry CA certificate Filepath", "", "") + if err != nil { + return nil, nil, err + } + } + // ensure Docker OCI CA config regardless of whether we are reusing the proxy CA cert + if registryType == "Image" { + if err := EnsureDockerOciCaConfig(caCertName, caPath, endpoint); err != nil { + return nil, nil, err + } + } + } + + var mirrorRegistries string + + if registryType == "Image" { + var err error + log.InfoCLI("Configure registry mirror(s)") + time.Sleep(2 * time.Second) + defaultMirrorRegistries := generateMirrorRegistries(endpoint, baseContentPath) + mirrorRegistries, err = file.EditFileValidated( + cfg.RegistryMirrorPrompt, defaultMirrorRegistries, ",", validateMirrorRegistry, -1, + ) + if err != nil { + log.Error("Error auto-generating Registry Mirror config: %v", err) + return nil, nil, err + } + } + + auth := &palette_utils.Auth{ + Tls: palette_utils.TlsConfig{ + Ca: string(caCertData), + InsecureSkipVerify: insecure, + }, + } + + ociRegistry := &repo.OCIRegistry{} + + switch registryOciType { + case repo.RegistryTypeOCI: + ociRegistry.OCIRegistryBasic = &repo.OCIRegistryBasic{} + ociRegistry.OCIRegistryBasic.Name = name + ociRegistry.OCIRegistryBasic.Endpoint = endpoint + ociRegistry.OCIRegistryBasic.BaseContentPath = baseContentPath + ociRegistry.OCIRegistryBasic.InsecureSkipVerify = insecure + ociRegistry.OCIRegistryBasic.CACertData = base64.StdEncoding.EncodeToString(caCertData) + ociRegistry.OCIRegistryBasic.CACertName = caCertName + ociRegistry.OCIRegistryBasic.CACertPath = caPath + ociRegistry.OCIRegistryBasic.ReusedProxyCACert = reuseProxyCaCert + ociRegistry.OCIRegistryBasic.MirrorRegistries = mirrorRegistries + if err := ociRegistry.OCIRegistryBasic.ReadCredentials(auth); err != nil { + return nil, nil, err + } + case repo.RegistryTypeOCIECR: + ociRegistry.OCIRegistryECR = &repo.OCIRegistryECR{} + ociRegistry.OCIRegistryECR.Name = name + ociRegistry.OCIRegistryECR.Endpoint = endpoint + ociRegistry.OCIRegistryECR.BaseContentPath = baseContentPath + ociRegistry.OCIRegistryECR.InsecureSkipVerify = insecure + ociRegistry.OCIRegistryECR.CACertData = base64.StdEncoding.EncodeToString(caCertData) + ociRegistry.OCIRegistryECR.CACertName = caCertName + ociRegistry.OCIRegistryECR.CACertPath = caPath + ociRegistry.OCIRegistryECR.ReusedProxyCACert = reuseProxyCaCert + if err := ociRegistry.OCIRegistryECR.ReadCredentials(auth); err != nil { + return nil, nil, err + } + } + + if err := ociRegistry.UpdateAuthJson(endpoint, auth); err != nil { + return nil, nil, err + } + + return ®istryOciType, ociRegistry, nil +} + +// generateMirrorRegistries returns a comma-separated string of registry mirrors +func generateMirrorRegistries(registryEndpoint string, baseContentPath string) string { + if registryEndpoint == "" { + return "" + } + + mirrorRegistries := make([]string, 0) + for _, registry := range cfg.RegistryMirrors { + // Generate the endpoint for OCI format (with /v2) + registryMirrorEndpoint := fmt.Sprintf("%s/v2", registryEndpoint) + if baseContentPath != "" { + // Optionally add a base content path + registryMirrorEndpoint = fmt.Sprintf("%s/%s", registryMirrorEndpoint, baseContentPath) + } + mirrorRegistries = append(mirrorRegistries, + fmt.Sprintf("%s%s%s", registry, cfg.RegistryMirrorSeparator, registryMirrorEndpoint), + ) + } + return strings.Join(mirrorRegistries, ",") +} + +func validateMirrorRegistry(s string) error { + parts := strings.Split(s, cfg.RegistryMirrorSeparator) + if len(parts) != 2 { + log.InfoCLI("Invalid registry mirror: %s. Missing separator: '%s'", s, cfg.RegistryMirrorSeparator) + return prompts.ValidationError + } + _, err := url.Parse(parts[1]) + if err != nil { + log.InfoCLI("Invalid registry mirror: %s. Failed to parse endpoint %s: %v", s, parts[1], err) + return prompts.ValidationError + } + return nil +} + +func EnsureDockerOciCaConfig(caCertName, caPath, endpoint string) error { + // TODO: mock this function properly + if os.Getenv("IS_TEST") == "true" { + return nil + } + + dockerOciCaDir := fmt.Sprintf("/etc/docker/certs.d/%s", endpoint) + dockerOciCaPath := fmt.Sprintf("%s/%s", dockerOciCaDir, caCertName) + + if _, err := os.Stat(dockerOciCaPath); err != nil { + log.InfoCLI("OCI CA configuration for Docker not found") + + if err := ensureDockerCACertDir(dockerOciCaDir); err != nil { + return err + } + + cmd := exec.Command("sudo", "cp", caPath, dockerOciCaPath) //#nosec G204 + _, stderr, err := exec_utils.Execute(true, cmd) + if err != nil { + log.InfoCLI("Failed to configure OCI CA certificate") + return errors.Wrap(err, stderr) + } + log.InfoCLI("Copied OCA CA certificate from %s to %s", caPath, dockerOciCaPath) + + log.InfoCLI("Restarting Docker...") + cmd = exec.Command("sudo", "systemctl", "daemon-reload") + _, stderr, err = exec_utils.Execute(true, cmd) + if err != nil { + log.InfoCLI("Failed to reload systemd manager configuration") + log.InfoCLI("Please execute 'sudo systemctl daemon-reload' manually and retry") + return errors.Wrap(err, stderr) + } + + cmd = exec.Command("sudo", "systemctl", "restart", "docker") + _, stderr, err = exec_utils.Execute(true, cmd) + if err != nil { + log.InfoCLI("Failed to restart Docker") + log.InfoCLI("Please execute 'sudo systemctl restart docker' manually and retry") + return errors.Wrap(err, stderr) + } + log.InfoCLI("Configured OCA CA certificate for Docker") + } + return nil +} + +func ensureDockerCACertDir(path string) error { + fi, err := os.Stat(path) + if err != nil { + return createDockerCACertDir(path) + } + if !fi.IsDir() { + cmd := exec.Command("sudo", "rm", "-f", path) //#nosec G204 + _, stderr, err := exec_utils.Execute(true, cmd) + if err != nil { + return errors.Wrapf(err, stderr) + } + return createDockerCACertDir(path) + } + return nil +} + +func createDockerCACertDir(path string) error { + cmd := exec.Command("sudo", "mkdir", "-p", path) //#nosec G204 + _, stderr, err := exec_utils.Execute(true, cmd) + if err != nil { + return errors.Wrapf(err, stderr) + } + log.InfoCLI("Created Docker OCI CA certificate directory: %s", path) + return nil +} diff --git a/pkg/services/k8s_service.go b/pkg/services/k8s_service.go new file mode 100644 index 00000000..f3d33c33 --- /dev/null +++ b/pkg/services/k8s_service.go @@ -0,0 +1,213 @@ +package services + +import ( + "context" + "fmt" + "os" + "path" + "regexp" + + "golang.org/x/exp/slices" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + prompt_utils "github.com/spectrocloud-labs/prompts-tui/prompts" + + cfg "github.com/validator-labs/validatorctl/pkg/config" + log "github.com/validator-labs/validatorctl/pkg/logging" + kube_utils "github.com/validator-labs/validatorctl/pkg/utils/kube" +) + +func readConfigMap(k8sClient kubernetes.Interface, prompt, namespace string) (string, error) { + cm, err := prompt_utils.ReadK8sName(prompt, "", true) + if err != nil { + return "", err + } + if cm != "" { + if _, err := k8sClient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), cm, metav1.GetOptions{}); err != nil { + log.InfoCLI("ConfigMap %s does not exist in the %s namespace. Please try again.", cm, namespace) + return readConfigMap(k8sClient, prompt, namespace) + } + } + return cm, nil +} + +func GetSecretsWithKeys(k8sClient kubernetes.Interface, namespace string, keys []string) ([]corev1.Secret, error) { + secrets := make([]corev1.Secret, 0) + secretList, err := k8sClient.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + for _, s := range secretList.Items { + addSecret := true + for _, k := range keys { + if _, ok := s.Data[k]; !ok { + addSecret = false + break + } + } + + if addSecret { + secrets = append(secrets, s) + } + } + return secrets, nil +} + +func GetSecretsWithRegexKeys(k8sClient kubernetes.Interface, namespace string, keyExpr string) ([]corev1.Secret, error) { + pattern, err := regexp.Compile(keyExpr) + if err != nil { + return nil, err + } + + secrets := make([]corev1.Secret, 0) + secretList, err := k8sClient.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + + for _, s := range secretList.Items { + for key := range s.Data { + if pattern.MatchString(key) { + secrets = append(secrets, s) + break + } + } + } + return secrets, nil +} + +func ReadSecret(k8sClient kubernetes.Interface, namespace string, optional bool, keys []string) (*corev1.Secret, error) { + name, err := prompt_utils.ReadK8sName("Secret Name", "", optional) + if err != nil { + return nil, err + } + var secret *corev1.Secret + if name != "" { + secret, err = k8sClient.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + log.InfoCLI("Secret %s does not exist in the %s namespace. Please try again.", name, namespace) + return ReadSecret(k8sClient, namespace, optional, keys) + } + for _, k := range keys { + if _, ok := secret.Data[k]; !ok { + log.InfoCLI("Secret %s does not contain required key %s. Please try again.", name, k) + return ReadSecret(k8sClient, namespace, optional, keys) + } + } + } + return secret, nil +} + +func ReadServiceAccount(k8sClient kubernetes.Interface, namespace string) (string, error) { + serviceAccount, err := prompt_utils.ReadK8sName("ServiceAccount Name", "", true) + if err != nil { + return "", err + } + if serviceAccount != "" { + if _, err := k8sClient.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), serviceAccount, metav1.GetOptions{}); err != nil { + log.InfoCLI("ServiceAccount %s does not exist in the %s namespace. Please try again.", serviceAccount, namespace) + return ReadServiceAccount(k8sClient, namespace) + } + } + return serviceAccount, nil +} + +func ReadKubeconfig() (kubernetes.Interface, string, error) { + var err error + kubeconfigPath := os.Getenv("KUBECONFIG") + + if kubeconfigPath != "" { + log.InfoCLI("Using active KUBECONFIG: %s", kubeconfigPath) + } else { + var defaultKubeConfigPath string + homeDir, err := os.UserHomeDir() + if err == nil { + defaultKubeConfigPath = path.Join(homeDir, ".kube", "config") + } else { + log.Warn("unable to determine user home directory path: %v", err) + } + kubeconfigPath, err = prompt_utils.ReadFilePath("KUBECONFIG path", defaultKubeConfigPath, "Invalid KUBECONFIG path", false) + if err != nil { + return nil, "", err + } + if err := os.Setenv("KUBECONFIG", kubeconfigPath); err != nil { + return nil, "", err + } + } + + k8sClient, err := kube_utils.GetKubeClientset(kubeconfigPath) + if err != nil { + return nil, "", err + } + return k8sClient, kubeconfigPath, nil +} + +func readNamespacedName(k8sClient kubernetes.Interface, prefix string) (string, string, error) { + nsPrompt := fmt.Sprintf("%s Namespace", prefix) + namePrompt := fmt.Sprintf("%s Name", prefix) + + namespace, err := readNamespace(k8sClient, nsPrompt, "") + if err != nil { + return "", "", err + } + + name, err := prompt_utils.ReadK8sName(namePrompt, "", false) + if err != nil { + return "", "", err + } + + return namespace, name, nil +} + +func readNamespace(k8sClient kubernetes.Interface, prompt, defaultVal string) (string, error) { + namespace, err := prompt_utils.ReadK8sName(prompt, defaultVal, false) + if err != nil { + return "", err + } + if _, err := k8sClient.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}); err != nil { + log.InfoCLI("Namespace %s does not exist. Please try again.", namespace) + return readNamespace(k8sClient, prompt, defaultVal) + } + return namespace, nil +} + +func readAccessMode(prompt, defaultVal string, optional bool) (string, error) { + accessMode, err := prompt_utils.ReadText(prompt, defaultVal, optional, 13) + if err != nil { + return "", err + } + if !slices.Contains([]string{"", "ReadOnlyMany", "ReadWriteMany", "ReadWriteOnce"}, accessMode) { + log.InfoCLI("Access mode %s is invalid. Please try again.", accessMode) + return readAccessMode(prompt, defaultVal, optional) + } + return accessMode, nil +} + +func readStorageClass(ctx context.Context, k8sClient kubernetes.Interface, prompt string) (string, error) { + var choices []prompt_utils.ChoiceItem + + storageClassList, err := k8sClient.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{}) + if err != nil { + return "", err + } + + for _, sc := range storageClassList.Items { + var storageClassInfo string + + if sc.Annotations[cfg.DefaultStorageClassAnnotation] == "true" { + storageClassInfo = " (default)" + } + choices = append(choices, prompt_utils.ChoiceItem{ + ID: sc.Name, + Name: fmt.Sprintf("%s%s", sc.Name, storageClassInfo), + }) + } + + storageClass, err := prompt_utils.SelectID(prompt, choices) + if err != nil { + return readStorageClass(ctx, k8sClient, prompt) + } + return storageClass.ID, nil +} diff --git a/pkg/services/validator/validator_service.go b/pkg/services/validator/validator_service.go new file mode 100644 index 00000000..45a31d24 --- /dev/null +++ b/pkg/services/validator/validator_service.go @@ -0,0 +1,401 @@ +package validator + +import ( + "fmt" + "path/filepath" + "slices" + "strconv" + "strings" + + vtypes "github.com/validator-labs/validator/pkg/types" + "k8s.io/client-go/kubernetes" + + "github.com/spectrocloud-labs/prompts-tui/prompts" + + "github.com/validator-labs/validatorctl/pkg/components" + cfg "github.com/validator-labs/validatorctl/pkg/config" + log "github.com/validator-labs/validatorctl/pkg/logging" + //"github.com/spectrocloud/palette-cli/pkg/repo" + "github.com/validator-labs/validatorctl/pkg/services" + "github.com/validator-labs/validatorctl/pkg/utils/crypto" + "github.com/validator-labs/validatorctl/pkg/utils/kind" + string_utils "github.com/validator-labs/validatorctl/pkg/utils/string" +) + +var ( + pluginFuncs = map[string]func(*components.ValidatorConfig, kubernetes.Interface) error{ + /* TODO: uncomment these lines + "AWS": readAwsPlugin, + "Azure": readAzurePlugin, + "Network": readNetworkPlugin, + "OCI": readOciPlugin, + "vSphere": readVspherePlugin, + */ + } + plugins = make([]string, 0, len(pluginFuncs)) + + imageRegistry = cfg.ValidatorImageRegistry +) + +func init() { + for k := range pluginFuncs { + plugins = append(plugins, k) + } + slices.Sort(plugins) +} + +func ReadValidatorConfig(c *cfg.Config, tc *cfg.TaskConfig, vc *components.ValidatorConfig) error { + log.Header("Enter Validator Configuration") + + var err error + var k8sClient kubernetes.Interface + + vc.UseKindCluster, err = prompts.ReadBool("Provision & use kind cluster", true) + if err != nil { + return err + } + if vc.UseKindCluster { + if err := kind.ValidateClusters("Validator installation"); err != nil { + return err + } + vc.Kubeconfig = filepath.Join(c.RunLoc, "kind-cluster.kubeconfig") + } else { + k8sClient, vc.Kubeconfig, err = services.ReadKubeconfig() + if err != nil { + return err + } + } + + /* + if c.EnvironmentConfig.ImageRegistryType == cfg.ImageRegistryTypeCustom { + vc.ScarProps.ImageRegistryType = repo.RegistryTypeOCI + _, vc.ScarProps.OCIImageRegistry, err = services.ReadOCIRegistry(vc.ProxyConfig.Env, "Image", tc) + if err != nil { + return err + } + // use quay.io/validator-labs, as it will be mirrored by the OCI registry + vc.ImageRegistry = imageRegistry + } else { + if vc.ImageRegistry != "" { + imageRegistry = vc.ImageRegistry + } + vc.ImageRegistry, err = prompts.ReadText("Validator image registry", imageRegistry, false, -1) + if err != nil { + return err + } + vc.ScarProps.ImageRegistryType = repo.RegistryTypeSpectro + } + */ + + if err := readProxyConfig(vc); err != nil { + return err + } + if err := readSinkConfig(vc, k8sClient); err != nil { + return err + } + /* + if err := readHelmRelease(cfg.Validator, k8sClient, vc, vc.Release, vc.ReleaseSecret); err != nil { + return err + } + */ + + log.Header("Enter Validator Plugin Configuration") + + vc.AWSPlugin.Enabled, err = prompts.ReadBool("Enable AWS plugin", true) + if err != nil { + return err + } + if vc.AWSPlugin.Enabled { + /* + if err = readAwsPlugin(vc, k8sClient); err != nil { + return err + } + */ + } + + vc.AzurePlugin.Enabled, err = prompts.ReadBool("Enable Azure plugin", true) + if err != nil { + return fmt.Errorf("failed to prompt for bool for enable Azure plugin: %w", err) + } + if vc.AzurePlugin.Enabled { + /* + if err = readAzurePlugin(vc, k8sClient); err != nil { + return err + } + */ + } + + vc.NetworkPlugin.Enabled, err = prompts.ReadBool("Enable Network plugin", true) + if err != nil { + return err + } + if vc.NetworkPlugin.Enabled { + /* + if err = readNetworkPlugin(vc, k8sClient); err != nil { + return err + } + */ + } + + vc.OCIPlugin.Enabled, err = prompts.ReadBool("Enable OCI plugin", true) + if err != nil { + return err + } + if vc.OCIPlugin.Enabled { + /* + if err = readOciPlugin(vc, k8sClient); err != nil { + return err + } + */ + } + + vc.VspherePlugin.Enabled, err = prompts.ReadBool("Enable vSphere plugin", true) + if err != nil { + return err + } + if vc.VspherePlugin.Enabled { + /* + if err = readVspherePlugin(vc, k8sClient); err != nil { + return err + } + */ + } + + restart, err := prompts.ReadBool("Restart configuration", false) + if err != nil { + return err + } + if restart { + return ReadValidatorConfig(c, tc, vc) + } + + for { + revisit, err := prompts.ReadBool("Reconfigure plugin(s)", false) + if err != nil { + return err + } + if revisit { + pluginFunc, err := prompts.Select("Plugin", plugins) + if err != nil { + return err + } + if err := pluginFuncs[pluginFunc](vc, k8sClient); err != nil { + return err + } + continue + } + break + } + + return nil +} + +func UpdateValidatorCredentials(c *components.ValidatorConfig) error { + var err error + var k8sClient kubernetes.Interface + + if !c.UseKindCluster { + k8sClient, c.Kubeconfig, err = services.ReadKubeconfig() + if err != nil { + return err + } + } + + fmt.Println(k8sClient) // TODO: remove this line + + log.InfoCLI("Configure Helm release credentials for validator chart") + /* + if err := readHelmCredentials(c.Release, c.ReleaseSecret, k8sClient, c); err != nil { + return err + } + */ + + if c.AWSPlugin != nil && c.AWSPlugin.Enabled { + log.InfoCLI("Configure Helm release credentials for validator-plugin-aws chart") + /* + if err := readHelmCredentials(c.AWSPlugin.Release, c.AWSPlugin.ReleaseSecret, k8sClient, c); err != nil { + return err + } + if err := readAwsCredentials(c.AWSPlugin, k8sClient); err != nil { + return err + } + */ + } + if c.AzurePlugin != nil && c.AzurePlugin.Enabled { + log.InfoCLI("Configure Helm release credentials for validator-plugin-azure chart") + /* + if err := readHelmCredentials(c.AzurePlugin.Release, c.AzurePlugin.ReleaseSecret, k8sClient, c); err != nil { + return err + } + if err := readAzureCredentials(c.AzurePlugin, k8sClient); err != nil { + return err + } + */ + } + if c.OCIPlugin != nil && c.OCIPlugin.Enabled { + log.InfoCLI("Configure Helm release credentials for validator-plugin-oci chart") + /* + if err := readHelmCredentials(c.OCIPlugin.Release, c.OCIPlugin.ReleaseSecret, k8sClient, c); err != nil { + return err + } + for _, secret := range c.OCIPlugin.Secrets { + if err := readSecret(secret); err != nil { + return err + } + } + */ + } + if c.VspherePlugin != nil && c.VspherePlugin.Enabled { + log.InfoCLI("Configure Helm release credentials for validator-plugin-vsphere chart") + /* + if err = readHelmCredentials(c.VspherePlugin.Release, c.VspherePlugin.ReleaseSecret, k8sClient, c); err != nil { + return err + } + if err := readVsphereCredentials(c.VspherePlugin, k8sClient); err != nil { + return err + } + */ + } + + return nil +} + +func readProxyConfig(vc *components.ValidatorConfig) error { + vc.ProxyConfig.Env.PodCIDR = &cfg.DefaultPodCIDR + vc.ProxyConfig.Env.ServiceIPRange = &cfg.DefaultServiceIPRange + + configureProxy, err := prompts.ReadBool("Configure an HTTP proxy", false) + if err != nil { + return err + } + if !configureProxy { + vc.ProxyConfig.Enabled = false + return nil + } + if err := services.ReadProxyProps(vc.ProxyConfig.Env); err != nil { + return err + } + vc.ProxyConfig.Enabled = vc.ProxyConfig.Env.ProxyCaCertPath != "" + + return nil +} + +func readSinkConfig(vc *components.ValidatorConfig, k8sClient kubernetes.Interface) error { + var err error + vc.SinkConfig.Enabled, err = prompts.ReadBool("Configure a sink", false) + if err != nil { + return err + } + if !vc.SinkConfig.Enabled { + return nil + } + + sinkType, err := prompts.Select("Sink Type", sinkTypes()) + if err != nil { + return err + } + vc.SinkConfig.Type = strings.ToLower(sinkType) + + // always create sink credential secret if creating a new kind cluster + vc.SinkConfig.CreateSecret = true + + if k8sClient != nil { + keys := cfg.ValidatorSinkKeys[vtypes.SinkType(vc.SinkConfig.Type)] + log.InfoCLI(` + Either specify sink credentials or provide the name of a secret in the target K8s cluster's %s namespace. + If using an existing secret, it must contain the following keys: %+v. + `, cfg.Validator, keys, + ) + vc.SinkConfig.CreateSecret, err = prompts.ReadBool("Create sink credential secret", true) + if err != nil { + return err + } + if !vc.SinkConfig.CreateSecret { + secret, err := services.ReadSecret(k8sClient, cfg.Validator, false, keys) + if err != nil { + return err + } + vc.SinkConfig.SecretName = secret.Name + return nil + } + } + + vc.SinkConfig.SecretName, err = prompts.ReadText("Sink credentials secret name", "sink-creds", false, -1) + if err != nil { + return err + } + + switch vc.SinkConfig.Type { + case string(vtypes.SinkTypeAlertmanager): + if vc.SinkConfig.Values == nil { + vc.SinkConfig.Values = map[string]string{ + "endpoint": "", + "caCert": "", + "username": "", + "password": "", + } + } + + endpoint, err := prompts.ReadURL( + "Alertmanager endpoint", vc.SinkConfig.Values["endpoint"], "Alertmanager endpoint must be a valid URL", false, + ) + if err != nil { + return err + } + vc.SinkConfig.Values["endpoint"] = endpoint + + insecure, err := prompts.ReadBool("Allow Insecure Connection (Bypass x509 Verification)", true) + if err != nil { + return err + } + vc.SinkConfig.Values["insecureSkipVerify"] = strconv.FormatBool(insecure) + + if !insecure { + var caCertData []byte + _, _, caCertData, err = crypto.ReadCACert("Alertmanager CA certificate filepath", vc.SinkConfig.Values["caCert"], "") + if err != nil { + return err + } + vc.SinkConfig.Values["caCert"] = string(caCertData) + } + + username, password, err := prompts.ReadBasicCreds( + "Alertmanager Username", "Alertmanager Password", + vc.SinkConfig.Values["username"], vc.SinkConfig.Values["password"], true, false, + ) + if err != nil { + return err + } + vc.SinkConfig.Values["username"] = username + vc.SinkConfig.Values["password"] = password + + case string(vtypes.SinkTypeSlack): + if vc.SinkConfig.Values == nil { + vc.SinkConfig.Values = map[string]string{ + "apiToken": "", + "channelId": "", + } + } + + botToken, err := prompts.ReadPassword("Bot token", vc.SinkConfig.Values["apiToken"], false, -1) + if err != nil { + return err + } + vc.SinkConfig.Values["apiToken"] = botToken + + channelId, err := prompts.ReadText("Channel ID", vc.SinkConfig.Values["channelId"], false, -1) + if err != nil { + return err + } + vc.SinkConfig.Values["channelId"] = channelId + } + + return nil +} + +func sinkTypes() []string { + return []string{ + string_utils.Capitalize(string(vtypes.SinkTypeAlertmanager)), + string_utils.Capitalize(string(vtypes.SinkTypeSlack)), + } +} diff --git a/pkg/utils/aws/aws.go b/pkg/utils/aws/aws.go new file mode 100644 index 00000000..ee001d09 --- /dev/null +++ b/pkg/utils/aws/aws.go @@ -0,0 +1,45 @@ +package aws + +import ( + "bytes" + "encoding/base64" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecr" +) + +var GetECRCredentials func(accessKey, secretKey, region string) (string, string, error) = getECRCredentials + +func getECRCredentials(accessKey, secretKey, region string) (string, string, error) { + // Create an AWS session & ECR client + sess := session.Must(session.NewSession(&aws.Config{ + Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), + Region: ®ion, + })) + ecrClient := ecr.New(sess) + + // Get the ECR authorization token + tokenOutput, err := ecrClient.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{}) + if err != nil { + return "", "", err + } + + // Extract the authorization token from the response + if len(tokenOutput.AuthorizationData) == 0 { + return "", "", err + } + authorizationTokenB64 := aws.StringValue(tokenOutput.AuthorizationData[0].AuthorizationToken) + + // Decode & parse authorization token + authorizationTokenBytes, err := base64.StdEncoding.DecodeString(authorizationTokenB64) + if err != nil { + return "", "", err + } + authTokenParts := bytes.Split(authorizationTokenBytes, []byte(":")) + username := string(authTokenParts[0]) + password := string(authTokenParts[1]) + + return username, password, nil +} diff --git a/pkg/utils/cmd/cmd.go b/pkg/utils/cmd/cmd.go new file mode 100644 index 00000000..7e920b6f --- /dev/null +++ b/pkg/utils/cmd/cmd.go @@ -0,0 +1,13 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + log "github.com/validator-labs/validatorctl/pkg/logging" +) + +func MarkFlagRequired(cmd *cobra.Command, name string) { + if err := cmd.MarkFlagRequired(name); err != nil { + log.FatalCLI(err.Error()) + } +} diff --git a/pkg/utils/crypto/crypto.go b/pkg/utils/crypto/crypto.go new file mode 100644 index 00000000..10e3ccb6 --- /dev/null +++ b/pkg/utils/crypto/crypto.go @@ -0,0 +1,114 @@ +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "io" + "os" + + prompt_utils "github.com/spectrocloud-labs/prompts-tui/prompts" + + log "github.com/validator-labs/validatorctl/pkg/logging" +) + +// TODO: should this value be changed? Is it safe to have it in the code and not in some env var that can be configured on build? +const k = "TxXW4Qg4vqorgiCtgeEFW7inLXKLv4bC" + +func DecryptB64(cipherstringB64 string) (*[]byte, error) { + if cipherstringB64 == "" { + return new([]byte), nil + } + + encrypted, err := base64.StdEncoding.DecodeString(cipherstringB64) + if err != nil { + return nil, err + } + + ciphertext := []byte(encrypted) + key := []byte(k) + + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + if len(ciphertext) < aes.BlockSize { + return nil, errors.New("cipherstring is too short") + } + + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + + stream := cipher.NewCFBDecrypter(block, iv) + stream.XORKeyStream(ciphertext, ciphertext) + + return &ciphertext, nil +} + +func EncryptB64(plainbytes []byte) (string, error) { + if len(plainbytes) == 0 { + return "", nil + } + + key := []byte(k) + + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + ciphertext := make([]byte, aes.BlockSize+len(plainbytes)) + + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "", err + } + + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], plainbytes) + + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +func ReadCACert(prompt string, defaultCaCertPath, caCertPathOverride string) (caCertPath string, caCertName string, caCertData []byte, err error) { + if caCertPathOverride != "" { + caCertPath = caCertPathOverride + } else { + log.InfoCLI("Optionally enter the file path to your desired CA certificate, e.g., /usr/local/share/ca-certificates/ca.crt") + log.InfoCLI("Press enter to skip if your certificates are publicly verifiable") + caCertPath, err = prompt_utils.ReadFilePath(prompt, defaultCaCertPath, "Invalid filepath specified", true) + } + if err != nil { + return "", "", nil, err + } + if caCertPath == "" { + return "", "", nil, nil + } + caFile, _ := os.Stat(caCertPath) + caBytes, err := os.ReadFile(caCertPath) //#nosec + if err != nil { + return "", "", nil, err + } + // Validate CA cert + var blocks []byte + rest := caBytes + for { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + return "", "", nil, fmt.Errorf("PEM parse failure for %s", caCertPath) + } + blocks = append(blocks, block.Bytes...) + if len(rest) == 0 { + break + } + } + if _, err = x509.ParseCertificates(blocks); err != nil { + return "", "", nil, err + } + return caCertPath, caFile.Name(), caBytes, nil +} diff --git a/pkg/utils/embed/bin/.gitkeep b/pkg/utils/embed/bin/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/pkg/utils/embed/embed.go b/pkg/utils/embed/embed.go new file mode 100644 index 00000000..23acffe7 --- /dev/null +++ b/pkg/utils/embed/embed.go @@ -0,0 +1,163 @@ +package embed + +import ( + "bytes" + "embed" + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "text/tabwriter" + "text/template" + + "github.com/Masterminds/sprig/v3" + + cfg "github.com/validator-labs/validatorctl/pkg/config" + log "github.com/validator-labs/validatorctl/pkg/logging" +) + +var Docker, Helm, Kind, Kubectl, Vmtoolsd string + +//go:embed bin/docker +var docker []byte + +//go:embed bin/helm +var helm []byte + +//go:embed bin/kind +var kind []byte + +//go:embed bin/kubectl +var kubectl []byte + +//go:embed bin/vmtoolsd +var vmtoolsd []byte + +func InitBinaries(c *cfg.Config) { + if runtime.GOOS == "windows" { + Docker = filepath.Join(c.WorkspaceLoc, "bin", "docker.exe") + Helm = filepath.Join(c.WorkspaceLoc, "bin", "helm.exe") + Kind = filepath.Join(c.WorkspaceLoc, "bin", "kind.exe") + Kubectl = filepath.Join(c.WorkspaceLoc, "bin", "kubectl.exe") + } else { + Docker = filepath.Join(c.WorkspaceLoc, "bin", "docker") + Helm = filepath.Join(c.WorkspaceLoc, "bin", "helm") + Kind = filepath.Join(c.WorkspaceLoc, "bin", "kind") + Kubectl = filepath.Join(c.WorkspaceLoc, "bin", "kubectl") + } + Vmtoolsd = filepath.Join(c.WorkspaceLoc, "bin", "vmtoolsd") + + if _, err := os.Stat(Docker); os.IsNotExist(err) { + if err := os.WriteFile(Docker, docker, 0755); err != nil /* #nosec G306 */ { + log.FatalCLI(err.Error()) + } + } + if _, err := os.Stat(Helm); os.IsNotExist(err) { + if err := os.WriteFile(Helm, helm, 0755); err != nil /* #nosec G306 */ { + log.FatalCLI(err.Error()) + } + } + if _, err := os.Stat(Kind); os.IsNotExist(err) { + if err := os.WriteFile(Kind, kind, 0755); err != nil /* #nosec G306 */ { + log.FatalCLI(err.Error()) + } + } + if _, err := os.Stat(Kubectl); os.IsNotExist(err) { + if err := os.WriteFile(Kubectl, kubectl, 0755); err != nil /* #nosec G306 */ { + log.FatalCLI(err.Error()) + } + } + if _, err := os.Stat(Vmtoolsd); os.IsNotExist(err) { + if err := os.WriteFile(Vmtoolsd, vmtoolsd, 0755); err != nil /* #nosec G306 */ { + log.FatalCLI(err.Error()) + } + } +} + +//go:embed resources/* +var resources embed.FS + +// CopyFile copies a file from the embedded file system into a physical disk location. +func CopyFile(destPath, sourceDir, sourceName string) error { + bytes, err := resources.ReadFile(toEmbeddedFilePath(sourceDir, sourceName)) + if err != nil { + return err + } + if err := os.WriteFile(destPath, bytes, 0600); err != nil { + return err + } + return nil +} + +// ReadFile reads a file from the embedded file system. +func ReadFile(dir, filename string) ([]byte, error) { + return resources.ReadFile(toEmbeddedFilePath(dir, filename)) +} + +// WriteFile Writes bytes to the specified file. +func WriteFile(outFilename string, data []byte) error { + if err := os.WriteFile(outFilename, data, 0600); err != nil { + log.Error("failed to write rendered template to disk: %v", err) + return err + } + return nil +} + +// RenderTemplate renders a template from the embedded file system and writes it to disk. +func RenderTemplate(args interface{}, dir, filename, outputPath string) error { + data, err := RenderTemplateBytes(args, dir, filename) + if err != nil { + return err + } + if err := WriteFile(outputPath, data); err != nil { + return err + } + return nil +} + +// RenderTemplateBytes renders a template from the embedded file system and returns the resulting bytes. +func RenderTemplateBytes(args interface{}, dir, filename string) ([]byte, error) { + var writer bytes.Buffer + if err := render(args, &writer, dir, filename); err != nil { + return nil, err + } + return writer.Bytes(), nil +} + +// render renders a template from the embedded file system. +func render(args interface{}, writer *bytes.Buffer, dir, filename string) error { + // Use sprig library functions for templates + tfm := sprig.TxtFuncMap() + tmpl, err := template.New(filename).Funcs(tfm).ParseFS(resources, toEmbeddedFilePath(dir, filename)) + if err != nil { + return err + } + + if err := tmpl.Option("missingkey=zero").Execute(writer, args); err != nil { + log.Error("failed to parse template: args: %+v. Error: %v", args, err) + return err + } + + return nil +} + +// PrintTableTemplate renders a tabular data template and flushes it to stdout. +func PrintTableTemplate(out io.Writer, args interface{}, dir, filename string) error { + tabw := tabwriter.NewWriter(out, 8, 8, 8, ' ', 0) + tmpl, err := template.ParseFS(resources, toEmbeddedFilePath(dir, filename)) + if err != nil { + return err + } + if err := tmpl.Execute(tabw, args); err != nil { + log.Error("failed to parse template: args: %+v. Error: %v", args, err) + return err + } + return tabw.Flush() +} + +// toEmbeddedFilePath retrieves the full path of a file within the embedded file system. +// Note that filepath.Join is NOT used here, as embed requires the '/' separator. +func toEmbeddedFilePath(dir, filename string) string { + return fmt.Sprintf("resources/%s/%s", dir, filename) +} diff --git a/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-base.tmpl b/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-base.tmpl new file mode 100644 index 00000000..85aa439b --- /dev/null +++ b/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-base.tmpl @@ -0,0 +1,288 @@ +apiVersion: validation.spectrocloud.labs/v1alpha1 +kind: AwsValidator +metadata: + name: validator-plugin-aws-iam-base + namespace: validator +spec: + auth: +{{ .Auth }} + defaultRegion: us-west-1 + iamRoleRules: + - iamPolicies: + - name: Controllers Policy + statements: + - actions: + - "iam:DeleteOpenIDConnectProvider" + - "iam:GetOpenIDConnectProvider" + - "iam:ListOpenIDConnectProviders" + - "iam:TagOpenIDConnectProvider" + - "autoscaling:DescribeAutoScalingGroups" + - "autoscaling:DescribeInstanceRefreshes" + - "ec2:AllocateAddress" + - "ec2:AssociateRouteTable" + - "ec2:AttachInternetGateway" + - "ec2:AuthorizeSecurityGroupIngress" + - "ec2:CreateInternetGateway" + - "ec2:CreateLaunchTemplate" + - "ec2:CreateLaunchTemplateVersion" + - "ec2:CreateNatGateway" + - "ec2:CreateRoute" + - "ec2:CreateRouteTable" + - "ec2:CreateSecurityGroup" + - "ec2:CreateSubnet" + - "ec2:CreateTags" + - "ec2:CreateVpc" + - "ec2:DeleteInternetGateway" + - "ec2:DeleteLaunchTemplate" + - "ec2:DeleteLaunchTemplateVersions" + - "ec2:DeleteNatGateway" + - "ec2:DeleteRouteTable" + - "ec2:DeleteSecurityGroup" + - "ec2:DeleteSubnet" + - "ec2:DeleteTags" + - "ec2:DeleteVpc" + - "ec2:DescribeAccountAttributes" + - "ec2:DescribeAddresses" + - "ec2:DescribeAvailabilityZones" + - "ec2:DescribeImages" + - "ec2:DescribeInstances" + - "ec2:DescribeInternetGateways" + - "ec2:DescribeKeyPairs" + - "ec2:DescribeLaunchTemplates" + - "ec2:DescribeLaunchTemplateVersions" + - "ec2:DescribeNatGateways" + - "ec2:DescribeNetworkInterfaceAttribute" + - "ec2:DescribeNetworkInterfaces" + - "ec2:DescribeRouteTables" + - "ec2:DescribeSecurityGroups" + - "ec2:DescribeSubnets" + - "ec2:DescribeVolumes" + - "ec2:DescribeVpcAttribute" + - "ec2:DescribeVpcs" + - "ec2:DetachInternetGateway" + - "ec2:DisassociateAddress" + - "ec2:DisassociateRouteTable" + - "ec2:ModifyInstanceAttribute" + - "ec2:ModifyNetworkInterfaceAttribute" + - "ec2:ModifySubnetAttribute" + - "ec2:ModifyVpcAttribute" + - "ec2:ReleaseAddress" + - "ec2:ReplaceRoute" + - "ec2:RevokeSecurityGroupIngress" + - "ec2:RunInstances" + - "ec2:TerminateInstances" + - "elasticloadbalancing:AddTags" + - "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer" + - "elasticloadbalancing:ConfigureHealthCheck" + - "elasticloadbalancing:CreateLoadBalancer" + - "elasticloadbalancing:DeleteLoadBalancer" + - "elasticloadbalancing:DeleteTargetGroup" + - "elasticloadbalancing:DeregisterInstancesFromLoadBalancer" + - "elasticloadbalancing:DescribeLoadBalancerAttributes" + - "elasticloadbalancing:DescribeLoadBalancers" + - "elasticloadbalancing:DescribeTags" + - "elasticloadbalancing:ModifyLoadBalancerAttributes" + - "elasticloadbalancing:RegisterInstancesWithLoadBalancer" + - "elasticloadbalancing:RemoveTags" + - "iam:CreateOpenIDConnectProvider" + - "tag:GetResources" + effect: Allow + resources: + - "*" + - actions: + - "autoscaling:CreateAutoScalingGroup" + - "autoscaling:UpdateAutoScalingGroup" + - "autoscaling:CreateOrUpdateTags" + - "autoscaling:StartInstanceRefresh" + - "autoscaling:DeleteAutoScalingGroup" + - "autoscaling:DeleteTags" + effect: Allow + resources: + - "arn:*:autoscaling:*:*:autoScalingGroup:*:autoScalingGroupName/*" + - actions: + - "iam:CreateServiceLinkedRole" + condition: + type: StringLike + key: "iam:AWSServiceName" + values: + - autoscaling.amazonaws.com + effect: Allow + resources: + - "arn:*:iam::*:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling" + - actions: + - "iam:CreateServiceLinkedRole" + condition: + type: StringLike + key: "iam:AWSServiceName" + values: + - elasticloadbalancing.amazonaws.com + effect: Allow + resources: + - "arn:*:iam::*:role/aws-service-role/elasticloadbalancing.amazonaws.com/AWSServiceRoleForElasticLoadBalancing" + - actions: + - "iam:CreateServiceLinkedRole" + condition: + type: StringLike + key: "iam:AWSServiceName" + values: + - spot.amazonaws.com + effect: Allow + resources: + - "arn:*:iam::*:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot" + - actions: + - "iam:PassRole" + effect: Allow + resources: + - "arn:*:iam::*:role/*.cluster-api-provider-aws.sigs.k8s.io" + - actions: + - "secretsmanager:CreateSecret" + - "secretsmanager:DeleteSecret" + - "secretsmanager:TagResource" + effect: Allow + resources: + - "arn:*:secretsmanager:*:*:secret:aws.cluster.x-k8s.io/*" + - actions: + - "s3:DeleteObject" + - "s3:PutBucketOwnershipControls" + - "s3:PutBucketPolicy" + - "s3:PutBucketPublicAccessBlock" + - "s3:PutObjectAcl" + - "s3:PutObject" + effect: Allow + resources: + - "arn:*:s3:::*" + version: "2012-10-17" + - name: Control Plane Policy + statements: + - actions: + - "autoscaling:DescribeAutoScalingGroups" + - "autoscaling:DescribeLaunchConfigurations" + - "autoscaling:DescribeTags" + - "ec2:DescribeInstances" + - "ec2:DescribeImages" + - "ec2:DescribeRegions" + - "ec2:DescribeRouteTables" + - "ec2:DescribeSecurityGroups" + - "ec2:DescribeSubnets" + - "ec2:DescribeVolumes" + - "ec2:CreateSecurityGroup" + - "ec2:CreateTags" + - "ec2:CreateVolume" + - "ec2:ModifyInstanceAttribute" + - "ec2:ModifyVolume" + - "ec2:AttachVolume" + - "ec2:AuthorizeSecurityGroupIngress" + - "ec2:CreateRoute" + - "ec2:DeleteRoute" + - "ec2:DeleteSecurityGroup" + - "ec2:DeleteVolume" + - "ec2:DetachVolume" + - "ec2:RevokeSecurityGroupIngress" + - "ec2:DescribeVpcs" + - "elasticloadbalancing:AddTags" + - "elasticloadbalancing:AttachLoadBalancerToSubnets" + - "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer" + - "elasticloadbalancing:CreateLoadBalancer" + - "elasticloadbalancing:CreateLoadBalancerPolicy" + - "elasticloadbalancing:CreateLoadBalancerListeners" + - "elasticloadbalancing:ConfigureHealthCheck" + - "elasticloadbalancing:DeleteLoadBalancer" + - "elasticloadbalancing:DeleteLoadBalancerListeners" + - "elasticloadbalancing:DescribeLoadBalancers" + - "elasticloadbalancing:DescribeLoadBalancerAttributes" + - "elasticloadbalancing:DetachLoadBalancerFromSubnets" + - "elasticloadbalancing:DeregisterInstancesFromLoadBalancer" + - "elasticloadbalancing:ModifyLoadBalancerAttributes" + - "elasticloadbalancing:RegisterInstancesWithLoadBalancer" + - "elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer" + - "elasticloadbalancing:CreateListener" + - "elasticloadbalancing:CreateTargetGroup" + - "elasticloadbalancing:DeleteListener" + - "elasticloadbalancing:DeleteTargetGroup" + - "elasticloadbalancing:DescribeListeners" + - "elasticloadbalancing:DescribeLoadBalancerPolicies" + - "elasticloadbalancing:DescribeTargetGroups" + - "elasticloadbalancing:DescribeTargetHealth" + - "elasticloadbalancing:ModifyListener" + - "elasticloadbalancing:ModifyTargetGroup" + - "elasticloadbalancing:RegisterTargets" + - "elasticloadbalancing:SetLoadBalancerPoliciesOfListener" + - "iam:CreateServiceLinkedRole" + - "kms:DescribeKey" + effect: Allow + resources: + - "*" + version: "2012-10-17" + - name: Nodes Policy + statements: + - actions: + - "ec2:DescribeInstances" + - "ec2:DescribeRegions" + - "ecr:GetAuthorizationToken" + - "ecr:BatchCheckLayerAvailability" + - "ecr:GetDownloadUrlForLayer" + - "ecr:GetRepositoryPolicy" + - "ecr:DescribeRepositories" + - "ecr:ListImages" + - "ecr:BatchGetImage" + effect: Allow + resources: + - "*" + - actions: + - "secretsmanager:DeleteSecret" + - "secretsmanager:GetSecretValue" + effect: Allow + resources: + - "arn:*:secretsmanager:*:*:secret:aws.cluster.x-k8s.io/*" + - actions: + - "ssm:UpdateInstanceInformation" + - "ssmmessages:CreateControlChannel" + - "ssmmessages:CreateDataChannel" + - "ssmmessages:OpenControlChannel" + - "ssmmessages:OpenDataChannel" + - "s3:GetEncryptionConfiguration" + effect: Allow + resources: + - "*" + version: "2012-10-17" + - name: Deployment Policy + statements: + - actions: + - "cloudformation:CreateStack" + - "cloudformation:DescribeStacks" + - "cloudformation:UpdateStack" + - "ec2:CreateSnapshot" + - "ec2:DeleteSnapshot" + - "ec2:DescribeSnapshots" + - "ec2:DescribeTags" + - "ec2:DescribeVolumesModifications" + - "ec2:DescribeKeyPairs" + - "iam:AttachGroupPolicy" + - "iam:CreatePolicy" + - "iam:CreatePolicyVersion" + - "iam:DeletePolicy" + - "iam:DeletePolicyVersion" + - "iam:DetachGroupPolicy" + - "iam:GetGroup" + - "iam:GetInstanceProfile" + - "iam:GetPolicy" + - "iam:GetUser" + - "iam:ListPolicies" + - "iam:ListPolicyVersions" + - "pricing:GetProducts" + - "sts:AssumeRole" + - "sts:GetServiceBearerToken" + - "iam:AddRoleToInstanceProfile" + - "iam:AddUserToGroup" + - "iam:CreateGroup" + - "iam:CreateInstanceProfile" + - "iam:CreateUser" + - "iam:DeleteGroup" + - "iam:DeleteInstanceProfile" + - "iam:RemoveRoleFromInstanceProfile" + - "iam:RemoveUserFromGroup" + effect: Allow + resources: + - "*" + version: "2012-10-17" + iamRoleName: {{ .IamRoleName }} diff --git a/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-eks.tmpl b/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-eks.tmpl new file mode 100644 index 00000000..1cf496aa --- /dev/null +++ b/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-eks.tmpl @@ -0,0 +1,135 @@ +apiVersion: validation.spectrocloud.labs/v1alpha1 +kind: AwsValidator +metadata: + name: validator-plugin-aws-iam-eks + namespace: validator +spec: + auth: +{{ .Auth }} + defaultRegion: us-west-1 + iamRoleRules: + - iamPolicies: + - name: Controllers EKS Policy + statements: + - actions: + - "ssm:GetParameter" + effect: Allow + resources: + - "arn:*:ssm:*:*:parameter/aws/service/eks/optimized-ami/*" + - actions: + - "iam:CreateServiceLinkedRole" + condition: + type: StringLike + key: "iam:AWSServiceName" + values: + - eks.amazonaws.com + effect: Allow + resources: + - "arn:*:iam::*:role/aws-service-role/eks.amazonaws.com/AWSServiceRoleForAmazonEKS" + - actions: + - "iam:CreateServiceLinkedRole" + condition: + type: StringLike + key: "iam:AWSServiceName" + values: + - eks-nodegroup.amazonaws.com + effect: Allow + resources: + - "arn:*:iam::*:role/aws-service-role/eks-nodegroup.amazonaws.com/AWSServiceRoleForAmazonEKSNodegroup" + - actions: + - "iam:CreateServiceLinkedRole" + condition: + type: StringLike + key: "iam:AWSServiceName" + values: + - eks-fargate.amazonaws.com + effect: Allow + resources: + - "arn:*:iam::*:role/aws-service-role/eks-fargate-pods.amazonaws.com/AWSServiceRoleForAmazonEKSForFargate" + - actions: + - "iam:AddClientIDToOpenIDConnectProvider" + - "iam:CreateOpenIDConnectProvider" + - "iam:DeleteOpenIDConnectProvider" + - "iam:ListOpenIDConnectProviders" + - "iam:UpdateOpenIDConnectProviderThumbprint" + effect: Allow + resources: + - "*" + - actions: + - "iam:GetRole" + - "iam:ListAttachedRolePolicies" + - "iam:DetachRolePolicy" + - "iam:DeleteRole" + - "iam:CreateRole" + - "iam:TagRole" + - "iam:AttachRolePolicy" + effect: Allow + resources: + - "arn:*:iam::*:role/*" + - actions: + - "iam:GetPolicy" + effect: Allow + resources: + - "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" + - actions: + - "eks:DescribeCluster" + - "eks:ListClusters" + - "eks:CreateCluster" + - "eks:TagResource" + - "eks:UpdateClusterVersion" + - "eks:DeleteCluster" + - "eks:UpdateClusterConfig" + - "eks:UntagResource" + - "eks:UpdateNodegroupVersion" + - "eks:DescribeNodegroup" + - "eks:DeleteNodegroup" + - "eks:UpdateNodegroupConfig" + - "eks:CreateNodegroup" + - "eks:AssociateEncryptionConfig" + - "eks:ListIdentityProviderConfigs" + - "eks:AssociateIdentityProviderConfig" + - "eks:DescribeIdentityProviderConfig" + - "eks:DisassociateIdentityProviderConfig" + effect: Allow + resources: + - "arn:*:eks:*:*:cluster/*" + - "arn:*:eks:*:*:nodegroup/*/*/*" + - actions: + - "ec2:AssociateVpcCidrBlock" + - "ec2:DisassociateVpcCidrBlock" + - "eks:ListAddons" + - "eks:CreateAddon" + - "eks:DescribeAddonVersions" + - "eks:DescribeAddon" + - "eks:DeleteAddon" + - "eks:UpdateAddon" + - "eks:TagResource" + - "eks:DescribeFargateProfile" + - "eks:CreateFargateProfile" + - "eks:DeleteFargateProfile" + effect: Allow + resources: + - "*" + - actions: + - "iam:PassRole" + condition: + type: StringEquals + key: "iam:PassedToService" + values: + - eks.amazonaws.com + effect: Allow + resources: + - "*" + - actions: + - "kms:CreateGrant" + - "kms:DescribeKey" + condition: + type: "ForAnyValue:StringLike" + key: "kms:ResourceAliases" + values: + - "alias/cluster-api-provider-aws-*" + effect: Allow + resources: + - "*" + version: "2012-10-17" + iamRoleName: {{ .IamRoleName }} \ No newline at end of file diff --git a/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-minimal-dynamic.tmpl b/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-minimal-dynamic.tmpl new file mode 100644 index 00000000..49826d7e --- /dev/null +++ b/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-minimal-dynamic.tmpl @@ -0,0 +1,120 @@ +apiVersion: validation.spectrocloud.labs/v1alpha1 +kind: AwsValidator +metadata: + name: validator-plugin-aws-iam-minimal-dynamic + namespace: validator +spec: + auth: +{{ .Auth }} + defaultRegion: us-west-1 + iamRoleRules: + - iamPolicies: + - name: Minimal Policy - Dynamic Provisioning + statements: + - actions: + - "autoscaling:StartInstanceRefresh" + - "cloudformation:CreateStack" + - "cloudformation:DescribeStacks" + - "cloudformation:UpdateStack" + - "ec2:AllocateAddress" + - "ec2:AssociateAddress" + - "ec2:AssociateRouteTable" + - "ec2:AttachInternetGateway" + - "ec2:AuthorizeSecurityGroupIngress" + - "ec2:CreateInternetGateway" + - "ec2:CreateNatGateway" + - "ec2:CreateRoute" + - "ec2:CreateRouteTable" + - "ec2:CreateSecurityGroup" + - "ec2:CreateSubnet" + - "ec2:CreateTags" + - "ec2:CreateVpc" + - "ec2:DeleteInternetGateway" + - "ec2:DeleteNatGateway" + - "ec2:DeleteNetworkInterface" + - "ec2:DeleteRoute" + - "ec2:DeleteRouteTable" + - "ec2:DeleteSecurityGroup" + - "ec2:DeleteSubnet" + - "ec2:DeleteTags" + - "ec2:DeleteVpc" + - "ec2:DescribeAddresses" + - "ec2:DescribeAvailabilityZones" + - "ec2:DescribeDhcpOptions" + - "ec2:DescribeImages" + - "ec2:DescribeInstances" + - "ec2:DescribeInternetGateways" + - "ec2:DescribeIpamPools" + - "ec2:DescribeIpv6Pools" + - "ec2:DescribeKeyPairs" + - "ec2:DescribeNatGateways" + - "ec2:DescribeNetworkAcls" + - "ec2:DescribeNetworkInterfaceAttribute" + - "ec2:DescribeNetworkInterfaces" + - "ec2:DescribeRegions" + - "ec2:DescribeRouteTables" + - "ec2:DescribeSecurityGroups" + - "ec2:DescribeSubnets" + - "ec2:DescribeTags" + - "ec2:DescribeVolumes" + - "ec2:DescribeVpcAttribute" + - "ec2:DescribeVpcs" + - "ec2:DetachInternetGateway" + - "ec2:DisassociateAddress" + - "ec2:DisassociateRouteTable" + - "ec2:ModifyInstanceAttribute" + - "ec2:ModifyNetworkInterfaceAttribute" + - "ec2:ModifySubnetAttribute" + - "ec2:ModifyVpcAttribute" + - "ec2:ReleaseAddress" + - "ec2:ReplaceRoute" + - "ec2:RevokeSecurityGroupIngress" + - "ec2:RunInstances" + - "ec2:TerminateInstances" + - "ecr:BatchCheckLayerAvailability" + - "ecr:BatchGetImage" + - "ecr:DescribeRepositories" + - "ecr:GetAuthorizationToken" + - "ecr:GetDownloadUrlForLayer" + - "ecr:GetRepositoryPolicy" + - "ecr:ListImages" + - "eks:DescribeCluster" + - "eks:ListClusters" + - "elasticloadbalancing:ConfigureHealthCheck" + - "elasticloadbalancing:CreateLoadBalancer" + - "elasticloadbalancing:DeleteLoadBalancer" + - "elasticloadbalancing:DeregisterInstancesFromLoadBalancer" + - "elasticloadbalancing:DescribeLoadBalancerAttributes" + - "elasticloadbalancing:DescribeLoadBalancers" + - "elasticloadbalancing:DescribeTags" + - "elasticloadbalancing:DescribeTargetHealth" + - "elasticloadbalancing:ModifyLoadBalancerAttributes" + - "elasticloadbalancing:RegisterInstancesWithLoadBalancer" + - "iam:AddRoleToInstanceProfile" + - "iam:CreateInstanceProfile" + - "iam:DeleteInstanceProfile" + - "iam:GetInstanceProfile" + - "iam:PassRole" + - "iam:RemoveRoleFromInstanceProfile" + - "pricing:GetProducts" + - "secretsmanager:CreateSecret" + - "secretsmanager:DeleteSecret" + - "secretsmanager:GetSecretValue" + - "secretsmanager:TagResource" + - "ssm:UpdateInstanceInformation" + - "ssmmessages:CreateControlChannel" + - "ssmmessages:CreateDataChannel" + - "ssmmessages:OpenControlChannel" + - "ssmmessages:OpenDataChannel" + - "sts:AssumeRole" + - "tag:GetResources" + effect: Allow + resources: + - "*" + - actions: + - "iam:PassRole" + effect: Allow + resources: + - "arn:*:iam::*:role/*.cluster-api-provider-aws.sigs.k8s.io" + version: "2012-10-17" + iamRoleName: {{ .IamRoleName }} \ No newline at end of file diff --git a/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-minimal-static.tmpl b/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-minimal-static.tmpl new file mode 100644 index 00000000..8e6bda97 --- /dev/null +++ b/pkg/utils/embed/resources/validator/awsvalidator-iam-role-spectro-cloud-minimal-static.tmpl @@ -0,0 +1,94 @@ +apiVersion: validation.spectrocloud.labs/v1alpha1 +kind: AwsValidator +metadata: + name: validator-plugin-aws-iam-minimal-static + namespace: validator +spec: + auth: +{{ .Auth }} + defaultRegion: us-west-1 + iamRoleRules: + - iamPolicies: + - name: Minimal Policy - Static Provisioning + statements: + - actions: + - "autoscaling:StartInstanceRefresh" + - "cloudformation:CreateStack" + - "cloudformation:DescribeStacks" + - "cloudformation:UpdateStack" + - "ec2:AllocateAddress" + - "ec2:AssociateAddress" + - "ec2:AttachVolume" + - "ec2:AuthorizeSecurityGroupIngress" + - "ec2:CreateSecurityGroup" + - "ec2:CreateTags" + - "ec2:DeleteNetworkInterface" + - "ec2:DeleteSecurityGroup" + - "ec2:DescribeAddresses" + - "ec2:DescribeAvailabilityZones" + - "ec2:DescribeImages" + - "ec2:DescribeInstances" + - "ec2:DescribeKeyPairs" + - "ec2:DescribeNatGateways" + - "ec2:DescribeNetworkInterfaceAttribute" + - "ec2:DescribeNetworkInterfaces" + - "ec2:DescribeRegions" + - "ec2:DescribeRouteTables" + - "ec2:DescribeSecurityGroups" + - "ec2:DescribeSubnets" + - "ec2:DescribeVolumes" + - "ec2:DescribeVolumesModifications" + - "ec2:DescribeVpcAttribute" + - "ec2:DescribeVpcs" + - "ec2:DetachInternetGateway" + - "ec2:DetachVolume" + - "ec2:DisassociateAddress" + - "ec2:ModifyInstanceAttribute" + - "ec2:ModifyNetworkInterfaceAttribute" + - "ec2:ModifyVolume" + - "ec2:ModifyVpcAttribute" + - "ec2:ReleaseAddress" + - "ec2:RevokeSecurityGroupIngress" + - "ec2:RunInstances" + - "ec2:TerminateInstances" + - "eks:DescribeCluster" + - "eks:ListClusters" + - "elasticloadbalancing:ConfigureHealthCheck" + - "elasticloadbalancing:CreateLoadBalancer" + - "elasticloadbalancing:DeleteLoadBalancer" + - "elasticloadbalancing:DeregisterInstancesFromLoadBalancer" + - "elasticloadbalancing:DescribeLoadBalancerAttributes" + - "elasticloadbalancing:DescribeLoadBalancers" + - "elasticloadbalancing:DescribeTags" + - "elasticloadbalancing:DescribeTargetHealth" + - "elasticloadbalancing:DetachLoadBalancerFromSubnets" + - "elasticloadbalancing:ModifyLoadBalancerAttributes" + - "elasticloadbalancing:RegisterInstancesWithLoadBalancer" + - "iam:AddRoleToInstanceProfile" + - "iam:CreateInstanceProfile" + - "iam:DeleteInstanceProfile" + - "iam:GetInstanceProfile" + - "iam:RemoveRoleFromInstanceProfile" + - "pricing:GetProducts" + - "s3:GetEncryptionConfiguration" + - "secretsmanager:CreateSecret" + - "secretsmanager:DeleteSecret" + - "secretsmanager:GetSecretValue" + - "secretsmanager:TagResource" + - "ssm:UpdateInstanceInformation" + - "ssmmessages:CreateControlChannel" + - "ssmmessages:CreateDataChannel" + - "ssmmessages:OpenControlChannel" + - "ssmmessages:OpenDataChannel" + - "sts:AssumeRole" + - "tag:GetResources" + effect: Allow + resources: + - "*" + - actions: + - "iam:PassRole" + effect: Allow + resources: + - "arn:*:iam::*:role/*.cluster-api-provider-aws.sigs.k8s.io" + version: "2012-10-17" + iamRoleName: {{ .IamRoleName }} \ No newline at end of file diff --git a/pkg/utils/embed/resources/validator/validation-result.tmpl b/pkg/utils/embed/resources/validator/validation-result.tmpl new file mode 100644 index 00000000..eeb8fe31 --- /dev/null +++ b/pkg/utils/embed/resources/validator/validation-result.tmpl @@ -0,0 +1,4 @@ +{{- $values := .Values }} +{{- range $i, $k := .Keys }} +{{ printf "%s:" $k }} {{ index $values $i }} +{{- end }} diff --git a/pkg/utils/embed/resources/validator/validator-base-values.tmpl b/pkg/utils/embed/resources/validator/validator-base-values.tmpl new file mode 100644 index 00000000..aec0c11f --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-base-values.tmpl @@ -0,0 +1,173 @@ +controllerManager: + kubeRbacProxy: + args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=0 + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: gcr.io/kubebuilder/kube-rbac-proxy + tag: v0.14.1 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + manager: + args: + - --health-probe-bind-address=:8081 + - --leader-elect + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: {{ printf "%s/validator" .ImageRegistry }} + tag: {{ .Tag }} + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 10m + memory: 64Mi + sinkWebhookTimeout: 30s + replicas: 1 + serviceAccount: + annotations: {} +kubernetesClusterDomain: cluster.local +metricsService: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + type: ClusterIP + +{{- if or .ProxyConfig.Env.HTTPProxy .ProxyConfig.Env.HTTPSProxy }} +env: +- name: HTTP_PROXY + value: "{{ .ProxyConfig.Env.HTTPProxy }}" +- name: HTTPS_PROXY + value: "{{ .ProxyConfig.Env.HTTPSProxy }}" +- name: NO_PROXY + value: "{{ .ProxyConfig.Env.NoProxy }}" +{{- else }} +env: [] +{{- end }} + +{{- if .ProxyConfig.Enabled }} +proxy: + enabled: true + image: {{ printf "%s/validator-certs-init:1.0.0" .ImageRegistry }} + secretName: proxy-cert + createSecret: true + caCert: | + {{- range .ProxyCaCertData }} + {{ . }} + {{- end }} +{{- else }} +proxy: + enabled: false +{{- end }} + +{{- if .SinkConfig.Enabled }} +sink: + secretName: {{ .SinkConfig.SecretName }} + createSecret: {{ .SinkConfig.CreateSecret }} + + {{- if eq .SinkConfig.Type "alertmanager" }} + type: alertmanager + endpoint: {{ index .SinkConfig.Values "endpoint" }} + insecureSkipVerify: {{ index .SinkConfig.Values "insecureSkipVerify" }} + caCert: | +{{ index .SinkConfig.Values "caCert" | indent 4 }} + username: "{{ index .SinkConfig.Values "username" }}" + password: "{{ index .SinkConfig.Values "password" }}" + {{- end }} + + {{- if eq .SinkConfig.Type "slack" }} + type: slack + apiToken: {{ index .SinkConfig.Values "apiToken" }} + channelId: {{ index .SinkConfig.Values "channelId" }} + {{- end }} +{{- else }} +sink: {} +{{- end }} + +cleanup: + image: {{ printf "%s/spectro-cleanup:1.2.0" .ImageRegistry }} + grpcServerEnabled: true + hostname: validator-cleanup-service + port: 3006 + +pluginSecrets: + {{- if .AWSPlugin.Enabled }} + {{- if .AWSPlugin.Validator.Auth.Implicit }} + aws: {} + {{- else }} + aws: + secretName: {{ .AWSPlugin.Validator.Auth.SecretName }} + env: + AWS_ACCESS_KEY_ID: {{ .AWSPlugin.AccessKeyId }} + AWS_SECRET_ACCESS_KEY: {{ .AWSPlugin.SecretAccessKey }} + AWS_SESSION_TOKEN: {{ .AWSPlugin.SessionToken }} + {{- end }} + {{- end }} + + {{- if .VspherePlugin.Validator.Auth.SecretName }} + vSphere: + secretName: {{ .VspherePlugin.Validator.Auth.SecretName }} + username: {{ .VspherePlugin.Account.Username }} + password: {{ .VspherePlugin.Account.Password }} + vcenterServer: {{ .VspherePlugin.Account.VcenterServer }} + insecureSkipVerify: {{ .VspherePlugin.Account.Insecure }} + {{- else }} + vSphere: {} + {{- end }} + + {{- if .OCIPlugin.Enabled }} + oci: + {{- if gt (len .OCIPlugin.Secrets) 0 }} + auth: + {{- range .OCIPlugin.Secrets }} + - secretName: {{ .Name }} + username: {{ .Username }} + password: {{ .Password }} + {{- end }} + {{- else }} + auth: [] + {{- end }} + {{- if gt (len .OCIPlugin.PublicKeySecrets) 0 }} + pubKeys: + {{- range .OCIPlugin.PublicKeySecrets }} + - secretName: {{ .Name }} + {{- range $index, $pubkey := .Keys }} + pubkey-{{ $index }}.pub: | {{ $pubkey | nindent 8 }} + {{- end }} + {{- end }} + {{- else}} + pubKeys: [] + {{- end }} + {{- end }} + + {{- if .AzurePlugin.Enabled }} + {{- if .AzurePlugin.Validator.Auth.Implicit }} + azure: {} + {{- else }} + azure: + secretName: {{ .AzurePlugin.Validator.Auth.SecretName }} + env: + AZURE_TENANT_ID: {{ .AzurePlugin.TenantID }} + AZURE_CLIENT_ID: {{ .AzurePlugin.ClientID }} + AZURE_CLIENT_SECRET: {{ .AzurePlugin.ClientSecret }} + {{- end }} + {{- end }} diff --git a/pkg/utils/embed/resources/validator/validator-plugin-aws-values.tmpl b/pkg/utils/embed/resources/validator/validator-plugin-aws-values.tmpl new file mode 100644 index 00000000..97c13636 --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-plugin-aws-values.tmpl @@ -0,0 +1,60 @@ +controllerManager: + kubeRbacProxy: + args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=0 + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: gcr.io/kubebuilder/kube-rbac-proxy + tag: v0.14.1 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + manager: + args: + - --health-probe-bind-address=:8081 + - --leader-elect + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: {{ printf "%s/validator-plugin-aws" .ImageRegistry }} + tag: {{ .Config.Release.Chart.Version }} + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + replicas: 1 + serviceAccount: + annotations: {} +kubernetesClusterDomain: cluster.local +metricsService: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + type: ClusterIP +auth: + {{- if .Config.Validator.Auth.Implicit }} + secret: {} + {{- else }} + secret: + secretName: {{ .Config.Validator.Auth.SecretName }} + {{- end }} + serviceAccountName: "{{ .Config.ServiceAccountName }}" diff --git a/pkg/utils/embed/resources/validator/validator-plugin-azure-values.tmpl b/pkg/utils/embed/resources/validator/validator-plugin-azure-values.tmpl new file mode 100644 index 00000000..96a0fd36 --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-plugin-azure-values.tmpl @@ -0,0 +1,52 @@ +controllerManager: + kubeRbacProxy: + args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=0 + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: gcr.io/kubebuilder/kube-rbac-proxy + tag: v0.15.0 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + manager: + args: + - --health-probe-bind-address=:8081 + - --leader-elect + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: {{ printf "%s/validator-plugin-azure" .ImageRegistry }} + tag: {{ .Config.Release.Chart.Version }} + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + replicas: 1 + serviceAccount: + annotations: {} +kubernetesClusterDomain: cluster.local +metricsService: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + type: ClusterIP diff --git a/pkg/utils/embed/resources/validator/validator-plugin-network-values.tmpl b/pkg/utils/embed/resources/validator/validator-plugin-network-values.tmpl new file mode 100644 index 00000000..8fc8da8f --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-plugin-network-values.tmpl @@ -0,0 +1,54 @@ +controllerManager: + kubeRbacProxy: + args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=0 + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: gcr.io/kubebuilder/kube-rbac-proxy + tag: v0.14.1 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + manager: + args: + - --health-probe-bind-address=:8081 + - --leader-elect + containerSecurityContext: + allowPrivilegeEscalation: true + capabilities: + add: + - NET_RAW + drop: + - ALL + image: + repository: {{ printf "%s/validator-plugin-network" .ImageRegistry }} + tag: {{ .Tag }} + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + replicas: 1 + serviceAccount: + annotations: {} +kubernetesClusterDomain: cluster.local +metricsService: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + type: ClusterIP diff --git a/pkg/utils/embed/resources/validator/validator-plugin-oci-values.tmpl b/pkg/utils/embed/resources/validator/validator-plugin-oci-values.tmpl new file mode 100644 index 00000000..b4adccfe --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-plugin-oci-values.tmpl @@ -0,0 +1,52 @@ +controllerManager: + kubeRbacProxy: + args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=0 + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: gcr.io/kubebuilder/kube-rbac-proxy + tag: v0.15.0 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + manager: + args: + - --health-probe-bind-address=:8081 + - --leader-elect + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: {{ printf "%s/validator-plugin-oci" .ImageRegistry }} + tag: {{ .Config.Release.Chart.Version }} + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + replicas: 1 + serviceAccount: + annotations: {} +kubernetesClusterDomain: cluster.local +metricsService: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + type: ClusterIP diff --git a/pkg/utils/embed/resources/validator/validator-plugin-vsphere-values.tmpl b/pkg/utils/embed/resources/validator/validator-plugin-vsphere-values.tmpl new file mode 100644 index 00000000..3bb2d96f --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-plugin-vsphere-values.tmpl @@ -0,0 +1,54 @@ +controllerManager: + kubeRbacProxy: + args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=0 + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: gcr.io/kubebuilder/kube-rbac-proxy + tag: v0.14.1 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + manager: + args: + - --health-probe-bind-address=:8081 + - --leader-elect + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: {{ printf "%s/validator-plugin-vsphere" .ImageRegistry }} + tag: {{ .Config.Release.Chart.Version }} + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + replicas: 1 + serviceAccount: + annotations: {} +kubernetesClusterDomain: cluster.local +metricsService: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + type: ClusterIP +auth: + secretName: {{ .Config.Validator.Auth.SecretName }} diff --git a/pkg/utils/embed/resources/validator/validator-rules-aws.tmpl b/pkg/utils/embed/resources/validator/validator-rules-aws.tmpl new file mode 100644 index 00000000..00cbd687 --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-rules-aws.tmpl @@ -0,0 +1,7 @@ +apiVersion: validation.spectrocloud.labs/v1alpha1 +kind: AwsValidator +metadata: + name: {{ .Name }} + namespace: {{ .Namespace }} +spec: +{{ .Spec }} \ No newline at end of file diff --git a/pkg/utils/embed/resources/validator/validator-rules-azure.tmpl b/pkg/utils/embed/resources/validator/validator-rules-azure.tmpl new file mode 100644 index 00000000..46fd1265 --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-rules-azure.tmpl @@ -0,0 +1,7 @@ +apiVersion: validation.spectrocloud.labs/v1alpha1 +kind: AzureValidator +metadata: + name: {{ .Name }} + namespace: {{ .Namespace }} +spec: +{{ .Spec }} diff --git a/pkg/utils/embed/resources/validator/validator-rules-network.tmpl b/pkg/utils/embed/resources/validator/validator-rules-network.tmpl new file mode 100644 index 00000000..f35f7eb0 --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-rules-network.tmpl @@ -0,0 +1,7 @@ +apiVersion: validation.spectrocloud.labs/v1alpha1 +kind: NetworkValidator +metadata: + name: {{ .Name }} + namespace: {{ .Namespace }} +spec: +{{ .Spec }} \ No newline at end of file diff --git a/pkg/utils/embed/resources/validator/validator-rules-oci.tmpl b/pkg/utils/embed/resources/validator/validator-rules-oci.tmpl new file mode 100644 index 00000000..48ff41c2 --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-rules-oci.tmpl @@ -0,0 +1,7 @@ +apiVersion: validation.spectrocloud.labs/v1alpha1 +kind: OciValidator +metadata: + name: {{ .Name }} + namespace: {{ .Namespace }} +spec: +{{ .Spec }} diff --git a/pkg/utils/embed/resources/validator/validator-rules-vsphere.tmpl b/pkg/utils/embed/resources/validator/validator-rules-vsphere.tmpl new file mode 100644 index 00000000..0d1ba635 --- /dev/null +++ b/pkg/utils/embed/resources/validator/validator-rules-vsphere.tmpl @@ -0,0 +1,7 @@ +apiVersion: validation.spectrocloud.labs/v1alpha1 +kind: VsphereValidator +metadata: + name: {{ .Name }} + namespace: {{ .Namespace }} +spec: +{{ .Spec }} diff --git a/pkg/utils/embed/resources/validator/vsphere-root-level-privileges-7.0.yaml b/pkg/utils/embed/resources/validator/vsphere-root-level-privileges-7.0.yaml new file mode 100644 index 00000000..d9b37549 --- /dev/null +++ b/pkg/utils/embed/resources/validator/vsphere-root-level-privileges-7.0.yaml @@ -0,0 +1,108 @@ +privilegeIds: + - Cns.Searchable + - InventoryService.Tagging.DeleteTag + - InventoryService.Tagging.CreateTag + - InventoryService.Tagging.AttachTag + - InventoryService.Tagging.EditTag + - Network.Assign + - Resource.ApplyRecommendation + - Resource.AssignVMToPool + - Resource.ColdMigrate + - Resource.HotMigrate + - Resource.QueryVMotion + - Sessions.ValidateSession + - StorageProfile.View + - StorageViews.ConfigureService + - StorageViews.View + - Datastore.AllocateSpace + - Datastore.Browse + - Datastore.FileManagement + - Datastore.DeleteFile + - Datastore.UpdateVirtualMachineFiles + - Datastore.UpdateVirtualMachineMetadata + - Folder.Create + - Folder.Delete + - Folder.Move + - Folder.Rename + - Host.Local.ReconfigVM + - Task.Create + - Task.Update + - VApp.Export + - VApp.Import + - VApp.ExtractOvfEnvironment + - VApp.ApplicationConfig + - VApp.InstanceConfig + - VirtualMachine.Config.AddRemoveDevice + - VirtualMachine.Inventory.Delete + - VirtualMachine.Provisioning.Clone + - VirtualMachine.State.CreateSnapshot + - VirtualMachine.Provisioning.FileRandomAccess + - VirtualMachine.Config.Annotation + - VirtualMachine.Config.RawDevice + - VirtualMachine.Config.ToggleForkParent + - VirtualMachine.Namespace.ModifyContent + - VirtualMachine.Interact.SetCDMedia + - VirtualMachine.Provisioning.MarkAsVM + - VirtualMachine.Config.RemoveDisk + - VirtualMachine.Config.EditDevice + - VirtualMachine.Hbr.ConfigureReplication + - VirtualMachine.Config.Resource + - VirtualMachine.Config.CPUCount + - VirtualMachine.Config.AddNewDisk + - VirtualMachine.State.RevertToSnapshot + - VirtualMachine.Config.Rename + - VirtualMachine.GuestOperations.ModifyAliases + - VirtualMachine.Config.ReloadFromPath + - VirtualMachine.Config.ResetGuestInfo + - VirtualMachine.Interact.Backup + - VirtualMachine.Provisioning.DeployTemplate + - VirtualMachine.Config.ChangeTracking + - VirtualMachine.State.RenameSnapshot + - VirtualMachine.Provisioning.ModifyCustSpecs + - VirtualMachine.Config.DiskLease + - VirtualMachine.Config.HostUSBDevice + - VirtualMachine.GuestOperations.Query + - VirtualMachine.Provisioning.CreateTemplateFromVM + - VirtualMachine.Provisioning.Customize + - VirtualMachine.Provisioning.GetVmFiles + - VirtualMachine.Provisioning.PutVmFiles + - VirtualMachine.GuestOperations.Execute + - VirtualMachine.Namespace.EventNotify + - VirtualMachine.Interact.SetFloppyMedia + - VirtualMachine.Namespace.Management + - VirtualMachine.Provisioning.DiskRandomAccess + - VirtualMachine.Inventory.Create + - VirtualMachine.Interact.PowerOn + - VirtualMachine.Config.AdvancedConfig + - VirtualMachine.Config.ManagedBy + - VirtualMachine.Interact.PutUsbScanCodes + - VirtualMachine.Config.DiskExtend + - VirtualMachine.Inventory.Unregister + - VirtualMachine.Config.UpgradeVirtualHardware + - VirtualMachine.Interact.PowerOff + - VirtualMachine.State.RemoveSnapshot + - VirtualMachine.Provisioning.ReadCustSpecs + - VirtualMachine.Provisioning.CloneTemplate + - VirtualMachine.Inventory.Register + - VirtualMachine.Inventory.Move + - VirtualMachine.GuestOperations.Modify + - VirtualMachine.Config.Memory + - VirtualMachine.Provisioning.PromoteDisks + - VirtualMachine.Config.QueryFTCompatibility + - VirtualMachine.Provisioning.MarkAsTemplate + - VirtualMachine.Config.QueryUnownedFiles + - VirtualMachine.Config.SwapPlacement + - VirtualMachine.Namespace.ReadContent + - VirtualMachine.Namespace.Event + - VirtualMachine.Hbr.ReplicaManagement + - VirtualMachine.Hbr.MonitorReplication + - VirtualMachine.Config.MksControl + - VirtualMachine.Interact.DeviceConnection + - VirtualMachine.Config.AddExistingDisk + - VirtualMachine.Config.Settings + - VirtualMachine.Inventory.CreateFromExisting + - VirtualMachine.Namespace.Query + - VirtualMachine.Interact.ConsoleInteract + - VirtualMachine.GuestOperations.QueryAliases + - VirtualMachine.Provisioning.DiskRandomRead + - Vsan.Cluster.ShallowRekey diff --git a/pkg/utils/embed/resources/validator/vsphere-root-level-privileges-8.0.yaml b/pkg/utils/embed/resources/validator/vsphere-root-level-privileges-8.0.yaml new file mode 100644 index 00000000..d9b37549 --- /dev/null +++ b/pkg/utils/embed/resources/validator/vsphere-root-level-privileges-8.0.yaml @@ -0,0 +1,108 @@ +privilegeIds: + - Cns.Searchable + - InventoryService.Tagging.DeleteTag + - InventoryService.Tagging.CreateTag + - InventoryService.Tagging.AttachTag + - InventoryService.Tagging.EditTag + - Network.Assign + - Resource.ApplyRecommendation + - Resource.AssignVMToPool + - Resource.ColdMigrate + - Resource.HotMigrate + - Resource.QueryVMotion + - Sessions.ValidateSession + - StorageProfile.View + - StorageViews.ConfigureService + - StorageViews.View + - Datastore.AllocateSpace + - Datastore.Browse + - Datastore.FileManagement + - Datastore.DeleteFile + - Datastore.UpdateVirtualMachineFiles + - Datastore.UpdateVirtualMachineMetadata + - Folder.Create + - Folder.Delete + - Folder.Move + - Folder.Rename + - Host.Local.ReconfigVM + - Task.Create + - Task.Update + - VApp.Export + - VApp.Import + - VApp.ExtractOvfEnvironment + - VApp.ApplicationConfig + - VApp.InstanceConfig + - VirtualMachine.Config.AddRemoveDevice + - VirtualMachine.Inventory.Delete + - VirtualMachine.Provisioning.Clone + - VirtualMachine.State.CreateSnapshot + - VirtualMachine.Provisioning.FileRandomAccess + - VirtualMachine.Config.Annotation + - VirtualMachine.Config.RawDevice + - VirtualMachine.Config.ToggleForkParent + - VirtualMachine.Namespace.ModifyContent + - VirtualMachine.Interact.SetCDMedia + - VirtualMachine.Provisioning.MarkAsVM + - VirtualMachine.Config.RemoveDisk + - VirtualMachine.Config.EditDevice + - VirtualMachine.Hbr.ConfigureReplication + - VirtualMachine.Config.Resource + - VirtualMachine.Config.CPUCount + - VirtualMachine.Config.AddNewDisk + - VirtualMachine.State.RevertToSnapshot + - VirtualMachine.Config.Rename + - VirtualMachine.GuestOperations.ModifyAliases + - VirtualMachine.Config.ReloadFromPath + - VirtualMachine.Config.ResetGuestInfo + - VirtualMachine.Interact.Backup + - VirtualMachine.Provisioning.DeployTemplate + - VirtualMachine.Config.ChangeTracking + - VirtualMachine.State.RenameSnapshot + - VirtualMachine.Provisioning.ModifyCustSpecs + - VirtualMachine.Config.DiskLease + - VirtualMachine.Config.HostUSBDevice + - VirtualMachine.GuestOperations.Query + - VirtualMachine.Provisioning.CreateTemplateFromVM + - VirtualMachine.Provisioning.Customize + - VirtualMachine.Provisioning.GetVmFiles + - VirtualMachine.Provisioning.PutVmFiles + - VirtualMachine.GuestOperations.Execute + - VirtualMachine.Namespace.EventNotify + - VirtualMachine.Interact.SetFloppyMedia + - VirtualMachine.Namespace.Management + - VirtualMachine.Provisioning.DiskRandomAccess + - VirtualMachine.Inventory.Create + - VirtualMachine.Interact.PowerOn + - VirtualMachine.Config.AdvancedConfig + - VirtualMachine.Config.ManagedBy + - VirtualMachine.Interact.PutUsbScanCodes + - VirtualMachine.Config.DiskExtend + - VirtualMachine.Inventory.Unregister + - VirtualMachine.Config.UpgradeVirtualHardware + - VirtualMachine.Interact.PowerOff + - VirtualMachine.State.RemoveSnapshot + - VirtualMachine.Provisioning.ReadCustSpecs + - VirtualMachine.Provisioning.CloneTemplate + - VirtualMachine.Inventory.Register + - VirtualMachine.Inventory.Move + - VirtualMachine.GuestOperations.Modify + - VirtualMachine.Config.Memory + - VirtualMachine.Provisioning.PromoteDisks + - VirtualMachine.Config.QueryFTCompatibility + - VirtualMachine.Provisioning.MarkAsTemplate + - VirtualMachine.Config.QueryUnownedFiles + - VirtualMachine.Config.SwapPlacement + - VirtualMachine.Namespace.ReadContent + - VirtualMachine.Namespace.Event + - VirtualMachine.Hbr.ReplicaManagement + - VirtualMachine.Hbr.MonitorReplication + - VirtualMachine.Config.MksControl + - VirtualMachine.Interact.DeviceConnection + - VirtualMachine.Config.AddExistingDisk + - VirtualMachine.Config.Settings + - VirtualMachine.Inventory.CreateFromExisting + - VirtualMachine.Namespace.Query + - VirtualMachine.Interact.ConsoleInteract + - VirtualMachine.GuestOperations.QueryAliases + - VirtualMachine.Provisioning.DiskRandomRead + - Vsan.Cluster.ShallowRekey diff --git a/pkg/utils/embed/resources/validator/vsphere-root-level-privileges-all.yaml b/pkg/utils/embed/resources/validator/vsphere-root-level-privileges-all.yaml new file mode 100644 index 00000000..ad555c5f --- /dev/null +++ b/pkg/utils/embed/resources/validator/vsphere-root-level-privileges-all.yaml @@ -0,0 +1,431 @@ +privilegeIds: + - System.Anonymous + - System.Read + - System.View + - Folder.Create + - Datastore.UpdateVirtualMachineMetadata + - Host.Config.PciPassthru + - VcIntegrity.HardwareCompatibility.Read + - ExternalStatsProvider.Unregister + - VirtualMachine.Config.RawDevice + - VirtualMachine.Namespace.ModifyContent + - VcIntegrity.ClusterConfiguration.Remediate + - ContentLibrary.ManageRegistry + - Global.SetCustomField + - TenantManager.Query + - VirtualMachine.Provisioning.MarkAsVM + - InventoryService.Tagging.AttachTag + - Extension.Update + - InventoryService.Tagging.ModifyUsedByForCategory + - Task.Update + - OvfManager.OvfConsumerAccess + - Cns.Searchable + - Host.Config.GuestStore + - VApp.ApplicationConfig + - VApp.Delete + - ContentLibrary.ManageClusterRegistryResource + - Infraprofile.Write + - DVSwitch.HostOp + - VirtualMachine.Config.ResetGuestInfo + - Cryptographer.ManageEncryptionPolicy + - TrustedAdmin.ReadTrustedHosts + - Host.Local.ManageUserGroups + - ContentLibrary.EvictLibraryItem + - VirtualMachine.Provisioning.Customize + - InventoryService.Tagging.DeleteCategory + - Nsx.Manage + - vStats.QueryAny + - Folder.Delete + - VirtualMachine.GuestOperations.Execute + - VirtualMachine.Interact.SetFloppyMedia + - Cryptographer.RegisterVM + - TrustedAdmin.ReadKMSTrust + - VirtualMachine.Provisioning.DiskRandomAccess + - VApp.Create + - VcIntegrity.lifecycleSoftwareSpecification.Read + - DVPortgroup.Modify + - Global.Health + - VirtualMachine.Interact.Pause + - VcIdentityProviders.Read + - TrustedAdmin.ConfigureHostMetadata + - ContentLibrary.TypeIntrospection + - Cryptographer.Encrypt + - Host.Config.DateTime + - Datastore.Delete + - VirtualMachine.Config.ManagedBy + - VcIntegrity.ClusterConfiguration.Modify + - Global.ScriptAction + - Global.DisableMethods + - Alarm.Create + - Profile.Edit + - VirtualMachine.Interact.EnableSecondary + - ContentLibrary.GetConfiguration + - Host.Config.AdvancedConfig + - vStats.CollectAny + - Host.Config.Resources + - ScheduledTask.Create + - CertificateManagement.Manage + - Datastore.AllocateSpace + - ContentLibrary.DeleteCertFromTrustStore + - Namespaces.Backup + - Performance.ModifyIntervals + - VApp.PowerOn + - Alarm.Edit + - EAM.Modify + - VirtualMachine.Config.Memory + - Namespaces.ManageDisks + - Host.Inventory.DeleteCluster + - Alarm.Delete + - VirtualMachine.Provisioning.PromoteDisks + - vStats.Settings + - VirtualMachine.Config.QueryUnownedFiles + - VirtualMachine.Interact.TerminateFaultTolerantVM + - VirtualMachine.Namespace.ReadContent + - VApp.Clone + - VirtualMachine.Interact.SuspendToMemory + - Certificate.Manage + - Host.Config.SystemManagement + - VirtualMachine.Hbr.ReplicaManagement + - VcIntegrity.ClusterConfiguration.Export + - InventoryService.Tagging.EditTag + - VirtualMachine.Interact.DeviceConnection + - VirtualMachine.Config.AddExistingDisk + - ContentLibrary.UpdateSession + - AutoDeploy.Host.AssociateMachine + - Profile.Create + - VirtualMachine.Inventory.CreateFromExisting + - VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus + - VApp.Export + - Host.Inventory.AddHostToCluster + - VirtualMachine.Namespace.Query + - ScheduledTask.Delete + - Datacenter.Rename + - DVSwitch.PortSetting + - VirtualMachine.Interact.ConsoleInteract + - VirtualMachine.Provisioning.DiskRandomRead + - Authorization.ModifyRoles + - DVPortgroup.Delete + - VirtualMachine.Inventory.Delete + - VcIntegrity.lifecycleHealth.Write + - ContentLibrary.AddSubscription + - ContentLibrary.DeleteSubscription + - Resource.QueryVMotion + - VirtualMachine.Provisioning.Clone + - Host.Hbr.HbrManagement + - VirtualMachine.State.CreateSnapshot + - VirtualMachine.Config.Annotation + - VirtualMachine.Interact.SetCDMedia + - ContentLibrary.UpdateLibrary + - DVSwitch.Vspan + - TransferService.Monitor + - InventoryService.Tagging.ModifyUsedByForTag + - VirtualMachine.Config.EditDevice + - VcIntegrity.General.com.vmware.vcIntegrity.Configure + - ScheduledTask.Run + - IntercomNamespace.Read + - Host.Config.Patch + - Cryptographer.ReadKeyServersInfo + - Cryptographer.Recrypt + - VirtualMachine.Interact.Suspend + - ContentLibrary.SyncLibraryItem + - Host.Config.Maintenance + - VirtualMachine.Interact.Replay + - Global.Proxy + - VirtualMachine.GuestOperations.ModifyAliases + - VApp.Import + - AutoDeploy.Rule.Create + - Trust.Manage + - Global.LogEvent + - Extension.Unregister + - VirtualMachine.Provisioning.DeployTemplate + - Datacenter.IpPoolReleaseIp + - Host.Cim.CimInteraction + - Host.Config.NetService + - VirtualMachine.State.RenameSnapshot + - VirtualMachine.Interact.Record + - Host.Inventory.CreateCluster + - VcIntegrity.HardwareCompatibility.Write + - HLM.Manage + - VApp.PowerOff + - VirtualMachine.Config.DiskLease + - VirtualMachine.Config.HostUSBDevice + - ContentLibrary.UpdateSubscribedLibrary + - VApp.ResourceConfig + - Vsan.Cluster.ShallowRekey + - EAM.Config + - VcIntegrity.ClusterConfiguration.View + - VirtualMachine.Provisioning.GetVmFiles + - DVSwitch.Delete + - VirtualMachine.Interact.SESparseMaintenance + - VirtualMachine.Provisioning.PutVmFiles + - DVPortgroup.Ipfix + - VirtualMachine.Namespace.EventNotify + - VirtualMachine.Namespace.Management + - Host.Config.Quarantine + - Resource.MovePool + - VApp.PullFromUrls + - AutoDeploy.RuleSet.Activate + - VirtualMachine.Interact.PowerOn + - Sessions.GlobalMessage + - Namespaces.Upgrade + - SupervisorServices.Manage + - Datastore.Rename + - Global.ServiceManagers + - VirtualMachine.Interact.PutUsbScanCodes + - ContentLibrary.UpdateLibraryItem + - Host.Config.Power + - Host.Config.Snmp + - DVPortgroup.PolicyOp + - Namespaces.Manage + - ContentLibrary.DeleteLibraryItem + - vService.DestroyDependency + - ServiceAccount.ManageAccount + - VirtualMachine.Interact.PowerOff + - Host.Config.AutoStart + - VirtualMachine.Interact.MakePrimary + - InventoryService.Tagging.CreateTag + - Global.EnableMethods + - vService.UpdateDependency + - Infraprofile.Read + - InventoryService.Tagging.DeleteTag + - VirtualMachine.Provisioning.ReadCustSpecs + - CertificateAuthority.Manage + - ContentLibrary.AddCertToTrustStore + - ContentLibrary.ManageRegistryProject + - VcIntegrity.lifecycleSoftwareRemediation.Write + - AutoDeploy.Rule.Edit + - ServiceAccount.Administer + - Datastore.Move + - Cryptographer.Access + - Datacenter.Reconfigure + - TenantManager.Update + - DVPortgroup.ScopeOp + - VirtualMachine.Hbr.MonitorReplication + - Host.Config.Image + - TrustedAdmin.RetrieveTPMHostCertificates + - ContentLibrary.SyncLibrary + - ContentLibrary.UpdateConfiguration + - Host.Config.Network + - vSphereDataProtection.Protection + - Host.Inventory.RenameCluster + - Resource.ApplyRecommendation + - Host.Local.DeleteVM + - Cryptographer.ManageKeys + - VcIntegrity.lifecycleHealth.Read + - VApp.Unregister + - AutoDeploy.RuleSet.Edit + - ContentLibrary.CheckOutTemplate + - Global.Diagnostics + - VcIntegrity.Updates.com.vmware.vcIntegrity.Stage + - GuestDataPublisher.GetData + - Datacenter.Move + - VApp.Suspend + - ContentLibrary.PublishLibraryItem + - Sessions.TerminateSession + - VirtualMachine.Provisioning.FileRandomAccess + - Network.Move + - Folder.Move + - Host.Config.Nvdimm + - Namespaces.SelfServiceManage + - Datastore.UpdateVirtualMachineFiles + - Global.Licenses + - Alarm.Acknowledge + - HealthUpdateProvider.Register + - VirtualMachine.Config.RemoveDisk + - ContentLibrary.UpdateSubscription + - Nsx.ModifyAll + - Alarm.DisableActions + - VirtualMachine.Config.Resource + - Host.Config.Connection + - VirtualMachine.Config.Rename + - VcIntegrity.lifecycleGeneral.Write + - VirtualMachine.Config.ReloadFromPath + - VApp.Rename + - ContentLibrary.DownloadSession + - Resource.HotMigrate + - VirtualMachine.Interact.Backup + - vService.ReconfigureDependency + - VApp.AssignVApp + - vSphereDataProtection.Recovery + - ContentLibrary.CheckInTemplate + - Namespaces.Configure + - VirtualMachine.GuestOperations.Query + - Host.Local.InstallAgent + - Profile.Export + - Observability.Admin + - VcIntegrity.lifecycleSoftwareRemediation.Read + - ExternalStatsProvider.Update + - TrustedAdmin.ReadAttestingSSO + - DVSwitch.ResourceManagement + - Datacenter.Delete + - VirtualMachine.Interact.ToolsInstall + - Authorization.ModifyPermissions + - Global.SystemTag + - DVSwitch.Move + - TrustedAdmin.ConfigureHostCertificates + - TrustedAdmin.ConfigureTokenConversionPolicy + - Datastore.Config + - VirtualMachine.Config.AdvancedConfig + - Datastore.DeleteFile + - Host.Local.ReconfigVM + - VcLifecycle.View + - Global.CancelTask + - VirtualMachine.Config.DiskExtend + - VcIntegrity.lifecycleSoftwareSpecification.Write + - ContentLibrary.DeleteLocalLibrary + - Cryptographer.RegisterHost + - VirtualMachine.Inventory.Unregister + - AutoDeploy.Profile.Edit + - VirtualMachine.Config.UpgradeVirtualHardware + - Trust.Administer + - Resource.EditPool + - Cryptographer.Clone + - DVSwitch.PortConfig + - CertificateAuthority.Administer + - DVSwitch.Ipfix + - HealthUpdateProvider.Update + - VcIntegrity.lifecycleSettings.Write + - SettingsStore.Manage + - InventoryService.Tagging.ObjectAttachable + - Host.Inventory.MoveHost + - Network.Assign + - VirtualMachine.Inventory.Move + - Authorization.ModifyPrivileges + - VirtualMachine.GuestOperations.Modify + - VApp.AssignVM + - StorageProfile.Update + - DVSwitch.PolicyOp + - Network.Config + - Cryptographer.ManageKeyServers + - VirtualMachine.Config.QueryFTCompatibility + - ContentLibrary.EvictSubscribedLibrary + - Host.Inventory.AddStandaloneHost + - DVPortgroup.Create + - Datastore.FileManagement + - VcIntegrity.Baseline.com.vmware.vcIntegrity.AssignBaselines + - VcIdentityProviders.Create + - VirtualMachine.Config.MksControl + - Resource.AssignVMToPool + - VcIntegrity.Updates.com.vmware.vcIntegrity.Scan + - ContentLibrary.DeleteSubscribedLibrary + - Host.Inventory.MoveCluster + - VirtualMachine.Interact.CreateSecondary + - Host.Inventory.EditCluster + - StoragePod.Config + - StorageProfile.View + - VcLifecycle.Upgrade + - TrustedAdmin.ManageAttestingSSO + - VirtualMachine.Interact.Reset + - ComputePolicy.Manage + - Nsx.Read + - Host.Config.Firmware + - Sessions.ValidateSession + - Host.Config.HyperThreading + - ContentLibrary.ImportStorage + - ExternalStatsProvider.Register + - VirtualMachine.Config.AddRemoveDevice + - Host.Config.Memory + - Network.Delete + - Plugin.Management + - VirtualMachineClasses.Manage + - TransferService.Manage + - Global.ManageCustomFields + - StorageViews.ConfigureService + - CertificateManagement.Administer + - VirtualMachine.Config.ToggleForkParent + - Resource.CreatePool + - Cryptographer.EncryptNew + - Profile.Delete + - TrustedAdmin.ManageKMSTrust + - VirtualMachine.Interact.AnswerQuestion + - Host.Config.ProductLocker + - VirtualMachine.Interact.TurnOffFaultTolerance + - VirtualMachine.Hbr.ConfigureReplication + - Cryptographer.Migrate + - VApp.InstanceConfig + - VirtualMachine.Config.CPUCount + - VirtualMachine.Config.AddNewDisk + - TrustedAdmin.RetrieveHostMetadata + - VirtualMachine.State.RevertToSnapshot + - Datastore.Browse + - VApp.ExtractOvfEnvironment + - HLM.Create + - ScheduledTask.Edit + - VirtualMachine.Config.ChangeTracking + - VirtualMachine.Interact.DefragmentAllDisks + - Profile.Clear + - VirtualMachine.Provisioning.ModifyCustSpecs + - VirtualMachine.Provisioning.CreateTemplateFromVM + - ContentLibrary.CreateSubscribedLibrary + - Namespaces.ListAccess + - Global.CapacityPlanning + - Alarm.ToggleEnableOnEntity + - Host.Local.CreateVM + - Authorization.ReassignRolePermissions + - EAM.View + - Global.VCServer + - Task.Create + - VirtualMachine.Inventory.Create + - AutoDeploy.Rule.Delete + - VcIntegrity.lifecycleSettings.Read + - VcIntegrity.Baseline.com.vmware.vcIntegrity.ManageBaselines + - Sessions.ImpersonateUser + - Alarm.SetStatus + - VirtualMachine.Interact.DnD + - VcIntegrity.lifecycleGeneral.Read + - VcIntegrity.Updates.com.vmware.vcIntegrity.Remediate + - Namespaces.ManageCapabilities + - Global.Settings + - vService.CreateDependency + - Datacenter.IpPoolConfig + - Global.GlobalTag + - VcIntegrity.FileUpload.com.vmware.vcIntegrity.ImportFile + - DVSwitch.Modify + - HealthUpdateProvider.Unregister + - VcIdentityProviders.Manage + - Resource.ColdMigrate + - TrustedAdmin.ManageTrustedHosts + - VcIntegrity.lifecycleDepots.Delete + - Resource.RenamePool + - Host.Inventory.ManageClusterLifecyle + - DVSwitch.Create + - VirtualMachine.Interact.CreateScreenshot + - VirtualMachine.State.RemoveSnapshot + - AutoDeploy.Profile.Create + - IntercomNamespace.Write + - ContentLibrary.UpdateLocalLibrary + - InventoryService.Tagging.EditCategory + - VirtualMachine.Provisioning.CloneTemplate + - VirtualMachine.Inventory.Register + - VApp.Move + - ServiceAccount.ManagePassword + - VirtualMachine.Interact.GuestControl + - InventoryService.Tagging.CreateCategory + - VApp.ManagedByConfig + - VirtualMachine.Provisioning.MarkAsTemplate + - Datacenter.IpPoolQueryAllocations + - VirtualMachine.Config.SwapPlacement + - VirtualMachine.Namespace.Event + - Resource.DeletePool + - ContentLibrary.PublishLibrary + - Datacenter.Create + - VApp.AssignResourcePool + - vSphereClient.UtilizeVerificationCode + - Host.Config.Settings + - Folder.Rename + - Cryptographer.Decrypt + - Extension.Register + - Cryptographer.AddDisk + - TrustedAdmin.ReadStsInfo + - VirtualMachine.Config.Settings + - VirtualMachine.Interact.DisableSecondary + - ContentLibrary.ProbeSubscription + - ContentLibrary.ReadStorage + - Host.Config.AuthenticationStore + - StorageViews.View + - Profile.View + - ContentLibrary.AddLibraryItem + - ContentLibrary.CreateLocalLibrary + - Resource.AssignVAppToPool + - VirtualMachine.GuestOperations.QueryAliases + - Host.Inventory.RemoveHostFromCluster diff --git a/pkg/utils/embed/resources/validator/vsphere-spectro-cloud-tags.yaml b/pkg/utils/embed/resources/validator/vsphere-spectro-cloud-tags.yaml new file mode 100644 index 00000000..94997350 --- /dev/null +++ b/pkg/utils/embed/resources/validator/vsphere-spectro-cloud-tags.yaml @@ -0,0 +1,15 @@ +--- +- name: "Datacenter: k8s-region (ensure that the selected datacenter has a 'k8s-region' tag)" + entityType: "datacenter" + entityName: "" + tag: "k8s-region" + clusterName: "" + clusterScoped: false + ruleType: Spectro Cloud Tags +- name: "Cluster: k8s-zone (ensure that the selected cluster has a 'k8s-zone' tag)" + entityType: "cluster" + entityName: "" + tag: "k8s-zone" + clusterName: "" + clusterScoped: true + ruleType: Spectro Cloud Tags diff --git a/pkg/utils/embed/resources/validator/vsphere-spectro-entity-privileges.yaml b/pkg/utils/embed/resources/validator/vsphere-spectro-entity-privileges.yaml new file mode 100644 index 00000000..8c22ba18 --- /dev/null +++ b/pkg/utils/embed/resources/validator/vsphere-spectro-entity-privileges.yaml @@ -0,0 +1,8 @@ +- name: "Read folder: spectro-templates" + entityType: "folder" + entityName: "spectro-templates" + privileges: + - Folder.Create + clusterName: "" + clusterScoped: false + ruleType: Spectro Entity Privileges diff --git a/pkg/utils/exec/exec.go b/pkg/utils/exec/exec.go new file mode 100644 index 00000000..3e6eb950 --- /dev/null +++ b/pkg/utils/exec/exec.go @@ -0,0 +1,154 @@ +package exec + +import ( + "bufio" + "bytes" + "io" + "os/exec" + "strings" + + "github.com/pkg/errors" + + log "github.com/validator-labs/validatorctl/pkg/logging" +) + +// Execute enables monkey-patching cmd execution for integration tests +var Execute = execute + +func GetCmds(commandStr string) []*exec.Cmd { + + cmdArr := strings.Split(commandStr, "|") + cmds := make([]*exec.Cmd, 0) + + for _, command := range cmdArr { + command = strings.TrimSpace(command) + args := strings.Split(command, " ") + if len(args) >= 1 { + bin, args := args[0], args[1:] + cmds = append(cmds, exec.Command(bin, args...)) //#nosec + } + } + return cmds +} + +func StreamingOutput(cmd *exec.Cmd) (string, string, error) { + var errs string + stdout, err := cmd.StdoutPipe() + if err != nil { + return "", "", err + } + + stderr, err := cmd.StderrPipe() + if err != nil { + return "", "", err + } + + errScanner := bufio.NewScanner(stderr) + go func() { + for errScanner.Scan() { + errs += errScanner.Text() + log.Error("%v", errScanner.Text()) + } + }() + + if err := cmd.Start(); err != nil { + return "", "", err + } + + stdoutb, err := io.ReadAll(stdout) + if err != nil { + return "", "", err + } + + stderrb, err := io.ReadAll(stdout) + if err != nil { + return "", "", err + } + + if err := cmd.Wait(); err != nil { + if errs == "" { + return "", "", err + } + return "", "", errors.WithMessage(err, errs) + } + outStr, errStr := string(stdoutb), string(stderrb) + + return outStr, errStr, nil +} + +type WriterStringer interface { + String() string + Write(p []byte) (n int, err error) +} + +// logWriter implements io.Writer while also logging to the terminal +type logWriter struct { + buffer bytes.Buffer +} + +func (l *logWriter) Write(p []byte) (n int, err error) { + log.InfoCLI(string(p)) + return l.buffer.Write(p) +} + +func (l *logWriter) String() string { + return l.buffer.String() +} + +func execute(logStdout bool, stack ...*exec.Cmd) (stdout, stderr string, err error) { + var stdout_buffer WriterStringer + if !logStdout { + stdout_buffer = &bytes.Buffer{} + } else { + stdout_buffer = &logWriter{} + } + stderr_buffer := logWriter{} + + pipe_stack := make([]*io.PipeWriter, len(stack)-1) + i := 0 + for ; i < len(stack)-1; i++ { + stdin_pipe, stdout_pipe := io.Pipe() + stack[i].Stdout = stdout_pipe + stack[i].Stderr = &stderr_buffer + stack[i+1].Stdin = stdin_pipe + pipe_stack[i] = stdout_pipe + } + stack[i].Stdout = stdout_buffer + stack[i].Stderr = &stderr_buffer + + if err := call(stack, pipe_stack); err != nil { + return "", stderr_buffer.String(), err + } + return stdout_buffer.String(), stderr_buffer.String(), err +} + +func call(stack []*exec.Cmd, pipes []*io.PipeWriter) (err error) { + + if stack[0].Process == nil { + if err = stack[0].Start(); err != nil { + return err + } + } + if len(stack) > 1 { + if err = stack[1].Start(); err != nil { + return err + } + defer func() { + if err == nil { + err := pipes[0].Close() + if err != nil { + log.Error("Error closing pipe: %v", err) + return + } + err = call(stack[1:], pipes[1:]) + if err != nil { + log.Error("Error calling stack: %v", err) + return + } + } else { + err = stack[1].Wait() + } + }() + } + return stack[0].Wait() +} diff --git a/pkg/utils/extra/utils.go b/pkg/utils/extra/utils.go new file mode 100644 index 00000000..7956cc79 --- /dev/null +++ b/pkg/utils/extra/utils.go @@ -0,0 +1,39 @@ +package extra + +// TODO: put these in a better spot, currently just importing this path and naming it models to avoid changes +type V1Env struct { + HTTPProxy string `yaml:"httpProxy,omitempty"` + HTTPSProxy string `yaml:"httpsProxy,omitempty"` + NoProxy string `yaml:"noProxy,omitempty"` + PodCIDR *string `yaml:"podCIDR"` + ProxyCaCertData string `yaml:"proxyCaCertData,omitempty"` + ProxyCaCertName string `yaml:"proxyCaCertName,omitempty"` + ProxyCaCertPath string `yaml:"proxyCaCertPath,omitempty"` + ServiceIPRange *string `yaml:"serviceIPRange"` +} + +type V1VsphereCloudAccount struct { + Insecure bool `yaml:"insecure"` + Password *string `yaml:"password"` + Username *string `yaml:"username"` + VcenterServer *string `yaml:"vcenterServer"` +} + +// Auth username/password/tls or other auth +type Auth struct { + Username string `json:"username"` + Password string `json:"password"` + Tls TlsConfig `json:"tls"` +} + +// TlsConfig config +type TlsConfig struct { + Enabled bool `json:"enabled" bson:"enabled"` + Certificate string `json:"certificate" bson:"certificate"` + Key string `json:"key" bson:"key"` + Ca string `json:"ca" bson:"ca"` + InsecureSkipVerify bool `json:"insecureSkipVerify" bson:"insecureSkipVerify"` + CaFile string `json:"caFile" bson:"caFile"` +} + +type RegistryAuthMap map[string]Auth diff --git a/pkg/utils/file/file.go b/pkg/utils/file/file.go new file mode 100644 index 00000000..ad372b2b --- /dev/null +++ b/pkg/utils/file/file.go @@ -0,0 +1,178 @@ +package file + +import ( + "archive/tar" + "bytes" + "errors" + "fmt" + "io" + "os" + "os/exec" + "strings" + "time" + + "github.com/spectrocloud-labs/prompts-tui/prompts" + + log "github.com/validator-labs/validatorctl/pkg/logging" + //"github.com/validator-labs/validatorctl/tests/utils/test/mocks" +) + +var ( + editorBinary = "vi" + editorPath = "" +) + +func init() { + visual := os.Getenv("VISUAL") + editor := os.Getenv("EDITOR") + if visual != "" { + editorBinary = visual + log.InfoCLI("Detected VISUAL env var. Overrode default editor (vi) with %s.", visual) + } else if editor != "" { + editorBinary = editor + log.InfoCLI("Detected EDITOR env var. Overrode default editor (vi) with %s.", editor) + } + var err error + editorPath, err = exec.LookPath(editorBinary) + if err != nil { + log.InfoCLI("Error: %s not found on PATH. Either install vi or export VISUAL or EDITOR to an editor of your choosing.", editorBinary) + os.Exit(1) + } +} + +var ( + GetCmdExecutor = getEditorExecutor + FileReader = os.ReadFile +) + +// TODO: see if i need the mock or not +func getEditorExecutor(editor, filename string) *exec.Cmd /*mocks.CommandExecutor*/ { + cmd := exec.Command(editor, filename) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd +} + +func EditFile(initialContent []byte) ([]byte, error) { + tmpFile, err := os.CreateTemp(os.TempDir(), "validator") + if err != nil { + return nil, err + } + filename := tmpFile.Name() + if err := tmpFile.Close(); err != nil { + return nil, err + } + + if initialContent != nil { + if err := os.WriteFile(filename, initialContent, 0600); err != nil { + return nil, err + } + } + + cmd := GetCmdExecutor(editorPath, filename) + if err := cmd.Start(); err != nil { + return nil, err + } + if err := cmd.Wait(); err != nil { + return nil, err + } + + data, err := os.ReadFile(filename) //#nosec + if err != nil { + return nil, err + } + if err := os.Remove(filename); err != nil { + return nil, err + } + return data, nil +} + +// EditFileValidated prompts a user to edit a file with a predefined prompt, initial content, and separator. +// An optional validation function can be specified to validate the content of each line. +// Entries within the file must be newline-separated. Additionally, a minimum number of entries can be specified. +// The values on each line are joined by the separator and returned to the caller. +func EditFileValidated(prompt, content, separator string, validate func(input string) error, minEntries int) (string, error) { + if separator == "" { + return "", errors.New("a non-empty separator is required") + } + + for { + var partsBytes []byte + if content != "" { + parts := bytes.Split([]byte(content), []byte(separator)) + partsBytes = bytes.Join(parts, []byte("\n")) + } + + partsBytes, err := EditFile(append([]byte(prompt), partsBytes...)) + if err != nil { + return content, err + } + lines := strings.Split(string(partsBytes), "\n") + + // Parse final lines, skipping comments and optionally validating each line + finalLines := make([]string, 0) + for _, l := range lines { + l = strings.TrimSpace(l) + if l != "" && !strings.HasPrefix(l, "#") { + if validate != nil { + if err = validate(l); err != nil { + break + } + } + finalLines = append(finalLines, l) + } + } + + if err != nil && errors.Is(err, prompts.ValidationError) { + // for integration tests, return the error + if os.Getenv("IS_TEST") == "true" { + return "", err + } + // otherwise, we assume the validation function logged + // a meaningful error message and let the user try again + time.Sleep(5 * time.Second) + continue + } + if minEntries > 0 && len(finalLines) < minEntries { + log.InfoCLI("Error editing file: %d or more entries are required", minEntries) + time.Sleep(5 * time.Second) + continue + } + + content = strings.TrimRight(strings.Join(finalLines, separator), separator) + return content, err + } +} + +func FindFileInTar(r io.Reader, suffix string) ([]byte, error) { + tarReader := tar.NewReader(r) + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + switch header.Typeflag { + case tar.TypeReg: + if !strings.HasSuffix(header.Name, suffix) { + log.Warn("FindFileInTar: skipping file: %s", header.Name) + continue + } + data, err := io.ReadAll(tarReader) + if err != nil { + return nil, err + } + return data, nil + default: + log.Warn("FindFileInTar: ignoring file of type: %v in %s", header.Typeflag, header.Name) + } + } + return nil, fmt.Errorf("FindFileInTar: no file with suffix %s was found", suffix) +} + +func ReadFile(filepath string) ([]byte, error) { + return FileReader(filepath) +} diff --git a/pkg/utils/kind/kind.go b/pkg/utils/kind/kind.go new file mode 100644 index 00000000..6d7a2075 --- /dev/null +++ b/pkg/utils/kind/kind.go @@ -0,0 +1,175 @@ +package kind + +import ( + "fmt" + "os" + "os/exec" + "regexp" + "strings" + + "github.com/pkg/errors" + "github.com/pterm/pterm" + + "github.com/spectrocloud-labs/prompts-tui/prompts" + + cfg "github.com/validator-labs/validatorctl/pkg/config" + log "github.com/validator-labs/validatorctl/pkg/logging" + models "github.com/validator-labs/validatorctl/pkg/utils/extra" + //"github.com/spectrocloud/palette-cli/pkg/repo" + embed_utils "github.com/validator-labs/validatorctl/pkg/utils/embed" + exec_utils "github.com/validator-labs/validatorctl/pkg/utils/exec" +) + +var caCertRegex = regexp.MustCompile("/usr/local/share/ca-certificates") + +func ValidateClusters(action string) error { + if os.Getenv("DISABLE_KIND_CLUSTER_CHECK") != "" { + return nil + } + clusters, err := getClusters() + if err != nil { + return err + } + if clusters != nil { + prompt := fmt.Sprintf( + "Existing kind cluster(s) %s detected. This may cause too many open files errors. Proceed with %s", + clusters, action, + ) + ok, err := prompts.ReadBool(prompt, true) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("%s aborted", action) + } + } + return nil +} + +func StartCluster(name, kindConfig, kubeconfig string) error { + args := []string{ + "create", "cluster", "--name", name, + "--kubeconfig", kubeconfig, "--config", kindConfig, + } + cmd := exec.Command(embed_utils.Kind, args...) //#nosec G204 + _, stderr, err := exec_utils.Execute(true, cmd) + if err != nil { + return errors.Wrap(err, stderr) + } + update, err := requiresCaCertUpdate(kindConfig) + if err != nil { + return errors.Wrap(err, "failed to determine if kind cluster requires CA cert updates") + } + if update { + return updateCaCerts(name) + } + return nil +} + +func DeleteCluster(name string) error { + args := []string{"delete", "cluster", "--name", name} + cmd := exec.Command(embed_utils.Kind, args...) //#nosec G204 + _, stderr, err := exec_utils.Execute(false, cmd) + if err != nil { + return errors.Wrap(err, stderr) + } + log.InfoCLI("Deleted local Kind cluster: %s", name) + return nil +} + +func DefaultKindArgs() map[string]interface{} { + return map[string]interface{}{ + "Env": models.V1Env{ + PodCIDR: &cfg.DefaultPodCIDR, + ServiceIPRange: &cfg.DefaultServiceIPRange, + }, + "Image": fmt.Sprintf("%s:%s", cfg.KindImage, cfg.KindImageTag), + } +} + +func DefaultConfig(kindConfig string) error { + return embed_utils.RenderTemplate(DefaultKindArgs(), cfg.Kind, cfg.ClusterConfigTemplate, kindConfig) +} + +// AdvancedConfig renders a kind cluster configuration file with optional proxy and registry mirror customizations +func AdvancedConfig(env *models.V1Env /*, p *repo.ScarProps*/, kindConfig string) error { + image := fmt.Sprintf("%s:%s", cfg.KindImage, cfg.KindImageTag) + + clusterConfigArgs := map[string]interface{}{ + "Env": env, + "Image": image, + } + + /*// TODO: commented part out to get it compiling + // update kind image to pull from registry mirror in airgapped envs + if p != nil && p.ImageRegistryType != repo.RegistryTypeSpectro { + image = os.Getenv("KIND_IMAGE") + if image == "" { + // For airgap & private registry cases, pull kind image from spectro-images-public/kindest/node + image = p.ImageUrl(cfg.KindImageInternalRepo, cfg.KindImageTag) + } + ep := p.OCIImageRegistry.Endpoint(p.ImageRegistryType) + basePath := p.OCIImageRegistry.BaseContentPath(p.ImageRegistryType) + mirrorEndpoint := strings.TrimSuffix(fmt.Sprintf("%s/v2/%s", ep, basePath), "/") + insecure := p.OCIImageRegistry.InsecureSkipVerify(p.ImageRegistryType) + username, err := p.OCIImageRegistry.Username(p.ImageRegistryType) + if err != nil { + return err + } + password, err := p.OCIImageRegistry.Password(p.ImageRegistryType) + if err != nil { + return err + } + + clusterConfigArgs["Image"] = image + clusterConfigArgs["RegistryBaseContentPath"] = basePath + clusterConfigArgs["RegistryEndpoint"] = ep + clusterConfigArgs["RegistryInsecure"] = strconv.FormatBool(insecure) + clusterConfigArgs["RegistryCaCertName"] = p.OCIImageRegistry.CACertName(p.ImageRegistryType) + clusterConfigArgs["ReusedProxyCACert"] = p.OCIImageRegistry.ReusedProxyCACert(p.ImageRegistryType) + clusterConfigArgs["RegistryUsername"] = username + clusterConfigArgs["RegistryPassword"] = password + clusterConfigArgs["RegistryMirrorEndpoint"] = mirrorEndpoint + clusterConfigArgs["RegistryMirrors"] = strings.Split(p.OCIImageRegistry.OCIRegistryBasic.MirrorRegistries, ",") + } + */ + + return embed_utils.RenderTemplate(clusterConfigArgs, cfg.Kind, cfg.ClusterConfigTemplate, kindConfig) +} + +func getClusters() ([]string, error) { + cmd := exec.Command(embed_utils.Kind, "get", "clusters") //#nosec G204 + + stdout, stderr, err := exec_utils.Execute(false, cmd) + if err != nil { + return nil, errors.Wrap(err, stderr) + } + if os.Getenv("IS_TEST") == "true" && len(stdout) > 0 { + log.HeaderCustom("WARNING: integration tests will fail until you 'export DISABLE_KIND_CLUSTER_CHECK=true' or delete all kind clusters", pterm.BgRed, pterm.FgBlack) + } + if len(stdout) > 0 { + return strings.Split(strings.TrimSpace(stdout), "\n"), nil + } + return nil, nil +} + +func requiresCaCertUpdate(kindConfig string) (bool, error) { + bytes, err := os.ReadFile(kindConfig) //#nosec G304 + if err != nil { + return false, errors.Wrap(err, "failed to read kind cluster configuration file") + } + return caCertRegex.Match(bytes), nil +} + +func updateCaCerts(name string) error { + args := []string{ + "exec", fmt.Sprintf("%s-control-plane", name), + "sh", "-c", "update-ca-certificates && systemctl restart containerd", + } + cmd := exec.Command(embed_utils.Docker, args...) //#nosec G204 + _, stderr, err := exec_utils.Execute(true, cmd) + if err != nil { + return errors.Wrap(err, stderr) + } + return nil +} diff --git a/pkg/utils/kube/kube.go b/pkg/utils/kube/kube.go new file mode 100644 index 00000000..daff3181 --- /dev/null +++ b/pkg/utils/kube/kube.go @@ -0,0 +1,227 @@ +package kube + +import ( + "context" + "fmt" + "os" + "os/exec" + "os/user" + "path/filepath" + "regexp" + "time" + + "golang.org/x/exp/slices" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + + //"github.com/spectrocloud/gomi/pkg/k8s" // TODO: see if this is actually needed. maybe i dont need the functions + log "github.com/validator-labs/validatorctl/pkg/logging" + embed_utils "github.com/validator-labs/validatorctl/pkg/utils/embed" + exec_utils "github.com/validator-labs/validatorctl/pkg/utils/exec" +) + +type KubectlCmd struct { + Cmd []string + Delay *time.Duration + DelayMsg string +} + +func KubectlCommand(params []string, kConfig string) (out, stderr string, err error) { + params = append(params, fmt.Sprintf("--kubeconfig=%s", kConfig)) + cmd := exec.Command(embed_utils.Kubectl, params...) //#nosec + + if slices.Contains(params, "secret") { + log.InfoCLI("\n==== Kubectl Command ==== Create Secret") + } else { + log.InfoCLI("\n==== Kubectl Command ====") + log.InfoCLI(cmd.String()) + } + + out, stderr, err = exec_utils.Execute(true, cmd) + return +} + +func KubectlDelayCommand(cmd KubectlCmd, kConfig string) (out, stderr string, err error) { + out, stderr, err = KubectlCommand(cmd.Cmd, kConfig) + if cmd.Delay != nil { + log.InfoCLI("waiting %v to %s", cmd.Delay, cmd.DelayMsg) + time.Sleep(*cmd.Delay) + } + return +} + +func GetKubeClientset(kubeconfigPath string) (kubernetes.Interface, error) { + return getClientFromKubeconfig(kubeconfigPath, "") +} + +func GetGroupVersion(group, version string) schema.GroupVersion { + return schema.GroupVersion{Group: group, Version: version} +} + +func GetCRDClient(groupVersion schema.GroupVersion, crd Crd) (dynamic.NamespaceableResourceInterface, error) { + return getCrdClient(groupVersion, crd) +} + +func GetAPIConfig(kubeconfig string) (*clientcmdapi.Config, error) { + bytes, err := os.ReadFile(kubeconfig) //#nosec + if err != nil { + return nil, err + } + clientCfg, err := clientcmd.NewClientConfigFromBytes(bytes) + if err != nil { + return nil, err + } + apiCfg, err := clientCfg.RawConfig() + if err != nil { + return nil, err + } + return &apiCfg, nil +} + +type Client interface { + BuildConfig(kubeconfig string) (*rest.Config, error) + NewClient(config *rest.Config) (kubernetes.Interface, error) + IsImported(client kubernetes.Interface) bool +} + +type KubeClient struct { +} + +func (kc KubeClient) BuildConfig(kubeconfig string) (*rest.Config, error) { + return clientcmd.BuildConfigFromFlags("", kubeconfig) +} + +func (kc KubeClient) NewClient(config *rest.Config) (kubernetes.Interface, error) { + return kubernetes.NewForConfig(config) +} + +func (kc KubeClient) IsImported(client kubernetes.Interface) bool { + namespacePattern := `cluster-([0-9a-z]{24})` + namespaceMatch := regexp.MustCompile(namespacePattern) + + namespaces, err := client.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return false + } + + var clusterInfo, hubbleInfo *v1.ConfigMap + var clusterInfoErr, hubbleInfoErr error + imported := false + + for _, ns := range namespaces.Items { + if namespaceMatch.MatchString(ns.Name) { + + hubbleInfo, clusterInfoErr = client.CoreV1().ConfigMaps(ns.Name).Get(context.TODO(), "hubble-info", metav1.GetOptions{}) + clusterInfo, hubbleInfoErr = client.CoreV1().ConfigMaps(ns.Name).Get(context.TODO(), "cluster-info", metav1.GetOptions{}) + + if hubbleInfo != nil && hubbleInfoErr == nil && clusterInfo != nil && clusterInfoErr == nil { + log.InfoCLI("Cluster has already been imported as %s at %s.", clusterInfo.Data["clusterName"], hubbleInfo.Data["apiEndpoint"]) + imported = true + } + } + } + return imported +} + +// TODO: -------------------- Everything below is from spectrocloud/gomi/pkg/k8s -------------------- + +type Crd string + +func getCrdClient(groupVersion schema.GroupVersion, crd Crd) (dynamic.NamespaceableResourceInterface, error) { + dynClient, err := getDynamicClient() + if err != nil { + return nil, err + } + + version := schema.GroupVersionResource{ + Group: groupVersion.Group, + Version: groupVersion.Version, + Resource: string(crd), + } + + return dynClient.Resource(version), nil +} + +func getDynamicClient() (dynamic.Interface, error) { + config, err := getConfig() + if err != nil { + return nil, err + } + + return getDynamicClientForConfig(config) +} + +func getConfig() (*rest.Config, error) { + // If an env variable is specified with the config locaiton, use that + if len(os.Getenv("KUBECONFIG")) > 0 { + return clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG")) + } + // If no explicit location, try the in-cluster config + if c, err := rest.InClusterConfig(); err == nil { + return c, nil + } + // If no in-cluster config, try the default location in the user's home directory + if usr, err := user.Current(); err == nil { + if c, err := clientcmd.BuildConfigFromFlags( + "", filepath.Join(usr.HomeDir, ".kube", "config")); err == nil { + return c, nil + } + } + + return nil, fmt.Errorf("could not locate a kubeconfig") +} + +func getDynamicClientForConfig(config *rest.Config) (dynamic.Interface, error) { + + dynClient, err := dynamic.NewForConfig(config) + if err != nil { + return nil, err + } + + return dynClient, nil + +} + +func getClientFromKubeconfig(kubeconfig, masterURL string) (*kubernetes.Clientset, error) { + config, err := getConfigFromKubeconfig(kubeconfig, masterURL) + if err != nil { + return nil, err + } + // creates the clientset + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + return clientset, nil +} + +func getConfigFromKubeconfig(kubeconfig, masterURL string) (*rest.Config, error) { + // If a flag is specified with the config location, use that + if len(kubeconfig) > 0 { + return clientcmd.BuildConfigFromFlags(masterURL, kubeconfig) + } + // If an env variable is specified with the config locaiton, use that + if len(os.Getenv("KUBECONFIG")) > 0 { + return clientcmd.BuildConfigFromFlags(masterURL, os.Getenv("KUBECONFIG")) + } + // If no explicit location, try the in-cluster config + if c, err := rest.InClusterConfig(); err == nil { + return c, nil + } + // If no in-cluster config, try the default location in the user's home directory + if usr, err := user.Current(); err == nil { + if c, err := clientcmd.BuildConfigFromFlags( + "", filepath.Join(usr.HomeDir, ".kube", "config")); err == nil { + return c, nil + } + } + + return nil, fmt.Errorf("could not locate a kubeconfig") +} diff --git a/pkg/utils/ptr/ptr.go b/pkg/utils/ptr/ptr.go new file mode 100644 index 00000000..e74ec128 --- /dev/null +++ b/pkg/utils/ptr/ptr.go @@ -0,0 +1,160 @@ +package ptr + +import "time" + +func Bool(b *bool) bool { + if b == nil { + return false + } + return *b +} + +func BoolPtr(b bool) *bool { + bol := b + return &bol +} + +func Int(i *int) int { + if i == nil { + return 0 + } + return *i +} + +func IntPtr(i int) *int { + it := i + return &it +} + +func Int8(i *int8) int8 { + if i == nil { + return 0 + } + return *i +} + +func Int8WithDefault(i *int8, defaultVal int8) int8 { + if i == nil { + return defaultVal + } + return *i +} + +func Int8Ptr(i int8) *int8 { + it := i + return &it +} + +func Int16(i *int16) int16 { + if i == nil { + return 0 + } + return *i +} + +func Int16WithDefault(i *int16, defaultVal int16) int16 { + if i == nil { + return defaultVal + } + return *i +} + +func Int16Ptr(i int16) *int16 { + it := i + return &it +} + +func Int32(i *int32) int32 { + if i == nil { + return 0 + } + return *i +} + +func Int32WithDefault(i *int32, defaultVal int32) int32 { + if i == nil { + return defaultVal + } + return *i +} + +func Int32Ptr(i int32) *int32 { + it := i + return &it +} + +func Int64(i *int64) int64 { + if i == nil { + return 0 + } + return *i +} + +func Int64WithDefault(i *int64, defaultVal int64) int64 { + if i == nil { + return defaultVal + } + return *i +} + +func Int64Ptr(i int64) *int64 { + it := i + return &it +} + +func Float32Ptr(f float32) *float32 { + it := f + return &it +} + +func Float32WithDefault(f *float32, defaultVal float32) float32 { + if f == nil { + return defaultVal + } + return *f +} + +func Float64Ptr(f float64) *float64 { + it := f + return &it +} + +func Float64WithDefault(f *float64, defaultVal float64) float64 { + if f == nil { + return defaultVal + } + return *f +} + +func String(s *string) string { + if s == nil { + return "" + } + return *s +} + +func StringWithDefaultValue(s *string, defaultVal string) string { + if s == nil { + return defaultVal + } + return *s +} + +func StringPtr(s string) *string { + str := s + return &str +} + +func Time(t *time.Time) time.Time { + if t == nil { + return time.Time{} + } + return *t +} + +func Interface(val *interface{}) interface{} { + if val == nil { + return nil + } + return val +} diff --git a/pkg/utils/string/string.go b/pkg/utils/string/string.go new file mode 100644 index 00000000..94a2c8db --- /dev/null +++ b/pkg/utils/string/string.go @@ -0,0 +1,87 @@ +package string + +import ( + "bufio" + "os" + "regexp" + "strings" +) + +var ( + dnsNameRegex = regexp.MustCompile(`[^a-zA-Z0-9\-\.]`) + dotsRegex = regexp.MustCompile(`\.{2,}`) + dashesRegex = regexp.MustCompile(`\-{2,}`) +) + +func Contains(arr []string, str string) bool { + for _, a := range arr { + if a == str { + return true + } + } + return false +} + +func Capitalize(s string) string { + if len(s) == 1 { + return strings.ToUpper(s) + } + return strings.ToUpper(s[0:1]) + s[1:] +} + +func MultiTrim(str string, prefixes, suffixes []string) string { + for _, p := range prefixes { + str = strings.TrimPrefix(str, p) + } + for _, s := range suffixes { + str = strings.TrimSuffix(str, s) + } + return str +} + +func ConvertToDNSName(input string) string { + // Lowercase & remove leading/trailing whitespaces + input = strings.ToLower(strings.TrimSpace(input)) + + // Remove invalid characters + input = dnsNameRegex.ReplaceAllString(input, "-") + + // Replace consecutive dots with a single dot + input = dotsRegex.ReplaceAllString(input, ".") + + // Replace consecutive dashes with a single dash + input = dashesRegex.ReplaceAllString(input, "-") + + // Truncate the string to 253 characters + if len(input) > 253 { + input = input[:253] + } + + // Remove leading/trailing dots + input = strings.Trim(input, ".") + + return input +} + +func GetAirgapValues(f *os.File, airgapKeys []string) ([]string, error) { + _, err := f.Seek(0, 0) + if err != nil { + return nil, err + } + airgapValue := make([]string, len(airgapKeys)) + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + for i, key := range airgapKeys { + if strings.Contains(line, key) { + airgapValue[i] = getEnvValue(line) + } + } + } + return airgapValue, nil +} + +func getEnvValue(line string) string { + arr := strings.Split(line, "=") + return strings.TrimSpace(arr[1]) +} diff --git a/validator.go b/validator.go new file mode 100644 index 00000000..9d5ef788 --- /dev/null +++ b/validator.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/validator-labs/validatorctl/cmd" +) + +func main() { + cmd.Execute() +}