diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..0530745 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,121 @@ +--- +run: + concurrency: 6 + timeout: 10m +issues: + exclude-dirs: + - integration/* + + # Maximum issues count per one linter. Set to 0 to disable. Default is 50. + max-issues-per-linter: 0 + + # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + max-same-issues: 0 +linters: + disable-all: true + enable: + - asciicheck + - bodyclose + - dogsled + - dupl + - durationcheck + - errcheck + - goconst + - gocritic + - gocyclo + - godox + - gofmt + - gofumpt + - goheader + - goimports + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosimple + - govet + - importas + - ineffassign + - makezero + - misspell + - nakedret + - nolintlint + - prealloc + - predeclared + - promlinter + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - whitespace + +linters-settings: + godox: + keywords: + - BUG + - FIXME + - HACK + errcheck: + check-type-assertions: true + check-blank: true + gocritic: + disabled-checks: + - ifElseChain + enabled-checks: + # Diagnostic + - commentedOutCode + - nilValReturn + - sloppyReassign + - weakCond + - octalLiteral + + # Performance + - appendCombine + - equalFold + - hugeParam + - indexAlloc + - rangeExprCopy + - rangeValCopy + + # Style + - boolExprSimplify + - commentedOutImport + - docStub + - emptyFallthrough + - emptyStringTest + - hexLiteral + - methodExprCall + - stringXbytes + - typeAssertChain + - unlabelStmt + - yodaStyleExpr + # - ifElseChain + + # Opinionated + - builtinShadow + - importShadow + - initClause + - nestingReduce + - paramTypeCombine + - ptrToRefParam + - typeUnparen + - unnamedResult + - unnecessaryBlock + nolintlint: + # Enable to ensure that nolint directives are all used. Default is true. + allow-unused: false + # Disable to ensure that nolint directives don't have a leading space. Default is true. + # TODO(lint): Enforce machine-readable `nolint` directives + allow-leading-space: true + # Exclude following linters from requiring an explanation. Default is []. + allow-no-explanation: [] + # Enable to require an explanation of nonzero length after each nolint directive. Default is false. + # TODO(lint): Enforce explanations for `nolint` directives + require-explanation: false + # Enable to require nolint directives to mention the specific linter being suppressed. Default is false. + require-specific: true diff --git a/cmd/app/app.go b/cmd/app/app.go index 45f1c55..a5fe74b 100644 --- a/cmd/app/app.go +++ b/cmd/app/app.go @@ -2,16 +2,16 @@ package app import ( "fmt" - "github.com/hashmap-kz/kubectl-envsubst/pkg/cmd" "io" "os" "os/exec" "strings" + + "github.com/hashmap-kz/kubectl-envsubst/pkg/cmd" ) // 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 { @@ -44,7 +44,7 @@ func RunApp() error { // apply STDIN (if any) if flags.HasStdin { - err := applyStdin(flags, kubectl) + err := applyStdin(&flags, kubectl) if err != nil { return err } @@ -52,7 +52,7 @@ func RunApp() error { // apply passed files for _, filename := range files { - err := applyOneFile(flags, kubectl, filename) + err := applyOneFile(&flags, kubectl, filename) if err != nil { return err } @@ -62,7 +62,7 @@ func RunApp() error { } // applyStdin substitutes content, passed to stdin `kubectl apply -f -` -func applyStdin(flags cmd.CmdArgsRawRecognized, kubectl string) error { +func applyStdin(flags *cmd.ArgsRawRecognized, kubectl string) error { stdin, err := io.ReadAll(os.Stdin) if err != nil { return err @@ -78,8 +78,7 @@ func applyStdin(flags cmd.CmdArgsRawRecognized, kubectl string) error { } // applyOneFile read file (url, local-path), substitute its content, apply result -func applyOneFile(flags cmd.CmdArgsRawRecognized, kubectl string, filename string) error { - +func applyOneFile(flags *cmd.ArgsRawRecognized, kubectl, filename string) error { // recognize file type var contentForSubst []byte @@ -107,7 +106,7 @@ func applyOneFile(flags cmd.CmdArgsRawRecognized, kubectl string, filename strin } // substituteContent runs the subst module for a given content -func substituteContent(flags cmd.CmdArgsRawRecognized, contentForSubst []byte) (string, error) { +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 { @@ -117,12 +116,11 @@ func substituteContent(flags cmd.CmdArgsRawRecognized, contentForSubst []byte) ( } // execKubectl applies a result buffer, bu running `kubectl apply -f -` -func execKubectl(flags cmd.CmdArgsRawRecognized, kubectl string, substitutedBuffer string) error { +func execKubectl(flags *cmd.ArgsRawRecognized, kubectl, substitutedBuffer string) error { // prepare kubectl args args := []string{} args = append(args, flags.Others...) - args = append(args, "-f") - args = append(args, "-") + args = append(args, "-f", "-") // pass stream of files to stdin execCmd, err := cmd.ExecWithStdin(kubectl, []byte(substitutedBuffer), args...) diff --git a/cmd/app/app_test.go b/cmd/app/app_test.go index f7d611b..79b9e7d 100644 --- a/cmd/app/app_test.go +++ b/cmd/app/app_test.go @@ -88,7 +88,10 @@ data: // Flush stdout and reset pointer for reading os.Stdout.Sync() - _, _ = stdoutFile.Seek(0, io.SeekStart) + _, 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) diff --git a/cmd/kubectl-envsubst.go b/cmd/kubectl-envsubst.go index 5dfbccb..926093d 100644 --- a/cmd/kubectl-envsubst.go +++ b/cmd/kubectl-envsubst.go @@ -2,12 +2,12 @@ package main import ( "fmt" - "github.com/hashmap-kz/kubectl-envsubst/cmd/app" "os" + + "github.com/hashmap-kz/kubectl-envsubst/cmd/app" ) func main() { - err := app.RunApp() if err != nil { _, _ = fmt.Fprintf(os.Stderr, "%s", err.Error()) diff --git a/integration/apply_file_test.go b/integration/apply_file_test.go index f00bc18..6ec40c7 100644 --- a/integration/apply_file_test.go +++ b/integration/apply_file_test.go @@ -9,7 +9,6 @@ import ( ) func TestEnvsubstIntegration_SubstApplyFromFile(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -56,5 +55,4 @@ func TestEnvsubstIntegration_SubstApplyFromFile(t *testing.T) { if !strings.Contains(string(validateOutput), resourceName) { t.Errorf("Expected resource %s to exist, got %s", resourceName, string(validateOutput)) } - } diff --git a/integration/apply_mixed_test.go b/integration/apply_mixed_test.go index c9db960..bfe050f 100644 --- a/integration/apply_mixed_test.go +++ b/integration/apply_mixed_test.go @@ -9,7 +9,6 @@ import ( ) func TestEnvsubstIntegration_SubstApplyConfigmapMixed(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return diff --git a/integration/apply_stdin_large_test.go b/integration/apply_stdin_large_test.go index 92d2a02..819ae52 100644 --- a/integration/apply_stdin_large_test.go +++ b/integration/apply_stdin_large_test.go @@ -42,7 +42,6 @@ data: ` func TestEnvsubstIntegration_SubstApplyFromStdinWithLargeContent(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -79,5 +78,4 @@ func TestEnvsubstIntegration_SubstApplyFromStdinWithLargeContent(t *testing.T) { t.Errorf("Expected substituted output to contain '%s', got %s", er, stringOutput) } } - } diff --git a/integration/apply_stdin_test.go b/integration/apply_stdin_test.go index 6485cce..1db1e67 100644 --- a/integration/apply_stdin_test.go +++ b/integration/apply_stdin_test.go @@ -9,7 +9,6 @@ import ( ) func TestEnvsubstIntegration_SubstApplyFromStdin(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -81,5 +80,4 @@ spec: if !strings.Contains(string(validateOutput), resourceName) { t.Errorf("Expected resource %s to exist, got %s", resourceName, string(validateOutput)) } - } diff --git a/integration/apply_url_test.go b/integration/apply_url_test.go index af5a2bc..3d69f41 100644 --- a/integration/apply_url_test.go +++ b/integration/apply_url_test.go @@ -9,7 +9,6 @@ import ( ) func TestEnvsubstIntegration_SubstApplyFromUrl(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -57,5 +56,4 @@ func TestEnvsubstIntegration_SubstApplyFromUrl(t *testing.T) { if !strings.Contains(string(validateOutput), resourceName) { t.Errorf("Expected resource %s to exist, got %s", resourceName, string(validateOutput)) } - } diff --git a/integration/helpers.go b/integration/helpers.go index 39b1796..34f8dc5 100644 --- a/integration/helpers.go +++ b/integration/helpers.go @@ -85,7 +85,6 @@ func printEnvsubstVersionInfo(t *testing.T) { } } } - } func getDeploymentImageName(t *testing.T, deploymentName string) string { @@ -100,7 +99,6 @@ func getDeploymentImageName(t *testing.T, deploymentName string) string { } func createTempFile(t *testing.T, content string, extension string) (string, error) { - tempFile, err := os.CreateTemp("", "kubectl-envsubst-tmp-*."+extension) if err != nil { return "", fmt.Errorf("failed to create temp file: %w", err) diff --git a/integration/plain_combined_test.go b/integration/plain_combined_test.go index 8aa8840..b33b0e4 100644 --- a/integration/plain_combined_test.go +++ b/integration/plain_combined_test.go @@ -8,7 +8,6 @@ import ( ) func TestEnvsubstIntegration_NoSubst_MixedManifests(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -49,5 +48,4 @@ func TestEnvsubstIntegration_NoSubst_MixedManifests(t *testing.T) { t.Errorf("Expected substituted output to contain '%s', got %s", er, stringOutput) } } - } diff --git a/integration/plain_glob_test.go b/integration/plain_glob_test.go index b817349..8a3c60b 100644 --- a/integration/plain_glob_test.go +++ b/integration/plain_glob_test.go @@ -8,7 +8,6 @@ import ( ) func TestEnvsubstIntegration_NoSubst_GlobPatterns_Yaml(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -54,11 +53,9 @@ func TestEnvsubstIntegration_NoSubst_GlobPatterns_Yaml(t *testing.T) { t.Errorf("Expected substituted output does not contain '%s'", er) } } - } func TestEnvsubstIntegration_NoSubst_GlobPatterns_Json(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -104,5 +101,4 @@ func TestEnvsubstIntegration_NoSubst_GlobPatterns_Json(t *testing.T) { t.Errorf("Expected substituted output does not contain '%s'", er) } } - } diff --git a/integration/plain_recursive_test.go b/integration/plain_recursive_test.go index 2033a5b..0bc0313 100644 --- a/integration/plain_recursive_test.go +++ b/integration/plain_recursive_test.go @@ -8,7 +8,6 @@ import ( ) func TestEnvsubstIntegration_NoSubst_Recursive(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -54,5 +53,4 @@ func TestEnvsubstIntegration_NoSubst_Recursive(t *testing.T) { t.Errorf("Expected substituted output to contain '%s', got %s", er, stringOutput) } } - } diff --git a/integration/plain_yaml_json_test.go b/integration/plain_yaml_json_test.go index 124480b..ea913e5 100644 --- a/integration/plain_yaml_json_test.go +++ b/integration/plain_yaml_json_test.go @@ -8,7 +8,6 @@ import ( ) func TestEnvsubstIntegration_NoSubst_MixedManifests_MixedFileFormats(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -49,5 +48,4 @@ func TestEnvsubstIntegration_NoSubst_MixedManifests_MixedFileFormats(t *testing. t.Errorf("Expected substituted output to contain '%s', got %s", er, stringOutput) } } - } diff --git a/integration/subst_combined_test.go b/integration/subst_combined_test.go index e7f9714..457deeb 100644 --- a/integration/subst_combined_test.go +++ b/integration/subst_combined_test.go @@ -8,7 +8,6 @@ import ( ) func TestEnvsubstIntegration_SubstMixedManifestsCombined(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -63,5 +62,4 @@ func TestEnvsubstIntegration_SubstMixedManifestsCombined(t *testing.T) { t.Errorf("Expected substituted output to contain '%s', got %s", er, stringOutput) } } - } diff --git a/integration/subst_inplace_test.go b/integration/subst_inplace_test.go index 5e4dd6c..406d82e 100644 --- a/integration/subst_inplace_test.go +++ b/integration/subst_inplace_test.go @@ -8,7 +8,6 @@ import ( ) func TestSubstFromFile(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -42,7 +41,8 @@ spec: envVars: []string{ "SERVICE_NAME=my-service", "APP_NAME=my-app", - "ENVSUBST_ALLOWED_PREFIXES=SERVICE_,APP_"}, + "ENVSUBST_ALLOWED_PREFIXES=SERVICE_,APP_", + }, expectedOutput: "service/my-service", }, @@ -85,7 +85,6 @@ spec: for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // prepare tmp file with content tmpFile, err := createTempFile(t, tc.inputContent, "yaml") if err != nil { @@ -115,7 +114,6 @@ spec: t.Errorf("Expected image: %s, got %s", tc.expectedImage, imageName) } } - }) } } diff --git a/integration/subst_separated_test.go b/integration/subst_separated_test.go index b30a901..b14fb17 100644 --- a/integration/subst_separated_test.go +++ b/integration/subst_separated_test.go @@ -8,7 +8,6 @@ import ( ) func TestEnvsubstIntegration_SubstMixedManifestsSeparated(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -63,5 +62,4 @@ func TestEnvsubstIntegration_SubstMixedManifestsSeparated(t *testing.T) { t.Errorf("Expected substituted output to contain '%s', got %s", er, stringOutput) } } - } diff --git a/integration/subst_yaml_json_test.go b/integration/subst_yaml_json_test.go index 4a46139..9c8de8b 100644 --- a/integration/subst_yaml_json_test.go +++ b/integration/subst_yaml_json_test.go @@ -8,7 +8,6 @@ import ( ) func TestEnvsubstIntegration_Subst_MixedManifests_MixedExtensions_MixedFileFormats(t *testing.T) { - if os.Getenv(integrationTestEnv) != integrationTestFlag { t.Log("integration test was skipped due to configuration") return @@ -63,5 +62,4 @@ func TestEnvsubstIntegration_Subst_MixedManifests_MixedExtensions_MixedFileForma t.Errorf("Expected substituted output to contain '%s', got %s", er, stringOutput) } } - } diff --git a/pkg/cmd/execs_test.go b/pkg/cmd/execs_test.go index 49952db..d8879f7 100644 --- a/pkg/cmd/execs_test.go +++ b/pkg/cmd/execs_test.go @@ -8,7 +8,7 @@ import ( ) // Cross-platform helper to get a command -func getCommand(command string, args []string) (string, []string) { +func getCommand(command string, args []string) (c string, a []string) { if runtime.GOOS == "windows" { // Adjust commands for Windows switch command { @@ -81,7 +81,6 @@ func TestExecWithStdin(t *testing.T) { // Execute the command result, err := ExecWithStdin(command, input, args...) - // Validate error (should not fail because the command exits with 0) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -123,7 +122,6 @@ func TestExecWithStdin(t *testing.T) { } func TestExecWithLargeInput(t *testing.T) { - // Generate a large input string var largeInput strings.Builder for i := 0; i < 1000000; i++ { // 1 million lines @@ -132,7 +130,6 @@ func TestExecWithLargeInput(t *testing.T) { // Execute the command with the large input result, err := ExecWithStdin("cat", []byte(largeInput.String())) - // Validate that the command executed successfully if err != nil { t.Fatalf("Unexpected error: %v", err) diff --git a/pkg/cmd/rawargs.go b/pkg/cmd/rawargs.go index f4e2ad4..4398920 100644 --- a/pkg/cmd/rawargs.go +++ b/pkg/cmd/rawargs.go @@ -11,7 +11,7 @@ const ( envsubstAllowedPrefixesEnv = "ENVSUBST_ALLOWED_PREFIXES" ) -type CmdArgsRawRecognized struct { +type ArgsRawRecognized struct { Filenames []string EnvsubstAllowedVars []string EnvsubstAllowedPrefix []string @@ -30,14 +30,13 @@ func allEmpty(values []string) bool { return true } -func ParseArgs() (CmdArgsRawRecognized, error) { +func ParseArgs() (ArgsRawRecognized, error) { args := os.Args[1:] // Skip the program name - var result CmdArgsRawRecognized + var result ArgsRawRecognized for i := 0; i < len(args); i++ { arg := args[i] switch { - // Handle --filename= or -f= case strings.HasPrefix(arg, "--filename="), strings.HasPrefix(arg, "-f="): if err := handleFilename(strings.SplitN(arg, "=", 2)[1], &result); err != nil { @@ -122,7 +121,7 @@ func ParseArgs() (CmdArgsRawRecognized, error) { return result, nil } -func handleFilename(filename string, result *CmdArgsRawRecognized) error { +func handleFilename(filename string, result *ArgsRawRecognized) error { if filename == "" { return fmt.Errorf("missing filename value") } diff --git a/pkg/cmd/rawargs_test.go b/pkg/cmd/rawargs_test.go index 4f10a8d..e58b121 100644 --- a/pkg/cmd/rawargs_test.go +++ b/pkg/cmd/rawargs_test.go @@ -10,73 +10,73 @@ func TestParseArgs(t *testing.T) { cases := []struct { name string args []string - expectedResult CmdArgsRawRecognized + expectedResult ArgsRawRecognized expectedError bool }{ { name: "No arguments", args: []string{}, - expectedResult: CmdArgsRawRecognized{}, + expectedResult: ArgsRawRecognized{}, expectedError: false, }, { name: "Single filename with =", args: []string{"--filename=file1.yaml"}, - expectedResult: CmdArgsRawRecognized{Filenames: []string{"file1.yaml"}}, + expectedResult: ArgsRawRecognized{Filenames: []string{"file1.yaml"}}, expectedError: false, }, { name: "Recursive flag with long option", args: []string{"--recursive"}, - expectedResult: CmdArgsRawRecognized{Recursive: true}, + expectedResult: ArgsRawRecognized{Recursive: true}, expectedError: false, }, { name: "Recursive flag with short option", args: []string{"-R"}, - expectedResult: CmdArgsRawRecognized{Recursive: true}, + expectedResult: ArgsRawRecognized{Recursive: true}, expectedError: false, }, { name: "Envsubst allowed vars", args: []string{"--envsubst-allowed-vars=HOME,USER"}, - expectedResult: CmdArgsRawRecognized{EnvsubstAllowedVars: []string{"HOME", "USER"}}, + expectedResult: ArgsRawRecognized{EnvsubstAllowedVars: []string{"HOME", "USER"}}, expectedError: false, }, { name: "Envsubst allowed vars (no =)", args: []string{"--envsubst-allowed-vars", "HOME,USER"}, - expectedResult: CmdArgsRawRecognized{EnvsubstAllowedVars: []string{"HOME", "USER"}}, + expectedResult: ArgsRawRecognized{EnvsubstAllowedVars: []string{"HOME", "USER"}}, expectedError: false, }, { name: "Envsubst allowed vars, with append", args: []string{"--envsubst-allowed-vars=HOME,USER", "--envsubst-allowed-vars=PWD"}, - expectedResult: CmdArgsRawRecognized{EnvsubstAllowedVars: []string{"HOME", "USER", "PWD"}}, + expectedResult: ArgsRawRecognized{EnvsubstAllowedVars: []string{"HOME", "USER", "PWD"}}, expectedError: false, }, { name: "Envsubst allowed vars, with append (no =)", args: []string{"--envsubst-allowed-vars", "HOME,USER", "--envsubst-allowed-vars", "PWD"}, - expectedResult: CmdArgsRawRecognized{EnvsubstAllowedVars: []string{"HOME", "USER", "PWD"}}, + expectedResult: ArgsRawRecognized{EnvsubstAllowedVars: []string{"HOME", "USER", "PWD"}}, expectedError: false, }, { name: "Missing value for --filename", args: []string{"--filename"}, - expectedResult: CmdArgsRawRecognized{}, + expectedResult: ArgsRawRecognized{}, expectedError: true, }, { name: "Unknown flag", args: []string{"--unknown-flag"}, - expectedResult: CmdArgsRawRecognized{Others: []string{"--unknown-flag"}}, + expectedResult: ArgsRawRecognized{Others: []string{"--unknown-flag"}}, expectedError: false, }, { name: "Mix of valid and invalid args", args: []string{"--filename=file.yaml", "--unknown"}, - expectedResult: CmdArgsRawRecognized{ + expectedResult: ArgsRawRecognized{ Filenames: []string{"file.yaml"}, Others: []string{"--unknown"}, @@ -86,67 +86,67 @@ func TestParseArgs(t *testing.T) { { name: "Multiple filenames", args: []string{"--filename=file1.yaml", "--filename=file2.yaml"}, - expectedResult: CmdArgsRawRecognized{Filenames: []string{"file1.yaml", "file2.yaml"}}, + expectedResult: ArgsRawRecognized{Filenames: []string{"file1.yaml", "file2.yaml"}}, expectedError: false, }, { name: "Envsubst allowed prefixes", args: []string{"--envsubst-allowed-prefixes=CI_,APP"}, - expectedResult: CmdArgsRawRecognized{EnvsubstAllowedPrefix: []string{"CI_", "APP"}}, + expectedResult: ArgsRawRecognized{EnvsubstAllowedPrefix: []string{"CI_", "APP"}}, expectedError: false, }, { name: "Envsubst allowed prefixes (no =)", args: []string{"--envsubst-allowed-prefixes", "CI_,APP"}, - expectedResult: CmdArgsRawRecognized{EnvsubstAllowedPrefix: []string{"CI_", "APP"}}, + expectedResult: ArgsRawRecognized{EnvsubstAllowedPrefix: []string{"CI_", "APP"}}, expectedError: false, }, { name: "Envsubst allowed prefixes, with append", args: []string{"--envsubst-allowed-prefixes=CI_,APP", "--envsubst-allowed-prefixes=TF_VAR_"}, - expectedResult: CmdArgsRawRecognized{EnvsubstAllowedPrefix: []string{"CI_", "APP", "TF_VAR_"}}, + expectedResult: ArgsRawRecognized{EnvsubstAllowedPrefix: []string{"CI_", "APP", "TF_VAR_"}}, expectedError: false, }, { name: "Envsubst allowed prefixes with append (no =)", args: []string{"--envsubst-allowed-prefixes", "CI_,APP", "--envsubst-allowed-prefixes", "TF_VAR_"}, - expectedResult: CmdArgsRawRecognized{EnvsubstAllowedPrefix: []string{"CI_", "APP", "TF_VAR_"}}, + expectedResult: ArgsRawRecognized{EnvsubstAllowedPrefix: []string{"CI_", "APP", "TF_VAR_"}}, expectedError: false, }, { name: "Empty value for --filename", args: []string{"--filename="}, - expectedResult: CmdArgsRawRecognized{}, + expectedResult: ArgsRawRecognized{}, expectedError: true, }, { name: "Empty value for --envsubst-allowed-vars", args: []string{"--envsubst-allowed-vars="}, - expectedResult: CmdArgsRawRecognized{}, + expectedResult: ArgsRawRecognized{}, expectedError: true, }, { name: "Single filename with short flag", args: []string{"-f=file3.yaml"}, - expectedResult: CmdArgsRawRecognized{Filenames: []string{"file3.yaml"}}, + expectedResult: ArgsRawRecognized{Filenames: []string{"file3.yaml"}}, expectedError: false, }, { name: "Unrecognized argument without prefix", args: []string{"random-arg"}, - expectedResult: CmdArgsRawRecognized{Others: []string{"random-arg"}}, + expectedResult: ArgsRawRecognized{Others: []string{"random-arg"}}, expectedError: false, }, { name: "Multiple unrecognized arguments", args: []string{"random-arg1", "random-arg2"}, - expectedResult: CmdArgsRawRecognized{Others: []string{"random-arg1", "random-arg2"}}, + expectedResult: ArgsRawRecognized{Others: []string{"random-arg1", "random-arg2"}}, expectedError: false, }, { name: "Mixed valid and unrecognized arguments", args: []string{"random-arg", "--filename=file.yaml"}, - expectedResult: CmdArgsRawRecognized{ + expectedResult: ArgsRawRecognized{ Filenames: []string{"file.yaml"}, Others: []string{"random-arg"}, }, @@ -155,13 +155,13 @@ func TestParseArgs(t *testing.T) { { name: "Unrecognized argument resembling a flag", args: []string{"-notarealflag"}, - expectedResult: CmdArgsRawRecognized{Others: []string{"-notarealflag"}}, + expectedResult: ArgsRawRecognized{Others: []string{"-notarealflag"}}, expectedError: false, }, { name: "Unrecognized argument with spaces", args: []string{"random-arg", "--filename=file.yaml", "another-random-arg"}, - expectedResult: CmdArgsRawRecognized{ + expectedResult: ArgsRawRecognized{ Filenames: []string{"file.yaml"}, Others: []string{"random-arg", "another-random-arg"}, }, @@ -170,7 +170,7 @@ func TestParseArgs(t *testing.T) { { name: "Valid args with unrecognized argument that looks like a short flag (1)", args: []string{"-R", "-xyz"}, - expectedResult: CmdArgsRawRecognized{ + expectedResult: ArgsRawRecognized{ Recursive: true, Others: []string{"-xyz"}, }, @@ -179,7 +179,7 @@ func TestParseArgs(t *testing.T) { { name: "Valid args with unrecognized argument that looks like a short flag (2)", args: []string{"-h", "-xyz"}, - expectedResult: CmdArgsRawRecognized{ + expectedResult: ArgsRawRecognized{ Help: true, Others: []string{"-xyz"}, }, @@ -209,7 +209,6 @@ func TestParseArgs(t *testing.T) { func TestParseArgs_SingleStdin(t *testing.T) { os.Args = []string{"cmd", "--filename", "-"} result, err := ParseArgs() - if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -329,7 +328,7 @@ func TestParseArgs_ErrorsAndReturns(t *testing.T) { args []string envVars map[string]string expectErr string - validate func(t *testing.T, result CmdArgsRawRecognized) + validate func(t *testing.T, result ArgsRawRecognized) }{ { name: "Missing value for --filename=", @@ -385,7 +384,7 @@ func TestParseArgs_ErrorsAndReturns(t *testing.T) { { name: "Successful parsing with all flags", args: []string{"app", "--filename=test.yaml", "--envsubst-allowed-vars=VAR1,VAR2", "--envsubst-allowed-prefixes=PREFIX1,PREFIX2", "--recursive", "--help"}, - validate: func(t *testing.T, result CmdArgsRawRecognized) { + validate: func(t *testing.T, result ArgsRawRecognized) { if len(result.Filenames) != 1 || result.Filenames[0] != "test.yaml" { t.Errorf("Expected filename 'test.yaml', got %v", result.Filenames) } diff --git a/pkg/cmd/remote.go b/pkg/cmd/remote.go index 706f7bd..dfbc7d7 100644 --- a/pkg/cmd/remote.go +++ b/pkg/cmd/remote.go @@ -4,12 +4,18 @@ import ( "fmt" "io" "net/http" + "net/url" ) -func ReadRemoteFileContent(url string) ([]byte, error) { +func ReadRemoteFileContent(inputURL string) ([]byte, error) { + // Parse and validate the URL + parsedURL, err := url.Parse(inputURL) + if err != nil || parsedURL.Scheme == "" || parsedURL.Host == "" { + return nil, fmt.Errorf("invalid URL: %s", inputURL) + } // Make the HTTP GET request - response, err := http.Get(url) + response, err := http.Get(parsedURL.String()) if err != nil { return nil, err } @@ -17,7 +23,7 @@ func ReadRemoteFileContent(url string) ([]byte, error) { // Check for HTTP errors if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("cannot GET file content from: %s", url) + return nil, fmt.Errorf("cannot GET file content from: %s", inputURL) } // Read the response body diff --git a/pkg/cmd/remote_test.go b/pkg/cmd/remote_test.go index 86a4e57..26e73f9 100644 --- a/pkg/cmd/remote_test.go +++ b/pkg/cmd/remote_test.go @@ -8,11 +8,10 @@ import ( ) func TestReadRemote(t *testing.T) { - t.Run("Successful Request", func(t *testing.T) { mockHTTPResponse := "Remote file content" http.DefaultClient = &http.Client{ - Transport: roundTripper(func(req *http.Request) *http.Response { + Transport: roundTripper(func(_ *http.Request) *http.Response { return &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(mockHTTPResponse)), @@ -30,7 +29,7 @@ func TestReadRemote(t *testing.T) { t.Run("Failed Request", func(t *testing.T) { http.DefaultClient = &http.Client{ - Transport: roundTripper(func(req *http.Request) *http.Response { + Transport: roundTripper(func(_ *http.Request) *http.Response { return &http.Response{ StatusCode: 404, Body: io.NopCloser(strings.NewReader("Not Found")), diff --git a/pkg/cmd/resolve_test.go b/pkg/cmd/resolve_test.go index 738fa7b..eda44e9 100644 --- a/pkg/cmd/resolve_test.go +++ b/pkg/cmd/resolve_test.go @@ -22,19 +22,19 @@ func TestResolveFilenames2(t *testing.T) { testFile4 := filepath.Join(testSubDir, "file4.xml") // Create test files and directories - if err := os.WriteFile(testFile1, []byte("content1"), 0644); err != nil { + if err := os.WriteFile(testFile1, []byte("content1"), 0o600); err != nil { t.Fatal(err) } - if err := os.WriteFile(testFile2, []byte("content2"), 0644); err != nil { + if err := os.WriteFile(testFile2, []byte("content2"), 0o600); err != nil { t.Fatal(err) } - if err := os.Mkdir(testSubDir, 0755); err != nil { + if err := os.Mkdir(testSubDir, 0o755); err != nil { t.Fatal(err) } - if err := os.WriteFile(testFile3, []byte("content3"), 0644); err != nil { + if err := os.WriteFile(testFile3, []byte("content3"), 0o600); err != nil { t.Fatal(err) } - if err := os.WriteFile(testFile4, []byte("content4"), 0644); err != nil { + if err := os.WriteFile(testFile4, []byte("content4"), 0o600); err != nil { t.Fatal(err) } @@ -165,19 +165,19 @@ func TestResolveAllFiles(t *testing.T) { subDir := filepath.Join(tempDir, "subdir") subFile := filepath.Join(subDir, "file3.yaml") - if err := os.WriteFile(file1, []byte("test content"), 0644); err != nil { + if err := os.WriteFile(file1, []byte("test content"), 0o600); err != nil { t.Fatalf("Failed to create test file: %v", err) } - if err := os.WriteFile(file2, []byte("test content"), 0644); err != nil { + if err := os.WriteFile(file2, []byte("test content"), 0o600); err != nil { t.Fatalf("Failed to create test file: %v", err) } - if err := os.WriteFile(file3, []byte("test content"), 0644); err != nil { + if err := os.WriteFile(file3, []byte("test content"), 0o600); err != nil { t.Fatalf("Failed to create test file: %v", err) } - if err := os.Mkdir(subDir, 0755); err != nil { + if err := os.Mkdir(subDir, 0o755); err != nil { t.Fatalf("Failed to create test subdir: %v", err) } - if err := os.WriteFile(subFile, []byte("test content"), 0644); err != nil { + if err := os.WriteFile(subFile, []byte("test content"), 0o600); err != nil { t.Fatalf("Failed to create test subfile: %v", err) } diff --git a/pkg/cmd/subst.go b/pkg/cmd/subst.go index 62bf423..4734dc3 100644 --- a/pkg/cmd/subst.go +++ b/pkg/cmd/subst.go @@ -9,10 +9,8 @@ import ( "strings" ) -var ( - // Match placeholders like ${VAR} or $VAR - envVarRegex = regexp.MustCompile(`\$\{?([a-zA-Z_][a-zA-Z0-9_]*)\}?`) -) +// Match placeholders like ${VAR} or $VAR +var envVarRegex = regexp.MustCompile(`\$\{?([a-zA-Z_][a-zA-Z0-9_]*)\}?`) type Envsubst struct { allowedVars []string @@ -21,7 +19,7 @@ type Envsubst struct { verbose bool } -func NewEnvsubst(allowedVars []string, allowedPrefixes []string, strict bool) *Envsubst { +func NewEnvsubst(allowedVars, allowedPrefixes []string, strict bool) *Envsubst { return &Envsubst{ allowedVars: allowedVars, allowedPrefixes: allowedPrefixes, @@ -30,7 +28,6 @@ func NewEnvsubst(allowedVars []string, allowedPrefixes []string, strict bool) *E } func (p *Envsubst) SubstituteEnvs(text string) (string, error) { - // Collect allowed environment variables envMap := p.collectAllowedEnvVars() diff --git a/pkg/cmd/subst_test.go b/pkg/cmd/subst_test.go index 7f10941..0d82220 100644 --- a/pkg/cmd/subst_test.go +++ b/pkg/cmd/subst_test.go @@ -113,7 +113,7 @@ func TestEnvsubst(t *testing.T) { } func TestEnvsubstWithManifestsParts(t *testing.T) { - var manifestSnippetInput = strings.TrimSpace(` + manifestSnippetInput := strings.TrimSpace(` apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -142,7 +142,7 @@ spec: pathType: ImplementationSpecific `) - var manifestSnippetExpect = strings.TrimSpace(` + manifestSnippetExpect := strings.TrimSpace(` apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -320,7 +320,7 @@ func TestStrictMode(t *testing.T) { } func TestComplexManifests(t *testing.T) { - var complexMixedTest = ` + complexMixedTest := ` --- apiVersion: v1 kind: ConfigMap @@ -713,7 +713,6 @@ spec: t.Run(test.name, func(t *testing.T) { envsubst := NewEnvsubst(test.allowedEnvs, []string{}, true) output, err := envsubst.SubstituteEnvs(test.text) - if err != nil { t.Errorf("Unexpected error status for complex test") } @@ -755,7 +754,10 @@ func TestSubstituteEnvs_VerboseMode_DebugUnresolvedVars(t *testing.T) { log.SetOutput(&logBuffer) text := "Hello $VAR1 and ${VAR2}!" - _, _ = envsubst.SubstituteEnvs(text) + _, err := envsubst.SubstituteEnvs(text) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } logBuf := logBuffer.String() if !strings.Contains(logBuf, "DEBUG: an unresolved variable that is not in the filter list remains unchanged: VAR2") { @@ -769,9 +771,8 @@ func TestSubstituteEnvs_UnresolvedVarsNotInFilter_NoError(t *testing.T) { envsubst := NewEnvsubst([]string{"VAR1"}, []string{}, true) - text := "Hello $VAR1 and ${VAR3}!" - result, err := envsubst.SubstituteEnvs(text) - + const s = "Hello $VAR1 and ${VAR3}!" + result, err := envsubst.SubstituteEnvs(s) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -812,7 +813,6 @@ func TestSubstituteEnvs_AllResolved(t *testing.T) { text := "Hello $VAR1 and ${VAR2}!" expected := "Hello value1 and value2!" result, err := envsubst.SubstituteEnvs(text) - if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -830,7 +830,6 @@ func TestSubstituteEnvs_NoFilter_NoErrorForUnresolved(t *testing.T) { text := "Hello $VAR1 and ${VAR3}!" expected := "Hello $VAR1 and ${VAR3}!" result, err := envsubst.SubstituteEnvs(text) - if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -845,7 +844,6 @@ func TestSubstituteEnvs_EmptyInput(t *testing.T) { text := "" expected := "" result, err := envsubst.SubstituteEnvs(text) - if err != nil { t.Fatalf("Unexpected error for empty input: %v", err) } @@ -860,15 +858,13 @@ func TestSubstituteEnvs_EmptyAllowedLists(t *testing.T) { envsubst := NewEnvsubst([]string{}, []string{}, true) - text := "Hello $VAR1!" - expected := "Hello $VAR1!" - result, err := envsubst.SubstituteEnvs(text) - + const s = "Hello $VAR1!" + result, err := envsubst.SubstituteEnvs(s) if err != nil { t.Fatalf("Unexpected error: %v", err) } - if result != expected { - t.Errorf("Expected '%s', got '%s'", expected, result) + if result != s { + t.Errorf("Expected '%s', got '%s'", s, result) } } @@ -894,7 +890,6 @@ func TestSubstituteEnvs_OverlappingPrefixes(t *testing.T) { text := "Hello $APP_VAR1 and ${APP_VAR2}!" expected := "Hello value1 and value2!" result, err := envsubst.SubstituteEnvs(text) - if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -913,7 +908,6 @@ func TestSubstituteEnvs_StrictMode_MixedFilters(t *testing.T) { text := "Hello $VAR1 and ${APP_VAR2} and ${VAR3}!" result, err := envsubst.SubstituteEnvs(text) - // Should not raise an error since unresolved ${VAR3} is not in filters if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -1061,17 +1055,16 @@ func TestVarInSlice(t *testing.T) { } func TestRandomText_AllowedVars(t *testing.T) { + alpha := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - var alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - - var input = strings.TrimSpace(` + input := strings.TrimSpace(` Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. `) - var template = strings.TrimSpace(` + template := strings.TrimSpace(` $L_$O$R$E$M $I$P$S$U$M $D$O$L$O$R $S$I$T $A$M$E$T, $C$O$N$S$E$C$T$E$T$U$R $A$D$I$P$I$S$C$I$N$G $E$L$I$T, $S$E$D $D$O $E$I$U$S$M$O$D $T$E$M$P$O$R $I$N$C$I$D$I$D$U$N$T $U$T $L$A$B$O$R$E $E$T $D$O$L$O$R$E $M$A$G$N$A $A$L$I$Q$U$A. $U_$T $E$N$I$M $A$D $M$I$N$I$M $V$E$N$I$A$M, $Q$U$I$S $N$O$S$T$R$U$D $E$X$E$R$C$I$T$A$T$I$O$N $U$L$L$A$M$C$O $L$A$B$O$R$I$S $N$I$S$I $U$T $A$L$I$Q$U$I$P $E$X $E$A $C$O$M$M$O$D$O $C$O$N$S$E$Q$U$A$T. $D_$U$I$S $A$U$T$E $I$R$U$R$E $D$O$L$O$R $I$N $R$E$P$R$E$H$E$N$D$E$R$I$T $I$N $V$O$L$U$P$T$A$T$E $V$E$L$I$T $E$S$S$E $C$I$L$L$U$M $D$O$L$O$R$E $E$U $F$U$G$I$A$T $N$U$L$L$A $P$A$R$I$A$T$U$R. @@ -1084,8 +1077,7 @@ $E_$X$C$E$P$T$E$U$R $S$I$N$T $O$C$C$A$E$C$A$T $C$U$P$I$D$A$T$A$T $N$O$N $P$R$O$I c := string(c) os.Setenv(strings.ToUpper(c)+"_", strings.ToUpper(c)) os.Setenv(strings.ToUpper(c), strings.ToLower(c)) - allowedVars = append(allowedVars, strings.ToUpper(c)) - allowedVars = append(allowedVars, strings.ToUpper(c)+"_") + allowedVars = append(allowedVars, strings.ToUpper(c), strings.ToUpper(c)+"_") } defer func() { for _, c := range alpha { @@ -1106,17 +1098,16 @@ $E_$X$C$E$P$T$E$U$R $S$I$N$T $O$C$C$A$E$C$A$T $C$U$P$I$D$A$T$A$T $N$O$N $P$R$O$I } func TestRandomText_AllowedPrefixes(t *testing.T) { + alpha := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - var alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - - var input = strings.TrimSpace(` + input := strings.TrimSpace(` Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. `) - var template = strings.TrimSpace(` + template := strings.TrimSpace(` $L_$O$R$E$M $I$P$S$U$M $D$O$L$O$R $S$I$T $A$M$E$T, $C$O$N$S$E$C$T$E$T$U$R $A$D$I$P$I$S$C$I$N$G $E$L$I$T, $S$E$D $D$O $E$I$U$S$M$O$D $T$E$M$P$O$R $I$N$C$I$D$I$D$U$N$T $U$T $L$A$B$O$R$E $E$T $D$O$L$O$R$E $M$A$G$N$A $A$L$I$Q$U$A. $U_$T $E$N$I$M $A$D $M$I$N$I$M $V$E$N$I$A$M, $Q$U$I$S $N$O$S$T$R$U$D $E$X$E$R$C$I$T$A$T$I$O$N $U$L$L$A$M$C$O $L$A$B$O$R$I$S $N$I$S$I $U$T $A$L$I$Q$U$I$P $E$X $E$A $C$O$M$M$O$D$O $C$O$N$S$E$Q$U$A$T. $D_$U$I$S $A$U$T$E $I$R$U$R$E $D$O$L$O$R $I$N $R$E$P$R$E$H$E$N$D$E$R$I$T $I$N $V$O$L$U$P$T$A$T$E $V$E$L$I$T $E$S$S$E $C$I$L$L$U$M $D$O$L$O$R$E $E$U $F$U$G$I$A$T $N$U$L$L$A $P$A$R$I$A$T$U$R.