Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kmoe committed Jun 8, 2021
1 parent f6be95f commit b4017cb
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 1 deletion.
2 changes: 2 additions & 0 deletions attr/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type Type interface {
// Equal must return true if the Type is considered semantically equal
// to the Type passed as an argument.
Equal(Type) bool

tftypes.AttributePathStepper
}

// TypeWithAttributeTypes extends the Type interface to include information about
Expand Down
18 changes: 17 additions & 1 deletion schema/attribute.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package schema

import "github.com/hashicorp/terraform-plugin-framework/attr"
import (
"fmt"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// Attribute defines the constraints and behaviors of a single field in a
// schema. Attributes are the fields that show up in Terraform state files and
Expand Down Expand Up @@ -60,3 +65,14 @@ type Attribute struct {
// instructing them on what upgrade steps to take.
DeprecationMessage string
}

func (a Attribute) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) {
if a.Type != nil {
return a.Type.ApplyTerraform5AttributePathStep(step)
}
if a.Attributes != nil {
return a.Attributes.ApplyTerraform5AttributePathStep(step)
}

return nil, fmt.Errorf("could not apply step %T to Attribute, because it has no Type or Attributes set", step)
}
39 changes: 39 additions & 0 deletions schema/nested_attributes.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package schema

import (
"fmt"

"github.com/hashicorp/terraform-plugin-go/tftypes"
)

type nestingMode uint8

const (
Expand Down Expand Up @@ -34,6 +40,7 @@ const (
type NestedAttributes interface {
getNestingMode() nestingMode
getAttributes() map[string]Attribute
tftypes.AttributePathStepper
}

type nestedAttributes map[string]Attribute
Expand All @@ -59,6 +66,14 @@ func (s singleNestedAttributes) getNestingMode() nestingMode {
return nestingModeSingle
}

func (s singleNestedAttributes) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) {
if _, ok := step.(tftypes.ElementKeyString); !ok {
return nil, fmt.Errorf("cannot apply step %T to SingleNestedAttributes", step)
}

return s.nestedAttributes, nil
}

// ListNestedAttributes nests `attributes` under another attribute, allowing
// multiple instances of that group of attributes to appear in the
// configuration. Minimum and maximum numbers of times the group can appear in
Expand Down Expand Up @@ -88,6 +103,14 @@ func (l listNestedAttributes) getNestingMode() nestingMode {
return nestingModeList
}

func (l listNestedAttributes) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) {
if _, ok := step.(tftypes.ElementKeyInt); !ok {
return nil, fmt.Errorf("cannot apply step %T to ListNestedAttributes", step)
}

return l.nestedAttributes, nil
}

// SetNestedAttributes nests `attributes` under another attribute, allowing
// multiple instances of that group of attributes to appear in the
// configuration, while requiring each group of values be unique. Minimum and
Expand Down Expand Up @@ -118,6 +141,14 @@ func (s setNestedAttributes) getNestingMode() nestingMode {
return nestingModeSet
}

func (s setNestedAttributes) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) {
if _, ok := step.(tftypes.ElementKeyInt); !ok {
return nil, fmt.Errorf("cannot apply step %T to ListNestedAttributes", step)
}

return s.nestedAttributes, nil
}

// MapNestedAttributes nests `attributes` under another attribute, allowing
// multiple instances of that group of attributes to appear in the
// configuration. Each group will need to be associated with a unique string by
Expand Down Expand Up @@ -147,3 +178,11 @@ type MapNestedAttributesOptions struct {
func (m mapNestedAttributes) getNestingMode() nestingMode {
return nestingModeMap
}

func (m mapNestedAttributes) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) {
if _, ok := step.(tftypes.ElementKeyString); !ok {
return nil, fmt.Errorf("cannot apply step %T to SingleNestedAttributes", step)
}

return m.nestedAttributes, nil
}
47 changes: 47 additions & 0 deletions schema/schema.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package schema

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// Schema is used to define the shape of practitioner-provider information,
// like resources, data sources, and providers. Think of it as a type
// definition, but for Terraform.
Expand All @@ -17,3 +26,41 @@ type Schema struct {
// Versions should only be incremented by one each release.
Version int64
}

func (s Schema) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) {
if v, ok := step.(tftypes.AttributeName); ok {
if attr, ok := s.Attributes[string(v)]; ok {
return attr, nil
} else {
return nil, fmt.Errorf("could not find attribute %q in schema", v)
}
} else {
return nil, fmt.Errorf("cannot apply AttributePathStep %T to schema", step)
}
}

func (s Schema) AttributeType() attr.Type {
attrTypes := map[string]attr.Type{}
for name, attr := range s.Attributes {
if attr.Type != nil {
attrTypes[name] = attr.Type
}
if attr.Attributes != nil {
// TODO: handle nested attributes
}
}
return types.ObjectType{AttrTypes: attrTypes}
}

func (s Schema) TerraformType(ctx context.Context) tftypes.Type {
attrTypes := map[string]tftypes.Type{}
for name, attr := range s.Attributes {
if attr.Type != nil {
attrTypes[name] = attr.Type.TerraformType(ctx)
}
if attr.Attributes != nil {
// TODO: handle nested attributes
}
}
return tftypes.Object{AttributeTypes: attrTypes}
}
41 changes: 41 additions & 0 deletions state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package tfsdk

import (
"context"
"reflect"
"regexp"

"github.com/hashicorp/terraform-plugin-framework/attr"
tfReflect "github.com/hashicorp/terraform-plugin-framework/internal/reflect"
"github.com/hashicorp/terraform-plugin-framework/schema"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

var attributeValueReflectType = reflect.TypeOf(new(attr.Value)).Elem()

type State struct {
Raw tftypes.Value
Schema schema.Schema
}

func isValidFieldName(name string) bool {
re := regexp.MustCompile("^[a-z][a-z0-9_]*$")
return re.MatchString(name)
}

// Get populates the struct passed as `target` with the entire state. No type assertion necessary.
func (s State) Get(ctx context.Context, target interface{}) error {
return tfReflect.Into(ctx, s.Schema.AttributeType(), s.Raw, target, tfReflect.Options{})
}

// // GetAttribute retrieves the attribute found at `path` and returns it as an attr.Value,
// // which provider developers need to assert the type of
// func (s State) GetAttribute(ctx context.Context, path tftypes.AttributePath) (attr.Value, error) {

// }

// // MustGetAttribute retrieves the attribute as GetAttribute does, but populates target using As,
// // using the simplified representation without Unknown. Errors if Unknown present
// func (s State) MustGetAttribute(ctx context.Context, path tftypes.AttributePath, target interface{}) error {
// return nil
// }
81 changes: 81 additions & 0 deletions state_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package tfsdk

import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

func TestStateGet(t *testing.T) {
schema := schema.Schema{
Attributes: map[string]schema.Attribute{
"foo": {
Type: types.StringType,
Required: true,
},
"bar": {
Type: types.ListType{
ElemType: types.StringType,
},
Required: true,
},
},
}
state := State{
Raw: tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"foo": tftypes.String,
"bar": tftypes.List{ElementType: tftypes.String},
},
}, map[string]tftypes.Value{
"foo": tftypes.NewValue(tftypes.String, "hello, world"),
"bar": tftypes.NewValue(tftypes.List{
ElementType: tftypes.String,
}, []tftypes.Value{
tftypes.NewValue(tftypes.String, "red"),
tftypes.NewValue(tftypes.String, "blue"),
tftypes.NewValue(tftypes.String, "green"),
}),
}),
Schema: schema,
}
type myType struct {
Foo types.String `tfsdk:"foo"`
Bar types.List `tfsdk:"bar"`
}
var val myType
err := state.Get(context.Background(), &val)
if err != nil {
t.Errorf("Error running As: %s", err)
}
if val.Foo.Unknown {
t.Error("Expected Foo to be known")
}
if val.Foo.Null {
t.Error("Expected Foo to be non-null")
}
if val.Foo.Value != "hello, world" {
t.Errorf("Expected Foo to be %q, got %q", "hello, world", val.Foo.Value)
}
if val.Bar.Unknown {
t.Error("Expected Bar to be known")
}
if val.Bar.Null {
t.Errorf("Expected Bar to be non-null")
}
if len(val.Bar.Elems) != 3 {
t.Errorf("Expected Bar to have 3 elements, had %d", len(val.Bar.Elems))
}
if val.Bar.Elems[0].(types.String).Value != "red" {
t.Errorf("Expected Bar's first element to be %q, got %q", "red", val.Bar.Elems[0].(types.String).Value)
}
if val.Bar.Elems[1].(types.String).Value != "blue" {
t.Errorf("Expected Bar's second element to be %q, got %q", "blue", val.Bar.Elems[1].(types.String).Value)
}
if val.Bar.Elems[2].(types.String).Value != "green" {
t.Errorf("Expected Bar's third element to be %q, got %q", "green", val.Bar.Elems[2].(types.String).Value)
}
}
8 changes: 8 additions & 0 deletions types/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ func (l ListType) Equal(o attr.Type) bool {
return l.ElemType.Equal(other.ElemType)
}

func (l ListType) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) {
if _, ok := step.(tftypes.ElementKeyInt); !ok {
return nil, fmt.Errorf("cannot apply step %T to ListType", step)
}

return l.ElemType, nil
}

// List represents a list of AttributeValues, all of the same type, indicated
// by ElemType.
type List struct {
Expand Down
8 changes: 8 additions & 0 deletions types/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ func (o ObjectType) Equal(candidate attr.Type) bool {
return true
}

func (o ObjectType) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) {
if _, ok := step.(tftypes.ElementKeyString); !ok {
return nil, fmt.Errorf("cannot apply step %T to ObjectType", step)
}

return o.AttrTypes[string(step.(tftypes.ElementKeyString))], nil
}

// Object represents an object
type Object struct {
// Unknown will be set to true if the entire object is an unknown value.
Expand Down
4 changes: 4 additions & 0 deletions types/primitive.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,7 @@ func (p primitive) Equal(o attr.Type) bool {
return false
}
}

func (p primitive) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) {
return nil, fmt.Errorf("cannot apply AttributePathStep %T to %s", step, p.String())
}
4 changes: 4 additions & 0 deletions types/primitive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ func (t testAttributeType) Equal(_ attr.Type) bool {
panic("not implemented")
}

func (t testAttributeType) ApplyTerraform5AttributePathStep(_ tftypes.AttributePathStep) (interface{}, error) {
panic("not implemented")
}

func TestPrimitiveEqual(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit b4017cb

Please sign in to comment.