github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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 }