This is an automated email from the ASF dual-hosted git repository. vishesh pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git
The following commit(s) were added to refs/heads/main by this push: new e6bcc74 feat: migrate to terraform plugin framework (#113) e6bcc74 is described below commit e6bcc7489e84db74e30117146b6dec8f76461eeb Author: Fábio Matavelli <fabiomatave...@gmail.com> AuthorDate: Thu Jul 25 01:36:50 2024 -0300 feat: migrate to terraform plugin framework (#113) --- cloudstack/provider.go | 4 +- cloudstack/provider_test.go | 82 +++++++++++++++++------ cloudstack/provider_v6.go | 155 ++++++++++++++++++++++++++++++++++++++++++++ cloudstack/resources.go | 23 +++++++ go.mod | 8 ++- go.sum | 16 +++-- main.go | 32 ++++++--- 7 files changed, 280 insertions(+), 40 deletions(-) diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 6d7b7b5..5aad7c5 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -26,7 +26,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func New() *schema.Provider { +func Provider() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ "api_url": { @@ -41,6 +41,7 @@ func New() *schema.Provider { Optional: true, DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_API_KEY", nil), ConflictsWith: []string{"config", "profile"}, + Sensitive: true, }, "secret_key": { @@ -48,6 +49,7 @@ func New() *schema.Provider { Optional: true, DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_SECRET_KEY", nil), ConflictsWith: []string{"config", "profile"}, + Sensitive: true, }, "config": { diff --git a/cloudstack/provider_test.go b/cloudstack/provider_test.go index 0a3acf0..fb868e4 100644 --- a/cloudstack/provider_test.go +++ b/cloudstack/provider_test.go @@ -22,10 +22,13 @@ package cloudstack import ( "context" "os" + "regexp" "testing" - "github.com/hashicorp/terraform-plugin-go/tfprotov5" - "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-mux/tf5to6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) @@ -33,44 +36,65 @@ import ( var testAccProviders map[string]*schema.Provider var testAccProvider *schema.Provider +var testAccMuxProvider map[string]func() (tfprotov6.ProviderServer, error) + var cloudStackTemplateURL = os.Getenv("CLOUDSTACK_TEMPLATE_URL") func init() { - testAccProvider = New() + testAccProvider = Provider() testAccProviders = map[string]*schema.Provider{ "cloudstack": testAccProvider, } + + testAccMuxProvider = map[string]func() (tfprotov6.ProviderServer, error){ + "cloudstack": func() (tfprotov6.ProviderServer, error) { + ctx := context.Background() + + upgradedSdkServer, err := tf5to6server.UpgradeServer( + ctx, + Provider().GRPCProvider, + ) + + if err != nil { + return nil, err + } + + providers := []func() tfprotov6.ProviderServer{ + providerserver.NewProtocol6(New()), + func() tfprotov6.ProviderServer { + return upgradedSdkServer + }, + } + + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + } } func TestProvider(t *testing.T) { - if err := New().InternalValidate(); err != nil { + if err := Provider().InternalValidate(); err != nil { t.Fatalf("err: %s", err) } } func TestProvider_impl(t *testing.T) { - var _ *schema.Provider = New() + var _ *schema.Provider = Provider() } func TestMuxServer(t *testing.T) { resource.Test(t, resource.TestCase{ - ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ - "cloudstack": func() (tfprotov5.ProviderServer, error) { - ctx := context.Background() - providers := []func() tfprotov5.ProviderServer{ - New().GRPCProvider, - } - - muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) - - if err != nil { - return nil, err - } - - return muxServer.ProviderServer(), nil - }, - }, + ProtoV6ProviderFactories: testAccMuxProvider, Steps: []resource.TestStep{ + { + Config: testMuxServerConfig_conflict, + ExpectError: regexp.MustCompile("Invalid Attribute Combination"), + }, { Config: testMuxServerConfig_basic, }, @@ -94,6 +118,22 @@ resource "cloudstack_zone" "zone_resource"{ } ` +const testMuxServerConfig_conflict = ` +provider "cloudstack" { + api_url = "http://localhost:8080/client/api" + api_key = "xxxxx" + secret_key = "xxxxx" + config = "cloudstack.ini" +} + +data "cloudstack_zone" "zone_data_source"{ + filter{ + name = "name" + value = "test" + } +} + ` + func testAccPreCheck(t *testing.T) { if v := os.Getenv("CLOUDSTACK_API_URL"); v == "" { t.Fatal("CLOUDSTACK_API_URL must be set for acceptance tests") diff --git a/cloudstack/provider_v6.go b/cloudstack/provider_v6.go new file mode 100644 index 0000000..339e4b3 --- /dev/null +++ b/cloudstack/provider_v6.go @@ -0,0 +1,155 @@ +package cloudstack + +import ( + "context" + "fmt" + "os" + "strconv" + + "github.com/hashicorp/terraform-plugin-framework-validators/providervalidator" + "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" +) + +type CloudstackProvider struct{} + +type CloudstackProviderModel struct { + ApiUrl types.String `tfsdk:"api_url"` + ApiKey types.String `tfsdk:"api_key"` + SecretKey types.String `tfsdk:"secret_key"` + Config types.String `tfsdk:"config"` + Profile types.String `tfsdk:"profile"` + HttpGetOnly types.Bool `tfsdk:"http_get_only"` + Timeout types.Int64 `tfsdk:"timeout"` +} + +var _ provider.Provider = (*CloudstackProvider)(nil) + +func New() provider.Provider { + return &CloudstackProvider{} +} + +func (p *CloudstackProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "cloudstack" +} + +func (p *CloudstackProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "api_url": schema.StringAttribute{ + Optional: true, + }, + "api_key": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "secret_key": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "config": schema.StringAttribute{ + Optional: true, + }, + "profile": schema.StringAttribute{ + Optional: true, + }, + "http_get_only": schema.BoolAttribute{ + Optional: true, + }, + "timeout": schema.Int64Attribute{ + Optional: true, + }, + }, + } +} + +func (p *CloudstackProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + apiUrl := os.Getenv("CLOUDSTACK_API_URL") + apiKey := os.Getenv("CLOUDSTACK_API_KEY") + secretKey := os.Getenv("CLOUDSTACK_SECRET_KEY") + httpGetOnly, _ := strconv.ParseBool(os.Getenv("CLOUDSTACK_HTTP_GET_ONLY")) + timeout, _ := strconv.ParseInt(os.Getenv("CLOUDSTACK_TIMEOUT"), 2, 64) + + var data CloudstackProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if data.ApiUrl.ValueString() != "" { + apiUrl = data.ApiUrl.ValueString() + } + + if data.ApiKey.ValueString() != "" { + apiKey = data.ApiKey.ValueString() + } + + if data.SecretKey.ValueString() != "" { + secretKey = data.SecretKey.ValueString() + } + + if data.HttpGetOnly.ValueBool() { + httpGetOnly = true + } + + if data.Timeout.ValueInt64() != 0 { + timeout = data.Timeout.ValueInt64() + } + + cfg := Config{ + APIURL: apiUrl, + APIKey: apiKey, + SecretKey: secretKey, + HTTPGETOnly: httpGetOnly, + Timeout: timeout, + } + + client, err := cfg.NewClient() + + if err != nil { + resp.Diagnostics.AddError("cloudstack", fmt.Sprintf("failed to create client: %T", err)) + return + } + + resp.ResourceData = client + resp.DataSourceData = client +} + +func (p *CloudstackProvider) ConfigValidators(ctx context.Context) []provider.ConfigValidator { + return []provider.ConfigValidator{ + providervalidator.Conflicting( + path.MatchRoot("api_url"), + path.MatchRoot("config"), + ), + providervalidator.Conflicting( + path.MatchRoot("api_url"), + path.MatchRoot("profile"), + ), + providervalidator.Conflicting( + path.MatchRoot("api_key"), + path.MatchRoot("config"), + ), + providervalidator.Conflicting( + path.MatchRoot("api_key"), + path.MatchRoot("profile"), + ), + providervalidator.Conflicting( + path.MatchRoot("secret_key"), + path.MatchRoot("config"), + ), + providervalidator.Conflicting( + path.MatchRoot("secret_key"), + path.MatchRoot("profile"), + ), + } +} + +func (p *CloudstackProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{} +} + +func (p *CloudstackProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{} +} diff --git a/cloudstack/resources.go b/cloudstack/resources.go index 0fedb70..22b2adc 100644 --- a/cloudstack/resources.go +++ b/cloudstack/resources.go @@ -20,6 +20,7 @@ package cloudstack import ( + "context" "fmt" "log" "regexp" @@ -27,6 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -170,3 +172,24 @@ func importStatePassthrough(d *schema.ResourceData, meta interface{}) ([]*schema return []*schema.ResourceData{d}, nil } + +type ResourceWithConfigure struct { + client *cloudstack.CloudStackClient +} + +func (r *ResourceWithConfigure) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*cloudstack.CloudStackClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *cloudstack.CloudStackClient, got %T", req.ProviderData), + ) + } + + r.client = client +} diff --git a/go.mod b/go.mod index 7a55244..8952c35 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ require ( github.com/apache/cloudstack-go/v2 v2.16.1 github.com/go-ini/ini v1.67.0 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/terraform-plugin-go v0.22.0 + github.com/hashicorp/terraform-plugin-framework v1.7.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 + github.com/hashicorp/terraform-plugin-go v0.22.1 github.com/hashicorp/terraform-plugin-mux v0.15.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 github.com/hashicorp/terraform-plugin-testing v1.7.0 @@ -59,8 +61,8 @@ require ( golang.org/x/tools v0.13.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/grpc v1.62.0 // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/grpc v1.62.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index 010e7b8..51980c3 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,12 @@ github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8J github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw= github.com/hashicorp/terraform-json v0.21.0 h1:9NQxbLNqPbEMze+S6+YluEdXgJmhQykRyRNd+zTI05U= github.com/hashicorp/terraform-json v0.21.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk= -github.com/hashicorp/terraform-plugin-go v0.22.0 h1:1OS1Jk5mO0f5hrziWJGXXIxBrMe2j/B8E+DVGw43Xmc= -github.com/hashicorp/terraform-plugin-go v0.22.0/go.mod h1:mPULV91VKss7sik6KFEcEu7HuTogMLLO/EvWCuFkRVE= +github.com/hashicorp/terraform-plugin-framework v1.7.0 h1:wOULbVmfONnJo9iq7/q+iBOBJul5vRovaYJIu2cY/Pw= +github.com/hashicorp/terraform-plugin-framework v1.7.0/go.mod h1:jY9Id+3KbZ17OMpulgnWLSfwxNVYSoYBQFTgsx044CI= +github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= +github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= +github.com/hashicorp/terraform-plugin-go v0.22.1 h1:iTS7WHNVrn7uhe3cojtvWWn83cm2Z6ryIUDTRO0EV7w= +github.com/hashicorp/terraform-plugin-go v0.22.1/go.mod h1:qrjnqRghvQ6KnDbB12XeZ4FluclYwptntoWCr9QaXTI= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-plugin-mux v0.15.0 h1:+/+lDx0WUsIOpkAmdwBIoFU8UP9o2eZASoOnLsWbKME= @@ -224,12 +228,12 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= -google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/main.go b/main.go index 0ec5162..b018e6a 100644 --- a/main.go +++ b/main.go @@ -24,10 +24,12 @@ import ( "flag" "log" - "github.com/hashicorp/terraform-plugin-go/tfprotov5" - "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" - "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-mux/tf5to6server" "github.com/terraform-providers/terraform-provider-cloudstack/cloudstack" ) @@ -39,23 +41,35 @@ func main() { flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") flag.Parse() - providers := []func() tfprotov5.ProviderServer{ - cloudstack.New().GRPCProvider, + updatedSdkServer, err := tf5to6server.UpgradeServer( + ctx, + cloudstack.Provider().GRPCProvider, + ) + + if err != nil { + log.Fatal(err) + } + + providers := []func() tfprotov6.ProviderServer{ + providerserver.NewProtocol6(cloudstack.New()), + func() tfprotov6.ProviderServer { + return updatedSdkServer + }, } - muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) if err != nil { log.Fatal(err) } - var serveOpts []tf5server.ServeOpt + var serveOpts []tf6server.ServeOpt if debug { - serveOpts = append(serveOpts, tf5server.WithManagedDebug()) + serveOpts = append(serveOpts, tf6server.WithManagedDebug()) } - err = tf5server.Serve( + err = tf6server.Serve( "registry.terraform.io/cloudstack/cloudstack", muxServer.ProviderServer, serveOpts...,