github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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": &schema.Schema{ 23 Type: schema.TypeList, 24 Required: true, 25 Elem: &schema.Resource{ 26 Schema: map[string]*schema.Schema{ 27 "content_type": &schema.Schema{ 28 Type: schema.TypeString, 29 Optional: true, 30 }, 31 "content": &schema.Schema{ 32 Type: schema.TypeString, 33 Required: true, 34 }, 35 "filename": &schema.Schema{ 36 Type: schema.TypeString, 37 Optional: true, 38 }, 39 "merge_type": &schema.Schema{ 40 Type: schema.TypeString, 41 Optional: true, 42 }, 43 }, 44 }, 45 }, 46 "gzip": &schema.Schema{ 47 Type: schema.TypeBool, 48 Optional: true, 49 Default: true, 50 }, 51 "base64_encode": &schema.Schema{ 52 Type: schema.TypeBool, 53 Optional: true, 54 Default: true, 55 }, 56 "rendered": &schema.Schema{ 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 := v.(map[string]interface{}) 88 89 part := cloudInitPart{} 90 if p, ok := p["content_type"]; ok { 91 part.ContentType = p.(string) 92 } 93 if p, ok := p["content"]; ok { 94 part.Content = p.(string) 95 } 96 if p, ok := p["merge_type"]; ok { 97 part.MergeType = p.(string) 98 } 99 if p, ok := p["filename"]; ok { 100 part.Filename = p.(string) 101 } 102 cloudInitParts[i] = part 103 } 104 105 var buffer bytes.Buffer 106 107 var err error 108 if gzipOutput { 109 gzipWriter := gzip.NewWriter(&buffer) 110 err = renderPartsToWriter(cloudInitParts, gzipWriter) 111 gzipWriter.Close() 112 } else { 113 err = renderPartsToWriter(cloudInitParts, &buffer) 114 } 115 if err != nil { 116 return "", err 117 } 118 119 output := "" 120 if base64Output { 121 output = base64.StdEncoding.EncodeToString(buffer.Bytes()) 122 } else { 123 output = buffer.String() 124 } 125 126 return output, nil 127 } 128 129 func renderPartsToWriter(parts cloudInitParts, writer io.Writer) error { 130 mimeWriter := multipart.NewWriter(writer) 131 defer mimeWriter.Close() 132 133 // we need to set the boundary explictly, otherwise the boundary is random 134 // and this causes terraform to complain about the resource being different 135 if err := mimeWriter.SetBoundary("MIMEBOUNDARY"); err != nil { 136 return err 137 } 138 139 writer.Write([]byte(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\n", mimeWriter.Boundary()))) 140 writer.Write([]byte("MIME-Version: 1.0\r\n")) 141 142 for _, part := range parts { 143 header := textproto.MIMEHeader{} 144 if part.ContentType == "" { 145 header.Set("Content-Type", "text/plain") 146 } else { 147 header.Set("Content-Type", part.ContentType) 148 } 149 150 header.Set("MIME-Version", "1.0") 151 header.Set("Content-Transfer-Encoding", "7bit") 152 153 if part.Filename != "" { 154 header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, part.Filename)) 155 } 156 157 if part.MergeType != "" { 158 header.Set("X-Merge-Type", part.MergeType) 159 } 160 161 partWriter, err := mimeWriter.CreatePart(header) 162 if err != nil { 163 return err 164 } 165 166 _, err = partWriter.Write([]byte(part.Content)) 167 if err != nil { 168 return err 169 } 170 } 171 172 return nil 173 } 174 175 type cloudInitPart struct { 176 ContentType string 177 MergeType string 178 Filename string 179 Content string 180 } 181 182 type cloudInitParts []cloudInitPart