Skip to content

Commit e6bcc74

Browse files
authoredJul 25, 2024··
feat: migrate to terraform plugin framework (#113)
1 parent bf3bcec commit e6bcc74

File tree

7 files changed

+280
-40
lines changed

7 files changed

+280
-40
lines changed
 

‎cloudstack/provider.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
2727
)
2828

29-
func New() *schema.Provider {
29+
func Provider() *schema.Provider {
3030
return &schema.Provider{
3131
Schema: map[string]*schema.Schema{
3232
"api_url": {
@@ -41,13 +41,15 @@ func New() *schema.Provider {
4141
Optional: true,
4242
DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_API_KEY", nil),
4343
ConflictsWith: []string{"config", "profile"},
44+
Sensitive: true,
4445
},
4546

4647
"secret_key": {
4748
Type: schema.TypeString,
4849
Optional: true,
4950
DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_SECRET_KEY", nil),
5051
ConflictsWith: []string{"config", "profile"},
52+
Sensitive: true,
5153
},
5254

5355
"config": {

‎cloudstack/provider_test.go

+61-21
Original file line numberDiff line numberDiff line change
@@ -22,55 +22,79 @@ package cloudstack
2222
import (
2323
"context"
2424
"os"
25+
"regexp"
2526
"testing"
2627

27-
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
28-
"github.com/hashicorp/terraform-plugin-mux/tf5muxserver"
28+
"github.com/hashicorp/terraform-plugin-framework/providerserver"
29+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
30+
"github.com/hashicorp/terraform-plugin-mux/tf5to6server"
31+
"github.com/hashicorp/terraform-plugin-mux/tf6muxserver"
2932
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
3033
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
3134
)
3235

3336
var testAccProviders map[string]*schema.Provider
3437
var testAccProvider *schema.Provider
3538

39+
var testAccMuxProvider map[string]func() (tfprotov6.ProviderServer, error)
40+
3641
var cloudStackTemplateURL = os.Getenv("CLOUDSTACK_TEMPLATE_URL")
3742

3843
func init() {
39-
testAccProvider = New()
44+
testAccProvider = Provider()
4045
testAccProviders = map[string]*schema.Provider{
4146
"cloudstack": testAccProvider,
4247
}
48+
49+
testAccMuxProvider = map[string]func() (tfprotov6.ProviderServer, error){
50+
"cloudstack": func() (tfprotov6.ProviderServer, error) {
51+
ctx := context.Background()
52+
53+
upgradedSdkServer, err := tf5to6server.UpgradeServer(
54+
ctx,
55+
Provider().GRPCProvider,
56+
)
57+
58+
if err != nil {
59+
return nil, err
60+
}
61+
62+
providers := []func() tfprotov6.ProviderServer{
63+
providerserver.NewProtocol6(New()),
64+
func() tfprotov6.ProviderServer {
65+
return upgradedSdkServer
66+
},
67+
}
68+
69+
muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...)
70+
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
return muxServer.ProviderServer(), nil
76+
},
77+
}
4378
}
4479

4580
func TestProvider(t *testing.T) {
46-
if err := New().InternalValidate(); err != nil {
81+
if err := Provider().InternalValidate(); err != nil {
4782
t.Fatalf("err: %s", err)
4883
}
4984
}
5085

5186
func TestProvider_impl(t *testing.T) {
52-
var _ *schema.Provider = New()
87+
var _ *schema.Provider = Provider()
5388
}
5489

5590
func TestMuxServer(t *testing.T) {
5691
resource.Test(t, resource.TestCase{
57-
ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){
58-
"cloudstack": func() (tfprotov5.ProviderServer, error) {
59-
ctx := context.Background()
60-
providers := []func() tfprotov5.ProviderServer{
61-
New().GRPCProvider,
62-
}
63-
64-
muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...)
65-
66-
if err != nil {
67-
return nil, err
68-
}
69-
70-
return muxServer.ProviderServer(), nil
71-
},
72-
},
92+
ProtoV6ProviderFactories: testAccMuxProvider,
7393
Steps: []resource.TestStep{
94+
{
95+
Config: testMuxServerConfig_conflict,
96+
ExpectError: regexp.MustCompile("Invalid Attribute Combination"),
97+
},
7498
{
7599
Config: testMuxServerConfig_basic,
76100
},
@@ -94,6 +118,22 @@ resource "cloudstack_zone" "zone_resource"{
94118
}
95119
`
96120

121+
const testMuxServerConfig_conflict = `
122+
provider "cloudstack" {
123+
api_url = "http://localhost:8080/client/api"
124+
api_key = "xxxxx"
125+
secret_key = "xxxxx"
126+
config = "cloudstack.ini"
127+
}
128+
129+
data "cloudstack_zone" "zone_data_source"{
130+
filter{
131+
name = "name"
132+
value = "test"
133+
}
134+
}
135+
`
136+
97137
func testAccPreCheck(t *testing.T) {
98138
if v := os.Getenv("CLOUDSTACK_API_URL"); v == "" {
99139
t.Fatal("CLOUDSTACK_API_URL must be set for acceptance tests")

‎cloudstack/provider_v6.go

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package cloudstack
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"strconv"
8+
9+
"github.com/hashicorp/terraform-plugin-framework-validators/providervalidator"
10+
"github.com/hashicorp/terraform-plugin-framework/datasource"
11+
"github.com/hashicorp/terraform-plugin-framework/path"
12+
"github.com/hashicorp/terraform-plugin-framework/provider"
13+
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
14+
"github.com/hashicorp/terraform-plugin-framework/resource"
15+
"github.com/hashicorp/terraform-plugin-framework/types"
16+
)
17+
18+
type CloudstackProvider struct{}
19+
20+
type CloudstackProviderModel struct {
21+
ApiUrl types.String `tfsdk:"api_url"`
22+
ApiKey types.String `tfsdk:"api_key"`
23+
SecretKey types.String `tfsdk:"secret_key"`
24+
Config types.String `tfsdk:"config"`
25+
Profile types.String `tfsdk:"profile"`
26+
HttpGetOnly types.Bool `tfsdk:"http_get_only"`
27+
Timeout types.Int64 `tfsdk:"timeout"`
28+
}
29+
30+
var _ provider.Provider = (*CloudstackProvider)(nil)
31+
32+
func New() provider.Provider {
33+
return &CloudstackProvider{}
34+
}
35+
36+
func (p *CloudstackProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
37+
resp.TypeName = "cloudstack"
38+
}
39+
40+
func (p *CloudstackProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
41+
resp.Schema = schema.Schema{
42+
Attributes: map[string]schema.Attribute{
43+
"api_url": schema.StringAttribute{
44+
Optional: true,
45+
},
46+
"api_key": schema.StringAttribute{
47+
Optional: true,
48+
Sensitive: true,
49+
},
50+
"secret_key": schema.StringAttribute{
51+
Optional: true,
52+
Sensitive: true,
53+
},
54+
"config": schema.StringAttribute{
55+
Optional: true,
56+
},
57+
"profile": schema.StringAttribute{
58+
Optional: true,
59+
},
60+
"http_get_only": schema.BoolAttribute{
61+
Optional: true,
62+
},
63+
"timeout": schema.Int64Attribute{
64+
Optional: true,
65+
},
66+
},
67+
}
68+
}
69+
70+
func (p *CloudstackProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
71+
apiUrl := os.Getenv("CLOUDSTACK_API_URL")
72+
apiKey := os.Getenv("CLOUDSTACK_API_KEY")
73+
secretKey := os.Getenv("CLOUDSTACK_SECRET_KEY")
74+
httpGetOnly, _ := strconv.ParseBool(os.Getenv("CLOUDSTACK_HTTP_GET_ONLY"))
75+
timeout, _ := strconv.ParseInt(os.Getenv("CLOUDSTACK_TIMEOUT"), 2, 64)
76+
77+
var data CloudstackProviderModel
78+
79+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
80+
81+
if data.ApiUrl.ValueString() != "" {
82+
apiUrl = data.ApiUrl.ValueString()
83+
}
84+
85+
if data.ApiKey.ValueString() != "" {
86+
apiKey = data.ApiKey.ValueString()
87+
}
88+
89+
if data.SecretKey.ValueString() != "" {
90+
secretKey = data.SecretKey.ValueString()
91+
}
92+
93+
if data.HttpGetOnly.ValueBool() {
94+
httpGetOnly = true
95+
}
96+
97+
if data.Timeout.ValueInt64() != 0 {
98+
timeout = data.Timeout.ValueInt64()
99+
}
100+
101+
cfg := Config{
102+
APIURL: apiUrl,
103+
APIKey: apiKey,
104+
SecretKey: secretKey,
105+
HTTPGETOnly: httpGetOnly,
106+
Timeout: timeout,
107+
}
108+
109+
client, err := cfg.NewClient()
110+
111+
if err != nil {
112+
resp.Diagnostics.AddError("cloudstack", fmt.Sprintf("failed to create client: %T", err))
113+
return
114+
}
115+
116+
resp.ResourceData = client
117+
resp.DataSourceData = client
118+
}
119+
120+
func (p *CloudstackProvider) ConfigValidators(ctx context.Context) []provider.ConfigValidator {
121+
return []provider.ConfigValidator{
122+
providervalidator.Conflicting(
123+
path.MatchRoot("api_url"),
124+
path.MatchRoot("config"),
125+
),
126+
providervalidator.Conflicting(
127+
path.MatchRoot("api_url"),
128+
path.MatchRoot("profile"),
129+
),
130+
providervalidator.Conflicting(
131+
path.MatchRoot("api_key"),
132+
path.MatchRoot("config"),
133+
),
134+
providervalidator.Conflicting(
135+
path.MatchRoot("api_key"),
136+
path.MatchRoot("profile"),
137+
),
138+
providervalidator.Conflicting(
139+
path.MatchRoot("secret_key"),
140+
path.MatchRoot("config"),
141+
),
142+
providervalidator.Conflicting(
143+
path.MatchRoot("secret_key"),
144+
path.MatchRoot("profile"),
145+
),
146+
}
147+
}
148+
149+
func (p *CloudstackProvider) Resources(ctx context.Context) []func() resource.Resource {
150+
return []func() resource.Resource{}
151+
}
152+
153+
func (p *CloudstackProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
154+
return []func() datasource.DataSource{}
155+
}

‎cloudstack/resources.go

+23
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
package cloudstack
2121

2222
import (
23+
"context"
2324
"fmt"
2425
"log"
2526
"regexp"
2627
"strings"
2728
"time"
2829

2930
"github.com/apache/cloudstack-go/v2/cloudstack"
31+
"github.com/hashicorp/terraform-plugin-framework/resource"
3032
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
3133
)
3234

@@ -170,3 +172,24 @@ func importStatePassthrough(d *schema.ResourceData, meta interface{}) ([]*schema
170172

171173
return []*schema.ResourceData{d}, nil
172174
}
175+
176+
type ResourceWithConfigure struct {
177+
client *cloudstack.CloudStackClient
178+
}
179+
180+
func (r *ResourceWithConfigure) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
181+
if req.ProviderData == nil {
182+
return
183+
}
184+
185+
client, ok := req.ProviderData.(*cloudstack.CloudStackClient)
186+
187+
if !ok {
188+
resp.Diagnostics.AddError(
189+
"Unexpected Resource Configure Type",
190+
fmt.Sprintf("Expected *cloudstack.CloudStackClient, got %T", req.ProviderData),
191+
)
192+
}
193+
194+
r.client = client
195+
}

‎go.mod

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ require (
44
github.com/apache/cloudstack-go/v2 v2.16.1
55
github.com/go-ini/ini v1.67.0
66
github.com/hashicorp/go-multierror v1.1.1
7-
github.com/hashicorp/terraform-plugin-go v0.22.0
7+
github.com/hashicorp/terraform-plugin-framework v1.7.0
8+
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0
9+
github.com/hashicorp/terraform-plugin-go v0.22.1
810
github.com/hashicorp/terraform-plugin-mux v0.15.0
911
github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0
1012
github.com/hashicorp/terraform-plugin-testing v1.7.0
@@ -59,8 +61,8 @@ require (
5961
golang.org/x/tools v0.13.0 // indirect
6062
google.golang.org/appengine v1.6.8 // indirect
6163
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
62-
google.golang.org/grpc v1.62.0 // indirect
63-
google.golang.org/protobuf v1.32.0 // indirect
64+
google.golang.org/grpc v1.62.1 // indirect
65+
google.golang.org/protobuf v1.33.0 // indirect
6466
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
6567
)
6668

‎go.sum

+10-6
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,12 @@ github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8J
7979
github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw=
8080
github.com/hashicorp/terraform-json v0.21.0 h1:9NQxbLNqPbEMze+S6+YluEdXgJmhQykRyRNd+zTI05U=
8181
github.com/hashicorp/terraform-json v0.21.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk=
82-
github.com/hashicorp/terraform-plugin-go v0.22.0 h1:1OS1Jk5mO0f5hrziWJGXXIxBrMe2j/B8E+DVGw43Xmc=
83-
github.com/hashicorp/terraform-plugin-go v0.22.0/go.mod h1:mPULV91VKss7sik6KFEcEu7HuTogMLLO/EvWCuFkRVE=
82+
github.com/hashicorp/terraform-plugin-framework v1.7.0 h1:wOULbVmfONnJo9iq7/q+iBOBJul5vRovaYJIu2cY/Pw=
83+
github.com/hashicorp/terraform-plugin-framework v1.7.0/go.mod h1:jY9Id+3KbZ17OMpulgnWLSfwxNVYSoYBQFTgsx044CI=
84+
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc=
85+
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg=
86+
github.com/hashicorp/terraform-plugin-go v0.22.1 h1:iTS7WHNVrn7uhe3cojtvWWn83cm2Z6ryIUDTRO0EV7w=
87+
github.com/hashicorp/terraform-plugin-go v0.22.1/go.mod h1:qrjnqRghvQ6KnDbB12XeZ4FluclYwptntoWCr9QaXTI=
8488
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
8589
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
8690
github.com/hashicorp/terraform-plugin-mux v0.15.0 h1:+/+lDx0WUsIOpkAmdwBIoFU8UP9o2eZASoOnLsWbKME=
@@ -224,12 +228,12 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs
224228
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
225229
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
226230
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
227-
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
228-
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
231+
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
232+
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
229233
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
230234
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
231-
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
232-
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
235+
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
236+
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
233237
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
234238
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
235239
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

‎main.go

+23-9
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ import (
2424
"flag"
2525
"log"
2626

27-
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
28-
"github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server"
29-
"github.com/hashicorp/terraform-plugin-mux/tf5muxserver"
27+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
28+
"github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server"
29+
"github.com/hashicorp/terraform-plugin-mux/tf6muxserver"
3030

31+
"github.com/hashicorp/terraform-plugin-framework/providerserver"
32+
"github.com/hashicorp/terraform-plugin-mux/tf5to6server"
3133
"github.com/terraform-providers/terraform-provider-cloudstack/cloudstack"
3234
)
3335

@@ -39,23 +41,35 @@ func main() {
3941
flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve")
4042
flag.Parse()
4143

42-
providers := []func() tfprotov5.ProviderServer{
43-
cloudstack.New().GRPCProvider,
44+
updatedSdkServer, err := tf5to6server.UpgradeServer(
45+
ctx,
46+
cloudstack.Provider().GRPCProvider,
47+
)
48+
49+
if err != nil {
50+
log.Fatal(err)
51+
}
52+
53+
providers := []func() tfprotov6.ProviderServer{
54+
providerserver.NewProtocol6(cloudstack.New()),
55+
func() tfprotov6.ProviderServer {
56+
return updatedSdkServer
57+
},
4458
}
4559

46-
muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...)
60+
muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...)
4761

4862
if err != nil {
4963
log.Fatal(err)
5064
}
5165

52-
var serveOpts []tf5server.ServeOpt
66+
var serveOpts []tf6server.ServeOpt
5367

5468
if debug {
55-
serveOpts = append(serveOpts, tf5server.WithManagedDebug())
69+
serveOpts = append(serveOpts, tf6server.WithManagedDebug())
5670
}
5771

58-
err = tf5server.Serve(
72+
err = tf6server.Serve(
5973
"registry.terraform.io/cloudstack/cloudstack",
6074
muxServer.ProviderServer,
6175
serveOpts...,

0 commit comments

Comments
 (0)
Please sign in to comment.