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