config: Data source loading

This allows the config loader to read "data" blocks from the config and
turn them into DataSource objects.

This just reads the data from the config file. It doesn't validate the
data nor do anything useful with it.
This commit is contained in:
Martin Atkins 2016-01-16 16:20:00 -08:00
parent fc4fa10981
commit 860140074f
10 changed files with 212 additions and 5 deletions

View File

@ -20,6 +20,7 @@ type hclConfigurable struct {
func (t *hclConfigurable) Config() (*Config, error) {
validKeys := map[string]struct{}{
"atlas": struct{}{},
"data": struct{}{},
"module": struct{}{},
"output": struct{}{},
"provider": struct{}{},
@ -113,12 +114,27 @@ func (t *hclConfigurable) Config() (*Config, error) {
}
// Build the resources
if resources := list.Filter("resource"); len(resources.Items) > 0 {
{
var err error
config.Resources, err = loadResourcesHcl(resources)
managedResourceConfigs := list.Filter("resource")
dataResourceConfigs := list.Filter("data")
config.Resources = make(
[]*Resource, 0,
len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items),
)
managedResources, err := loadManagedResourcesHcl(managedResourceConfigs)
if err != nil {
return nil, err
}
dataResources, err := loadDataResourcesHcl(dataResourceConfigs)
if err != nil {
return nil, err
}
config.Resources = append(config.Resources, dataResources...)
config.Resources = append(config.Resources, managedResources...)
}
// Build the outputs
@ -395,12 +411,130 @@ func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) {
}
// Given a handle to a HCL object, this recurses into the structure
// and pulls out a list of resources.
// and pulls out a list of data sources.
//
// The resulting data sources may not be unique, but each one
// represents exactly one data definition in the HCL configuration.
// We leave it up to another pass to merge them together.
func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
list = list.Children()
if len(list.Items) == 0 {
return nil, nil
}
// Where all the results will go
var result []*Resource
// Now go over all the types and their children in order to get
// all of the actual resources.
for _, item := range list.Items {
if len(item.Keys) != 2 {
return nil, fmt.Errorf(
"position %s: 'data' must be followed by exactly two strings: a type and a name",
item.Pos())
}
t := item.Keys[0].Token.Value().(string)
k := item.Keys[1].Token.Value().(string)
var listVal *ast.ObjectList
if ot, ok := item.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k)
}
var config map[string]interface{}
if err := hcl.DecodeObject(&config, item.Val); err != nil {
return nil, fmt.Errorf(
"Error reading config for %s[%s]: %s",
t,
k,
err)
}
// Remove the fields we handle specially
delete(config, "depends_on")
delete(config, "provider")
rawConfig, err := NewRawConfig(config)
if err != nil {
return nil, fmt.Errorf(
"Error reading config for %s[%s]: %s",
t,
k,
err)
}
// If we have a count, then figure it out
var count string = "1"
if o := listVal.Filter("count"); len(o.Items) > 0 {
err = hcl.DecodeObject(&count, o.Items[0].Val)
if err != nil {
return nil, fmt.Errorf(
"Error parsing count for %s[%s]: %s",
t,
k,
err)
}
}
countConfig, err := NewRawConfig(map[string]interface{}{
"count": count,
})
if err != nil {
return nil, err
}
countConfig.Key = "count"
// If we have depends fields, then add those in
var dependsOn []string
if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
if err != nil {
return nil, fmt.Errorf(
"Error reading depends_on for %s[%s]: %s",
t,
k,
err)
}
}
// If we have a provider, then parse it out
var provider string
if o := listVal.Filter("provider"); len(o.Items) > 0 {
err := hcl.DecodeObject(&provider, o.Items[0].Val)
if err != nil {
return nil, fmt.Errorf(
"Error reading provider for %s[%s]: %s",
t,
k,
err)
}
}
result = append(result, &Resource{
Mode: DataResourceMode,
Name: k,
Type: t,
RawCount: countConfig,
RawConfig: rawConfig,
Provider: provider,
Provisioners: []*Provisioner{},
DependsOn: dependsOn,
Lifecycle: ResourceLifecycle{},
})
}
return result, nil
}
// Given a handle to a HCL object, this recurses into the structure
// and pulls out a list of managed resources.
//
// The resulting resources may not be unique, but each resource
// represents exactly one resource definition in the HCL configuration.
// represents exactly one "resource" block in the HCL configuration.
// We leave it up to another pass to merge them together.
func loadResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
list = list.Children()
if len(list.Items) == 0 {
return nil, nil

View File

@ -65,6 +65,17 @@ func TestLoadFile_resourceArityMistake(t *testing.T) {
}
}
func TestLoadFile_dataSourceArityMistake(t *testing.T) {
_, err := LoadFile(filepath.Join(fixtureDir, "data-source-arity-mistake.tf"))
if err == nil {
t.Fatal("should have error")
}
expected := "Error loading test-fixtures/data-source-arity-mistake.tf: position 2:6: 'data' must be followed by exactly two strings: a type and a name"
if err.Error() != expected {
t.Fatalf("expected:\n%s\ngot:\n%s", expected, err)
}
}
func TestLoadFileWindowsLineEndings(t *testing.T) {
testFile := filepath.Join(fixtureDir, "windows-line-endings.tf")
@ -823,6 +834,11 @@ aws_instance.web (x1)
resource: aws_security_group.firewall.foo
user: var.foo
aws_security_group.firewall (x5)
data.do.depends (x1)
dependsOn
data.do.simple
data.do.simple (x1)
foo
`
const basicVariablesStr = `
@ -866,6 +882,11 @@ aws_instance.web (x1)
resource: aws_security_group.firewall.foo
user: var.foo
aws_security_group.firewall (x5)
data.do.depends (x1)
dependsOn
data.do.simple
data.do.simple (x1)
foo
`
const dirBasicVariablesStr = `
@ -903,6 +924,12 @@ aws_instance.web (x1)
resource: aws_security_group.firewall.foo
user: var.foo
aws_security_group.firewall (x5)
data.do.depends (x1)
hello
dependsOn
data.do.simple
data.do.simple (x1)
foo
`
const dirOverrideVariablesStr = `

View File

@ -24,6 +24,14 @@ provider "do" {
api_key = "${var.foo}"
}
data "do" "simple" {
foo = "baz"
}
data "do" "depends" {
depends_on = ["data.do.simple"]
}
resource "aws_security_group" "firewall" {
count = 5
}

View File

@ -26,6 +26,17 @@
}
},
"data": {
"do": {
"simple": {
"foo": "baz"
},
"depends": {
"depends_on": ["data.do.simple"]
}
}
},
"resource": {
"aws_instance": {
"db": {

View File

@ -0,0 +1,3 @@
# I forgot the data source name!
data "null" {
}

View File

@ -8,6 +8,10 @@ provider "aws" {
secret_key = "bar"
}
data "do" "simple" {
foo = "baz"
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
}

View File

@ -2,6 +2,10 @@ provider "do" {
api_key = "${var.foo}"
}
data "do" "depends" {
depends_on = ["data.do.simple"]
}
resource "aws_security_group" "firewall" {
count = 5
}

View File

@ -1,4 +1,11 @@
{
"data": {
"do": {
"depends": {
"hello": "world"
}
}
},
"resource": {
"aws_instance": {
"web": {

View File

@ -8,6 +8,11 @@ provider "aws" {
secret_key = "bar"
}
data "do" "simple" {
foo = "baz"
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
}

View File

@ -2,6 +2,10 @@ provider "do" {
api_key = "${var.foo}"
}
data "do" "depends" {
depends_on = ["data.do.simple"]
}
resource "aws_security_group" "firewall" {
count = 5
}