github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/template/datasource_cloudinit_config.go (about) 1 package template 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "encoding/base64" 7 "fmt" 8 "io" 9 "mime/multipart" 10 "net/textproto" 11 "strconv" 12 13 "github.com/hashicorp/terraform/helper/hashcode" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func dataSourceCloudinitConfig() *schema.Resource { 18 return &schema.Resource{ 19 Read: dataSourceCloudinitConfigRead, 20 21 Schema: map[string]*schema.Schema{ 22 "part": { 23 Type: schema.TypeList, 24 Required: true, 25 Elem: &schema.Resource{ 26 Schema: map[string]*schema.Schema{ 27 "content_type": { 28 Type: schema.TypeString, 29 Optional: true, 30 }, 31 "content": { 32 Type: schema.TypeString, 33 Required: true, 34 }, 35 "filename": { 36 Type: schema.TypeString, 37 Optional: true, 38 }, 39 "merge_type": { 40 Type: schema.TypeString, 41 Optional: true, 42 }, 43 }, 44 }, 45 }, 46 "gzip": { 47 Type: schema.TypeBool, 48 Optional: true, 49 Default: true, 50 }, 51 "base64_encode": { 52 Type: schema.TypeBool, 53 Optional: true, 54 Default: true, 55 }, 56 "rendered": { 57 Type: schema.TypeString, 58 Computed: true, 59 Description: "rendered cloudinit configuration", 60 }, 61 }, 62 } 63 } 64 65 func dataSourceCloudinitConfigRead(d *schema.ResourceData, meta interface{}) error { 66 rendered, err := renderCloudinitConfig(d) 67 if err != nil { 68 return err 69 } 70 71 d.Set("rendered", rendered) 72 d.SetId(strconv.Itoa(hashcode.String(rendered))) 73 return nil 74 } 75 76 func renderCloudinitConfig(d *schema.ResourceData) (string, error) { 77 gzipOutput := d.Get("gzip").(bool) 78 base64Output := d.Get("base64_encode").(bool) 79 80 partsValue, hasParts := d.GetOk("part") 81 if !hasParts { 82 return "", fmt.Errorf("No parts found in the cloudinit resource declaration") 83 } 84 85 cloudInitParts := make(cloudInitParts, len(partsValue.([]interface{}))) 86 for i, v := range partsValue.([]interface{}) { 87 p, castOk := v.(map[string]interface{}) 88 if !castOk { 89 return "", fmt.Errorf("Unable to parse parts in cloudinit resource declaration") 90 } 91 92 part := cloudInitPart{} 93 if p, ok := p["content_type"]; ok { 94 part.ContentType = p.(string) 95 } 96 if p, ok := p["content"]; ok { 97 part.Content = p.(string) 98 } 99 if p, ok := p["merge_type"]; ok { 100 part.MergeType = p.(string) 101 } 102 if p, ok := p["filename"]; ok { 103 part.Filename = p.(string) 104 } 105 cloudInitParts[i] = part 106 } 107 108 var buffer bytes.Buffer 109 110 var err error 111 if gzipOutput { 112 gzipWriter := gzip.NewWriter(&buffer) 113 err = renderPartsToWriter(cloudInitParts, gzipWriter) 114 gzipWriter.Close() 115 } else { 116 err = renderPartsToWriter(cloudInitParts, &buffer) 117 } 118 if err != nil { 119 return "", err 120 } 121 122 output := "" 123 if base64Output { 124 output = base64.StdEncoding.EncodeToString(buffer.Bytes()) 125 } else { 126 output = buffer.String() 127 } 128 129 return output, nil 130 } 131 132 func renderPartsToWriter(parts cloudInitParts, writer io.Writer) error { 133 mimeWriter := multipart.NewWriter(writer) 134 defer mimeWriter.Close() 135 136 // we need to set the boundary explictly, otherwise the boundary is random 137 // and this causes terraform to complain about the resource being different 138 if err := mimeWriter.SetBoundary("MIMEBOUNDARY"); err != nil { 139 return err 140 } 141 142 writer.Write([]byte(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\n", mimeWriter.Boundary()))) 143 writer.Write([]byte("MIME-Version: 1.0\r\n\r\n")) 144 145 for _, part := range parts { 146 header := textproto.MIMEHeader{} 147 if part.ContentType == "" { 148 header.Set("Content-Type", "text/plain") 149 } else { 150 header.Set("Content-Type", part.ContentType) 151 } 152 153 header.Set("MIME-Version", "1.0") 154 header.Set("Content-Transfer-Encoding", "7bit") 155 156 if part.Filename != "" { 157 header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, part.Filename)) 158 } 159 160 if part.MergeType != "" { 161 header.Set("X-Merge-Type", part.MergeType) 162 } 163 164 partWriter, err := mimeWriter.CreatePart(header) 165 if err != nil { 166 return err 167 } 168 169 _, err = partWriter.Write([]byte(part.Content)) 170 if err != nil { 171 return err 172 } 173 } 174 175 return nil 176 } 177 178 type cloudInitPart struct { 179 ContentType string 180 MergeType string 181 Filename string 182 Content string 183 } 184 185 type cloudInitParts []cloudInitPart