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