From 02de3a55d5a88aea6befc5958852a0f8585f9c83 Mon Sep 17 00:00:00 2001 From: Ahmad Malik Ibrahim Date: Wed, 19 Jun 2024 08:06:16 -0700 Subject: [PATCH] feat: ensure no binaries are embedded with validatorctl (#31) * ci: update Makefile to not install docker/helm/kind/kubectl * feat: check for existence of binaries * ci: ensure binaries are downloaded when running on gha * chore: improve log line * chore: try using RUNNER_TOOL_CACHE * ci: add RUNNER_TOOL_CACHE to PATH --------- Signed-off-by: Tyler Gillson Co-authored-by: Tyler Gillson --- Makefile | 94 +++++++++++++--------------------- cmd/root.go | 6 +++ pkg/cmd/common/common.go | 5 -- pkg/cmd/validator/validator.go | 25 ++++----- pkg/utils/embed/bin/.gitkeep | 0 pkg/utils/embed/embed.go | 52 ------------------- pkg/utils/exec/exec.go | 37 ++++++++++++- pkg/utils/kind/kind.go | 8 +-- pkg/utils/kube/kube.go | 3 +- 9 files changed, 94 insertions(+), 136 deletions(-) delete mode 100644 pkg/utils/embed/bin/.gitkeep diff --git a/Makefile b/Makefile index e466680f..e2a39eae 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,8 @@ VERSION ?= 0.0.1${VERSION_SUFFIX} # Common vars MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) CURRENT_DIR := $(dir $(MAKEFILE_PATH)) -EMBED_BIN := ./pkg/utils/embed/bin BIN_DIR ?= ./bin +export PATH := $(PATH):$(RUNNER_TOOL_CACHE) # Go env vars GOOS ?= $(shell go env GOOS) @@ -56,15 +56,12 @@ 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 +build: ## 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)" @@ -75,7 +72,7 @@ fmt: ## Run go fmt lint: golangci-lint ## Run golangci-lint $(GOLANGCI_LINT) run -vet: binaries ## Run go vet +vet: ## Run go vet go vet ./... ##@ Test Targets @@ -87,7 +84,7 @@ test-unit: ## Run unit tests # For now we can't enable -race for integration tests # due to https://github.com/spf13/viper/issues/174 -test-integration: binaries ## Run integration tests +test-integration: ## Run integration tests @mkdir -p $(COVER_DIR)/integration rm -rf $(COVER_DIR)/integration/* IS_TEST=true CLI_VERSION=$(VERSION) KUBECONFIG= DISABLE_KIND_CLUSTER_CHECK=true \ @@ -128,10 +125,10 @@ BUILD_ARGS = --build-arg CLI_VERSION=${VERSION} --build-arg BUILDER_GOLANG_VERSI docker-all: docker-cli docker-push ## Builds & pushes Docker images to container registry -docker-cli: binaries +docker-cli: 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: ## Rebuild images and restart docker-compose docker compose build docker compose up @@ -150,66 +147,45 @@ create-images-list: ## Create the image list for CICD ##@ Tools Targets binaries: docker helm kind kubectl -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 - -truncate-binaries: - @echo "Truncating embedded binaries..." - : > $(EMBED_BIN)/docker - : > $(EMBED_BIN)/helm - : > $(EMBED_BIN)/kind - : > $(EMBED_BIN)/kubectl - 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; \ + @if [ "$(GITHUB_ACTIONS)" = "true" ]; then \ + @command -v docker >/dev/null 2>&1 || { \ + echo "Docker not found, downloading..."; \ + curl -L https://download.docker.com/$(PLATFORM)/static/stable/x86_64/docker-$(DOCKER_VERSION).tgz | tar xz docker/docker; \ + mv docker/docker $(RUNNER_TOOL_CACHE)/docker; \ + chmod +x $(RUNNER_TOOL_CACHE)/docker; \ + rm -rf ./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 + @if [ "$(GITHUB_ACTIONS)" = "true" ]; then \ + @command -v kind >/dev/null 2>&1 || { \ + echo "Kind not found, downloading..."; \ + curl -Lo $(RUNNER_TOOL_CACHE)/kind https://github.com/kubernetes-sigs/kind/releases/download/v$(KIND_VERSION)/kind-$(GOOS)-$(GOARCH); \ + chmod +x $(RUNNER_TOOL_CACHE)/kind; \ + } \ + fi 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; \ + @if [ "$(GITHUB_ACTIONS)" = "true" ]; then \ + @command -v kubectl >/dev/null 2>&1 || { \ + echo "Kubectl not found, downloading..."; \ + curl -Lo $(RUNNER_TOOL_CACHE)/kubectl https://dl.k8s.io/release/v$(KUBECTL_VERSION)/bin/$(GOOS)/$(GOARCH)/kubectl; \ + chmod +x $(RUNNER_TOOL_CACHE)/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); \ + @if [ "$(GITHUB_ACTIONS)" = "true" ]; then \ + @command -v helm >/dev/null 2>&1 || { \ + echo "Helm not found, downloading..."; \ + curl -L https://get.helm.sh/helm-v$(HELM_VERSION)-$(GOOS)-$(GOARCH).tar.gz | tar xz; \ + mv $(GOOS)-$(GOARCH)/helm $(RUNNER_TOOL_CACHE)/helm; \ + rm -rf ./$(GOOS)-$(GOARCH); \ + chmod +x $(RUNNER_TOOL_CACHE)/helm; \ + } \ fi - chmod +x $(EMBED_BIN)/helm -endif golangci-lint: if ! test -f $(BIN_DIR)/golangci-lint-linux-amd64; then \ diff --git a/cmd/root.go b/cmd/root.go index c12599d0..4fbffda7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,6 +11,7 @@ import ( 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" + exec_utils "github.com/validator-labs/validatorctl/pkg/utils/exec" ) var ( @@ -55,6 +56,11 @@ Use 'validator help ' to explore all of the functionality the Valid exit(err) } + // Verify required binaries exist + if err := exec_utils.CheckBinaries(); err != nil { + exit(err) + } + // add base commands rootCmd.AddCommand(NewVersionCmd()) rootCmd.AddCommand(NewDeployValidatorCmd()) diff --git a/pkg/cmd/common/common.go b/pkg/cmd/common/common.go index f486268e..c93f75fb 100644 --- a/pkg/cmd/common/common.go +++ b/pkg/cmd/common/common.go @@ -4,7 +4,6 @@ import ( "fmt" cfg "github.com/validator-labs/validatorctl/pkg/config" - embed_utils "github.com/validator-labs/validatorctl/pkg/utils/embed" ) func InitWorkspace(c *cfg.Config, workspaceDir string, subdirs []string, timestamped bool) error { @@ -12,9 +11,5 @@ func InitWorkspace(c *cfg.Config, workspaceDir string, subdirs []string, timesta if err := c.CreateWorkspace(workspaceDir, subdirs, timestamped); err != nil { return fmt.Errorf("failed to initialize workspace: %v", err) } - - // Unpack binaries - embed_utils.InitBinaries(c) - return nil } diff --git a/pkg/cmd/validator/validator.go b/pkg/cmd/validator/validator.go index 51808455..d5d2bae4 100644 --- a/pkg/cmd/validator/validator.go +++ b/pkg/cmd/validator/validator.go @@ -24,7 +24,8 @@ import ( cfg "github.com/validator-labs/validatorctl/pkg/config" log "github.com/validator-labs/validatorctl/pkg/logging" "github.com/validator-labs/validatorctl/pkg/services/validator" - embed "github.com/validator-labs/validatorctl/pkg/utils/embed" + embed_utils "github.com/validator-labs/validatorctl/pkg/utils/embed" + exec_utils "github.com/validator-labs/validatorctl/pkg/utils/exec" "github.com/validator-labs/validatorctl/pkg/utils/kind" "github.com/validator-labs/validatorctl/pkg/utils/kube" ) @@ -210,7 +211,7 @@ func buildValidationResultString(vrObj unstructured.Unstructured) (string, error "Values": vals, } - if err := embed.PrintTableTemplate(sb, args, cfg.Validator, "validation-result.tmpl"); err != nil { + if err := embed_utils.PrintTableTemplate(sb, args, cfg.Validator, "validation-result.tmpl"); err != nil { return "", err } @@ -221,7 +222,7 @@ func buildValidationResultString(vrObj unstructured.Unstructured) (string, error "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 { + if err := embed_utils.PrintTableTemplate(sb, args, cfg.Validator, "validation-result.tmpl"); err != nil { return "", err } @@ -294,7 +295,7 @@ func applyValidator(c *cfg.Config, vc *components.ValidatorConfig) error { "Config": vc.AWSPlugin, "ImageRegistry": vc.ImageRegistry, } - values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-aws-values.tmpl") + values, err := embed_utils.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-aws-values.tmpl") if err != nil { return errors.Wrap(err, "failed to render validator plugin aws values.yaml") } @@ -313,7 +314,7 @@ func applyValidator(c *cfg.Config, vc *components.ValidatorConfig) error { "Config": vc.AzurePlugin, "ImageRegistry": vc.ImageRegistry, } - values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-azure-values.tmpl") + values, err := embed_utils.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-azure-values.tmpl") if err != nil { return errors.Wrap(err, "failed to render validator plugin azure values.yaml") } @@ -332,7 +333,7 @@ func applyValidator(c *cfg.Config, vc *components.ValidatorConfig) error { "Tag": vc.NetworkPlugin.Release.Chart.Version, "ImageRegistry": vc.ImageRegistry, } - values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-network-values.tmpl") + values, err := embed_utils.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-network-values.tmpl") if err != nil { return errors.Wrap(err, "failed to render validator plugin network values.yaml") } @@ -351,7 +352,7 @@ func applyValidator(c *cfg.Config, vc *components.ValidatorConfig) error { "Config": vc.OCIPlugin, "ImageRegistry": vc.ImageRegistry, } - values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-oci-values.tmpl") + values, err := embed_utils.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-oci-values.tmpl") if err != nil { return errors.Wrap(err, "failed to render validator plugin oci values.yaml") } @@ -370,7 +371,7 @@ func applyValidator(c *cfg.Config, vc *components.ValidatorConfig) error { "Config": vc.VspherePlugin, "ImageRegistry": vc.ImageRegistry, } - values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-vsphere-values.tmpl") + values, err := embed_utils.RenderTemplateBytes(args, cfg.Validator, "validator-plugin-vsphere-values.tmpl") if err != nil { return errors.Wrap(err, "failed to render validator plugin vsphere values.yaml") } @@ -403,7 +404,7 @@ func applyValidator(c *cfg.Config, vc *components.ValidatorConfig) error { args["ProxyCaCertData"] = strings.Split(vc.ProxyConfig.Env.ProxyCaCertData, "\n") } - values, err := embed.RenderTemplateBytes(args, cfg.Validator, "validator-base-values.tmpl") + values, err := embed_utils.RenderTemplateBytes(args, cfg.Validator, "validator-base-values.tmpl") if err != nil { return errors.Wrap(err, "failed to render validator base values.yaml") } @@ -506,7 +507,7 @@ func getHelmClient(vc *components.ValidatorConfig) (helm.HelmClient, error) { if err != nil { return nil, errors.Wrap(err, "failed to get API config from kubeconfig") } - helm.CommandPath = embed.Helm + helm.CommandPath = exec_utils.Helm helmClient := helm.NewHelmClient(apiCfg) return helmClient, nil } @@ -536,7 +537,7 @@ func applyPlugins(c *cfg.Config, vc *components.ValidatorConfig) error { "Auth": indent(auth, 4), "IamRoleName": vc.AWSPlugin.IamCheck.IamRoleName, } - if err := embed.RenderTemplate(args, cfg.Validator, template, outputPath); err != nil { + if err := embed_utils.RenderTemplate(args, cfg.Validator, template, outputPath); err != nil { return err } if err := applyValidatorManifest(vc.Kubeconfig, cfg.ValidatorPluginAws, outputPath); err != nil { @@ -595,7 +596,7 @@ func createValidator(kubeconfig, runLoc, name, template string, validator interf "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 { + if err := embed_utils.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) diff --git a/pkg/utils/embed/bin/.gitkeep b/pkg/utils/embed/bin/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/pkg/utils/embed/embed.go b/pkg/utils/embed/embed.go index 151e9ff3..d3ead37c 100644 --- a/pkg/utils/embed/embed.go +++ b/pkg/utils/embed/embed.go @@ -6,66 +6,14 @@ import ( "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 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 - -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") - } - - 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()) - } - } -} - //go:embed resources/* var resources embed.FS diff --git a/pkg/utils/exec/exec.go b/pkg/utils/exec/exec.go index cd9ffa0d..ee7fb9bd 100644 --- a/pkg/utils/exec/exec.go +++ b/pkg/utils/exec/exec.go @@ -2,14 +2,47 @@ package exec import ( "bytes" + "fmt" "io" "os/exec" log "github.com/validator-labs/validatorctl/pkg/logging" ) -// Execute enables monkey-patching cmd execution for integration tests -var Execute = execute +var ( + // Execute enables monkey-patching cmd execution for integration tests + Execute = execute + Docker, Helm, Kind, Kubectl string +) + +func CheckBinaries() error { + binaries := []struct { + name string + path *string + }{ + {"docker", &Docker}, + {"helm", &Helm}, + {"kind", &Kind}, + {"kubectl", &Kubectl}, + } + + hasAllBinaries := true + + for _, binary := range binaries { + path, err := exec.LookPath(binary.name) + if err != nil { + hasAllBinaries = false + log.ErrorCLI(fmt.Sprintf("%s is not installed.\nPlease install the missing dependency and ensure it's available on your PATH.", binary.name)) + } + *binary.path = path + } + + if !hasAllBinaries { + return fmt.Errorf("missing required binaries") + } + + return nil +} type WriterStringer interface { String() string diff --git a/pkg/utils/kind/kind.go b/pkg/utils/kind/kind.go index 8763f476..eab82b7e 100644 --- a/pkg/utils/kind/kind.go +++ b/pkg/utils/kind/kind.go @@ -50,7 +50,7 @@ func StartCluster(name, kindConfig, kubeconfig string) error { "create", "cluster", "--name", name, "--kubeconfig", kubeconfig, "--config", kindConfig, } - cmd := exec.Command(embed_utils.Kind, args...) //#nosec G204 + cmd := exec.Command(exec_utils.Kind, args...) //#nosec G204 _, stderr, err := exec_utils.Execute(true, cmd) if err != nil { return errors.Wrap(err, stderr) @@ -67,7 +67,7 @@ func StartCluster(name, kindConfig, kubeconfig string) error { func DeleteCluster(name string) error { args := []string{"delete", "cluster", "--name", name} - cmd := exec.Command(embed_utils.Kind, args...) //#nosec G204 + cmd := exec.Command(exec_utils.Kind, args...) //#nosec G204 _, stderr, err := exec_utils.Execute(false, cmd) if err != nil { return errors.Wrap(err, stderr) @@ -99,7 +99,7 @@ func AdvancedConfig(env *env.Env, kindConfig string) error { } func getClusters() ([]string, error) { - cmd := exec.Command(embed_utils.Kind, "get", "clusters") //#nosec G204 + cmd := exec.Command(exec_utils.Kind, "get", "clusters") //#nosec G204 stdout, stderr, err := exec_utils.Execute(false, cmd) if err != nil { @@ -127,7 +127,7 @@ func updateCaCerts(name string) error { "exec", fmt.Sprintf("%s-control-plane", name), "sh", "-c", "update-ca-certificates && systemctl restart containerd", } - cmd := exec.Command(embed_utils.Docker, args...) //#nosec G204 + cmd := exec.Command(exec_utils.Docker, args...) //#nosec G204 _, stderr, err := exec_utils.Execute(true, cmd) if err != nil { return errors.Wrap(err, stderr) diff --git a/pkg/utils/kube/kube.go b/pkg/utils/kube/kube.go index 7c10e405..f49ab872 100644 --- a/pkg/utils/kube/kube.go +++ b/pkg/utils/kube/kube.go @@ -17,7 +17,6 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 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" ) @@ -31,7 +30,7 @@ type Crd 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 + cmd := exec.Command(exec_utils.Kubectl, params...) //#nosec if slices.Contains(params, "secret") { log.InfoCLI("\n==== Kubectl Command ==== Create Secret")