Skip to content

Commit

Permalink
tfprotov6 (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
mildwonkey authored Apr 13, 2021
1 parent 014db87 commit ffa87cd
Show file tree
Hide file tree
Showing 38 changed files with 9,618 additions and 0 deletions.
98 changes: 98 additions & 0 deletions tfprotov6/data_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package tfprotov6

import (
"context"
)

// DataSourceServer is an interface containing the methods a data source
// implementation needs to fill.
type DataSourceServer interface {
// ValidateDataResourceConfig is called when Terraform is checking that a
// data source's configuration is valid. It is guaranteed to have types
// conforming to your schema, but it is not guaranteed that all values
// will be known. This is your opportunity to do custom or advanced
// validation prior to a plan being generated.
ValidateDataResourceConfig(context.Context, *ValidateDataResourceConfigRequest) (*ValidateDataResourceConfigResponse, error)

// ReadDataSource is called when Terraform is refreshing a data
// source's state.
ReadDataSource(context.Context, *ReadDataSourceRequest) (*ReadDataSourceResponse, error)
}

// ValidateDataResourceConfigRequest is the request Terraform sends when it wants
// to validate a data source's configuration.
type ValidateDataResourceConfigRequest struct {
// TypeName is the type of data source Terraform is validating.
TypeName string

// Config is the configuration the user supplied for that data source.
// See the documentation on `DynamicValue` for more information about
// safely accessing the configuration.
//
// The configuration is represented as a tftypes.Object, with each
// attribute and nested block getting its own key and value.
//
// This configuration may contain unknown values if a user uses
// interpolation or other functionality that would prevent Terraform
// from knowing the value at request time.
Config *DynamicValue
}

// ValidateDataResourceConfigResponse is the response from the provider about the
// validity of a data source's configuration.
type ValidateDataResourceConfigResponse struct {
// Diagnostics report errors or warnings related to the given
// configuration. Returning an empty slice indicates a successful
// validation with no warnings or errors generated.
Diagnostics []*Diagnostic
}

// ReadDataSourceRequest is the request Terraform sends when it wants to get
// the latest state for a data source.
type ReadDataSourceRequest struct {
// TypeName is the type of data source Terraform is requesting an
// updated state for.
TypeName string

// Config is the configuration the user supplied for that data source.
// See the documentation on `DynamicValue` for information about safely
// accessing the configuration.
//
// The configuration is represented as a tftypes.Object, with each
// attribute and nested block getting its own key and value.
//
// This configuration may have unknown values.
Config *DynamicValue

// ProviderMeta supplies the provider metadata configuration for the
// module this data source is in. Module-specific provider metadata is
// an advanced feature and usage of it should be coordinated with the
// Terraform Core team by raising an issue at
// https://github.com/hashicorp/terraform/issues/new/choose. See the
// documentation on `DynamicValue` for information about safely
// accessing the configuration.
//
// The configuration is represented as a tftypes.Object, with each
// attribute and nested block getting its own key and value.
//
// This configuration will have known values for all fields.
ProviderMeta *DynamicValue
}

// ReadDataSourceResponse is the response from the provider about the current
// state of the requested data source.
type ReadDataSourceResponse struct {
// State is the current state of the data source, represented as a
// `DynamicValue`. See the documentation on `DynamicValue` for
// information about safely creating the `DynamicValue`.
//
// The state should be represented as a tftypes.Object, with each
// attribute and nested block getting its own key and value.
State *DynamicValue

// Diagnostics report errors or warnings related to retrieving the
// current state of the requested data source. Returning an empty slice
// indicates a successful validation with no warnings or errors
// generated.
Diagnostics []*Diagnostic
}
58 changes: 58 additions & 0 deletions tfprotov6/diagnostic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package tfprotov6

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

const (
// DiagnosticSeverityInvalid is used to indicate an invalid
// `DiagnosticSeverity`. Provider developers should not use it.
DiagnosticSeverityInvalid DiagnosticSeverity = 0

// DiagnosticSeverityError is used to indicate that a `Diagnostic`
// represents an error and should halt Terraform execution.
DiagnosticSeverityError DiagnosticSeverity = 1

// DiagnosticSeverityWarning is used to indicate that a `Diagnostic`
// represents a warning and should not halt Terraform's execution, but
// it should be surfaced to the user.
DiagnosticSeverityWarning DiagnosticSeverity = 2
)

// Diagnostic is used to convey information back the user running Terraform.
type Diagnostic struct {
// Severity indicates how Terraform should handle the Diagnostic.
Severity DiagnosticSeverity

// Summary is a brief description of the problem, roughly
// sentence-sized, and should provide a concise description of what
// went wrong. For example, a Summary could be as simple as "Invalid
// value.".
Summary string

// Detail is a lengthier, more complete description of the problem.
// Detail should provide enough information that a user can resolve the
// problem entirely. For example, a Detail could be "Values must be
// alphanumeric and lowercase only."
Detail string

// Attribute indicates which field, specifically, has the problem. Not
// setting this will indicate the entire resource; setting it will
// indicate that the problem is with a certain field in the resource,
// which helps users find the source of the problem.
Attribute *tftypes.AttributePath
}

// DiagnosticSeverity represents different classes of Diagnostic which affect
// how Terraform handles the Diagnostics.
type DiagnosticSeverity int32

func (d DiagnosticSeverity) String() string {
switch d {
case 0:
return "INVALID"
case 1:
return "ERROR"
case 2:
return "WARNING"
}
return "UNKNOWN"
}
29 changes: 29 additions & 0 deletions tfprotov6/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Package tfprotov6 provides the interfaces and types needed to build a
// Terraform provider server.
//
// All Terraform provider servers should be built on
// these types, to take advantage of the ecosystem and tooling built around
// them.
//
// These types are small wrappers around the Terraform protocol. It is assumed
// that developers using tfprotov6 are familiar with the protocol, its
// requirements, and its semantics. Developers not comfortable working with the
// raw protocol should use the github.com/hashicorp/terraform-plugin-sdk/v2 Go
// module instead, which offers a less verbose, safer way to develop a
// Terraform provider, albeit with less flexibility and power.
//
// Provider developers should start by defining a type that implements the
// `ProviderServer` interface. A struct is recommended, as it will allow you to
// store the configuration information attached to your provider for use in
// requests, but any type is technically possible.
//
// `ProviderServer` implementations will need to implement the composed
// interfaces, `ResourceServer` and `DataSourceServer`. It is recommended, but
// not required, to use an embedded `ResourceRouter` and `DataSourceRouter` in
// your `ProviderServer` to achieve this, which will let you handle requests
// for each resource and data source in a resource-specific or data
// source-specific function.
//
// To serve the `ProviderServer` implementation as a gRPC server that Terraform
// can connect to, use the `tfprotov6/server.Serve` function.
package tfprotov6
91 changes: 91 additions & 0 deletions tfprotov6/dynamic_value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package tfprotov6

import (
"bytes"
"errors"

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

// ErrUnknownDynamicValueType is returned when a DynamicValue has no MsgPack or
// JSON bytes set. This should never be returned during the normal operation of
// a provider, and indicates one of the following:
//
// 1. terraform-plugin-go is out of sync with the protocol and should be
// updated.
//
// 2. terrafrom-plugin-go has a bug.
//
// 3. The `DynamicValue` was generated or modified by something other than
// terraform-plugin-go and is no longer a valid value.
var ErrUnknownDynamicValueType = errors.New("DynamicValue had no JSON or msgpack data set")

// NewDynamicValue creates a DynamicValue from a tftypes.Value. You must
// specify the tftype.Type you want to send the value as, and it must be a type
// that is compatible with the Type of the Value. Usually it should just be the
// Type of the Value, but it can also be the DynamicPseudoType.
func NewDynamicValue(t tftypes.Type, v tftypes.Value) (DynamicValue, error) {
b, err := v.MarshalMsgPack(t) //nolint:staticcheck
if err != nil {
return DynamicValue{}, err
}
return DynamicValue{
MsgPack: b,
}, nil
}

// DynamicValue represents a nested encoding value that came from the protocol.
// The only way providers should ever interact with it is by calling its
// `Unmarshal` method to retrive a `tftypes.Value`. Although the type system
// allows for other interactions, they are explicitly not supported, and will
// not be considered when evaluating for breaking changes. Treat this type as
// an opaque value, and *only* call its `Unmarshal` method.
type DynamicValue struct {
MsgPack []byte
JSON []byte
}

// Unmarshal returns a `tftypes.Value` that represents the information
// contained in the DynamicValue in an easy-to-interact-with way. It is the
// main purpose of the DynamicValue type, and is how provider developers should
// obtain config, state, and other values from the protocol.
//
// Pass in the type you want the `Value` to be interpreted as. Terraform's type
// system encodes in a lossy manner, meaning the type information is not
// preserved losslessly when going over the wire. Sets, lists, and tuples all
// look the same, as do user-specified values when the provider has a
// DynamicPseudoType in its schema. Objects and maps all look the same, as
// well, as do DynamicPseudoType values sometimes. Fortunately, the provider
// should already know the type; it should be the type of the schema, or
// PseudoDynamicType if that's what's in the schema. `Unmarshal` will then
// parse the value as though it belongs to that type, if possible, and return a
// `tftypes.Value` with the appropriate information. If the data can't be
// interpreted as that type, an error will be returned saying so. In these
// cases, double check to make sure the schema is declaring the same type being
// passed into `Unmarshal`.
//
// In the event an ErrUnknownDynamicValueType is returned, one of three things
// has happened:
//
// 1. terraform-plugin-go is out of date and out of sync with the protocol, and
// an issue should be opened on its repo to get it updated.
//
// 2. terraform-plugin-go has a bug somewhere, and an issue should be opened on
// its repo to get it fixed.
//
// 3. The provider or a dependency has modified the `DynamicValue` in an
// unsupported way, or has created one from scratch, and should treat it as
// opaque and not modify it, only calling `Unmarshal` on `DynamicValue`s
// received from RPC requests.
func (d DynamicValue) Unmarshal(typ tftypes.Type) (tftypes.Value, error) {
if d.JSON != nil {
return jsonUnmarshal(d.JSON, typ, tftypes.AttributePath{})
}
if d.MsgPack != nil {
r := bytes.NewReader(d.MsgPack)
dec := msgpack.NewDecoder(r)
return msgpackUnmarshal(dec, typ, tftypes.AttributePath{})
}
return tftypes.Value{}, ErrUnknownDynamicValueType
}
Loading

0 comments on commit ffa87cd

Please sign in to comment.