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

[WIP] Data-driven Terraform Configuration #4961

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,19 @@ import (
"github.com/hashicorp/terraform/state/remote"
)

func resourceRemoteState() *schema.Resource {
func dataSourceRemoteState() *schema.Resource {
return &schema.Resource{
Create: resourceRemoteStateCreate,
Read: resourceRemoteStateRead,
Delete: resourceRemoteStateDelete,
Read: dataSourceRemoteStateRead,

Schema: map[string]*schema.Schema{
"backend": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"config": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
},

"output": &schema.Schema{
Expand All @@ -35,11 +31,7 @@ func resourceRemoteState() *schema.Resource {
}
}

func resourceRemoteStateCreate(d *schema.ResourceData, meta interface{}) error {
return resourceRemoteStateRead(d, meta)
}

func resourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
backend := d.Get("backend").(string)
config := make(map[string]string)
for k, v := range d.Get("config").(map[string]interface{}) {
Expand Down Expand Up @@ -69,8 +61,3 @@ func resourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
d.Set("output", outputs)
return nil
}

func resourceRemoteStateDelete(d *schema.ResourceData, meta interface{}) error {
d.SetId("")
return nil
}
7 changes: 6 additions & 1 deletion builtin/providers/terraform/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import (
func Provider() terraform.ResourceProvider {
return &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"terraform_remote_state": resourceRemoteState(),
"terraform_remote_state": schema.DataSourceResourceShim(
dataSourceRemoteState(),
),
},
DataSourcesMap: map[string]*schema.Resource{
"terraform_remote_state": dataSourceRemoteState(),
},
}
}
8 changes: 8 additions & 0 deletions config/append.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ func Append(c1, c2 *Config) (*Config, error) {
c.ProviderConfigs = append(c.ProviderConfigs, c2.ProviderConfigs...)
}

if len(c1.DataSources) > 0 || len(c2.DataSources) > 0 {
c.DataSources = make(
[]*DataSource,
0, len(c1.DataSources)+len(c2.DataSources))
c.DataSources = append(c.DataSources, c1.DataSources...)
c.DataSources = append(c.DataSources, c2.DataSources...)
}

if len(c1.Resources) > 0 || len(c2.Resources) > 0 {
c.Resources = make(
[]*Resource,
Expand Down
36 changes: 36 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Config struct {
Atlas *AtlasConfig
Modules []*Module
ProviderConfigs []*ProviderConfig
DataSources []*DataSource
Resources []*Resource
Variables []*Variable
Outputs []*Output
Expand Down Expand Up @@ -67,6 +68,18 @@ type ProviderConfig struct {
RawConfig *RawConfig
}

// DataSource is the configuration for a data source.
// A data source represents retrieving some data from an external source for
// use elsewhere in a Terraform configuration, or computing some data
// internally within a logical provider.
type DataSource struct {
Name string
Type string
RawConfig *RawConfig
Provider string
DependsOn []string
}

// A resource represents a single Terraform resource in the configuration.
// A Terraform resource is something that represents some component that
// can be created and managed, and has some properties associated with it.
Expand Down Expand Up @@ -439,6 +452,14 @@ func (c *Config) Validate() error {
"%s: resource count can't reference resource variable: %s",
n,
v.FullKey()))
case *DataSourceVariable:
// FIXME: This should actually be allowed, but there is some
// further work to do first, so for the moment we'll just
// treat this like a resource variable.
errs = append(errs, fmt.Errorf(
"%s: resource count can't reference data source variable: %s",
n,
v.FullKey()))
case *UserVariable:
// Good
default:
Expand Down Expand Up @@ -770,6 +791,21 @@ func (c *ProviderConfig) mergerMerge(m merger) merger {
return &result
}

func (r *DataSource) mergerName() string {
return fmt.Sprintf("%s.%s", r.Type, r.Name)
}

func (r *DataSource) mergerMerge(m merger) merger {
r2 := m.(*DataSource)

result := *r
result.Name = r2.Name
result.Type = r2.Type
result.RawConfig = result.RawConfig.merge(r2.RawConfig)

return &result
}

func (r *Resource) mergerName() string {
return fmt.Sprintf("%s.%s", r.Type, r.Name)
}
Expand Down
78 changes: 78 additions & 0 deletions config/config_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ func outputsStr(os []*Output) string {
switch rawV.(type) {
case *ResourceVariable:
kind = "resource"
case *DataSourceVariable:
kind = "data source"
case *UserVariable:
kind = "user"
}
Expand Down Expand Up @@ -158,6 +160,80 @@ func providerConfigsStr(pcs []*ProviderConfig) string {
switch rawV.(type) {
case *ResourceVariable:
kind = "resource"
case *DataSourceVariable:
kind = "data source"
case *UserVariable:
kind = "user"
}

result += fmt.Sprintf(" %s: %s\n", kind, str)
}
}
}

return strings.TrimSpace(result)
}

// This helper turns a data sources field into a deterministic
// string value for comparison in tests.
func dataSourcesStr(rs []*DataSource) string {
result := ""
order := make([]int, 0, len(rs))
ks := make([]string, 0, len(rs))
mapping := make(map[string]int)
for i, r := range rs {
k := fmt.Sprintf("%s[%s]", r.Type, r.Name)
ks = append(ks, k)
mapping[k] = i
}
sort.Strings(ks)
for _, k := range ks {
order = append(order, mapping[k])
}

for _, i := range order {
r := rs[i]
result += fmt.Sprintf(
"%s[%s]\n",
r.Type,
r.Name)

ks := make([]string, 0, len(r.RawConfig.Raw))
for k, _ := range r.RawConfig.Raw {
ks = append(ks, k)
}
sort.Strings(ks)

for _, k := range ks {
result += fmt.Sprintf(" %s\n", k)
}

if len(r.DependsOn) > 0 {
result += fmt.Sprintf(" dependsOn\n")
for _, d := range r.DependsOn {
result += fmt.Sprintf(" %s\n", d)
}
}

if len(r.RawConfig.Variables) > 0 {
result += fmt.Sprintf(" vars\n")

ks := make([]string, 0, len(r.RawConfig.Variables))
for k, _ := range r.RawConfig.Variables {
ks = append(ks, k)
}
sort.Strings(ks)

for _, k := range ks {
rawV := r.RawConfig.Variables[k]
kind := "unknown"
str := rawV.FullKey()

switch rawV.(type) {
case *ResourceVariable:
kind = "resource"
case *DataSourceVariable:
kind = "data source"
case *UserVariable:
kind = "user"
}
Expand Down Expand Up @@ -246,6 +322,8 @@ func resourcesStr(rs []*Resource) string {
switch rawV.(type) {
case *ResourceVariable:
kind = "resource"
case *DataSourceVariable:
kind = "data source"
case *UserVariable:
kind = "user"
}
Expand Down
67 changes: 67 additions & 0 deletions config/interpolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ type ResourceVariable struct {
key string
}

// A DataSourceVariable is a variable that is referencing the field
// of a data source, such as "${data.aws_ami.foo.id}"
type DataSourceVariable struct {
Type string // Data source, i.e. "aws_ami"
Name string // Data block name
Field string // Data instance field name

Multi bool // True if multi-variable: aws_ami.foo.*.id
Index int // Index for multi-variable: aws_ami.foo.1.id == 1

key string
}

// SelfVariable is a variable that is referencing the same resource
// it is running on: "${self.address}"
type SelfVariable struct {
Expand Down Expand Up @@ -104,6 +117,8 @@ func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
return NewUserVariable(v)
} else if strings.HasPrefix(v, "module.") {
return NewModuleVariable(v)
} else if strings.HasPrefix(v, "data.") {
return NewDataSourceVariable(v)
} else if !strings.ContainsRune(v, '.') {
return NewSimpleVariable(v)
} else {
Expand Down Expand Up @@ -236,6 +251,58 @@ func (v *SelfVariable) GoString() string {
return fmt.Sprintf("*%#v", *v)
}

func NewDataSourceVariable(key string) (*DataSourceVariable, error) {
parts := strings.SplitN(key, ".", 4)
if len(parts) < 4 {
return nil, fmt.Errorf(
"%s: data variables must be three parts: data.type.name.attr",
key)
}

// We don't actually need the "data" part on the front, since it's
// always constant anyway.
parts = parts[1:]

field := parts[2]
multi := false
var index int

if idx := strings.Index(field, "."); idx != -1 {
indexStr := field[:idx]
multi = indexStr == "*"
index = -1

if !multi {
indexInt, err := strconv.ParseInt(indexStr, 0, 0)
if err == nil {
multi = true
index = int(indexInt)
}
}

if multi {
field = field[idx+1:]
}
}

return &DataSourceVariable{
Type: parts[0],
Name: parts[1],
Field: field,
Multi: multi,
Index: index,
key: key,
}, nil
}

func (v *DataSourceVariable) ResourceId() string {
return fmt.Sprintf("data.%s.%s", v.Type, v.Name)
}

func (v *DataSourceVariable) FullKey() string {
return v.key
}

func NewSimpleVariable(key string) (*SimpleVariable, error) {
return &SimpleVariable{key}, nil
}
Expand Down
Loading