diff --git a/.goreleaser.yml b/.goreleaser.yml index 6fa17ed..be56d05 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -6,7 +6,7 @@ before: builds: - id: kubectl-envsubst - main: ./cmd/kubectl-envsubst.go + main: ./cmd/kubectl-envsubst binary: kubectl-envsubst ldflags: - -s -w diff --git a/Makefile b/Makefile index 9a5d116..a8b2259 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ all: build # Build the binary (GOARCH=amd64 GOOS=linux; -o $(BINARY)) .PHONY: build build: $(SOURCES) - CGO_ENABLED=0 go build -ldflags="-s -w" ./cmd/$(BINARY).go + CGO_ENABLED=0 go build -ldflags="-s -w" ./cmd/kubectl-envsubst # Install the binary to /usr/local/bin .PHONY: install @@ -47,7 +47,7 @@ kind-teardown: .PHONY: test-integration test-integration: install kind-setup KUBECTL_ENVSUBST_INTEGRATION_TESTS_AVAILABLE=0xcafebabe go test -v integration/*.go - KUBECTL_ENVSUBST_INTEGRATION_TESTS_AVAILABLE=0xcafebabe go test -v cmd/app/*.go + KUBECTL_ENVSUBST_INTEGRATION_TESTS_AVAILABLE=0xcafebabe go test -v cmd/kubectl-envsubst/*.go $(MAKE) kind-teardown # Lint the code diff --git a/cmd/app/app.go b/cmd/app/app.go deleted file mode 100644 index 15f0237..0000000 --- a/cmd/app/app.go +++ /dev/null @@ -1,147 +0,0 @@ -package app - -import ( - "fmt" - "io" - "os" - "os/exec" - "strings" - - "github.com/hashmap-kz/kubectl-envsubst/pkg/cmd" - "github.com/hashmap-kz/kubectl-envsubst/pkg/version" -) - -// runApp executes the plugin, with logic divided into smaller, testable components -func RunApp() error { - // parse all passed cmd arguments without any modification - flags, err := cmd.ParseArgs() - if err != nil { - return err - } - - // show help message - if flags.Help { - fmt.Println(cmd.UsageMessage) - return nil - } - - // show version - if flags.Version { - fmt.Println(version.Version) - return nil - } - - // 'apply' was not provided - if len(flags.Others) == 0 { - fmt.Println(cmd.UsageMessage) - return nil - } - - // support apply operation only - if flags.Others[0] != "apply" { - fmt.Println(cmd.UsageMessage) - return nil - } - - // it checks that executable exists - kubectl, err := exec.LookPath("kubectl") - if err != nil { - return err - } - - // resolve all filenames: expand all glob-patterns, list directories, etc... - files, err := cmd.ResolveAllFiles(flags.Filenames, flags.Recursive) - if err != nil { - return err - } - - // apply STDIN (if any) - if flags.HasStdin { - err := applyStdin(&flags, kubectl) - if err != nil { - return err - } - } - - // apply passed files - for _, filename := range files { - err := applyOneFile(&flags, kubectl, filename) - if err != nil { - return err - } - } - - return nil -} - -// applyStdin substitutes content, passed to stdin `kubectl apply -f -` -func applyStdin(flags *cmd.ArgsRawRecognized, kubectl string) error { - stdin, err := io.ReadAll(os.Stdin) - if err != nil { - return err - } - - // substitute the whole stream of joined files at once - substitutedBuffer, err := substituteContent(flags, stdin) - if err != nil { - return err - } - - return execKubectl(flags, kubectl, substitutedBuffer) -} - -// applyOneFile read file (url, local-path), substitute its content, apply result -func applyOneFile(flags *cmd.ArgsRawRecognized, kubectl, filename string) error { - // recognize file type - - var contentForSubst []byte - if cmd.IsURL(filename) { - data, err := cmd.ReadRemoteFileContent(filename) - if err != nil { - return err - } - contentForSubst = data - } else { - data, err := os.ReadFile(filename) - if err != nil { - return err - } - contentForSubst = data - } - - // substitute the whole stream of joined files at once - substitutedBuffer, err := substituteContent(flags, contentForSubst) - if err != nil { - return err - } - - return execKubectl(flags, kubectl, substitutedBuffer) -} - -// substituteContent runs the subst module for a given content -func substituteContent(flags *cmd.ArgsRawRecognized, contentForSubst []byte) (string, error) { - envSubst := cmd.NewEnvsubst(flags.EnvsubstAllowedVars, flags.EnvsubstAllowedPrefix, true) - substitutedBuffer, err := envSubst.SubstituteEnvs(string(contentForSubst)) - if err != nil { - return "", err - } - return substitutedBuffer, nil -} - -// execKubectl applies a result buffer, bu running `kubectl apply -f -` -func execKubectl(flags *cmd.ArgsRawRecognized, kubectl, substitutedBuffer string) error { - // prepare kubectl args - args := []string{} - args = append(args, flags.Others...) - args = append(args, "-f", "-") - - // pass stream of files to stdin - execCmd, err := cmd.ExecWithStdin(kubectl, []byte(substitutedBuffer), args...) - if err != nil { - fmt.Println(strings.TrimSpace(execCmd.StderrContent)) - return err - } - - fmt.Println(strings.TrimSpace(execCmd.StdoutContent)) - return nil -} diff --git a/cmd/app/app_test.go b/cmd/app/app_test.go deleted file mode 100644 index 79b9e7d..0000000 --- a/cmd/app/app_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package app - -import ( - "io" - "os" - "strings" - "testing" -) - -// NOTE: This package contains integration tests. -// -// Key Characteristics: -// 1. **No Mocks**: These tests interact with actual external dependencies rather than mocked versions. -// The goal is to simulate real-world conditions as closely as possible. -// 2. **Sandboxed Environment**: To ensure safety and consistency, these tests are executed in a controlled and isolated environment (a kind-cluster sandbox). -// This prevents unintended side effects on production systems, local configurations, or external resources. -// -// Safeguards: -// - Before each test run, appropriate safeguards are implemented to validate the environment and prevent harmful actions. -// -// Expectations: -// - These tests may take longer to execute compared to unit tests because they involve real components. -// - -const ( - integrationTestEnv = "KUBECTL_ENVSUBST_INTEGRATION_TESTS_AVAILABLE" - integrationTestFlag = "0xcafebabe" -) - -func TestApp_ApplyFromStdin(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { - t.Log("integration test was skipped due to configuration") - return - } - - os.Args = []string{ - "kubectl-envsubst", "apply", "-f", "-", - } - - // Create a temporary file - tempFile, err := os.CreateTemp("", "mock-stdin-*") - if err != nil { - t.Fatal("Failed to create temporary file:", err) - } - defer os.Remove(tempFile.Name()) // Clean up the file afterwards - - // Write mock input to the temporary file - mockInput := ` ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: cm-c4a3d54857ef43398b9a557050a7c83c -data: - key1: value1 -` - if _, err := tempFile.Write([]byte(mockInput)); err != nil { - t.Fatal("Failed to write to temporary file:", err) - } - - // Reset the file offset to the beginning for reading - if _, err := tempFile.Seek(0, io.SeekStart); err != nil { - t.Fatal("Failed to seek temporary file:", err) - } - - // Replace os.Stdin with the temporary file - originalStdin := os.Stdin - defer func() { os.Stdin = originalStdin }() - os.Stdin = tempFile - - // Create a temporary file for capturing stdout - stdoutFile, err := os.CreateTemp("", "mock-stdout-*") - if err != nil { - t.Fatalf("Failed to create temporary stdout file: %v", err) - } - defer os.Remove(stdoutFile.Name()) // Clean up the temp file after the test - - // Replace os.Stdout with the temp file - originalStdout := os.Stdout - defer func() { os.Stdout = originalStdout }() - os.Stdout = stdoutFile - - // Run application - err = RunApp() - if err != nil { - t.Fatal(err) - } - - // Flush stdout and reset pointer for reading - os.Stdout.Sync() - _, err = stdoutFile.Seek(0, io.SeekStart) - if err != nil { - t.Fatalf("Failed to seek 0. Stdout file.") - } - - // Read and validate stdout content - output, err := io.ReadAll(stdoutFile) - if err != nil { - t.Fatalf("Failed to read from temporary stdout file: %v", err) - } - - // capture expected output - strOut := string(output) - if !strings.Contains(strOut, "configmap/cm-c4a3d54857ef43398b9a557050a7c83c") { - t.Errorf("expected 'configmap/cm-c4a3d54857ef43398b9a557050a7c83c', got: %s", strOut) - } - - t.Log(strOut) -} diff --git a/cmd/kubectl-envsubst.go b/cmd/kubectl-envsubst.go deleted file mode 100644 index 926093d..0000000 --- a/cmd/kubectl-envsubst.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/hashmap-kz/kubectl-envsubst/cmd/app" -) - -func main() { - err := app.RunApp() - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "%s", err.Error()) - os.Exit(1) - } -}