From d4b6fce4ea6ebb8c736eda63d3262402dc5651cf Mon Sep 17 00:00:00 2001 From: Yaroslav Borbat Date: Fri, 22 Mar 2024 15:33:41 +0300 Subject: [PATCH 1/3] add argument config_data_base64 Signed-off-by: Yaroslav Borbat --- internal/framework/provider/provider.go | 6 +++ .../framework/provider/provider_configure.go | 4 ++ kubernetes/provider.go | 28 ++++++++++---- manifest/provider/configure.go | 31 +++++++++++++-- manifest/provider/provider_config.go | 11 ++++++ util/loader.go | 38 +++++++++++++++++++ 6 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 util/loader.go diff --git a/internal/framework/provider/provider.go b/internal/framework/provider/provider.go index 7c6c6bb18e..82418244a3 100644 --- a/internal/framework/provider/provider.go +++ b/internal/framework/provider/provider.go @@ -59,6 +59,8 @@ type KubernetesProviderModel struct { ProxyURL types.String `tfsdk:"proxy_url"` + ConfigDataBase64 types.String `tfsdk:"config_data_base64"` + IgnoreAnnotations types.List `tfsdk:"ignore_annotations"` IgnoreLabels types.List `tfsdk:"ignore_labels"` @@ -143,6 +145,10 @@ func (p *KubernetesProvider) Schema(ctx context.Context, req provider.SchemaRequ Description: "URL to the proxy to be used for all API requests", Optional: true, }, + "config_data_base64": schema.StringAttribute{ + Description: "Kubeconfig content in base64 format", + Optional: true, + }, "ignore_annotations": schema.ListAttribute{ ElementType: types.StringType, Description: "List of Kubernetes metadata annotations to ignore across all resources handled by this provider for situations where external systems are managing certain resource annotations. Each item is a regular expression.", diff --git a/internal/framework/provider/provider_configure.go b/internal/framework/provider/provider_configure.go index 46898a51e1..77ad5cbce1 100644 --- a/internal/framework/provider/provider_configure.go +++ b/internal/framework/provider/provider_configure.go @@ -5,6 +5,10 @@ package provider import ( "context" + "fmt" + "github.com/hashicorp/terraform-provider-kubernetes/util" + "os" + "path/filepath" "github.com/hashicorp/terraform-plugin-framework/provider" diff --git a/kubernetes/provider.go b/kubernetes/provider.go index f0018379df..f759e5cfd6 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "fmt" + "github.com/hashicorp/terraform-provider-kubernetes/util" "log" "net/http" "os" @@ -21,7 +22,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -130,6 +130,13 @@ func Provider() *schema.Provider { Description: "URL to the proxy to be used for all API requests", DefaultFunc: schema.EnvDefaultFunc("KUBE_PROXY_URL", ""), }, + "config_data_base64": { + Type: schema.TypeString, + Optional: true, + Description: "Kubeconfig content in base64 format", + DefaultFunc: schema.EnvDefaultFunc("KUBE_CONFIG_DATA_BASE64", nil), + ConflictsWith: []string{"config_path", "config_paths"}, + }, "exec": { Type: schema.TypeList, Optional: true, @@ -500,9 +507,9 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, terraformVer func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, diag.Diagnostics) { diags := make(diag.Diagnostics, 0) overrides := &clientcmd.ConfigOverrides{} - loader := &clientcmd.ClientConfigLoadingRules{} + fileLoader := &clientcmd.ClientConfigLoadingRules{} - configPaths := []string{} + var configPaths []string if v, ok := d.Get("config_path").(string); ok && v != "" { configPaths = []string{v} @@ -517,7 +524,7 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, diag.D } if len(configPaths) > 0 { - expandedPaths := []string{} + var expandedPaths []string for _, p := range configPaths { path, err := homedir.Expand(p) if err != nil { @@ -529,9 +536,9 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, diag.D } if len(expandedPaths) == 1 { - loader.ExplicitPath = expandedPaths[0] + fileLoader.ExplicitPath = expandedPaths[0] } else { - loader.Precedence = expandedPaths + fileLoader.Precedence = expandedPaths } ctxSuffix := "; default context" @@ -558,7 +565,7 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, diag.D } log.Printf("[DEBUG] Using overridden context: %#v", overrides.Context) } - } + // Overriding with static configuration if v, ok := d.GetOk("insecure"); ok { @@ -631,7 +638,14 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, diag.D overrides.ClusterDefaults.ProxyURL = v.(string) } + var configDataBase64 string + if v, ok := d.GetOk("config_data_base64"); ok { + configDataBase64 = v.(string) + } + + loader := util.NewConfigLoader(fileLoader, configDataBase64) cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides) + cfg, err := cc.ClientConfig() if err != nil { nd := diag.Diagnostic{ diff --git a/manifest/provider/configure.go b/manifest/provider/configure.go index cd57f8a209..7a9e554d88 100644 --- a/manifest/provider/configure.go +++ b/manifest/provider/configure.go @@ -8,6 +8,7 @@ import ( "encoding/pem" "errors" "fmt" + "github.com/hashicorp/terraform-provider-kubernetes/util" "net/url" "os" "path/filepath" @@ -68,7 +69,7 @@ func (s *RawProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov } overrides := &clientcmd.ConfigOverrides{} - loader := &clientcmd.ClientConfigLoadingRules{} + fileLoader := &clientcmd.ClientConfigLoadingRules{} // Handle 'config_path' attribute // @@ -101,8 +102,30 @@ func (s *RawProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov Detail: fmt.Sprintf("'config_path' refers to an invalid path: %q: %v", configPathAbs, err), }) } - loader.ExplicitPath = configPathAbs + fileLoader.ExplicitPath = configPathAbs } + // Handle 'config_data_base64' attribute + // + var configDataBase64 string + + if !providerConfig["config_data_base64"].IsNull() && providerConfig["config_data_base64"].IsKnown() { + + err = providerConfig["config_data_base64"].As(&configDataBase64) + if err != nil { + // invalid attribute - this shouldn't happen, bail out now + response.Diagnostics = append(response.Diagnostics, &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Provider configuration: failed to extract 'config_data_base64' value", + Detail: err.Error(), + }) + return response, nil + } + } + // check environment - this overrides any value found in provider configuration + if configBase64Env, ok := os.LookupEnv("KUBE_CONFIG_DATA_BASE64"); ok && configBase64Env != "" { + configDataBase64 = configBase64Env + } + // Handle 'config_paths' attribute // var precedence []string @@ -143,7 +166,7 @@ func (s *RawProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov } precedence[i] = absPath } - loader.Precedence = precedence + fileLoader.Precedence = precedence } // Handle 'client_certificate' attribute @@ -589,7 +612,7 @@ func (s *RawProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov overrides.AuthInfo.Exec = &execCfg } } - + loader := util.NewConfigLoader(fileLoader, configDataBase64) cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides) clientConfig, err := cc.ClientConfig() if err != nil { diff --git a/manifest/provider/provider_config.go b/manifest/provider/provider_config.go index 2adfb1cc63..a109f245eb 100644 --- a/manifest/provider/provider_config.go +++ b/manifest/provider/provider_config.go @@ -178,6 +178,17 @@ func GetProviderConfigSchema() *tfprotov5.Schema { DescriptionKind: 0, Deprecated: false, }, + { + Name: "config_data_base64", + Type: tftypes.String, + Description: "Kubeconfig content in base64 format", + Required: false, + Optional: true, + Computed: false, + Sensitive: false, + DescriptionKind: 0, + Deprecated: false, + }, { Name: "ignore_annotations", Type: tftypes.List{ElementType: tftypes.String}, diff --git a/util/loader.go b/util/loader.go new file mode 100644 index 0000000000..768c3083f4 --- /dev/null +++ b/util/loader.go @@ -0,0 +1,38 @@ +package util + +import ( + "encoding/base64" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +func NewConfigLoader(loader *clientcmd.ClientConfigLoadingRules, configBase64Data string) *ConfigLoader { + return &ConfigLoader{ + ClientConfigLoadingRules: loader, + configBase64Data: configBase64Data, + } +} + +type ConfigLoader struct { + *clientcmd.ClientConfigLoadingRules + configBase64Data string +} + +func (cl ConfigLoader) Load() (*clientcmdapi.Config, error) { + if cl.configBase64Data == "" { + return cl.ClientConfigLoadingRules.Load() + } + data, err := base64.StdEncoding.DecodeString(cl.configBase64Data) + if err != nil { + return nil, err + } + cc, err := clientcmd.NewClientConfigFromBytes(data) + if err != nil { + return nil, err + } + cfg, err := cc.RawConfig() + if err != nil { + return nil, err + } + return &cfg, nil +} From 8335427b580d333d7dab0a5ce30cbc46ccb6e492 Mon Sep 17 00:00:00 2001 From: Yaroslav Borbat Date: Mon, 25 Mar 2024 12:41:42 +0300 Subject: [PATCH 2/3] add test Signed-off-by: Yaroslav Borbat --- kubernetes/provider_test.go | 24 +++++++++++++++++++ .../test-fixtures/kube-config-sa-token.yaml | 21 ++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 kubernetes/test-fixtures/kube-config-sa-token.yaml diff --git a/kubernetes/provider_test.go b/kubernetes/provider_test.go index 1158b51f34..6950279680 100644 --- a/kubernetes/provider_test.go +++ b/kubernetes/provider_test.go @@ -5,6 +5,7 @@ package kubernetes import ( "context" + "encoding/base64" "errors" "fmt" "net/url" @@ -102,6 +103,24 @@ func TestProvider_configure_paths(t *testing.T) { } } +func TestProvider_configure_config_data_base64(t *testing.T) { + ctx := context.TODO() + resetEnv := unsetEnv(t) + defer resetEnv() + data, err := os.ReadFile("test-fixtures/kube-config-sa-token.yaml") + if err != nil { + t.Fatal("Cannot read kubeconfig") + } + data64 := base64.StdEncoding.EncodeToString(data) + os.Setenv("KUBE_CONFIG_DATA_BASE64", data64) + rc := terraform.NewResourceConfigRaw(map[string]interface{}{}) + p := Provider() + diags := p.Configure(ctx, rc) + if diags.HasError() { + t.Fatal(diags) + } +} + func unsetEnv(t *testing.T) func() { e := getEnv() @@ -120,6 +139,7 @@ func unsetEnv(t *testing.T) func() { "KUBE_INSECURE": e.Insecure, "KUBE_TLS_SERVER_NAME": e.TLSServerName, "KUBE_TOKEN": e.Token, + "KUBE_CONFIG_DATA_BASE64": e.ConfigDataBase64, } for k := range envVars { @@ -158,6 +178,9 @@ func getEnv() *currentEnv { if v := os.Getenv("KUBE_CONFIG_PATH"); v != "" { e.ConfigPaths = filepath.SplitList(v) } + if v := os.Getenv("KUBE_CONFIG_DATA_BASE64"); v != "" { + e.ConfigDataBase64 = v + } return e } @@ -482,6 +505,7 @@ func clusterVersionGreaterThanOrEqual(vs string) bool { type currentEnv struct { ConfigPath string + ConfigDataBase64 string ConfigPaths []string Ctx string CtxAuthInfo string diff --git a/kubernetes/test-fixtures/kube-config-sa-token.yaml b/kubernetes/test-fixtures/kube-config-sa-token.yaml new file mode 100644 index 0000000000..19a3a78206 --- /dev/null +++ b/kubernetes/test-fixtures/kube-config-sa-token.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Config +preferences: {} +clusters: + - cluster: + certificate-authority-data: ZHVtbXk= + server: https://127.0.0.1 + name: dummy + +current-context: dummy + +contexts: + - context: + cluster: dummy + user: dummy + name: dummy + +users: + - name: dummy + user: + token: ZHVtbXk= \ No newline at end of file From defbe23846dc6f9f6f93409c173f567dcfcee4ce Mon Sep 17 00:00:00 2001 From: yaroslavborbat Date: Wed, 8 Jan 2025 17:28:57 +0300 Subject: [PATCH 3/3] fix rebase Signed-off-by: yaroslavborbat --- internal/framework/provider/provider_configure.go | 4 ---- kubernetes/provider.go | 5 +++-- kubernetes/provider_test.go | 3 ++- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/internal/framework/provider/provider_configure.go b/internal/framework/provider/provider_configure.go index 77ad5cbce1..46898a51e1 100644 --- a/internal/framework/provider/provider_configure.go +++ b/internal/framework/provider/provider_configure.go @@ -5,10 +5,6 @@ package provider import ( "context" - "fmt" - "github.com/hashicorp/terraform-provider-kubernetes/util" - "os" - "path/filepath" "github.com/hashicorp/terraform-plugin-framework/provider" diff --git a/kubernetes/provider.go b/kubernetes/provider.go index f759e5cfd6..b23bb4147d 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -7,13 +7,14 @@ import ( "bytes" "context" "fmt" - "github.com/hashicorp/terraform-provider-kubernetes/util" "log" "net/http" "os" "path/filepath" "strconv" + "github.com/hashicorp/terraform-provider-kubernetes/util" + "github.com/hashicorp/go-cty/cty" gversion "github.com/hashicorp/go-version" "github.com/mitchellh/go-homedir" @@ -565,7 +566,7 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, diag.D } log.Printf("[DEBUG] Using overridden context: %#v", overrides.Context) } - + } // Overriding with static configuration if v, ok := d.GetOk("insecure"); ok { diff --git a/kubernetes/provider_test.go b/kubernetes/provider_test.go index 6950279680..6a0e18ddb3 100644 --- a/kubernetes/provider_test.go +++ b/kubernetes/provider_test.go @@ -186,6 +186,7 @@ func getEnv() *currentEnv { func testAccPreCheck(t *testing.T) { ctx := context.TODO() + hasEnvCfg := os.Getenv("KUBE_CONFIG_DATA_BASE64") != "" hasFileCfg := (os.Getenv("KUBE_CTX_AUTH_INFO") != "" && os.Getenv("KUBE_CTX_CLUSTER") != "") || os.Getenv("KUBE_CTX") != "" || os.Getenv("KUBE_CONFIG_PATH") != "" @@ -195,7 +196,7 @@ func testAccPreCheck(t *testing.T) { os.Getenv("KUBE_CLUSTER_CA_CERT_DATA") != "") && (hasUserCredentials || hasClientCert || os.Getenv("KUBE_TOKEN") != "") - if !hasFileCfg && !hasStaticCfg && !hasUserCredentials { + if !hasEnvCfg && !hasFileCfg && !hasStaticCfg && !hasUserCredentials { t.Fatalf("File config (KUBE_CTX_AUTH_INFO and KUBE_CTX_CLUSTER) or static configuration"+ "(%s) or (%s) must be set for acceptance tests", strings.Join([]string{