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