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