diff --git a/builtin/providers/template/resource_cloudinit_config.go b/builtin/providers/template/datasource_cloudinit_config.go similarity index 83% rename from builtin/providers/template/resource_cloudinit_config.go rename to builtin/providers/template/datasource_cloudinit_config.go index c745b36fa..4bf8dfa34 100644 --- a/builtin/providers/template/resource_cloudinit_config.go +++ b/builtin/providers/template/datasource_cloudinit_config.go @@ -15,13 +15,9 @@ import ( "github.com/sthulb/mime/multipart" ) -func resourceCloudinitConfig() *schema.Resource { +func dataSourceCloudinitConfig() *schema.Resource { return &schema.Resource{ - Create: resourceCloudinitConfigCreate, - Delete: resourceCloudinitConfigDelete, - Update: resourceCloudinitConfigCreate, - Exists: resourceCloudinitConfigExists, - Read: resourceCloudinitConfigRead, + Read: dataSourceCloudinitConfigRead, Schema: map[string]*schema.Schema{ "part": &schema.Schema{ @@ -52,13 +48,11 @@ func resourceCloudinitConfig() *schema.Resource { Type: schema.TypeBool, Optional: true, Default: true, - ForceNew: true, }, "base64_encode": &schema.Schema{ Type: schema.TypeBool, Optional: true, Default: true, - ForceNew: true, }, "rendered": &schema.Schema{ Type: schema.TypeString, @@ -69,7 +63,7 @@ func resourceCloudinitConfig() *schema.Resource { } } -func resourceCloudinitConfigCreate(d *schema.ResourceData, meta interface{}) error { +func dataSourceCloudinitConfigRead(d *schema.ResourceData, meta interface{}) error { rendered, err := renderCloudinitConfig(d) if err != nil { return err @@ -80,24 +74,6 @@ func resourceCloudinitConfigCreate(d *schema.ResourceData, meta interface{}) err return nil } -func resourceCloudinitConfigDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} - -func resourceCloudinitConfigExists(d *schema.ResourceData, meta interface{}) (bool, error) { - rendered, err := renderCloudinitConfig(d) - if err != nil { - return false, err - } - - return strconv.Itoa(hashcode.String(rendered)) == d.Id(), nil -} - -func resourceCloudinitConfigRead(d *schema.ResourceData, meta interface{}) error { - return nil -} - func renderCloudinitConfig(d *schema.ResourceData) (string, error) { gzipOutput := d.Get("gzip").(bool) base64Output := d.Get("base64_encode").(bool) diff --git a/builtin/providers/template/datasource_cloudinit_config_test.go b/builtin/providers/template/datasource_cloudinit_config_test.go new file mode 100644 index 000000000..e3e7225db --- /dev/null +++ b/builtin/providers/template/datasource_cloudinit_config_test.go @@ -0,0 +1,80 @@ +package template + +import ( + "testing" + + r "github.com/hashicorp/terraform/helper/resource" +) + +func TestRender(t *testing.T) { + testCases := []struct { + ResourceBlock string + Expected string + }{ + { + `data "template_cloudinit_config" "foo" { + gzip = false + base64_encode = false + + part { + content_type = "text/x-shellscript" + content = "baz" + } + }`, + "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n", + }, + { + `data "template_cloudinit_config" "foo" { + gzip = false + base64_encode = false + + part { + content_type = "text/x-shellscript" + content = "baz" + filename = "foobar.sh" + } + }`, + "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Disposition: attachment; filename=\"foobar.sh\"\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n", + }, + { + `data "template_cloudinit_config" "foo" { + gzip = false + base64_encode = false + + part { + content_type = "text/x-shellscript" + content = "baz" + } + part { + content_type = "text/x-shellscript" + content = "ffbaz" + } + }`, + "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nffbaz\r\n--MIMEBOUNDARY--\r\n", + }, + } + + for _, tt := range testCases { + r.UnitTest(t, r.TestCase{ + Providers: testProviders, + Steps: []r.TestStep{ + r.TestStep{ + Config: tt.ResourceBlock, + Check: r.ComposeTestCheckFunc( + r.TestCheckResourceAttr("data.template_cloudinit_config.foo", "rendered", tt.Expected), + ), + }, + }, + }) + } +} + +var testCloudInitConfig_basic = ` +data "template_cloudinit_config" "config" { + part { + content_type = "text/x-shellscript" + content = "baz" + } +}` + +var testCloudInitConfig_basic_expected = `Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n` diff --git a/builtin/providers/template/resource_template_file.go b/builtin/providers/template/datasource_template_file.go similarity index 76% rename from builtin/providers/template/resource_template_file.go rename to builtin/providers/template/datasource_template_file.go index c5b3b3b09..865a24e06 100644 --- a/builtin/providers/template/resource_template_file.go +++ b/builtin/providers/template/datasource_template_file.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "log" "os" "path/filepath" @@ -15,19 +14,15 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) -func resourceFile() *schema.Resource { +func dataSourceFile() *schema.Resource { return &schema.Resource{ - Create: resourceFileCreate, - Delete: resourceFileDelete, - Exists: resourceFileExists, - Read: resourceFileRead, + Read: dataSourceFileRead, Schema: map[string]*schema.Schema{ "template": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "Contents of the template", - ForceNew: true, ConflictsWith: []string{"filename"}, ValidateFunc: validateTemplateAttribute, }, @@ -35,7 +30,6 @@ func resourceFile() *schema.Resource { Type: schema.TypeString, Optional: true, Description: "file to read template from", - ForceNew: true, // Make a "best effort" attempt to relativize the file path. StateFunc: func(v interface{}) string { if v == nil || v.(string) == "" { @@ -59,7 +53,6 @@ func resourceFile() *schema.Resource { Optional: true, Default: make(map[string]interface{}), Description: "variables to substitute", - ForceNew: true, }, "rendered": &schema.Schema{ Type: schema.TypeString, @@ -70,7 +63,7 @@ func resourceFile() *schema.Resource { } } -func resourceFileCreate(d *schema.ResourceData, meta interface{}) error { +func dataSourceFileRead(d *schema.ResourceData, meta interface{}) error { rendered, err := renderFile(d) if err != nil { return err @@ -80,32 +73,6 @@ func resourceFileCreate(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceFileDelete(d *schema.ResourceData, meta interface{}) error { - d.SetId("") - return nil -} - -func resourceFileExists(d *schema.ResourceData, meta interface{}) (bool, error) { - rendered, err := renderFile(d) - if err != nil { - if _, ok := err.(templateRenderError); ok { - log.Printf("[DEBUG] Got error while rendering in Exists: %s", err) - log.Printf("[DEBUG] Returning false so the template re-renders using latest variables from config.") - return false, nil - } else { - return false, err - } - } - return hash(rendered) == d.Id(), nil -} - -func resourceFileRead(d *schema.ResourceData, meta interface{}) error { - // Logic is handled in Exists, which only returns true if the rendered - // contents haven't changed. That means if we get here there's nothing to - // do. - return nil -} - type templateRenderError error func renderFile(d *schema.ResourceData) (string, error) { diff --git a/builtin/providers/template/resource_template_file_test.go b/builtin/providers/template/datasource_template_file_test.go similarity index 69% rename from builtin/providers/template/resource_template_file_test.go rename to builtin/providers/template/datasource_template_file_test.go index 05e88c4db..64a64102a 100644 --- a/builtin/providers/template/resource_template_file_test.go +++ b/builtin/providers/template/datasource_template_file_test.go @@ -23,13 +23,13 @@ func TestTemplateRendering(t *testing.T) { want string }{ {`{}`, `ABC`, `ABC`}, - {`{a="foo"}`, `${a}`, `foo`}, - {`{a="hello"}`, `${replace(a, "ello", "i")}`, `hi`}, + {`{a="foo"}`, `$${a}`, `foo`}, + {`{a="hello"}`, `$${replace(a, "ello", "i")}`, `hi`}, {`{}`, `${1+2+3}`, `6`}, } for _, tt := range cases { - r.Test(t, r.TestCase{ + r.UnitTest(t, r.TestCase{ Providers: testProviders, Steps: []r.TestStep{ r.TestStep{ @@ -47,39 +47,6 @@ func TestTemplateRendering(t *testing.T) { } } -// https://github.com/hashicorp/terraform/issues/2344 -func TestTemplateVariableChange(t *testing.T) { - steps := []struct { - vars string - template string - want string - }{ - {`{a="foo"}`, `${a}`, `foo`}, - {`{b="bar"}`, `${b}`, `bar`}, - } - - var testSteps []r.TestStep - for i, step := range steps { - testSteps = append(testSteps, r.TestStep{ - Config: testTemplateConfig(step.template, step.vars), - Check: func(i int, want string) r.TestCheckFunc { - return func(s *terraform.State) error { - got := s.RootModule().Outputs["rendered"] - if want != got.Value { - return fmt.Errorf("[%d] got:\n%q\nwant:\n%q\n", i, got, want) - } - return nil - } - }(i, step.want), - }) - } - - r.Test(t, r.TestCase{ - Providers: testProviders, - Steps: testSteps, - }) -} - func TestValidateTemplateAttribute(t *testing.T) { file, err := ioutil.TempFile("", "testtemplate") if err != nil { @@ -129,11 +96,11 @@ func TestTemplateSharedMemoryRace(t *testing.T) { func testTemplateConfig(template, vars string) string { return fmt.Sprintf(` - resource "template_file" "t0" { + data "template_file" "t0" { template = "%s" vars = %s } output "rendered" { - value = "${template_file.t0.rendered}" + value = "${data.template_file.t0.rendered}" }`, template, vars) } diff --git a/builtin/providers/template/provider.go b/builtin/providers/template/provider.go index 1ebf3ae22..ece6c9f34 100644 --- a/builtin/providers/template/provider.go +++ b/builtin/providers/template/provider.go @@ -7,9 +7,19 @@ import ( func Provider() terraform.ResourceProvider { return &schema.Provider{ + DataSourcesMap: map[string]*schema.Resource{ + "template_file": dataSourceFile(), + "template_cloudinit_config": dataSourceCloudinitConfig(), + }, ResourcesMap: map[string]*schema.Resource{ - "template_file": resourceFile(), - "template_cloudinit_config": resourceCloudinitConfig(), + "template_file": schema.DataSourceResourceShim( + "template_file", + dataSourceFile(), + ), + "template_cloudinit_config": schema.DataSourceResourceShim( + "template_cloudinit_config", + dataSourceCloudinitConfig(), + ), }, } } diff --git a/builtin/providers/template/resource_cloudinit_config_test.go b/builtin/providers/template/resource_cloudinit_config_test.go deleted file mode 100644 index 9667d74fc..000000000 --- a/builtin/providers/template/resource_cloudinit_config_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package template - -import ( - "testing" - - r "github.com/hashicorp/terraform/helper/resource" -) - -func TestRender(t *testing.T) { - testCases := []struct { - ResourceBlock string - Expected string - }{ - { - `resource "template_cloudinit_config" "foo" { - gzip = false - base64_encode = false - - part { - content_type = "text/x-shellscript" - content = "baz" - } - }`, - "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDRY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDRY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDRY--\r\n", - }, - { - `resource "template_cloudinit_config" "foo" { - gzip = false - base64_encode = false - - part { - content_type = "text/x-shellscript" - content = "baz" - filename = "foobar.sh" - } - }`, - "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDRY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDRY\r\nContent-Disposition: attachment; filename=\"foobar.sh\"\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDRY--\r\n", - }, - { - `resource "template_cloudinit_config" "foo" { - gzip = false - base64_encode = false - - part { - content_type = "text/x-shellscript" - content = "baz" - } - part { - content_type = "text/x-shellscript" - content = "ffbaz" - } - }`, - "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDRY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDRY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDRY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nffbaz\r\n--MIMEBOUNDRY--\r\n", - }, - { - `resource "template_cloudinit_config" "foo" { - gzip = true - base64_encode = false - - part { - content_type = "text/x-shellscript" - content = "baz" - filename = "ah" - } - part { - content_type = "text/x-shellscript" - content = "ffbaz" - } - }`, - "\x1f\x8b\b\x00\x00\tn\x88\x00\xff\xac\xce\xc1J\x031\x10\xc6\xf1{`\xdf!\xe4>VO\u0096^\xb4=xX\x05\xa9\x82\xc7\xd9݉;\x90LB2\x85\xadOo-\x88\x8b\xe2\xadDŽ\x1f\xf3\xfd\xef\x93(\x89\xc2\xfe\x98\xa9\xb5\xf1\x10\x943\x16]E\x9ei\\\xdb>\x1dd\xc4rܸ\xee\xa1\xdb\xdd=\xbd\x03\x00\x00\xff\xffmB\x8c\xeed\x01\x00\x00", - }, - } - - for _, tt := range testCases { - r.Test(t, r.TestCase{ - Providers: testProviders, - Steps: []r.TestStep{ - r.TestStep{ - Config: tt.ResourceBlock, - Check: r.ComposeTestCheckFunc( - r.TestCheckResourceAttr("template_cloudinit_config.foo", "rendered", tt.Expected), - ), - }, - }, - }) - } -} - -func TestCloudConfig_update(t *testing.T) { - r.Test(t, r.TestCase{ - Providers: testProviders, - Steps: []r.TestStep{ - r.TestStep{ - Config: testCloudInitConfig_basic, - Check: r.ComposeTestCheckFunc( - r.TestCheckResourceAttr("template_cloudinit_config.config", "rendered", testCloudInitConfig_basic_expected), - ), - }, - - r.TestStep{ - Config: testCloudInitConfig_update, - Check: r.ComposeTestCheckFunc( - r.TestCheckResourceAttr("template_cloudinit_config.config", "rendered", testCloudInitConfig_update_expected), - ), - }, - }, - }) -} - -var testCloudInitConfig_basic = ` -resource "template_cloudinit_config" "config" { - part { - content_type = "text/x-shellscript" - content = "baz" - } -}` - -var testCloudInitConfig_basic_expected = `Content-Type: multipart/mixed; boundary=\"MIMEBOUNDRY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDRY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDRY--\r\n` - -var testCloudInitConfig_update = ` -resource "template_cloudinit_config" "config" { - part { - content_type = "text/x-shellscript" - content = "baz" - } - - part { - content_type = "text/x-shellscript" - content = "ffbaz" - } -}` - -var testCloudInitConfig_update_expected = `Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nffbaz\r\n--MIMEBOUNDARY--\r\n` diff --git a/builtin/providers/terraform/data_source_state_test.go b/builtin/providers/terraform/data_source_state_test.go index 26db76db6..0bab4b261 100644 --- a/builtin/providers/terraform/data_source_state_test.go +++ b/builtin/providers/terraform/data_source_state_test.go @@ -17,7 +17,7 @@ func TestState_basic(t *testing.T) { Config: testAccState_basic, Check: resource.ComposeTestCheckFunc( testAccCheckStateValue( - "terraform_remote_state.foo", "foo", "bar"), + "data.terraform_remote_state.foo", "foo", "bar"), ), }, }, @@ -64,7 +64,7 @@ func testAccCheckStateValue(id, name, value string) resource.TestCheckFunc { } const testAccState_basic = ` -resource "terraform_remote_state" "foo" { +data "terraform_remote_state" "foo" { backend = "_local" config { diff --git a/website/source/docs/providers/template/r/cloudinit_config.html.markdown b/website/source/docs/providers/template/d/cloudinit_config.html.markdown similarity index 87% rename from website/source/docs/providers/template/r/cloudinit_config.html.markdown rename to website/source/docs/providers/template/d/cloudinit_config.html.markdown index 69dc722b3..030092ea5 100644 --- a/website/source/docs/providers/template/r/cloudinit_config.html.markdown +++ b/website/source/docs/providers/template/d/cloudinit_config.html.markdown @@ -1,7 +1,7 @@ --- layout: "template" page_title: "Template: cloudinit_multipart" -sidebar_current: "docs-template-resource-cloudinit-config" +sidebar_current: "docs-template-datasource-cloudinit-config" description: |- Renders a multi-part cloud-init config from source files. --- @@ -14,7 +14,7 @@ Renders a multi-part cloud-init config from source files. ``` # Render a part using a `template_file` -resource "template_file" "script" { +data "template_file" "script" { template = "${file("${path.module}/init.tpl")}" vars { @@ -24,7 +24,7 @@ resource "template_file" "script" { # Render a multi-part cloudinit config making use of the part # above, and other source files -resource "template_cloudinit_config" "config" { +data "template_cloudinit_config" "config" { gzip = true base64_encode = true @@ -32,7 +32,7 @@ resource "template_cloudinit_config" "config" { part { filename = "init.cfg" content_type = "text/part-handler" - content = "${template_file.script.rendered}" + content = "${data.template_file.script.rendered}" } part { @@ -50,7 +50,7 @@ resource "template_cloudinit_config" "config" { resource "aws_instance" "web" { ami = "ami-d05e75b8" instance_type = "t2.micro" - user_data = "${template_cloudinit_config.config.rendered}" + user_data = "${data.template_cloudinit_config.config.rendered}" } ``` diff --git a/website/source/docs/providers/template/r/file.html.md b/website/source/docs/providers/template/d/file.html.md similarity index 94% rename from website/source/docs/providers/template/r/file.html.md rename to website/source/docs/providers/template/d/file.html.md index b0e8af469..6b8381d61 100644 --- a/website/source/docs/providers/template/r/file.html.md +++ b/website/source/docs/providers/template/d/file.html.md @@ -1,7 +1,7 @@ --- layout: "template" page_title: "Template: template_file" -sidebar_current: "docs-template-resource-file" +sidebar_current: "docs-template-datasource-file" description: |- Renders a template from a file. --- @@ -13,7 +13,7 @@ Renders a template from a file. ## Example Usage ``` -resource "template_file" "init" { +data "template_file" "init" { template = "${file("${path.module}/init.tpl")}" vars { diff --git a/website/source/docs/providers/template/index.html.markdown b/website/source/docs/providers/template/index.html.markdown index 3d180e42f..9fce6da1c 100644 --- a/website/source/docs/providers/template/index.html.markdown +++ b/website/source/docs/providers/template/index.html.markdown @@ -8,23 +8,16 @@ description: |- # Template Provider -The template provider exposes resources to use templates to generate +The template provider exposes data sources to use templates to generate strings for other Terraform resources or outputs. -The template provider is what we call a _logical provider_. This has no -impact on how it behaves, but conceptually it is important to understand. -The template provider doesn't manage any _physical_ resources; it isn't -creating servers, writing files, etc. It is used to generate attributes that -can be used for interpolation for other resources. Examples will explain -this best. - -Use the navigation to the left to read about the available resources. +Use the navigation to the left to read about the available data sources. ## Example Usage ``` # Template for initial configuration bash script -resource "template_file" "init" { +data "template_file" "init" { template = "${file("init.tpl")}" vars { @@ -36,6 +29,6 @@ resource "template_file" "init" { resource "aws_instance" "web" { # ... - user_data = "${template_file.init.rendered}" + user_data = "${data.template_file.init.rendered}" } ``` diff --git a/website/source/layouts/template.erb b/website/source/layouts/template.erb index 1d666fce2..c710a5de0 100644 --- a/website/source/layouts/template.erb +++ b/website/source/layouts/template.erb @@ -10,14 +10,14 @@ Template Provider - > - Resources + > + Data Sources