-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from hashmap-kz/fix/gitignorecmd
gitignorecmd - fix gitignore
- Loading branch information
Showing
3 changed files
with
265 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,4 +28,4 @@ avatars/ | |
backups/ | ||
cover.out | ||
coverage.txt | ||
kubectl-envsubst | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/hashmap-kz/kubectl-envsubst/pkg/cmd" | ||
"github.com/hashmap-kz/kubectl-envsubst/pkg/version" | ||
) | ||
|
||
func main() { | ||
err := runApp() | ||
if err != nil { | ||
_, _ = fmt.Fprintf(os.Stderr, "%s", err.Error()) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package main | ||
|
||
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) | ||
} |