Skip to content

Commit a451305

Browse files
author
Paddy Carver
committed
Add support for defining schemas.
This is another attempt at allowing providers to define schemas. It's born out of the ashes of #11, which gradually grew from baking an apple pie from scratch into creating the universe. Or however that saying goes. This is intentionally limited in scope to just setting up the types for declaring schemas and the types required by that, namely our attribute interfaces. Unlike #11, it makes no attempt to use these types for anything or prove they're the right types; the work done with #11 gives me confidence that they're a worthwhile direction to pursue. I'm submitting this as a separate PR to make review easier and to optimize for mergeability, letting us get some shared types established while still taking an appropriate amount of time to review the reflection code that is in our future.
1 parent 668dd44 commit a451305

File tree

8 files changed

+326
-15
lines changed

8 files changed

+326
-15
lines changed

attr/type.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package attr
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
7+
"github.com/hashicorp/terraform-plugin-go/tftypes"
8+
)
9+
10+
// Type defines an interface for describing a kind of attribute. Types are
11+
// collections of constraints and behaviors such that they can be reused on
12+
// multiple attributes easily.
13+
type Type interface {
14+
// TerraformType returns the tftypes.Type that should be used to
15+
// represent this type. This constrains what user input will be
16+
// accepted and what kind of data can be set in state. The framework
17+
// will use this to translate the Type to something Terraform can
18+
// understand.
19+
TerraformType(context.Context) tftypes.Type
20+
21+
// ValueFromTerraform returns a Value given a tftypes.Value. This is
22+
// meant to convert the tftypes.Value into a more convenient Go type
23+
// for the provider to consume the data with.
24+
ValueFromTerraform(context.Context, tftypes.Value) (Value, error)
25+
26+
// Equal must return true if the Type is considered semantically equal
27+
// to the Type passed as an argument.
28+
Equal(Type) bool
29+
}
30+
31+
// TypeWithValidate extends the Type interface to include a Validate method,
32+
// used to bundle consistent validation logic with the Type.
33+
type TypeWithValidate interface {
34+
Type
35+
36+
// Validate returns any warnings or errors about the value that is
37+
// being used to populate the Type. It is generally used to check the
38+
// data format and ensure that it complies with the requirements of the
39+
// Type.
40+
//
41+
// TODO: don't use tfprotov6.Diagnostic, use our type
42+
Validate(context.Context, tftypes.Value) []*tfprotov6.Diagnostic
43+
}
44+
45+
// TypeWithPlaintextDescription extends the Type interface to include a
46+
// Description method, used to bundle extra information to include in attribute
47+
// descriptions with the Type. It expects the description to be written as
48+
// plain text, with no special formatting.
49+
type TypeWithPlaintextDescription interface {
50+
Type
51+
52+
// Description returns a practitioner-friendly explanation of the type
53+
// and the constraints of the data it accepts and returns. It will be
54+
// combined with the Description associated with the Attribute.
55+
Description(context.Context) string
56+
}
57+
58+
// TypeWithMarkdownDescription extends the Type interface to include a
59+
// MarkdownDescription method, used to bundle extra information to include in
60+
// attribute descriptions with the Type. It expects the description to be
61+
// formatted for display with Markdown.
62+
type TypeWithMarkdownDescription interface {
63+
Type
64+
65+
// MarkdownDescription returns a practitioner-friendly explanation of
66+
// the type and the constraints of the data it accepts and returns. It
67+
// will be combined with the MarkdownDescription associated with the
68+
// Attribute.
69+
MarkdownDescription(context.Context) string
70+
}

attr/value.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package attr
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-go/tftypes"
7+
)
8+
9+
// Value defines an interface for describing data associated with an attribute.
10+
// Values allow provider developers to specify data in a convenient format, and
11+
// have it transparently be converted to formats Terraform understands.
12+
type Value interface {
13+
// ToTerraformValue returns the data contained in the Value as
14+
// a Go type that tftypes.NewValue will accept.
15+
ToTerraformValue(context.Context) (interface{}, error)
16+
17+
// SetTerraformValue updates the data in Value to match the
18+
// passed tftypes.Value.
19+
SetTerraformValue(context.Context, tftypes.Value) error
20+
21+
// Equal must return true if the Value is considered semantically equal
22+
// to the Value passed as an argument.
23+
Equal(Value) bool
24+
}

framework.go

Lines changed: 0 additions & 12 deletions
This file was deleted.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ module github.com/hashicorp/terraform-plugin-framework
22

33
go 1.16
44

5-
require github.com/hashicorp/terraform-plugin-go v0.2.1
5+
require github.com/hashicorp/terraform-plugin-go v0.3.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
3030
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
3131
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
3232
github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0=
33-
github.com/hashicorp/terraform-plugin-go v0.2.1 h1:EW/R8bB2Zbkjmugzsy1d27yS8/0454b3MtYHkzOknqA=
34-
github.com/hashicorp/terraform-plugin-go v0.2.1/go.mod h1:10V6F3taeDWVAoLlkmArKttR3IULlRWFAGtQIQTIDr4=
33+
github.com/hashicorp/terraform-plugin-go v0.3.0 h1:AJqYzP52JFYl9NABRI7smXI1pNjgR5Q/y2WyVJ/BOZA=
34+
github.com/hashicorp/terraform-plugin-go v0.3.0/go.mod h1:dFHsQMaTLpON2gWhVWT96fvtlc/MF1vSy3OdMhWBzdM=
3535
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
3636
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
3737
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=

schema/attribute.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package schema
2+
3+
import "github.com/hashicorp/terraform-plugin-framework/attr"
4+
5+
// Attribute defines the constraints and behaviors of a single field in a
6+
// schema. Attributes are the fields that show up in Terraform state files and
7+
// can be used in configuration files.
8+
type Attribute struct {
9+
// Type indicates what kind of attribute this is. You'll most likely
10+
// want to use one of the types in the types package.
11+
//
12+
// If Type is set, Attributes cannot be.
13+
Type attr.Type
14+
15+
// Attributes can have their own, nested attributes. This nested map of
16+
// attributes behaves exactly like the map of attributes on the Schema
17+
// type.
18+
//
19+
// If Attributes is set, Type cannot be.
20+
Attributes NestedAttributes
21+
22+
// Description is used in various tooling, like the language server, to
23+
// give practitioners more information about what this attribute is,
24+
// what it's for, and how it should be used. It should be written as
25+
// plain text, with no special formatting.
26+
Description string
27+
28+
// MarkdownDescription is used in various tooling, like the
29+
// documentation generator, to give practitioners more information
30+
// about what this attribute is, what it's for, and how it should be
31+
// used. It should be formatted using Markdown.
32+
MarkdownDescription string
33+
34+
// Required indicates whether the practitioner must enter a value for
35+
// this attribute or not. Required and Optional cannot both be true,
36+
// and Required and Computed cannot both be true.
37+
Required bool
38+
39+
// Optional indicates whether the practitioner can choose not to enter
40+
// a value for this attribute or not. Optional and Required cannot both
41+
// be true.
42+
Optional bool
43+
44+
// Computed indicates whether the provider may return its own value for
45+
// this attribute or not. Required and Computed cannot both be true. If
46+
// Required and Optional are both false, Computed must be true, and the
47+
// attribute will be considered "read only" for the practitioner, with
48+
// only the provider able to set its value.
49+
Computed bool
50+
51+
// Sensitive indicates whether the value of this attribute should be
52+
// considered sensitive data. Setting it to true will obscure the value
53+
// in CLI output. Sensitive does not impact how values are stored, and
54+
// practitioners are encouraged to store their state as if the entire
55+
// file is sensitive.
56+
Sensitive bool
57+
58+
// DeprecationMessage defines a message to display to practitioners
59+
// using this attribute, warning them that it is deprecated and
60+
// instructing them on what upgrade steps to take.
61+
DeprecationMessage string
62+
}

schema/nested_attributes.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package schema
2+
3+
type nestingMode uint8
4+
5+
const (
6+
nestingModeSingle nestingMode = 0
7+
nestingModeList nestingMode = 1
8+
nestingModeSet nestingMode = 2
9+
nestingModeMap nestingMode = 3
10+
)
11+
12+
// NestedAttributes surfaces a group of attributes to nest beneath another
13+
// attribute, and how that nesting should behave. Nesting can have the
14+
// following modes:
15+
//
16+
// * SingleNestedAttributes are nested attributes that represent a struct or
17+
// object; there should only be one instance of them nested beneath that
18+
// specific attribute.
19+
//
20+
// * ListNestedAttributes are nested attributes that represent a list of
21+
// structs or objects; there can be multiple instances of them beneath that
22+
// specific attribute.
23+
//
24+
// * SetNestedAttributes are nested attributes that represent a set of structs
25+
// or objects; there can be multiple instances of them beneath that specific
26+
// attribute. Unlike ListNestedAttributes, these nested attributes must have
27+
// unique values.
28+
//
29+
// * MapNestedAttributes are nested attributes that represent a string-indexed
30+
// map of structs or objects; there can be multiple instances of them beneath
31+
// that specific attribute. Unlike ListNestedAttributes, these nested
32+
// attributes must be associated with a unique key. Unlike SetNestedAttributes,
33+
// the key must be explicitly set by the user.
34+
type NestedAttributes interface {
35+
getNestingMode() nestingMode
36+
getAttributes() map[string]Attribute
37+
}
38+
39+
type nestedAttributes map[string]Attribute
40+
41+
func (n nestedAttributes) getAttributes() map[string]Attribute {
42+
return map[string]Attribute(n)
43+
}
44+
45+
// SingleNestedAttributes nests `attributes` under another attribute, only
46+
// allowing one instance of that group of attributes to appear in the
47+
// configuration.
48+
func SingleNestedAttributes(attributes map[string]Attribute) NestedAttributes {
49+
return singleNestedAttributes{
50+
nestedAttributes(attributes),
51+
}
52+
}
53+
54+
type singleNestedAttributes struct {
55+
nestedAttributes
56+
}
57+
58+
func (s singleNestedAttributes) getNestingMode() nestingMode {
59+
return nestingModeSingle
60+
}
61+
62+
// ListNestedAttributes nests `attributes` under another attribute, allowing
63+
// multiple instances of that group of attributes to appear in the
64+
// configuration. Minimum and maximum numbers of times the group can appear in
65+
// the configuration can be set using `opts`.
66+
func ListNestedAttributes(attributes map[string]Attribute, opts ListNestedAttributesOptions) NestedAttributes {
67+
return listNestedAttributes{
68+
nestedAttributes: nestedAttributes(attributes),
69+
min: opts.MinItems,
70+
max: opts.MaxItems,
71+
}
72+
}
73+
74+
type listNestedAttributes struct {
75+
nestedAttributes
76+
77+
min, max int
78+
}
79+
80+
// ListNestedAttributesOptions captures additional, optional parameters for
81+
// ListNestedAttributes.
82+
type ListNestedAttributesOptions struct {
83+
MinItems int
84+
MaxItems int
85+
}
86+
87+
func (l listNestedAttributes) getNestingMode() nestingMode {
88+
return nestingModeList
89+
}
90+
91+
// SetNestedAttributes nests `attributes` under another attribute, allowing
92+
// multiple instances of that group of attributes to appear in the
93+
// configuration, while requiring each group of values be unique. Minimum and
94+
// maximum numbers of times the group can appear in the configuration can be
95+
// set using `opts`.
96+
func SetNestedAttributes(attributes map[string]Attribute, opts SetNestedAttributesOptions) NestedAttributes {
97+
return setNestedAttributes{
98+
nestedAttributes: nestedAttributes(attributes),
99+
min: opts.MinItems,
100+
max: opts.MaxItems,
101+
}
102+
}
103+
104+
type setNestedAttributes struct {
105+
nestedAttributes
106+
107+
min, max int
108+
}
109+
110+
// SetNestedAttributesOptions captures additional, optional parameters for
111+
// SetNestedAttributes.
112+
type SetNestedAttributesOptions struct {
113+
MinItems int
114+
MaxItems int
115+
}
116+
117+
func (s setNestedAttributes) getNestingMode() nestingMode {
118+
return nestingModeSet
119+
}
120+
121+
// MapNestedAttributes nests `attributes` under another attribute, allowing
122+
// multiple instances of that group of attributes to appear in the
123+
// configuration. Each group will need to be associated with a unique string by
124+
// the user. Minimum and maximum numbers of times the group can appear in the
125+
// configuration can be set using `opts`.
126+
func MapNestedAttributes(attributes map[string]Attribute, opts MapNestedAttributesOptions) NestedAttributes {
127+
return mapNestedAttributes{
128+
nestedAttributes: nestedAttributes(attributes),
129+
min: opts.MinItems,
130+
max: opts.MaxItems,
131+
}
132+
}
133+
134+
type mapNestedAttributes struct {
135+
nestedAttributes
136+
137+
min, max int
138+
}
139+
140+
// MapNestedAttributesOptions captures additional, optional parameters for
141+
// MapNestedAttributes.
142+
type MapNestedAttributesOptions struct {
143+
MinItems int
144+
MaxItems int
145+
}
146+
147+
func (m mapNestedAttributes) getNestingMode() nestingMode {
148+
return nestingModeMap
149+
}

schema/schema.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package schema
2+
3+
// Schema is used to define the shape of practitioner-provider information,
4+
// like resources, data sources, and providers. Think of it as a type
5+
// definition, but for Terraform.
6+
type Schema struct {
7+
// Attributes are the fields inside the resource, provider, or data
8+
// source that the schema is defining. The map key should be the name
9+
// of the attribute, and the body defines how it behaves. Names must
10+
// only contain lowercase letters, numbers, and underscores.
11+
Attributes map[string]Attribute
12+
13+
// Version indicates the current version of the schema. Schemas are
14+
// versioned to help with automatic upgrade process. Whenever you have
15+
// a change in the schema you'd like to provide a manual migration for,
16+
// you should increment that schema's version by one.
17+
Version int64
18+
}

0 commit comments

Comments
 (0)