From 7db5c662b7a07114602d21b5e314d20a5eb4717b Mon Sep 17 00:00:00 2001 From: Thiery Ouattara Date: Fri, 18 Oct 2024 16:28:16 +0000 Subject: [PATCH] Enable setting credentials using profile default:(~/.osc/config.json) --- go.mod | 3 + go.sum | 7 ++ outscale/config.go | 20 ++--- outscale/framework_config.go | 99 +++++++++++++++++++++++- outscale/framework_provider.go | 58 ++++++++++---- outscale/provider.go | 133 +++++++++++++++++++++++++++++---- 6 files changed, 278 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index fc31041ce..316b57bc8 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/nav-inc/datetime v0.1.3 github.com/outscale/osc-sdk-go/v2 v2.23.0 github.com/spf13/cast v1.6.0 + github.com/tidwall/gjson v1.18.0 ) require ( @@ -51,6 +52,8 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.1.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index d377af8e6..d850c36fa 100644 --- a/go.sum +++ b/go.sum @@ -162,6 +162,13 @@ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= diff --git a/outscale/config.go b/outscale/config.go index 4989bcafb..81865ce3b 100644 --- a/outscale/config.go +++ b/outscale/config.go @@ -13,14 +13,16 @@ import ( // Config ... type Config struct { - AccessKeyID string - SecretKeyID string - Region string - TokenID string - Endpoints map[string]interface{} - X509cert string - X509key string - Insecure bool + AccessKeyID string + SecretKeyID string + Region string + TokenID string + Endpoints map[string]interface{} + X509CertPath string + X509KeyPath string + Insecure bool + ConfigFilePath string + Profile string } // OutscaleClient client @@ -31,7 +33,7 @@ type OutscaleClient struct { // Client ... func (c *Config) Client() (*OutscaleClient, error) { tlsconfig := &tls.Config{InsecureSkipVerify: c.Insecure} - cert, err := tls.LoadX509KeyPair(c.X509cert, c.X509key) + cert, err := tls.LoadX509KeyPair(c.X509CertPath, c.X509KeyPath) if err == nil { tlsconfig = &tls.Config{ InsecureSkipVerify: false, diff --git a/outscale/framework_config.go b/outscale/framework_config.go index 77c13939d..fb3a7a887 100644 --- a/outscale/framework_config.go +++ b/outscale/framework_config.go @@ -4,7 +4,9 @@ import ( "context" "crypto/tls" "fmt" + "io/ioutil" "net/http" + "os" "strings" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -13,6 +15,7 @@ import ( oscgo "github.com/outscale/osc-sdk-go/v2" "github.com/outscale/terraform-provider-outscale/utils" "github.com/outscale/terraform-provider-outscale/version" + "github.com/tidwall/gjson" ) // OutscaleClient client @@ -22,8 +25,14 @@ type OutscaleClient_fw struct { // Client ... func (c *frameworkProvider) Client_fw(ctx context.Context, data *ProviderModel, diags *diag.Diagnostics) (*OutscaleClient_fw, error) { + ok, err := IsProfileSet(data) + if err != nil { + return nil, err + } + if !ok { + setDefaultEnv(data) + } - setDefaultEnv(data) tlsconfig := &tls.Config{InsecureSkipVerify: c.insecure} cert, err := tls.LoadX509KeyPair(data.X509CertPath.ValueString(), data.X509KeyPath.ValueString()) if err == nil { @@ -66,6 +75,90 @@ func (c *frameworkProvider) Client_fw(ctx context.Context, data *ProviderModel, return client, nil } +func IsProfileSet(data *ProviderModel) (bool, error) { + isProfSet := false + if profileName, ok := os.LookupEnv("OSC_PROFILE"); ok || !data.Profile.IsNull() { + if data.Profile.ValueString() != "" { + profileName = data.Profile.ValueString() + } + + var profilePath string + if envPath, ok := os.LookupEnv("OSC_CONFIG_FILE"); ok || !data.ConfigFilePath.IsNull() { + if data.ConfigFilePath.ValueString() != "" { + profilePath = data.ConfigFilePath.ValueString() + } else { + profilePath = envPath + } + if profilePath == "" { + homePath, err := os.UserHomeDir() + if err != nil { + return isProfSet, err + } + profilePath = homePath + "/.osc/config.json" + } + } + jsonFile, err := ioutil.ReadFile(profilePath) + if err != nil { + return isProfSet, err + } + profile := gjson.GetBytes(jsonFile, profileName) + if !gjson.Valid(profile.String()) { + return isProfSet, fmt.Errorf("Invalid json profile file") + } + if !profile.Get("access_key").Exists() || + !profile.Get("secret_key").Exists() { + return isProfSet, fmt.Errorf("Profile 'access_key' or 'secret_key' are not defined!") + } + setProfile(data, profile) + isProfSet = true + } + return isProfSet, nil +} + +func setProfile(data *ProviderModel, profile gjson.Result) { + if data.AccessKeyId.IsNull() { + if accessKeyId := profile.Get("access_key").String(); accessKeyId != "" { + data.AccessKeyId = types.StringValue(accessKeyId) + } + } + if data.SecretKeyId.IsNull() { + if secretKeyId := profile.Get("secret_key").String(); secretKeyId != "" { + data.SecretKeyId = types.StringValue(secretKeyId) + } + } + if data.Region.IsNull() { + if profile.Get("region").Exists() { + if region := profile.Get("region").String(); region != "" { + data.Region = types.StringValue(region) + } + } + } + if data.X509CertPath.IsNull() { + if profile.Get("x509_cert_path").Exists() { + if x509Cert := profile.Get("x509_cert_path").String(); x509Cert != "" { + data.X509CertPath = types.StringValue(x509Cert) + } + } + } + if data.X509KeyPath.IsNull() { + if profile.Get("x509_key_path").Exists() { + if x509Key := profile.Get("x509_key_path").String(); x509Key != "" { + data.X509KeyPath = types.StringValue(x509Key) + } + } + } + if len(data.Endpoints) == 0 { + if profile.Get("endpoints").Exists() { + endpoints := profile.Get("endpoints").Value().(map[string]interface{}) + if endpoint := endpoints["api"].(string); endpoint != "" { + endp := make([]Endpoints, 1) + endp[0].API = types.StringValue(endpoint) + data.Endpoints = endp + } + } + } +} + func setDefaultEnv(data *ProviderModel) { if data.AccessKeyId.IsNull() { if accessKeyId := utils.GetEnvVariableValue([]string{"OSC_ACCESS_KEY", "OUTSCALE_ACCESSKEYID"}); accessKeyId != "" { @@ -96,9 +189,9 @@ func setDefaultEnv(data *ProviderModel) { } } if len(data.Endpoints) == 0 { - if endpoints := utils.GetEnvVariableValue([]string{"OSC_ENDPOINT_API", "OUTSCALE_OAPI_URL"}); endpoints != "" { + if endpoint := utils.GetEnvVariableValue([]string{"OSC_ENDPOINT_API", "OUTSCALE_OAPI_URL"}); endpoint != "" { endp := make([]Endpoints, 1) - endp[0].API = types.StringValue(endpoints) + endp[0].API = types.StringValue(endpoint) data.Endpoints = endp } } diff --git a/outscale/framework_provider.go b/outscale/framework_provider.go index 979e34846..8c2927ed8 100644 --- a/outscale/framework_provider.go +++ b/outscale/framework_provider.go @@ -22,24 +22,28 @@ func New(version string) provider.Provider { } type frameworkProvider struct { - accessKeyId types.String - secretKeyId types.String - region types.String - endpoints []Endpoints - x509CertPath string - x509KeyPath string - insecure bool - version string + accessKeyId types.String + secretKeyId types.String + region types.String + endpoints []Endpoints + x509CertPath string + x509KeyPath string + configFilePath string + insecure bool + profile string + version string } type ProviderModel struct { - AccessKeyId types.String `tfsdk:"access_key_id"` - SecretKeyId types.String `tfsdk:"secret_key_id"` - Region types.String `tfsdk:"region"` - Endpoints []Endpoints `tfsdk:"endpoints"` - X509CertPath types.String `tfsdk:"x509_cert_path"` - X509KeyPath types.String `tfsdk:"x509_key_path"` - Insecure types.Bool `tfsdk:"insecure"` + AccessKeyId types.String `tfsdk:"access_key_id"` + SecretKeyId types.String `tfsdk:"secret_key_id"` + Region types.String `tfsdk:"region"` + Endpoints []Endpoints `tfsdk:"endpoints"` + X509CertPath types.String `tfsdk:"x509_cert_path"` + X509KeyPath types.String `tfsdk:"x509_key_path"` + ConfigFilePath types.String `tfsdk:"config_file_path"` + Profile types.String `tfsdk:"profile"` + Insecure types.Bool `tfsdk:"insecure"` } type Endpoints struct { @@ -87,6 +91,14 @@ func (p *frameworkProvider) Schema(ctx context.Context, req provider.SchemaReque Optional: true, Description: "The path to your x509 key", }, + "config_file_path": schema.StringAttribute{ + Optional: true, + Description: "The path to your configuration file in which you have defined your credentials.", + }, + "profile": schema.StringAttribute{ + Optional: true, + Description: "The name of your profile in which you define your credencial", + }, "insecure": schema.BoolAttribute{ Optional: true, Description: "tls insecure connection", @@ -149,6 +161,22 @@ func (p *frameworkProvider) Configure(ctx context.Context, req provider.Configur "Either target apply the source of the value first, set the value statically in the configuration, or use the 'OSC_X509_CLIENT_KEY or OUTSCALE_X509KEY' environment variable.", ) } + if config.ConfigFilePath.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("config_file_path"), + "Unknown Outscale API ConfigFilePath", + "The provider cannot create the Outscale API client as there is an unknown configuration value for the Outscale API profile. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the 'OSC_CONFIG_FILE' environment variable.", + ) + } + if config.Profile.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("profile"), + "Unknown Outscale API profile", + "The provider cannot create the Outscale API client as there is an unknown configuration value for the Outscale API profile. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the 'OSC_PROFILE' environment variable.", + ) + } if resp.Diagnostics.HasError() { return diff --git a/outscale/provider.go b/outscale/provider.go index 25ed9a4a0..bc474ccb8 100644 --- a/outscale/provider.go +++ b/outscale/provider.go @@ -1,8 +1,13 @@ package outscale import ( + "fmt" + "io/ioutil" + "os" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/outscale/terraform-provider-outscale/utils" + "github.com/tidwall/gjson" ) var endpointServiceNames []string @@ -55,6 +60,16 @@ func Provider() *schema.Provider { Optional: true, Description: "The path to your x509 key", }, + "config_file_path": { + Type: schema.TypeString, + Optional: true, + Description: "The path to your configuration file in which you have defined your credentials.", + }, + "profile": { + Type: schema.TypeString, + Optional: true, + Description: "The name of your profile in which you define your credencial", + }, "insecure": { Type: schema.TypeBool, Optional: true, @@ -200,26 +215,115 @@ func Provider() *schema.Provider { func providerConfigureClient(d *schema.ResourceData) (interface{}, error) { config := Config{ - AccessKeyID: d.Get("access_key_id").(string), - SecretKeyID: d.Get("secret_key_id").(string), - Region: d.Get("region").(string), - Endpoints: make(map[string]interface{}), - X509cert: d.Get("x509_cert_path").(string), - X509key: d.Get("x509_key_path").(string), - Insecure: d.Get("insecure").(bool), + AccessKeyID: d.Get("access_key_id").(string), + SecretKeyID: d.Get("secret_key_id").(string), + Region: d.Get("region").(string), + Endpoints: make(map[string]interface{}), + X509CertPath: d.Get("x509_cert_path").(string), + X509KeyPath: d.Get("x509_key_path").(string), + ConfigFilePath: d.Get("config_file_path").(string), + Profile: d.Get("profile").(string), + Insecure: d.Get("insecure").(bool), } - - setProviderDefaultEnv(&config) endpointsSet := d.Get("endpoints").(*schema.Set) - for _, endpointsSetI := range endpointsSet.List() { endpoints := endpointsSetI.(map[string]interface{}) for _, endpointServiceName := range endpointServiceNames { config.Endpoints[endpointServiceName] = endpoints[endpointServiceName].(string) } } + + ok, err := IsOldProfileSet(&config) + if err != nil { + return nil, err + } + if !ok { + setProviderDefaultEnv(&config) + } return config.Client() } +func IsOldProfileSet(conf *Config) (bool, error) { + isProfSet := false + if profileName, ok := os.LookupEnv("OSC_PROFILE"); ok || conf.Profile != "" { + if conf.Profile != "" { + profileName = conf.Profile + } + + var profilePath string + if envPath, ok := os.LookupEnv("OSC_CONFIG_FILE"); ok || conf.ConfigFilePath != "" { + if conf.ConfigFilePath != "" { + profilePath = conf.ConfigFilePath + } else { + profilePath = envPath + } + if profilePath == "" { + homePath, err := os.UserHomeDir() + if err != nil { + return isProfSet, err + } + profilePath = homePath + "/.osc/config.json" + } + } + jsonFile, err := ioutil.ReadFile(profilePath) + if err != nil { + return isProfSet, err + } + profile := gjson.GetBytes(jsonFile, profileName) + if !gjson.Valid(profile.String()) { + return isProfSet, fmt.Errorf("Invalid json profile file") + } + if !profile.Get("access_key").Exists() || + !profile.Get("secret_key").Exists() { + return isProfSet, fmt.Errorf("Profile 'access_key' or 'secret_key' are not defined!") + } + setOldProfile(conf, profile) + isProfSet = true + } + return isProfSet, nil +} + +func setOldProfile(conf *Config, profile gjson.Result) { + + if conf.AccessKeyID == "" { + if accessKeyId := profile.Get("access_key").String(); accessKeyId != "" { + conf.AccessKeyID = accessKeyId + } + } + if conf.SecretKeyID == "" { + if secretKeyId := profile.Get("secret_key").String(); secretKeyId != "" { + conf.SecretKeyID = secretKeyId + } + } + if conf.Region == "" { + if profile.Get("region").Exists() { + if region := profile.Get("region").String(); region != "" { + conf.Region = region + } + } + } + if conf.X509CertPath == "" { + if profile.Get("x509_cert_path").Exists() { + if x509Cert := profile.Get("x509_cert_path").String(); x509Cert != "" { + conf.X509CertPath = x509Cert + } + } + } + if conf.X509KeyPath == "" { + if profile.Get("x509_key_path").Exists() { + if x509Key := profile.Get("x509_key_path").String(); x509Key != "" { + conf.X509KeyPath = x509Key + } + } + } + if len(conf.Endpoints) == 0 { + if profile.Get("endpoints").Exists() { + endpoints := profile.Get("endpoints").Value().(map[string]interface{}) + if endpoint := endpoints["api"].(string); endpoint != "" { + conf.Endpoints["api"] = endpoint + } + } + } +} func setProviderDefaultEnv(conf *Config) { if conf.AccessKeyID == "" { @@ -239,18 +343,17 @@ func setProviderDefaultEnv(conf *Config) { } } - if conf.X509cert == "" { + if conf.X509CertPath == "" { if x509Cert := utils.GetEnvVariableValue([]string{"OSC_X509_CLIENT_CERT", "OUTSCALE_X509CERT"}); x509Cert != "" { - conf.X509cert = x509Cert + conf.X509CertPath = x509Cert } } - if conf.X509key == "" { + if conf.X509KeyPath == "" { if x509Key := utils.GetEnvVariableValue([]string{"OSC_X509_CLIENT_KEY", "OUTSCALE_X509KEY"}); x509Key != "" { - conf.X509key = x509Key + conf.X509KeyPath = x509Key } } - if len(conf.Endpoints) == 0 { if endpoints := utils.GetEnvVariableValue([]string{"OSC_ENDPOINT_API", "OUTSCALE_OAPI_URL"}); endpoints != "" { endpointsAttributes := make(map[string]interface{})