Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DNM: hack: terraform-plugin-framework and dynamic types #354

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ build: terraform-provider-coder

# Builds the provider. Note that as coder/coder is based on
# alpine, we need to disable cgo.
terraform-provider-coder: provider/*.go main.go
terraform-provider-coder: provider/*.go tpfprovider/*.go main.go
CGO_ENABLED=0 go build .

# Run integration tests
Expand All @@ -22,4 +22,4 @@ test-integration: terraform-provider-coder
# Run acceptance tests
.PHONY: testacc
testacc:
TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m
TF_ACC=1 go test ./... -v -count=1 $(TESTARGS) -timeout 120m
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ require (
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.22.0 // indirect
github.com/hashicorp/terraform-json v0.24.0 // indirect
github.com/hashicorp/terraform-plugin-framework v1.14.1 // indirect
github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-plugin-mux v0.18.0 // indirect
github.com/hashicorp/terraform-plugin-testing v1.11.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,18 @@ github.com/hashicorp/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8
github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ=
github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q=
github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow=
github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY=
github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4=
github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M=
github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY=
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.18.0 h1:7491JFSpWyAe0v9YqBT+kel7mzHAbO5EpxxT0cUL/Ms=
github.com/hashicorp/terraform-plugin-mux v0.18.0/go.mod h1:Ho1g4Rr8qv0qTJlcRKfjjXTIO67LNbDtM6r+zHUNHJQ=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.0 h1:7/iejAPyCRBhqAg3jOx+4UcAhY0A+Sg8B+0+d/GxSfM=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.0/go.mod h1:TiQwXAjFrgBf5tg5rvBRz8/ubPULpU0HjSaVi5UoJf8=
github.com/hashicorp/terraform-plugin-testing v1.11.0 h1:MeDT5W3YHbONJt2aPQyaBsgQeAIckwPX41EUHXEn29A=
github.com/hashicorp/terraform-plugin-testing v1.11.0/go.mod h1:WNAHQ3DcgV/0J+B15WTE6hDvxcUdkPPpnB1FR3M910U=
github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA=
github.com/hashicorp/terraform-registry-address v0.2.4/go.mod h1:tUNYTVyCtU4OIGXXMDp7WNcJ+0W1B4nmstVDgHMjfAU=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
Expand Down
55 changes: 51 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,65 @@
package main

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
"context"
"flag"
"log"

"github.com/hashicorp/terraform-plugin-framework/providerserver"

"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server"
"github.com/hashicorp/terraform-plugin-mux/tf5to6server"
"github.com/hashicorp/terraform-plugin-mux/tf6muxserver"

"github.com/coder/terraform-provider-coder/v2/provider"
"github.com/coder/terraform-provider-coder/v2/tpfprovider"
)

// Run the docs generation tool, check its repository for more information on how it works and how docs
// can be customized.
//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs

func main() {
ctx := context.Background()
var debug bool
flag.BoolVar(&debug, "debug", false, "enable debug logging")
flag.Parse()

servePprof()
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: provider.New,
})

upgradedSDKServer, err := tf5to6server.UpgradeServer(
ctx,
provider.New().GRPCProvider,
)
if err != nil {
log.Fatal(err)
}

providers := []func() tfprotov6.ProviderServer{
providerserver.NewProtocol6(tpfprovider.NewFrameworkProvider()()),
func() tfprotov6.ProviderServer {
return upgradedSDKServer
},
}

muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...)
if err != nil {
log.Fatal(err)
}

var serveOpts []tf6server.ServeOpt
if debug {
serveOpts = append(serveOpts, tf6server.WithManagedDebug())
}

err = tf6server.Serve(
"registry.terraform.io/coder/coder",
muxServer.ProviderServer,
serveOpts...,
)

if err != nil {
log.Fatal(err)
}
}
100 changes: 100 additions & 0 deletions tpfprovider/parameter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package tpfprovider

import (
"context"
"encoding/json"
"math/big"
"os"
"strings"

"github.com/coder/terraform-provider-coder/v2/provider"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type parameterDataSourceModel struct {
Name types.String `tfsdk:"name"`
Type types.Dynamic `tfsdk:"type"`
Value types.Dynamic `tfsdk:"value"`
}

type parameterDataSource struct{}

func NewParameterDataSource() datasource.DataSource {
return &parameterDataSource{}
}

func (m *parameterDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = "coder_parameter"
}

func (m *parameterDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Required: true,
},
"type": schema.DynamicAttribute{
Required: true,
},
"value": schema.DynamicAttribute{
Computed: true,
},
},
}
}

func (m *parameterDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data parameterDataSourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

if data.Name.IsNull() {
resp.Diagnostics.AddError("name is required", "name")
return
}

ds := data.Name.ValueString()
parameterEnv := provider.ParameterEnvironmentVariable(ds)
rawValue, ok := os.LookupEnv(parameterEnv)
if !ok {
resp.Diagnostics.AddError("parameter not found", "name")
return
}

switch data.Type.UnderlyingValue().(type) {
case types.String:
data.Value = types.DynamicValue(types.StringValue(rawValue))
case types.Number:
// convert the raw value to a number
var floatVal float64
if err := json.NewDecoder(strings.NewReader(rawValue)).Decode(&floatVal); err != nil {
resp.Diagnostics.AddError("failed to parse value as number", "value")
return
}

data.Value = types.DynamicValue(types.NumberValue(big.NewFloat(floatVal)))
case types.Bool:
// convert the raw value to a bool
var boolVal bool
if err := json.NewDecoder(strings.NewReader(rawValue)).Decode(&boolVal); err != nil {
resp.Diagnostics.AddError("failed to parse value as bool", "value")
return
}
data.Value = types.DynamicValue(types.BoolValue(boolVal))
case types.List:
// TODO: handle list
resp.Diagnostics.AddError("TODO: list type not supported", "type")
return
case types.Map:
// TODO: handle map
resp.Diagnostics.AddError("TODO: map type not supported", "type")
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
151 changes: 151 additions & 0 deletions tpfprovider/parameter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package tpfprovider

import (
"fmt"
"os"
"testing"

"github.com/coder/terraform-provider-coder/v2/provider"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)

func TestAccParameterDataSource(t *testing.T) {
t.Run("string", func(t *testing.T) {
t.Setenv(provider.ParameterEnvironmentVariable("test"), "test")
resource.Test(t, resource.TestCase{
IsUnitTest: true,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{{
Config: `
provider coder {}
data "coder_parameter" "test" {
name = "test"
type = ""
}`,
Check: resource.ComposeAggregateTestCheckFunc(
testParameterEnv("test", "test"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "name", "test"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "type", ""),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value", "test"),
),
}},
})
})

t.Run("number", func(t *testing.T) {
t.Setenv(provider.ParameterEnvironmentVariable("test"), "3.14")
resource.Test(t, resource.TestCase{
IsUnitTest: true,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{{
Config: `
provider coder {}
data "coder_parameter" "test" {
name = "test"
type = 0
}`,
Check: resource.ComposeAggregateTestCheckFunc(
testParameterEnv("test", "3.14"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "name", "test"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "type", "0"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value", "3.14"),
),
}},
})
})

t.Run("bool", func(t *testing.T) {
t.Setenv(provider.ParameterEnvironmentVariable("test"), "true")
resource.Test(t, resource.TestCase{
IsUnitTest: true,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{{
Config: `
provider coder {}
data "coder_parameter" "test" {
name = "test"
type = false
}`,
Check: resource.ComposeAggregateTestCheckFunc(
testParameterEnv("test", "true"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "name", "test"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "type", "false"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value", "true"),
),
}},
})
})

t.Run("list of string", func(t *testing.T) {
t.Skip("TODO: not implemented yet")
t.Setenv(provider.ParameterEnvironmentVariable("test"), `["a","b","c"]`)
resource.Test(t, resource.TestCase{
IsUnitTest: true,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{{
Config: `
provider coder {}
data "coder_parameter" "test" {
name = "test"
type = [""]
}`,
Check: resource.ComposeAggregateTestCheckFunc(
testParameterEnv("test", `["a","b","c"]`),
resource.TestCheckResourceAttr("data.coder_parameter.test", "name", "test"),
resource.TestCheckTypeSetElemAttr("data.coder_parameter.test", "type*", "[]"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value.#", "3"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value.0", "a"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value.1", "b"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value.2", "c"),
),
}},
})
})

t.Run("list of number", func(t *testing.T) {
t.Skip("TODO: not implemented yet")
t.Setenv(provider.ParameterEnvironmentVariable("test"), `[1, 2, 3]`)
resource.Test(t, resource.TestCase{
IsUnitTest: true,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{{
Config: `
provider coder {}
data "coder_parameter" "test" {
name = "test"
type = [0]
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
testParameterEnv("test", `[1,2,3]`),
resource.TestCheckResourceAttr("data.coder_parameter.test", "name", "test"),
resource.TestCheckTypeSetElemAttr("data.coder_parameter.test", "type*", "[]"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value.#", "3"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value.0", "1"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value.1", "2"),
resource.TestCheckResourceAttr("data.coder_parameter.test", "value.2", "3"),
),
}},
})
})
}

func testParameterEnv(name, value string) func(*terraform.State) error {
return func(*terraform.State) error {
penv := provider.ParameterEnvironmentVariable("test")
val, ok := os.LookupEnv(penv)
if !ok {
return fmt.Errorf("parameter environment variable %q not set", penv)
}
if val != value {
return fmt.Errorf("parameter environment variable %q has unexpected value %q", penv, val)
}
return nil
}
}
Loading
Loading