From f7dcbd7869211e9a077c7e3f26eb2b0542d5bdaf Mon Sep 17 00:00:00 2001 From: Alessio Pragliola <83355398+Al-Pragliola@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:35:23 +0100 Subject: [PATCH] fix: added env file support to kubeconfig in cfg (#469) --- .../v1alpha2/distribution/create/preflight.go | 10 +- .../v1alpha2/distribution/delete/preflight.go | 24 +++-- internal/cluster/common.go | 26 ------ internal/cluster/creator.go | 4 - internal/cluster/deleter.go | 4 - internal/parser/config.go | 78 ++++++++++++++++ internal/parser/config_test.go | 91 +++++++++++++++++++ internal/template/mapper/mapper.go | 59 +----------- internal/x/bytes/buffer.go | 6 +- 9 files changed, 204 insertions(+), 98 deletions(-) delete mode 100644 internal/cluster/common.go create mode 100644 internal/parser/config.go create mode 100644 internal/parser/config_test.go diff --git a/internal/apis/kfd/v1alpha2/distribution/create/preflight.go b/internal/apis/kfd/v1alpha2/distribution/create/preflight.go index 0ab282a8c..8964d3cc0 100644 --- a/internal/apis/kfd/v1alpha2/distribution/create/preflight.go +++ b/internal/apis/kfd/v1alpha2/distribution/create/preflight.go @@ -19,6 +19,7 @@ import ( "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/diffs" "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/parser" "github.com/sighupio/furyctl/internal/state" "github.com/sighupio/furyctl/internal/tool/kubectl" execx "github.com/sighupio/furyctl/internal/x/exec" @@ -86,11 +87,15 @@ func NewPreFlight( } func (p *PreFlight) Exec() (*Status, error) { + var err error + status := &Status{ Diffs: r3diff.Changelog{}, Success: false, } + cfgParser := parser.NewConfigParser(p.furyctlConfPath) + logrus.Info("Running preflight checks") if err := p.CreateRootFolder(); err != nil { @@ -100,7 +105,10 @@ func (p *PreFlight) Exec() (*Status, error) { kubeconfigPath := os.Getenv("KUBECONFIG") if distribution.HasFeature(p.kfd, distribution.FeatureKubeconfigInSchema) { - kubeconfigPath = p.furyctlConf.Spec.Distribution.Kubeconfig + kubeconfigPath, err = cfgParser.ParseDynamicValue(p.furyctlConf.Spec.Distribution.Kubeconfig) + if err != nil { + return status, fmt.Errorf("error parsing kubeconfig value: %w", err) + } } else if kubeconfigPath == "" { return status, ErrKubeconfigNotSet } diff --git a/internal/apis/kfd/v1alpha2/distribution/delete/preflight.go b/internal/apis/kfd/v1alpha2/distribution/delete/preflight.go index 436ae73a1..7dda88fe7 100644 --- a/internal/apis/kfd/v1alpha2/distribution/delete/preflight.go +++ b/internal/apis/kfd/v1alpha2/distribution/delete/preflight.go @@ -16,6 +16,7 @@ import ( "github.com/sighupio/fury-distribution/pkg/apis/kfddistribution/v1alpha2/public" "github.com/sighupio/furyctl/internal/cluster" "github.com/sighupio/furyctl/internal/distribution" + "github.com/sighupio/furyctl/internal/parser" "github.com/sighupio/furyctl/internal/tool/kubectl" execx "github.com/sighupio/furyctl/internal/x/exec" kubex "github.com/sighupio/furyctl/internal/x/kube" @@ -26,9 +27,10 @@ var ErrKubeconfigNotSet = errors.New("KUBECONFIG env variable is not set") type PreFlight struct { *cluster.OperationPhase - furyctlConf public.KfddistributionKfdV1Alpha2 - kubeRunner *kubectl.Runner - kfdManifest config.KFD + furyctlConf public.KfddistributionKfdV1Alpha2 + furyctlConfPath string + kubeRunner *kubectl.Runner + kfdManifest config.KFD } func NewPreFlight( @@ -43,9 +45,10 @@ func NewPreFlight( ) return &PreFlight{ - OperationPhase: phase, - furyctlConf: furyctlConf, - kfdManifest: kfdManifest, + OperationPhase: phase, + furyctlConf: furyctlConf, + furyctlConfPath: paths.ConfigPath, + kfdManifest: kfdManifest, kubeRunner: kubectl.NewRunner( execx.NewStdExecutor(), kubectl.Paths{ @@ -60,6 +63,10 @@ func NewPreFlight( } func (p *PreFlight) Exec() error { + var err error + + cfgParser := parser.NewConfigParser(p.furyctlConfPath) + logrus.Info("Running preflight checks") if err := p.CreateRootFolder(); err != nil { @@ -69,7 +76,10 @@ func (p *PreFlight) Exec() error { kubeconfigPath := os.Getenv("KUBECONFIG") if distribution.HasFeature(p.kfdManifest, distribution.FeatureKubeconfigInSchema) { - kubeconfigPath = p.furyctlConf.Spec.Distribution.Kubeconfig + kubeconfigPath, err = cfgParser.ParseDynamicValue(p.furyctlConf.Spec.Distribution.Kubeconfig) + if err != nil { + return fmt.Errorf("error parsing kubeconfig value: %w", err) + } } else if kubeconfigPath == "" { return ErrKubeconfigNotSet } diff --git a/internal/cluster/common.go b/internal/cluster/common.go deleted file mode 100644 index 94881e85d..000000000 --- a/internal/cluster/common.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cluster - -import ( - "errors" - "fmt" - "os" - - "github.com/sighupio/fury-distribution/pkg/apis/config" - "github.com/sighupio/furyctl/internal/distribution" -) - -var ErrCannotSetEnvVar = errors.New("cannot set env var") - -func resetKubeconfigEnv(kfdManifest config.KFD) error { - if distribution.HasFeature(kfdManifest, distribution.FeatureKubeconfigInSchema) { - if err := os.Setenv("KUBECONFIG", "willingly-invalid-kubeconfig-path-to-avoid-accidental-usage"); err != nil { - return fmt.Errorf("%w: %w", ErrCannotSetEnvVar, err) - } - } - - return nil -} diff --git a/internal/cluster/creator.go b/internal/cluster/creator.go index 43ac99864..4dd0c0d68 100644 --- a/internal/cluster/creator.go +++ b/internal/cluster/creator.go @@ -69,10 +69,6 @@ func NewCreator( upgrade bool, externalUpgradesPath string, ) (Creator, error) { - if err := resetKubeconfigEnv(kfdManifest); err != nil { - return nil, fmt.Errorf("error resetting kubeconfig env: %w", err) - } - lcAPIVersion := strings.ToLower(minimalConf.APIVersion) lcResourceType := strings.ToLower(minimalConf.Kind) diff --git a/internal/cluster/deleter.go b/internal/cluster/deleter.go index 04d2ef60e..ea5936387 100644 --- a/internal/cluster/deleter.go +++ b/internal/cluster/deleter.go @@ -57,10 +57,6 @@ func NewDeleter( vpnAutoConnect, dryRun bool, ) (Deleter, error) { - if err := resetKubeconfigEnv(kfdManifest); err != nil { - return nil, fmt.Errorf("error resetting kubeconfig env: %w", err) - } - lcAPIVersion := strings.ToLower(minimalConf.APIVersion) lcResourceType := strings.ToLower(minimalConf.Kind) diff --git a/internal/parser/config.go b/internal/parser/config.go new file mode 100644 index 000000000..891859fd9 --- /dev/null +++ b/internal/parser/config.go @@ -0,0 +1,78 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package parser + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" +) + +const ( + Env = "env" + File = "file" +) + +var RelativePathRegexp = regexp.MustCompile(`^\.{1,}\/`) + +type ConfigParser struct { + baseDir string +} + +func NewConfigParser(baseDir string) *ConfigParser { + return &ConfigParser{ + baseDir: baseDir, + } +} + +func (p *ConfigParser) ParseDynamicValue(val any) (string, error) { + strVal := fmt.Sprintf("%v", val) + + spl := strings.Split(strVal, "://") + + if len(spl) > 1 { + source := strings.TrimPrefix(spl[0], "{") + sourceValue := strings.TrimSuffix(spl[1], "}") + + switch source { + case Env: + envVar := os.Getenv(sourceValue) + + envVar = strings.TrimRight(envVar, "\n") + + return envVar, nil + + case File: + // If the value is a relative path, we need to convert it to an absolute path. + isRelativePath := RelativePathRegexp.MatchString(sourceValue) + if isRelativePath { + sourceValue = filepath.Clean(sourceValue) + sourceValue = filepath.Join(p.baseDir, sourceValue) + } + + content, err := readValueFromFile(sourceValue) + if err != nil { + return "", fmt.Errorf("%w", err) + } + + content = strings.TrimRight(content, "\n") + + return content, nil + + default: + return strVal, nil + } + } + + return strVal, nil +} + +func readValueFromFile(path string) (string, error) { + val, err := os.ReadFile(path) + + return string(val), err +} diff --git a/internal/parser/config_test.go b/internal/parser/config_test.go new file mode 100644 index 000000000..383b60e61 --- /dev/null +++ b/internal/parser/config_test.go @@ -0,0 +1,91 @@ +// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unit + +package parser_test + +import ( + "fmt" + "os" + "path" + "testing" + + "github.com/sighupio/furyctl/internal/parser" + "github.com/stretchr/testify/assert" +) + +func TestNewConfigParser(t *testing.T) { + t.Parallel() + + cfgParser := parser.NewConfigParser("dummy/base/dir") + + assert.NotNil(t, cfgParser) +} + +func TestConfigParser_ParseDynamicValue(t *testing.T) { + t.Parallel() + + tmpDir, err := os.MkdirTemp("", "test") + if err != nil { + t.Fatal(err) + } + + exampleStr := "test" + + err = os.WriteFile(tmpDir+"/test_file.txt", []byte(exampleStr), os.ModePerm) + + defer os.RemoveAll(tmpDir) + + assert.NoError(t, err) + + testCases := []struct { + name string + baseDir string + envName string + envValue string + value any + expected string + }{ + { + name: "parsing env", + baseDir: "dummy/base/dir", + envName: "TEST_ENV_VAR", + envValue: "test", + value: "{env://TEST_ENV_VAR}", + expected: "test", + }, + { + name: "parsing file - relative path", + baseDir: tmpDir, + value: fmt.Sprintf("{file://./test_file.txt}"), + expected: "test", + }, + { + name: "parsing file - absolute path", + baseDir: tmpDir, + value: fmt.Sprintf("{file://%s}", path.Join(tmpDir, "test_file.txt")), + expected: "test", + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + cfgParser := parser.NewConfigParser(tc.baseDir) + + if tc.envName != "" { + err := os.Setenv(tc.envName, tc.envValue) + + assert.NoError(t, err) + } + + res, err := cfgParser.ParseDynamicValue(tc.value) + + assert.NoError(t, err) + assert.Equal(t, tc.expected, res) + }) + } +} diff --git a/internal/template/mapper/mapper.go b/internal/template/mapper/mapper.go index b386bf200..6e300fa50 100644 --- a/internal/template/mapper/mapper.go +++ b/internal/template/mapper/mapper.go @@ -11,11 +11,8 @@ import ( "reflect" "regexp" "strings" -) -const ( - Env = "env" - File = "file" + "github.com/sighupio/furyctl/internal/parser" ) var ( @@ -127,12 +124,14 @@ func (m *Mapper) injectDynamicValuesAndPaths( } func (m *Mapper) injectDynamicValuesAndPathsString(value string) (string, error) { + cfgParser := parser.NewConfigParser(m.furyctlConfDir) + // If the value contains dynamic values, we need to parse them. dynamicValues := EnvRegexp.FindAllString(value, -1) for _, dynamicValue := range dynamicValues { - parsedDynamicValue, err := ParseDynamicValue(dynamicValue, m.furyctlConfDir) + parsedDynamicValue, err := cfgParser.ParseDynamicValue(dynamicValue) if err != nil { - return "", err + return "", fmt.Errorf("error parsing dynamic value: %w", err) } value = strings.Replace(value, dynamicValue, parsedDynamicValue, 1) @@ -147,51 +146,3 @@ func (m *Mapper) injectDynamicValuesAndPathsString(value string) (string, error) return value, nil } - -func readValueFromFile(path string) (string, error) { - val, err := os.ReadFile(path) - - return string(val), err -} - -func ParseDynamicValue(val any, baseDir string) (string, error) { - strVal := fmt.Sprintf("%v", val) - - spl := strings.Split(strVal, "://") - - if len(spl) > 1 { - source := strings.TrimPrefix(spl[0], "{") - sourceValue := strings.TrimSuffix(spl[1], "}") - - switch source { - case Env: - envVar := os.Getenv(sourceValue) - - envVar = strings.TrimRight(envVar, "\n") - - return envVar, nil - - case File: - // If the value is a relative path, we need to convert it to an absolute path. - isRelativePath := RelativePathRegexp.MatchString(sourceValue) - if isRelativePath { - sourceValue = filepath.Clean(sourceValue) - sourceValue = filepath.Join(baseDir, sourceValue) - } - - content, err := readValueFromFile(sourceValue) - if err != nil { - return "", fmt.Errorf("%w", err) - } - - content = strings.TrimRight(content, "\n") - - return content, nil - - default: - return strVal, nil - } - } - - return strVal, nil -} diff --git a/internal/x/bytes/buffer.go b/internal/x/bytes/buffer.go index 709e8ca9d..099de09ec 100644 --- a/internal/x/bytes/buffer.go +++ b/internal/x/bytes/buffer.go @@ -8,18 +8,20 @@ import ( "bytes" "fmt" - "github.com/sighupio/furyctl/internal/template/mapper" + "github.com/sighupio/furyctl/internal/parser" ) func SafeWriteToBuffer(buffer *bytes.Buffer, content, baseDir string, values ...any) error { vs := make([]any, 0) + cfgParser := parser.NewConfigParser(baseDir) + for _, sv := range values { if sv == nil { continue } - v, err := mapper.ParseDynamicValue(sv, baseDir) + v, err := cfgParser.ParseDynamicValue(sv) if err != nil { return fmt.Errorf("error parsing dynamic value: %w", err) }