From a926fa6fdd633fc43127bd18719099e92b1f4377 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 5 Oct 2015 19:08:33 -0400 Subject: [PATCH 1/7] Adds template_cloudinit_config resource to template This adds a new resource to template to generate multipart cloudinit configurations to be used with other providers/resources. The resource has the ability gzip and base64 encode the parts. --- builtin/providers/template/provider.go | 3 +- builtin/providers/template/resource.go | 24 +- .../template/resource_cloudinit_config.go | 223 ++++++++++++++++++ .../resource_cloudinit_config_test.go | 92 ++++++++ .../template/r/cloudinit_config.html.markdown | 68 ++++++ 5 files changed, 397 insertions(+), 13 deletions(-) create mode 100644 builtin/providers/template/resource_cloudinit_config.go create mode 100644 builtin/providers/template/resource_cloudinit_config_test.go create mode 100644 website/source/docs/providers/template/r/cloudinit_config.html.markdown diff --git a/builtin/providers/template/provider.go b/builtin/providers/template/provider.go index 7513341bc..1ebf3ae22 100644 --- a/builtin/providers/template/provider.go +++ b/builtin/providers/template/provider.go @@ -8,7 +8,8 @@ import ( func Provider() terraform.ResourceProvider { return &schema.Provider{ ResourcesMap: map[string]*schema.Resource{ - "template_file": resource(), + "template_file": resourceFile(), + "template_cloudinit_config": resourceCloudinitConfig(), }, } } diff --git a/builtin/providers/template/resource.go b/builtin/providers/template/resource.go index 8022c064b..fd0808828 100644 --- a/builtin/providers/template/resource.go +++ b/builtin/providers/template/resource.go @@ -15,12 +15,12 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) -func resource() *schema.Resource { +func resourceFile() *schema.Resource { return &schema.Resource{ - Create: Create, - Delete: Delete, - Exists: Exists, - Read: Read, + Create: resourceFileCreate, + Delete: resourceFileDelete, + Exists: resourceFileExists, + Read: resourceFileRead, Schema: map[string]*schema.Schema{ "template": &schema.Schema{ @@ -69,8 +69,8 @@ func resource() *schema.Resource { } } -func Create(d *schema.ResourceData, meta interface{}) error { - rendered, err := render(d) +func resourceFileCreate(d *schema.ResourceData, meta interface{}) error { + rendered, err := renderFile(d) if err != nil { return err } @@ -79,13 +79,13 @@ func Create(d *schema.ResourceData, meta interface{}) error { return nil } -func Delete(d *schema.ResourceData, meta interface{}) error { +func resourceFileDelete(d *schema.ResourceData, meta interface{}) error { d.SetId("") return nil } -func Exists(d *schema.ResourceData, meta interface{}) (bool, error) { - rendered, err := render(d) +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) @@ -98,7 +98,7 @@ func Exists(d *schema.ResourceData, meta interface{}) (bool, error) { return hash(rendered) == d.Id(), nil } -func Read(d *schema.ResourceData, meta interface{}) error { +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. @@ -107,7 +107,7 @@ func Read(d *schema.ResourceData, meta interface{}) error { type templateRenderError error -func render(d *schema.ResourceData) (string, error) { +func renderFile(d *schema.ResourceData) (string, error) { template := d.Get("template").(string) filename := d.Get("filename").(string) vars := d.Get("vars").(map[string]interface{}) diff --git a/builtin/providers/template/resource_cloudinit_config.go b/builtin/providers/template/resource_cloudinit_config.go new file mode 100644 index 000000000..88af9bef2 --- /dev/null +++ b/builtin/providers/template/resource_cloudinit_config.go @@ -0,0 +1,223 @@ +package template + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "io" + "mime/multipart" + "net/textproto" + "strconv" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceCloudinitConfig() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudinitConfigCreate, + Delete: resourceCloudinitConfigDelete, + Exists: resourceCloudinitConfigExists, + Read: resourceCloudinitConfigRead, + + Schema: map[string]*schema.Schema{ + "part": &schema.Schema{ + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "content_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if _, supported := supportedContentTypes[value]; !supported { + errors = append(errors, fmt.Errorf("Part has an unsupported content type: %s", v)) + } + + return + }, + }, + "content": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "filename": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "merge_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "gzip": &schema.Schema{ + 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, + Computed: true, + Description: "rendered cloudinit configuration", + }, + }, + } +} + +func resourceCloudinitConfigCreate(d *schema.ResourceData, meta interface{}) error { + rendered, err := renderCloudinitConfig(d) + if err != nil { + return err + } + + d.Set("rendered", rendered) + d.SetId(strconv.Itoa(hashcode.String(rendered))) + 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) + + partsValue, hasParts := d.GetOk("part") + if !hasParts { + return "", fmt.Errorf("No parts found in the cloudinit resource declaration") + } + + cloudInitParts := make(cloudInitParts, len(partsValue.([]interface{}))) + for i, v := range partsValue.([]interface{}) { + p := v.(map[string]interface{}) + + part := cloudInitPart{} + if p, ok := p["content_type"]; ok { + part.ContentType = p.(string) + } + if p, ok := p["content"]; ok { + part.Content = p.(string) + } + if p, ok := p["merge_type"]; ok { + part.MergeType = p.(string) + } + if p, ok := p["filename"]; ok { + part.Filename = p.(string) + } + cloudInitParts[i] = part + } + + var buffer bytes.Buffer + + var err error + if gzipOutput { + gzipWriter := gzip.NewWriter(&buffer) + err = renderPartsToWriter(cloudInitParts, gzipWriter) + gzipWriter.Close() + } else { + err = renderPartsToWriter(cloudInitParts, &buffer) + } + if err != nil { + return "", err + } + + output := "" + if base64Output { + output = base64.StdEncoding.EncodeToString(buffer.Bytes()) + } else { + output = buffer.String() + } + + return output, nil +} + +func renderPartsToWriter(parts cloudInitParts, writer io.Writer) error { + mimeWriter := multipart.NewWriter(writer) + defer mimeWriter.Close() + + // we need to set the boundary explictly, otherwise the boundary is random + // and this causes terraform to complain about the resource being different + if err := mimeWriter.SetBoundary("MIMEBOUNDRY"); err != nil { + return err + } + + writer.Write([]byte(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\n", mimeWriter.Boundary()))) + + for _, part := range parts { + header := textproto.MIMEHeader{} + if part.ContentType == "" { + header.Set("Content-Type", "text/plain") + } else { + header.Set("Content-Type", part.ContentType) + } + + if part.Filename != "" { + header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, part.Filename)) + } + + if part.MergeType != "" { + header.Set("X-Merge-Type", part.MergeType) + } + + partWriter, err := mimeWriter.CreatePart(header) + if err != nil { + return err + } + + _, err = partWriter.Write([]byte(part.Content)) + if err != nil { + return err + } + } + + return nil +} + +type cloudInitPart struct { + ContentType string + MergeType string + Filename string + Content string +} + +type cloudInitParts []cloudInitPart + +// Support content types as specified by http://cloudinit.readthedocs.org/en/latest/topics/format.html +var supportedContentTypes = map[string]bool{ + "text/x-include-once-url": true, + "text/x-include-url": true, + "text/cloud-config-archive": true, + "text/upstart-job": true, + "text/cloud-config": true, + "text/part-handler": true, + "text/x-shellscript": true, + "text/cloud-boothook": true, +} diff --git a/builtin/providers/template/resource_cloudinit_config_test.go b/builtin/providers/template/resource_cloudinit_config_test.go new file mode 100644 index 000000000..83d0a6152 --- /dev/null +++ b/builtin/providers/template/resource_cloudinit_config_test.go @@ -0,0 +1,92 @@ +package template + +import ( + "testing" + + r "github.com/hashicorp/terraform/helper/resource" + // "github.com/hashicorp/terraform/terraform" +) + +// var testProviders = map[string]terraform.ResourceProvider{ +// "template": Provider(), +// } + +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\"\n--MIMEBOUNDRY\r\nContent-Type: text/x-shellscript\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\"\n--MIMEBOUNDRY\r\nContent-Type: text/x-shellscript\r\nContent-Disposition: attachment; filename=\"foobar.sh\"\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\"\n--MIMEBOUNDRY\r\nContent-Type: text/x-shellscript\r\n\r\nbaz\r\n--MIMEBOUNDRY\r\nContent-Type: text/x-shellscript\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\x94\x8d\xbd\n\xc2@\x10\x84\xfb\x83{\x87\xe3\xfa%}B\x1a\x8d\x85E\x14D\v\xcbM\xb2!\v\xf7Gn\x03\x89O\xaf\x9d\x8a\x95\xe5\f3߷\x8fA(\b\\\xb7D\xa5\xf1\x8b\x13N8K\xe1y\xa5\xa12]\\\u0080\xf3V\xdb\xf6\xd8\x1ev\xe7۩\xb9ܭ\x02\xf8\x88Z}C\x84V)V\xc8\x139\x97\xfb\x99\x93\xbc\x17\r\xe7\x143\v\xc7P\x1a\x14\xc1~\xf2\xaf\xbe2#;\n詶8Y\xad\xb4\xea\xf0\xa1\xff\xf7h5\x8e\xbfO\x00\xad\x9e\x01\x00\x00\xff\xff\xecM\xd3\x1e\xe9\x00\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), + ), + }, + }, + }) + } +} diff --git a/website/source/docs/providers/template/r/cloudinit_config.html.markdown b/website/source/docs/providers/template/r/cloudinit_config.html.markdown new file mode 100644 index 000000000..84416e2d5 --- /dev/null +++ b/website/source/docs/providers/template/r/cloudinit_config.html.markdown @@ -0,0 +1,68 @@ +--- +layout: "Template" +page_title: "Template: cloudinit_multipart" +sidebar_current: "docs-template-resource-cloudinit-config" +description: |- + Renders a cloud-init config. +--- + +# template\_cloudinit\_config + +Renders a template from a file. + +## Example Usage + +``` +resource "template_file" "script" { + template = "${file("${path.module}/init.tpl")}" + + vars { + consul_address = "${aws_instance.consul.private_ip}" + } +} + +resource "template_cloudinit_config" "config" { + # Setup hello world script to be called by the cloud-config + part { + filename = "init.cfg" + content_type = "text/part-handler" + content = "${template_file.script.rendered}" + } + + # Setup cloud-config yaml + part { + content_type = "text/cloud-config" + content = "${file(\"config.yaml\")" + } +} + + + +``` + +## Argument Reference + +The following arguments are supported: + +* `gzip` - (Optional) Specify whether or not to gzip the rendered output. + +* `base64_encode` - (Optional) Base64 encoding of the rendered output. + +* `part` - (Required) One may specify this many times, this creates a fragment of the rendered cloud-init config. + +The `part` block supports: + +* `filename` - (Optional) Filename to save part as. + +* `content_type` - (Optional) Content type to send file as. + +* `content` - (Required) Body for the part. + +* `merge_type` - (Optional) Gives the ability to merge multiple blocks of cloud-config together. + + +## Attributes Reference + +The following attributes are exported: + +* `rendered` - The final rendered template. From abffa67a49ac76699718219ccd40c483e62261ef Mon Sep 17 00:00:00 2001 From: James Nugent Date: Fri, 27 Nov 2015 18:08:17 +0000 Subject: [PATCH 2/7] Rename resource.go -> resource_template_file.go This is necessitated by new resources being added to the template provider. --- .../providers/template/{resource.go => resource_template_file.go} | 0 .../template/{resource_test.go => resource_template_file_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename builtin/providers/template/{resource.go => resource_template_file.go} (100%) rename builtin/providers/template/{resource_test.go => resource_template_file_test.go} (100%) diff --git a/builtin/providers/template/resource.go b/builtin/providers/template/resource_template_file.go similarity index 100% rename from builtin/providers/template/resource.go rename to builtin/providers/template/resource_template_file.go diff --git a/builtin/providers/template/resource_test.go b/builtin/providers/template/resource_template_file_test.go similarity index 100% rename from builtin/providers/template/resource_test.go rename to builtin/providers/template/resource_template_file_test.go From ba9c7323a9d19bda4bc5540c3f7009bf0acb14d3 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Fri, 27 Nov 2015 18:10:24 +0000 Subject: [PATCH 3/7] Remove unnecessary commented imports --- builtin/providers/template/resource_cloudinit_config_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/builtin/providers/template/resource_cloudinit_config_test.go b/builtin/providers/template/resource_cloudinit_config_test.go index 83d0a6152..b0cd48fd3 100644 --- a/builtin/providers/template/resource_cloudinit_config_test.go +++ b/builtin/providers/template/resource_cloudinit_config_test.go @@ -4,13 +4,8 @@ import ( "testing" r "github.com/hashicorp/terraform/helper/resource" - // "github.com/hashicorp/terraform/terraform" ) -// var testProviders = map[string]terraform.ResourceProvider{ -// "template": Provider(), -// } - func TestRender(t *testing.T) { testCases := []struct { ResourceBlock string From 16c8750bab0bff98ab076750cc6d3c1fcb6b7aad Mon Sep 17 00:00:00 2001 From: James Nugent Date: Sat, 28 Nov 2015 10:07:06 +0000 Subject: [PATCH 4/7] Make minor alterations to the cloudinit docs --- .../template/r/cloudinit_config.html.markdown | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/website/source/docs/providers/template/r/cloudinit_config.html.markdown b/website/source/docs/providers/template/r/cloudinit_config.html.markdown index 84416e2d5..b82a8be2b 100644 --- a/website/source/docs/providers/template/r/cloudinit_config.html.markdown +++ b/website/source/docs/providers/template/r/cloudinit_config.html.markdown @@ -3,16 +3,17 @@ layout: "Template" page_title: "Template: cloudinit_multipart" sidebar_current: "docs-template-resource-cloudinit-config" description: |- - Renders a cloud-init config. + Renders a multi-part cloud-init config from source files. --- # template\_cloudinit\_config -Renders a template from a file. +Renders a multi-part cloud-init config from source files. ## Example Usage ``` +# Render a part using a `template_file` resource "template_file" "script" { template = "${file("${path.module}/init.tpl")}" @@ -21,7 +22,12 @@ 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" { + gzip = true + base64_encode = true + # Setup hello world script to be called by the cloud-config part { filename = "init.cfg" @@ -29,15 +35,16 @@ resource "template_cloudinit_config" "config" { content = "${template_file.script.rendered}" } - # Setup cloud-config yaml part { - content_type = "text/cloud-config" - content = "${file(\"config.yaml\")" + content_type = "text/x-shellscript" + content = "baz" + } + + part { + content_type = "text/x-shellscript" + content = "ffbaz" } } - - - ``` ## Argument Reference @@ -48,7 +55,7 @@ The following arguments are supported: * `base64_encode` - (Optional) Base64 encoding of the rendered output. -* `part` - (Required) One may specify this many times, this creates a fragment of the rendered cloud-init config. +* `part` - (Required) One may specify this many times, this creates a fragment of the rendered cloud-init config file. The order of the parts is maintained in the configuration is maintained in the rendered template. The `part` block supports: @@ -60,9 +67,8 @@ The `part` block supports: * `merge_type` - (Optional) Gives the ability to merge multiple blocks of cloud-config together. - ## Attributes Reference The following attributes are exported: -* `rendered` - The final rendered template. +* `rendered` - The final rendered multi-part cloudinit config. From 33d2afc26da82e815489c573a520ba54303f644e Mon Sep 17 00:00:00 2001 From: Simon Thulbourn Date: Mon, 7 Dec 2015 13:41:52 +0000 Subject: [PATCH 5/7] Add headers to output The original implmentation was missing headers to denote mime version & content transfer encoding, this caused issues with EC2. Signed-off-by: Simon Thulbourn --- builtin/providers/template/resource_cloudinit_config.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/builtin/providers/template/resource_cloudinit_config.go b/builtin/providers/template/resource_cloudinit_config.go index 88af9bef2..86798b8c7 100644 --- a/builtin/providers/template/resource_cloudinit_config.go +++ b/builtin/providers/template/resource_cloudinit_config.go @@ -170,6 +170,7 @@ func renderPartsToWriter(parts cloudInitParts, writer io.Writer) error { } writer.Write([]byte(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\n", mimeWriter.Boundary()))) + writer.Write([]byte("MIME-Version: 1.0\r\n")) for _, part := range parts { header := textproto.MIMEHeader{} @@ -179,6 +180,9 @@ func renderPartsToWriter(parts cloudInitParts, writer io.Writer) error { header.Set("Content-Type", part.ContentType) } + header.Set("MIME-Version", "1.0") + header.Set("Content-Transfer-Encoding", "7bit") + if part.Filename != "" { header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, part.Filename)) } From 3701342716ce28958239288699b4675f757b8da6 Mon Sep 17 00:00:00 2001 From: Simon Thulbourn Date: Mon, 7 Dec 2015 18:58:45 +0000 Subject: [PATCH 6/7] Alters template provider to use a fork of multipart. Signed-off-by: Simon Thulbourn --- builtin/providers/template/resource_cloudinit_config.go | 3 ++- .../providers/template/resource_cloudinit_config_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/builtin/providers/template/resource_cloudinit_config.go b/builtin/providers/template/resource_cloudinit_config.go index 86798b8c7..0082dde62 100644 --- a/builtin/providers/template/resource_cloudinit_config.go +++ b/builtin/providers/template/resource_cloudinit_config.go @@ -6,12 +6,13 @@ import ( "encoding/base64" "fmt" "io" - "mime/multipart" "net/textproto" "strconv" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" + + "github.com/sthulb/mime/multipart" ) func resourceCloudinitConfig() *schema.Resource { diff --git a/builtin/providers/template/resource_cloudinit_config_test.go b/builtin/providers/template/resource_cloudinit_config_test.go index b0cd48fd3..afaac1971 100644 --- a/builtin/providers/template/resource_cloudinit_config_test.go +++ b/builtin/providers/template/resource_cloudinit_config_test.go @@ -21,7 +21,7 @@ func TestRender(t *testing.T) { content = "baz" } }`, - "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDRY\"\n--MIMEBOUNDRY\r\nContent-Type: text/x-shellscript\r\n\r\nbaz\r\n--MIMEBOUNDRY--\r\n", + "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" { @@ -34,7 +34,7 @@ func TestRender(t *testing.T) { filename = "foobar.sh" } }`, - "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDRY\"\n--MIMEBOUNDRY\r\nContent-Type: text/x-shellscript\r\nContent-Disposition: attachment; filename=\"foobar.sh\"\r\n\r\nbaz\r\n--MIMEBOUNDRY--\r\n", + "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" { @@ -50,7 +50,7 @@ func TestRender(t *testing.T) { content = "ffbaz" } }`, - "Content-Type: multipart/mixed; boundary=\"MIMEBOUNDRY\"\n--MIMEBOUNDRY\r\nContent-Type: text/x-shellscript\r\n\r\nbaz\r\n--MIMEBOUNDRY\r\nContent-Type: text/x-shellscript\r\n\r\nffbaz\r\n--MIMEBOUNDRY--\r\n", + "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" { @@ -67,7 +67,7 @@ func TestRender(t *testing.T) { content = "ffbaz" } }`, - "\x1f\x8b\b\x00\x00\tn\x88\x00\xff\x94\x8d\xbd\n\xc2@\x10\x84\xfb\x83{\x87\xe3\xfa%}B\x1a\x8d\x85E\x14D\v\xcbM\xb2!\v\xf7Gn\x03\x89O\xaf\x9d\x8a\x95\xe5\f3߷\x8fA(\b\\\xb7D\xa5\xf1\x8b\x13N8K\xe1y\xa5\xa12]\\\u0080\xf3V\xdb\xf6\xd8\x1ev\xe7۩\xb9ܭ\x02\xf8\x88Z}C\x84V)V\xc8\x139\x97\xfb\x99\x93\xbc\x17\r\xe7\x143\v\xc7P\x1a\x14\xc1~\xf2\xaf\xbe2#;\n詶8Y\xad\xb4\xea\xf0\xa1\xff\xf7h5\x8e\xbfO\x00\xad\x9e\x01\x00\x00\xff\xff\xecM\xd3\x1e\xe9\x00\x00\x00", + "\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", }, } From a4568c596e56009d422590f1fd07bd6ddcb158de Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 21 Dec 2015 13:31:31 -0500 Subject: [PATCH 7/7] Fix CloudInit doc format and give better example --- .../template/r/cloudinit_config.html.markdown | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/website/source/docs/providers/template/r/cloudinit_config.html.markdown b/website/source/docs/providers/template/r/cloudinit_config.html.markdown index b82a8be2b..32f3fe07f 100644 --- a/website/source/docs/providers/template/r/cloudinit_config.html.markdown +++ b/website/source/docs/providers/template/r/cloudinit_config.html.markdown @@ -15,35 +15,42 @@ Renders a multi-part cloud-init config from source files. ``` # Render a part using a `template_file` resource "template_file" "script" { - template = "${file("${path.module}/init.tpl")}" + template = "${file("${path.module}/init.tpl")}" - vars { - consul_address = "${aws_instance.consul.private_ip}" - } + vars { + consul_address = "${aws_instance.consul.private_ip}" + } } # Render a multi-part cloudinit config making use of the part # above, and other source files resource "template_cloudinit_config" "config" { - gzip = true - base64_encode = true + gzip = true + base64_encode = true - # Setup hello world script to be called by the cloud-config - part { - filename = "init.cfg" - content_type = "text/part-handler" - content = "${template_file.script.rendered}" - } + # Setup hello world script to be called by the cloud-config + part { + filename = "init.cfg" + content_type = "text/part-handler" + content = "${template_file.script.rendered}" + } - part { - content_type = "text/x-shellscript" - content = "baz" - } + part { + content_type = "text/x-shellscript" + content = "baz" + } - part { - content_type = "text/x-shellscript" - content = "ffbaz" - } + part { + content_type = "text/x-shellscript" + content = "ffbaz" + } +} + +# Start an AWS instance with the cloudinit config as user data +resource "aws_instance" "web" { + ami = "ami-d05e75b8" + instance_type = "t2.micro" + user_data = "${template_cloudinit_config.config.rendered}" } ```