A libary to help trim down the verbosity of writing TF providers.
- Generates a typed
Resource
andProvider
for your plugin - Provides combinators for declarative and terse schema
If you want to use v2 Providers, you can use tf-schema/v2
!
You are generally writing a provider to interact with some client library.
tf-schema
works with go generate
to build typed versions of resources
and providers that are interchangeable with standard ones from terraform.
import (
lib "some/url/package/lib"
tfschema "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
terraform "github.com/hashicorp/terraform-plugin-sdk/terraform"
schema "github.com/stanistan/tf-schema"
)
This is mandatory to generate the types to interact with the client.
//go:generate go run -mod readonly github.com/stanistan/tf-schema/v2/cmd/generate lib.Client some/url/package/lib
func Provider() *tfschema.Provider {
// libProvider is a generated struct private to your code
return (&libProvider{
Schema: schema.Must(
// provider settings
),
// NOTE: the below return interface objects, not just structs
DataSourcesMap: map[string]schema.ResourceConvertible{
"lib_foo": dataSourceLibFoo(),
},
// NOTE: the below is now _typed_! to the client/struct type you want to be
// returning, and _not_ just returning an `interface{}`.
ConfigureFunc: func(d *tfschema.ResourceData) (*lib.Client, error) {
return lib.NewClient()
},
}).AsProvider()
}
func dataSourceLibFoo() schema.ResourceConvertible {
// libResource is a generated struct private to your code
return &libResource{
Schema: schema.Must(
schema.RequiredString("key"),
schema.OptionalInt("query_page"),
schema.ComputedString("value"),
),
// NOTE: Read is now _typed_ to the _kind_ of `meta` you would be returning
// from the ConfigureFunc in the provider, and not an interface you have to cast
// yourself. The validation has already been done.
Read: func(d *tfschema.ResourceData, c *lib.Client) error {
// snip...
data, err := c.GetFoo(d.Get("key").(string))
// snip...
return nil
},
}
}
An Option
is anything that can mutate a schema.Schema
(tf).
type Option func(*schema.Schema)
You can create your own types by using the defining Option
s, and common
ones are defined in option.go
and common types are defined
in types.go
using Type
.
For example:
// import (
// schema "github.com/stanistan/tf-schema/v2"
//)
// This is how schema.OptionalString is defined.
//
// It returns a schema.NamedSchemaFactory,
// to which you can provide a name, and other schema.Options.
var optionalString = schema.Type(schema.Optional, schema.String)
var optionalStringDefaultsToBanana = schema.Type(
schema.Optional, schema.String, schema.Default("banana"))
// and later use this with schema.Schema
schema.Must(
optionalStringDefaultsToBanana("fruit"),
)
// this is equivalent
schema.Must(
schema.OptionalString("fruit", schema.Default("banana")),
)
You can create a schema with anything that conforms to MapMutator
, and
use them in composition.
type Once struct {
named Named
}
func (s *Once) AddTo(m map[string]*schema.Schema) error {
name := s.named.Name()
_, exists := m[name]
if exists {
return fmt.Errorf("can't override name %s", name)
}
return s.named.AddTo(m)
}
or
type Fruit string
func (s *Fruit) AddTo(m map[string]*schema.Schema) error {
m["fruit"] = &schema.Schema{ /** stuff */ }
// this can set multiple keys
m["not_fruit"] = &schema.Schema{ /** stuff */ }
return nil
}