github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/builtin/providers/template/resource_template_dir.go (about)

     1  package template
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"crypto/sha1"
     7  	"encoding/hex"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  
    15  	"github.com/hashicorp/terraform/helper/pathorcontents"
    16  	"github.com/hashicorp/terraform/helper/schema"
    17  )
    18  
    19  func resourceDir() *schema.Resource {
    20  	return &schema.Resource{
    21  		Create: resourceTemplateDirCreate,
    22  		Read:   resourceTemplateDirRead,
    23  		Delete: resourceTemplateDirDelete,
    24  
    25  		Schema: map[string]*schema.Schema{
    26  			"source_dir": {
    27  				Type:        schema.TypeString,
    28  				Description: "Path to the directory where the files to template reside",
    29  				Required:    true,
    30  				ForceNew:    true,
    31  			},
    32  			"vars": {
    33  				Type:         schema.TypeMap,
    34  				Optional:     true,
    35  				Default:      make(map[string]interface{}),
    36  				Description:  "Variables to substitute",
    37  				ValidateFunc: validateVarsAttribute,
    38  				ForceNew:     true,
    39  			},
    40  			"destination_dir": {
    41  				Type:        schema.TypeString,
    42  				Description: "Path to the directory where the templated files will be written",
    43  				Required:    true,
    44  				ForceNew:    true,
    45  			},
    46  		},
    47  	}
    48  }
    49  
    50  func resourceTemplateDirRead(d *schema.ResourceData, meta interface{}) error {
    51  	sourceDir := d.Get("source_dir").(string)
    52  	destinationDir := d.Get("destination_dir").(string)
    53  
    54  	// If the output doesn't exist, mark the resource for creation.
    55  	if _, err := os.Stat(destinationDir); os.IsNotExist(err) {
    56  		d.SetId("")
    57  		return nil
    58  	}
    59  
    60  	// If the combined hash of the input and output directories is different from
    61  	// the stored one, mark the resource for re-creation.
    62  	//
    63  	// The output directory is technically enough for the general case, but by
    64  	// hashing the input directory as well, we make development much easier: when
    65  	// a developer modifies one of the input files, the generation is
    66  	// re-triggered.
    67  	hash, err := generateID(sourceDir, destinationDir)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	if hash != d.Id() {
    72  		d.SetId("")
    73  		return nil
    74  	}
    75  
    76  	return nil
    77  }
    78  
    79  func resourceTemplateDirCreate(d *schema.ResourceData, meta interface{}) error {
    80  	sourceDir := d.Get("source_dir").(string)
    81  	destinationDir := d.Get("destination_dir").(string)
    82  	vars := d.Get("vars").(map[string]interface{})
    83  
    84  	// Always delete the output first, otherwise files that got deleted from the
    85  	// input directory might still be present in the output afterwards.
    86  	if err := resourceTemplateDirDelete(d, meta); err != nil {
    87  		return err
    88  	}
    89  
    90  	// Create the destination directory and any other intermediate directories
    91  	// leading to it.
    92  	if _, err := os.Stat(destinationDir); err != nil {
    93  		if err := os.MkdirAll(destinationDir, 0777); err != nil {
    94  			return err
    95  		}
    96  	}
    97  
    98  	// Recursively crawl the input files/directories and generate the output ones.
    99  	err := filepath.Walk(sourceDir, func(p string, f os.FileInfo, err error) error {
   100  		if err != nil {
   101  			return err
   102  		}
   103  
   104  		if f.IsDir() {
   105  			return nil
   106  		}
   107  
   108  		relPath, _ := filepath.Rel(sourceDir, p)
   109  		return generateDirFile(p, path.Join(destinationDir, relPath), f, vars)
   110  	})
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	// Compute ID.
   116  	hash, err := generateID(sourceDir, destinationDir)
   117  	if err != nil {
   118  		return err
   119  	}
   120  	d.SetId(hash)
   121  
   122  	return nil
   123  }
   124  
   125  func resourceTemplateDirDelete(d *schema.ResourceData, _ interface{}) error {
   126  	d.SetId("")
   127  
   128  	destinationDir := d.Get("destination_dir").(string)
   129  	if _, err := os.Stat(destinationDir); os.IsNotExist(err) {
   130  		return nil
   131  	}
   132  
   133  	if err := os.RemoveAll(destinationDir); err != nil {
   134  		return fmt.Errorf("could not delete directory %q: %s", destinationDir, err)
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func generateDirFile(sourceDir, destinationDir string, f os.FileInfo, vars map[string]interface{}) error {
   141  	inputContent, _, err := pathorcontents.Read(sourceDir)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	outputContent, err := execute(inputContent, vars)
   147  	if err != nil {
   148  		return templateRenderError(fmt.Errorf("failed to render %v: %v", sourceDir, err))
   149  	}
   150  
   151  	outputDir := path.Dir(destinationDir)
   152  	if _, err := os.Stat(outputDir); err != nil {
   153  		if err := os.MkdirAll(outputDir, 0777); err != nil {
   154  			return err
   155  		}
   156  	}
   157  
   158  	err = ioutil.WriteFile(destinationDir, []byte(outputContent), f.Mode())
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  func generateID(sourceDir, destinationDir string) (string, error) {
   167  	inputHash, err := generateDirHash(sourceDir)
   168  	if err != nil {
   169  		return "", err
   170  	}
   171  	outputHash, err := generateDirHash(destinationDir)
   172  	if err != nil {
   173  		return "", err
   174  	}
   175  	checksum := sha1.Sum([]byte(inputHash + outputHash))
   176  	return hex.EncodeToString(checksum[:]), nil
   177  }
   178  
   179  func generateDirHash(directoryPath string) (string, error) {
   180  	tarData, err := tarDir(directoryPath)
   181  	if err != nil {
   182  		return "", fmt.Errorf("could not generate output checksum: %s", err)
   183  	}
   184  
   185  	checksum := sha1.Sum(tarData)
   186  	return hex.EncodeToString(checksum[:]), nil
   187  }
   188  
   189  func tarDir(directoryPath string) ([]byte, error) {
   190  	buf := new(bytes.Buffer)
   191  	tw := tar.NewWriter(buf)
   192  
   193  	writeFile := func(p string, f os.FileInfo, err error) error {
   194  		if err != nil {
   195  			return err
   196  		}
   197  
   198  		var header *tar.Header
   199  		var file *os.File
   200  
   201  		header, err = tar.FileInfoHeader(f, f.Name())
   202  		if err != nil {
   203  			return err
   204  		}
   205  		relPath, _ := filepath.Rel(directoryPath, p)
   206  		header.Name = relPath
   207  
   208  		if err := tw.WriteHeader(header); err != nil {
   209  			return err
   210  		}
   211  
   212  		if f.IsDir() {
   213  			return nil
   214  		}
   215  
   216  		file, err = os.Open(p)
   217  		if err != nil {
   218  			return err
   219  		}
   220  		defer file.Close()
   221  
   222  		_, err = io.Copy(tw, file)
   223  		return err
   224  	}
   225  
   226  	if err := filepath.Walk(directoryPath, writeFile); err != nil {
   227  		return []byte{}, err
   228  	}
   229  	if err := tw.Flush(); err != nil {
   230  		return []byte{}, err
   231  	}
   232  
   233  	return buf.Bytes(), nil
   234  }