diff --git a/main.go b/main.go index 99563f883..a23ee1af4 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,59 @@ package main import ( - "github.com/hashicorp/terraform-plugin-sdk/plugin" + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" "github.com/terraform-providers/terraform-provider-outscale/outscale" + vers "github.com/terraform-providers/terraform-provider-outscale/version" +) + +var ( + version string = vers.GetVersion() ) func main() { - plugin.Serve(&plugin.ServeOpts{ - ProviderFunc: outscale.Provider, - }) + + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5(outscale.New(version)), // Example terraform-plugin-framework provider + outscale.Provider().GRPCProvider, // Example terraform-plugin-sdk provider + } + + //using muxer + muxServer, err := tf5muxserver.NewMuxServer(context.Background(), providers...) + + if err != nil { + log.Fatal(err) + } + + var serveOpts []tf5server.ServeOpt + + if debug { + serveOpts = append(serveOpts, tf5server.WithManagedDebug()) + } + + err = tf5server.Serve( + "registry.terraform.io/providers/outscale/outscale/", + muxServer.ProviderServer, + serveOpts..., + ) + + if err != nil { + log.Fatal(err) + } } + +// plugin.Serve(&plugin.ServeOpts{ +// ProviderFunc: outscale.Provider, +// }) +//} diff --git a/outscale/config.go b/outscale/config.go index cee11c3d8..78c229019 100644 --- a/outscale/config.go +++ b/outscale/config.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/logging" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" oscgo "github.com/outscale/osc-sdk-go/v2" "github.com/terraform-providers/terraform-provider-outscale/version" ) @@ -65,6 +65,5 @@ func (c *Config) Client() (*OutscaleClient, error) { client := &OutscaleClient{ OSCAPI: oscClient, } - return client, nil } diff --git a/outscale/data_source_outscale_quota_test.go b/outscale/data_source_outscale_quota_test.go deleted file mode 100644 index 37633a6d2..000000000 --- a/outscale/data_source_outscale_quota_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package outscale - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" -) - -func TestAccOthers_DataSourceQuota(t *testing.T) { - t.Parallel() - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: testAccDataSourceOutscaleOAPIQuotaConfig, - Check: resource.ComposeTestCheckFunc(), - }, - }, - }) -} - -const testAccDataSourceOutscaleOAPIQuotaConfig = ` - data "outscale_quota" "lbu-quota" { - filter { - name = "quota_names" - values = ["lb_listeners_limit"] - } -} -` diff --git a/outscale/data_source_quota.go b/outscale/data_source_quota.go new file mode 100644 index 000000000..78c97039c --- /dev/null +++ b/outscale/data_source_quota.go @@ -0,0 +1,306 @@ +package outscale + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + oscgo "github.com/outscale/osc-sdk-go/v2" + "github.com/terraform-providers/terraform-provider-outscale/utils" +) + +var ( + _ datasource.DataSource = &dataSourceQuota{} + _ datasource.DataSourceWithConfigure = &dataSourceQuota{} +) + +func NewDataSourceQuota() datasource.DataSource { + return &dataSourceQuota{} +} + +func (d *dataSourceQuota) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + + if req.ProviderData == nil { + return + } + client := req.ProviderData.(OutscaleClient_fw) + d.Client = client.OSCAPI +} + +// ExampleDataSource defines the data source implementation. +type dataSourceQuota struct { + Client *oscgo.APIClient +} + +// ExampleDataSourceModel describes the data source data model. +type quotaModel struct { + //ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` + Id types.String `tfsdk:"id"` + Filter types.Set `tfsdk:"filter"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + MaxValue types.Int64 `tfsdk:"max_value"` + UsedValue types.Int64 `tfsdk:"used_value"` + QuotaType types.String `tfsdk:"quota_type"` + QuotaCollection types.String `tfsdk:"quota_collection"` + ShortDescription types.String `tfsdk:"short_description"` + AccountId types.String `tfsdk:"account_id"` + RequestId types.String `tfsdk:"request_id"` +} + +func FwDataSourceFiltersSchema() *schema.SetNestedBlock { + return &schema.SetNestedBlock{ + Validators: []validator.Set{ + setvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + }, + "values": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + }, + }, + }, + }, + } +} + +func (d *dataSourceQuota) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quota" +} + +func (d *dataSourceQuota) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "filter": FwDataSourceFiltersSchema(), + }, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Computed: true, + }, + "description": schema.StringAttribute{ + Computed: true, + }, + "max_value": schema.Int64Attribute{ + Computed: true, + }, + "used_value": schema.Int64Attribute{ + Computed: true, + }, + "quota_type": schema.StringAttribute{ + Computed: true, + }, + "quota_collection": schema.StringAttribute{ + Computed: true, + }, + "short_description": schema.StringAttribute{ + Computed: true, + }, + "id": schema.StringAttribute{ + Computed: true, + }, + "request_id": schema.StringAttribute{ + Computed: true, + }, + "account_id": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (d *dataSourceQuota) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + reqApi := oscgo.ReadQuotasRequest{} + mapTftypes := map[string]tftypes.Value{} + var respApi oscgo.ReadQuotasResponse + var quota oscgo.Quota + var quotaType oscgo.QuotaTypes + var dataState quotaModel + var filters *oscgo.FiltersQuota + var listFilters []tftypes.Value + var diags diag.Diagnostics + var flatenFilters basetypes.SetValue + + err := req.Config.Raw.As(&mapTftypes) + if err != nil { + goto CHECK_ERR + } + err = mapTftypes["filter"].As(&listFilters) + if err != nil { + goto CHECK_ERR + } + filters, err = buildOutscaleQuotaDataSourceFilters(listFilters) + if err != nil { + goto CHECK_ERR + } + reqApi.Filters = filters + + err = resource.Retry(120*time.Second, func() *resource.RetryError { + var err error + rp, httpResp, err := d.Client.QuotaApi.ReadQuotas(context.Background()).ReadQuotasRequest(reqApi).Execute() + if err != nil { + return utils.CheckThrottling(httpResp, err) + } + respApi = rp + return nil + }) + if err != nil { + goto CHECK_ERR + } + if len(respApi.GetQuotaTypes()) == 0 { + err = fmt.Errorf("no matching quotas type found") + goto CHECK_ERR + } + if len(respApi.GetQuotaTypes()) > 1 { + err = fmt.Errorf("multiple quotas type matched; use additional constraints to reduce matches to a single quotaType") + goto CHECK_ERR + } + quotaType = respApi.GetQuotaTypes()[0] + if len(quotaType.GetQuotas()) == 0 { + err = fmt.Errorf("no matching quotas found") + goto CHECK_ERR + } + + if len(quotaType.GetQuotas()) > 1 { + err = fmt.Errorf("multiple quotas matched; use additional constraints to reduce matches to a single quotaType") + goto CHECK_ERR + } + + flatenFilters, diags = flatenQuotaDataSourceFilters(listFilters) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + quota = quotaType.GetQuotas()[0] + dataState.QuotaType = types.StringValue(quotaType.GetQuotaType()) + dataState.Name = types.StringValue(quota.GetName()) + dataState.Description = types.StringValue(quota.GetDescription()) + dataState.MaxValue = types.Int64Value(int64(quota.GetMaxValue())) + dataState.UsedValue = types.Int64Value(int64(quota.GetUsedValue())) + dataState.QuotaCollection = types.StringValue(quota.GetQuotaCollection()) + dataState.ShortDescription = types.StringValue(quota.GetShortDescription()) + dataState.AccountId = types.StringValue(quota.GetAccountId()) + dataState.Filter = flatenFilters + dataState.Id = types.StringValue(resource.UniqueId()) + dataState.RequestId = types.StringValue(respApi.ResponseContext.GetRequestId()) + diags = resp.State.Set(ctx, &dataState) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + +CHECK_ERR: + if err != nil { // resp.Diagnostics.HasError() { + resp.Diagnostics.AddError( + "Unable to Read Outscale Quotas", + "If the error is not clear, please contact the provider developers.\n\n"+ + "Outscale Client Error: "+err.Error(), + ) + + //resp.Diagnostics.Append(err...) + if resp.Diagnostics.HasError() { + return + } + } +} + +func buildOutscaleQuotaDataSourceFilters(listFilters []tftypes.Value) (*oscgo.FiltersQuota, error) { + var filters oscgo.FiltersQuota + + for _, val := range listFilters { + var mapFilters map[string]tftypes.Value + val.As(&mapFilters) + var name string + mapFilters["name"].As(&name) + var listValues []tftypes.Value + mapFilters["values"].As(&listValues) + var filterValues []string + for _, val := range listValues { + var value string + val.As(&value) + filterValues = append(filterValues, value) + } + switch name { + case "quota_types": + filters.QuotaTypes = &filterValues + case "quota_names": + filters.QuotaNames = &filterValues + case "collections": + filters.Collections = &filterValues + case "short_descriptions": + filters.ShortDescriptions = &filterValues + default: + log.Printf("[Debug] Unknown Filter Name: %s.", name) + } + } + return &filters, nil +} + +func flatenQuotaDataSourceFilters(listFilters []tftypes.Value) (basetypes.SetValue, diag.Diagnostics) { + var setfil []attr.Value + var setValue basetypes.SetValue + var diags diag.Diagnostics + + filtersValuesType := basetypes.ObjectType{ + AttrTypes: map[string]attr.Type{ + "name": basetypes.StringType{}, + "values": basetypes.SetType{ElemType: basetypes.StringType{}}, + }, + } + mapObjectType := make(map[string]attr.Type) + mapObjectType["values"] = basetypes.SetType{ElemType: basetypes.StringType{}} + mapObjectType["name"] = basetypes.StringType{} + + for _, val := range listFilters { + var mapFilters map[string]tftypes.Value + val.As(&mapFilters) + mapObject := make(map[string]attr.Value) + var name string + mapFilters["name"].As(&name) + mapObject["name"] = types.StringValue(name) + + var listValues []tftypes.Value + mapFilters["values"].As(&listValues) + var nSet []attr.Value + for _, val := range listValues { + var value string + val.As(&value) + + nSet = append(nSet, types.StringValue(value)) + } + filtersNameType := basetypes.StringType{} + obt, diag := types.SetValue(filtersNameType, nSet) + if diag != nil { + return setValue, diag + } + mapObject["values"] = obt + retOK, diag := types.ObjectValue(mapObjectType, mapObject) + if diag != nil { + return setValue, diag + } + setfil = append(setfil, retOK) + } + setValue, diags = types.SetValue(filtersValuesType, setfil) + if diags != nil { + return setValue, diags + } + return setValue, nil +} diff --git a/outscale/data_source_quota_test.go b/outscale/data_source_quota_test.go new file mode 100644 index 000000000..8b80e609f --- /dev/null +++ b/outscale/data_source_quota_test.go @@ -0,0 +1,36 @@ +package outscale + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/terraform-providers/terraform-provider-outscale/version" +) + +func TestAccFwOthers_DataSourceQuota(t *testing.T) { + + t.Parallel() + resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + // new() is an example function that returns a provider.Provider + "outscale": providerserver.NewProtocol5WithError(New(version.GetVersion())), + }, + PreCheck: func() { TestAccFwPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccFwDataSourceOutscaleOAPIQuotaConfig, + Check: resource.ComposeTestCheckFunc(), + }, + }, + }) +} + +const testAccFwDataSourceOutscaleOAPIQuotaConfig = ` + data "outscale_quota" "lbuQuota1" { + filter { + name = "quota_names" + values = ["lb_listeners_limit"] + } + }` diff --git a/outscale/framework_config.go b/outscale/framework_config.go new file mode 100644 index 000000000..55abdc3ed --- /dev/null +++ b/outscale/framework_config.go @@ -0,0 +1,104 @@ +package outscale + +import ( + "context" + "crypto/tls" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" + oscgo "github.com/outscale/osc-sdk-go/v2" + "github.com/terraform-providers/terraform-provider-outscale/utils" + "github.com/terraform-providers/terraform-provider-outscale/version" +) + +// OutscaleClient client +type OutscaleClient_fw struct { + OSCAPI *oscgo.APIClient +} + +// Client ... +func (c *frameworkProvider) Client_fw(ctx context.Context, data *ProviderModel, diags *diag.Diagnostics) (*OutscaleClient_fw, error) { + + //setdefaut environnemant varibles + setDefaultEnv(data) + tlsconfig := &tls.Config{InsecureSkipVerify: c.insecure} + cert, err := tls.LoadX509KeyPair(data.X509CertPath.ValueString(), data.X509KeyPath.ValueString()) + if err == nil { + tlsconfig = &tls.Config{ + InsecureSkipVerify: false, + Certificates: []tls.Certificate{cert}, + } + } + + skipClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsconfig, + Proxy: http.ProxyFromEnvironment, + }, + } + + skipClient.Transport = logging.NewTransport("Outscale", skipClient.Transport) + + skipClient.Transport = NewTransport(data.AccessKeyId.ValueString(), data.SecretKeyId.ValueString(), data.Region.ValueString(), skipClient.Transport) + + basePath := fmt.Sprintf("api.%s.outscale.com", data.Region.ValueString()) + fmt.Printf("\n CONF_CLI: basePath= %##++v\n", basePath) + if endpoint, ok := data.Endpoints["api"]; ok { + basePath = endpoint.(string) + } + fmt.Printf("\n APRES CONF_CLI: basePath= %##++v\n", basePath) + + oscConfig := oscgo.NewConfiguration() + oscConfig.Debug = true + oscConfig.HTTPClient = skipClient + oscConfig.Host = basePath + oscConfig.UserAgent = fmt.Sprintf("terraform-provider-outscale/%s", version.GetVersion()) + + oscClient := oscgo.NewAPIClient(oscConfig) + + client := &OutscaleClient_fw{ + OSCAPI: oscClient, + } + return client, nil +} + +func setDefaultEnv(data *ProviderModel) { + if data.AccessKeyId.IsNull() { + if accessKeyId := utils.GetEnvVariableValue([]string{"OSC_ACCESS_KEY", "OUTSCALE_ACCESSKEYID"}); accessKeyId != "" { + data.AccessKeyId = types.StringValue(accessKeyId) + } + } + if data.SecretKeyId.IsNull() { + if secretKeyId := utils.GetEnvVariableValue([]string{"OSC_SECRET_KEY", "OUTSCALE_SECRETKEYID"}); secretKeyId != "" { + data.SecretKeyId = types.StringValue(secretKeyId) + } + } + + if data.Region.IsNull() { + if region := utils.GetEnvVariableValue([]string{"OSC_REGION", "OUTSCALE_REGION"}); region != "" { + data.Region = types.StringValue(region) + } + } + + if data.X509CertPath.IsNull() { + if x509Cert := utils.GetEnvVariableValue([]string{"OSC_X509_CLIENT_CERT", "OUTSCALE_X509CERT"}); x509Cert != "" { + data.X509CertPath = types.StringValue(x509Cert) + } + } + + if data.X509KeyPath.IsNull() { + if x509Key := utils.GetEnvVariableValue([]string{"OSC_X509_CLIENT_KEY", "OUTSCALE_X509KEY"}); x509Key != "" { + data.X509KeyPath = types.StringValue(x509Key) + } + } + /* + if data.Endpoints.IsNull() { + if endpoints := getEnvVariableValue([]string{"OSC_ENDPOINT_API", "OUTSCALE_OAPI_URL"}); endpoints != "" { + data.Endpoints = types.StringValue(endpoints) + } + } + */ +} diff --git a/outscale/framework_provider.go b/outscale/framework_provider.go new file mode 100644 index 000000000..6596cc6d4 --- /dev/null +++ b/outscale/framework_provider.go @@ -0,0 +1,379 @@ +package outscale + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ provider.Provider = &frameworkProvider{} + endpointFwServiceNames []string +) + +func init() { + endpointFwServiceNames = []string{ + "api", + } +} + +func New(version string) provider.Provider { + return &frameworkProvider{ + version: version, + } +} + +type frameworkProvider struct { + accessKeyId types.String + secretKeyId types.String + region types.String + endpoints map[string]interface{} + x509CertPath string + x509KeyPath string + insecure bool + 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 map[string]interface{} `tfsdk:"endpoints"` + X509CertPath types.String `tfsdk:"x509_cert_path"` + X509KeyPath types.String `tfsdk:"x509_key_path"` + Insecure types.Bool `tfsdk:"insecure"` +} + +func (p *frameworkProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "outscale" + resp.Version = p.version +} + +func (p *frameworkProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "access_key_id": schema.StringAttribute{ + Optional: true, + Description: "The Access Key ID for API operations.", + }, + "secret_key_id": schema.StringAttribute{ + Optional: true, + Description: "The Secret Key ID for API operations.", + }, + "region": schema.StringAttribute{ + Optional: true, + Description: "The Region for API operations.", + }, + "x509_cert_path": schema.StringAttribute{ + Optional: true, + Description: "The path to your x509 cert", + }, + "x509_key_path": schema.StringAttribute{ + Optional: true, + Description: "The path to your x509 key", + }, + "insecure": schema.BoolAttribute{ + Optional: true, + Description: "tls insecure connection", + }, + }, + Blocks: map[string]schema.Block{ + "endpoints": endpointsFwSchema(), + }, + } +} + +/* + func (p *frameworkProvider) MetaSchema(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) { + resp.Schema = metaschema.Schema{ + Attributes: map[string]metaschema.Attribute{ + "module_name": metaschema.StringAttribute{ + Optional: true, + }, + }, + } + } +*/ +func (p *frameworkProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + + var config ProviderModel + + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // If practitioner provided a configuration value for any of the + // attributes, it must be a known value. + + if config.AccessKeyId.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("access_key_id"), + "Unknown Outscale API AccessKeyId", + "The provider cannot create the Outscale API client as there is an unknown configuration value for the Outscale API access_key_id. "+ + "Either target apply the source Outscale of the value first, set the value statically in the configuration, or use the 'OSC_ACCESS_KEY or OUTSCALE_ACCESSKEYID' environment variable.", + ) + } + + if config.SecretKeyId.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("secret_key_id"), + "Unknown HashiCups API SecretKeyId", + "The provider cannot create the Outscale API client as there is an unknown configuration value for the Outscale API secret_key_id. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the 'OSC_SECRET_KEY or OUTSCALE_SECRETKEYID' environment variable.", + ) + } + + if config.Region.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("region"), + "Unknown Outscale API Region", + "The provider cannot create the Outscale API client as there is an unknown configuration value for the Outscale API region. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the 'OSC_REGION or OUTSCALE_REGION' environment variable.", + ) + } + if config.X509CertPath.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("x509_cert_path"), + "Unknown Outscale API X509CertPath", + "The provider cannot create the Outscale API client as there is an unknown configuration value for the Outscale API x509_cert_path. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the 'OSC_X509_CLIENT_CERT or OUTSCALE_X509CERT' environment variable.", + ) + } + + if config.X509KeyPath.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("x509_key_path"), + "Unknown Outscale API X509KeyPath", + "The provider cannot create the Outscale API client as there is an unknown configuration value for the Outscale API x509_key_path. "+ + "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 resp.Diagnostics.HasError() { + return + } + client, err := p.Client_fw(ctx, &config, &diags) + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create Outscale API Client", + "An unexpected error occurred when creating the Outscale API client. "+ + "If the error is not clear, please contact the provider developers.\n\n"+ + "Outscale Client Error: "+err.Error(), + ) + return + } + resp.DataSourceData = *client + resp.ResourceData = *client +} + +func (p *frameworkProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewDataSourceQuota, + } +} + +func (p *frameworkProvider) Resources(ctx context.Context) []func() resource.Resource { + /*return []func() resource.Resource{ + NewResource, + }*/ + return nil +} + +func endpointsFwSchema() schema.SetNestedBlock { + endpointsAttributes := make(map[string]schema.Attribute) + + for _, serviceKey := range endpointFwServiceNames { + endpointsAttributes[serviceKey] = schema.StringAttribute{ + Optional: true, + Description: "Use this to override the default service endpoint URL", + } + } + return schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: endpointsAttributes, + }, + } +} + +/* +func endpointsFwSchema() schema.Attribute { + endpointsAttributes := make(map[string]schema.Attribute) + + for _, endpointServiceName := range endpointFwServiceNames { + endpointsAttributes[endpointServiceName] = schema.StringAttribute{ + Optional: true, + Description: "Use this to override the default service endpoint URL", + } + } + return schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: endpointsAttributes, + }, + } + return schema.schema.Attribute{ + AttrTypes: map[string]attr.Type{ + Attributes: endpointsAttributes, + }, + } + +} +*/ +// Configuration values are now available. +// if data.Endpoint.IsNull() { /* ... */ } +/* + */ +// Configuration values are now available. +/* + p.LoadAndValidateFramework(ctx, data, req.TerraformVersion, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + var data ProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + // Example client configuration for data sources and resources + //resp.DataSourceData = p + //resp.ResourceData = p + // Example client configuration for data sources and resources + client := http.DefaultClient + resp.DataSourceData = client + resp.ResourceData = client + +*/ +// Configuration values are now available. +// if data.Endpoint.IsNull() { /* ... */ } +/* + */ +// Configuration values are now available. +/* + p.LoadAndValidateFramework(ctx, data, req.TerraformVersion, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + var data ProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + // Example client configuration for data sources and resources + //resp.DataSourceData = p + //resp.ResourceData = p + // Example client configuration for data sources and resources + client := http.DefaultClient + resp.DataSourceData = client + resp.ResourceData = client + + + + + + + + + + + + + + + + + + + accessKeyId := os.Getenv("OSC_ACCESS_KEY") + secretKeyId := os.Getenv("OSC_SECRET_KEY") + region := os.Getenv("OSC_REGION") + x509Cert := os.Getenv("OSC_X509CERT") + x509Key := os.Getenv("OSC_X509KEY") + + if !config.AccessKeyId.IsNull() { + accessKeyId = config.AccessKeyId.ValueString() + } + if !config.SecretKeyId.IsNull() { + secretKeyId = config.SecretKeyId.ValueString() + } + if !config.Region.IsNull() { + region = config.Region.ValueString() + } + + if !config.Endpoints.IsNull() { + endpoints = config.Endpoint.ValueString() + } + + if !config.X509CertPath.IsNull() { + x509Cert = config.X509CertPath.ValueString() + } + + if !config.X509KeyPath.IsNull() { + x509Key = config.X509KeyPath.ValueString() + } + + //if !config.Diagnostics.HasError() { + // return + //} + + if accessKeyId == "" { + resp.Diagnostics.AddAttributeError( + path.Root("access_key_id"), + "Unknown HashiCups API AccessKeyId", + "The provider cannot create the HashiCups API client as there is an unknown configuration value for the HashiCups API host. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the HASHICUPS_HOST environment variable.", + ) + } + + if secretKeyId == "" { + resp.Diagnostics.AddAttributeError( + path.Root("secret_key_id"), + "Unknown HashiCups API SecretKeyId", + "The provider cannot create the HashiCups API client as there is an unknown configuration value for the HashiCups API username. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the HASHICUPS_USERNAME environment variable.", + ) + } + + if region == "" { + resp.Diagnostics.AddAttributeError( + path.Root("region"), + "Unknown HashiCups API Region", + "The provider cannot create the HashiCups API client as there is an unknown configuration value for the HashiCups API password. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the HASHICUPS_PASSWORD environment variable.", + ) + } + if x509Cert == "" { + resp.Diagnostics.AddAttributeError( + path.Root("x509_cert_path"), + "Unknown HashiCups API X509CertPath", + "The provider cannot create the HashiCups API client as there is an unknown configuration value for the HashiCups API username. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the HASHICUPS_USERNAME environment variable.", + ) + } + + if x509Key == "" { + resp.Diagnostics.AddAttributeError( + path.Root("x509_key_path"), + "Unknown HashiCups API X509KeyPath", + "The provider cannot create the HashiCups API client as there is an unknown configuration value for the HashiCups API password. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the HASHICUPS_PASSWORD environment variable.", + ) + } + + if resp.Diagnostics.HasError() { + return + } + +*/ diff --git a/outscale/framework_provider_test.go b/outscale/framework_provider_test.go new file mode 100644 index 000000000..e8bec1a8b --- /dev/null +++ b/outscale/framework_provider_test.go @@ -0,0 +1,100 @@ +package outscale + +import ( + "context" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + vers "github.com/terraform-providers/terraform-provider-outscale/version" +) + +func TestFwProvider_impl(t *testing.T) { + var _ provider.Provider = New(vers.GetVersion()) +} + +func TestAccFwPreCheck(t *testing.T) { + if os.Getenv("OUTSCALE_ACCESSKEYID") == "" || + os.Getenv("OUTSCALE_REGION") == "" || + os.Getenv("OUTSCALE_SECRETKEYID") == "" || + os.Getenv("OUTSCALE_IMAGEID") == "" || + os.Getenv("OUTSCALE_ACCOUNT") == "" { + t.Fatal("`OUTSCALE_ACCESSKEYID`, `OUTSCALE_SECRETKEYID`, `OUTSCALE_REGION`, `OUTSCALE_ACCOUNT` and `OUTSCALE_IMAGEID` must be set for acceptance testing") + } +} + +func TestMuxServer(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "outscale": func() (tfprotov5.ProviderServer, error) { + ctx := context.Background() + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5(New(vers.GetVersion())), // Example terraform-plugin-framework provider + Provider().GRPCProvider, // Example terraform-plugin-sdk provider + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: fwtestAccDataSourceOutscaleOAPIQuotaConfig, + }, + }, + }) +} + +func TestDataSource_UpgradeFromVersion(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "outscale": { + VersionConstraint: "0.10.0", + Source: "outscale/outscale", + }, + }, + Config: fwtestAccDataSourceOutscaleOAPIQuotaConfig, + }, + { + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "outscale": func() (tfprotov5.ProviderServer, error) { + ctx := context.Background() + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5(New(vers.GetVersion())), // Example terraform-plugin-framework provider + Provider().GRPCProvider, // Example terraform-plugin-sdk provider + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + }, + Config: fwtestAccDataSourceOutscaleOAPIQuotaConfig, + PlanOnly: true, + }, + }, + }) +} + +const fwtestAccDataSourceOutscaleOAPIQuotaConfig = ` + data "outscale_quota" "lbuquota1" { + filter { + name = "quota_names" + values = ["lb_listeners_limit"] + } +} +` diff --git a/outscale/provider.go b/outscale/provider.go index 7ed82e53c..d456fdb63 100644 --- a/outscale/provider.go +++ b/outscale/provider.go @@ -1,8 +1,8 @@ package outscale import ( - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-outscale/utils" ) var endpointServiceNames []string @@ -14,38 +14,33 @@ func init() { } // Provider ... -func Provider() terraform.ResourceProvider { +func Provider() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ "access_key_id": { Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("OUTSCALE_ACCESSKEYID", nil), + Optional: true, Description: "The Access Key ID for API operations.", }, "secret_key_id": { Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("OUTSCALE_SECRETKEYID", nil), + Optional: true, Description: "The Secret Key ID for API operations.", }, "region": { Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("OUTSCALE_REGION", nil), + Optional: true, Description: "The Region for API operations.", }, "endpoints": endpointsSchema(), "x509_cert_path": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("OUTSCALE_X509CERT", nil), Description: "The path to your x509 cert", }, "x509_key_path": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("OUTSCALE_X509KEY", nil), Description: "The path to your x509 key", }, "insecure": { @@ -169,7 +164,6 @@ func Provider() terraform.ResourceProvider { "outscale_flexible_gpu_catalog": dataSourceOutscaleOAPIFlexibleGpuCatalog(), "outscale_product_type": dataSourceOutscaleOAPIProductType(), "outscale_product_types": dataSourceOutscaleOAPIProductTypes(), - "outscale_quota": dataSourceOutscaleOAPIQuota(), "outscale_quotas": dataSourceOutscaleOAPIQuotas(), "outscale_image_export_task": dataSourceOutscaleOAPIImageExportTask(), "outscale_image_export_tasks": dataSourceOutscaleOAPIImageExportTasks(), @@ -202,6 +196,7 @@ func providerConfigureClient(d *schema.ResourceData) (interface{}, error) { Insecure: d.Get("insecure").(bool), } + setProviderDefaultEnv(&config) endpointsSet := d.Get("endpoints").(*schema.Set) for _, endpointsSetI := range endpointsSet.List() { @@ -210,7 +205,6 @@ func providerConfigureClient(d *schema.ResourceData) (interface{}, error) { config.Endpoints[endpointServiceName] = endpoints[endpointServiceName].(string) } } - return config.Client() } @@ -234,3 +228,42 @@ func endpointsSchema() *schema.Schema { }, } } + +func setProviderDefaultEnv(conf *Config) { + if conf.AccessKeyID == "" { + if accessKeyId := utils.GetEnvVariableValue([]string{"OSC_ACCESS_KEY", "OUTSCALE_ACCESSKEYID"}); accessKeyId != "" { + conf.AccessKeyID = accessKeyId + } + } + if conf.SecretKeyID == "" { + if secretKeyId := utils.GetEnvVariableValue([]string{"OSC_SECRET_KEY", "OUTSCALE_SECRETKEYID"}); secretKeyId != "" { + conf.SecretKeyID = secretKeyId + } + } + + if conf.Region == "" { + if region := utils.GetEnvVariableValue([]string{"OSC_REGION", "OUTSCALE_REGION"}); region != "" { + conf.Region = region + } + } + + if conf.X509cert == "" { + if x509Cert := utils.GetEnvVariableValue([]string{"OSC_X509_CLIENT_CERT", "OUTSCALE_X509CERT"}); x509Cert != "" { + conf.X509cert = x509Cert + } + } + + if conf.X509key == "" { + if x509Key := utils.GetEnvVariableValue([]string{"OSC_X509_CLIENT_KEY", "OUTSCALE_X509KEY"}); x509Key != "" { + conf.X509key = x509Key + } + } + + /* + if data.Endpoints.IsNull() { + if endpoints := getEnvVariableValue([]string{"OSC_ENDPOINT_API", "OUTSCALE_OAPI_URL"}); endpoints != "" { + data.Endpoints = types.StringValue(endpoints) + } + } + */ +} diff --git a/outscale/provider_test.go b/outscale/provider_test.go index 0b60b43a5..bb90df7a3 100644 --- a/outscale/provider_test.go +++ b/outscale/provider_test.go @@ -4,32 +4,30 @@ import ( "os" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -var testAccProviders map[string]terraform.ResourceProvider +var testAccProviders map[string]*schema.Provider var testAccProvider *schema.Provider func init() { - testAccProvider = Provider().(*schema.Provider) + testAccProvider = Provider() - testAccProviders = map[string]terraform.ResourceProvider{ + testAccProviders = map[string]*schema.Provider{ "outscale": testAccProvider, } } func TestProvider(t *testing.T) { - if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + if err := Provider().InternalValidate(); err != nil { t.Fatalf("err: %s", err) } } func TestProvider_impl(t *testing.T) { - var _ terraform.ResourceProvider = Provider() + var _ *schema.Provider = Provider() } func testAccPreCheck(t *testing.T) { diff --git a/terraform-registry-manifest.json b/terraform-registry-manifest.json new file mode 100644 index 000000000..1931b0e00 --- /dev/null +++ b/terraform-registry-manifest.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "metadata": { + "protocol_versions": ["5.0"] + } +}