github.com/amanya/packer@v0.12.1-0.20161117214323-902ac5ab2eb6/post-processor/manifest/post-processor.go (about) 1 package manifest 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "os" 9 "path/filepath" 10 "time" 11 12 "github.com/mitchellh/packer/common" 13 "github.com/mitchellh/packer/helper/config" 14 "github.com/mitchellh/packer/packer" 15 "github.com/mitchellh/packer/template/interpolate" 16 ) 17 18 type Config struct { 19 common.PackerConfig `mapstructure:",squash"` 20 21 Filename string `mapstructure:"filename"` 22 StripPath bool `mapstructure:"strip_path"` 23 ctx interpolate.Context 24 } 25 26 type PostProcessor struct { 27 config Config 28 } 29 30 type ManifestFile struct { 31 Builds []Artifact `json:"builds"` 32 LastRunUUID string `json:"last_run_uuid"` 33 } 34 35 func (p *PostProcessor) Configure(raws ...interface{}) error { 36 err := config.Decode(&p.config, &config.DecodeOpts{ 37 Interpolate: true, 38 InterpolateContext: &p.config.ctx, 39 InterpolateFilter: &interpolate.RenderFilter{ 40 Exclude: []string{}, 41 }, 42 }, raws...) 43 if err != nil { 44 return err 45 } 46 47 if p.config.Filename == "" { 48 p.config.Filename = "packer-manifest.json" 49 } 50 51 return nil 52 } 53 54 func (p *PostProcessor) PostProcess(ui packer.Ui, source packer.Artifact) (packer.Artifact, bool, error) { 55 artifact := &Artifact{} 56 57 var err error 58 var fi os.FileInfo 59 60 // Create the current artifact. 61 for _, name := range source.Files() { 62 af := ArtifactFile{} 63 if fi, err = os.Stat(name); err == nil { 64 af.Size = fi.Size() 65 } 66 if p.config.StripPath { 67 af.Name = filepath.Base(name) 68 } else { 69 af.Name = name 70 } 71 artifact.ArtifactFiles = append(artifact.ArtifactFiles, af) 72 } 73 artifact.ArtifactId = source.Id() 74 artifact.BuilderType = p.config.PackerBuilderType 75 artifact.BuildName = p.config.PackerBuildName 76 artifact.BuildTime = time.Now().Unix() 77 // Since each post-processor runs in a different process we need a way to 78 // coordinate between various post-processors in a single packer run. We do 79 // this by setting a UUID per run and tracking this in the manifest file. 80 // When we detect that the UUID in the file is the same, we know that we are 81 // part of the same run and we simply add our data to the list. If the UUID 82 // is different we will check the -force flag and decide whether to truncate 83 // the file before we proceed. 84 artifact.PackerRunUUID = os.Getenv("PACKER_RUN_UUID") 85 86 // Create a lock file with exclusive access. If this fails we will retry 87 // after a delay. 88 lockFilename := p.config.Filename + ".lock" 89 for i := 0; i < 3; i++ { 90 // The file should not be locked for very long so we'll keep this short. 91 time.Sleep((time.Duration(i) * 200 * time.Millisecond)) 92 _, err = os.OpenFile(lockFilename, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) 93 if err == nil { 94 break 95 } 96 log.Printf("Error locking manifest file for reading and writing. Will sleep and retry. %s", err) 97 } 98 defer os.Remove(lockFilename) 99 100 // TODO fix error on first run: 101 // * Post-processor failed: open packer-manifest.json: no such file or directory 102 // 103 // Read the current manifest file from disk 104 contents := []byte{} 105 if contents, err = ioutil.ReadFile(p.config.Filename); err != nil && !os.IsNotExist(err) { 106 return source, true, fmt.Errorf("Unable to open %s for reading: %s", p.config.Filename, err) 107 } 108 109 // Parse the manifest file JSON, if we have one 110 manifestFile := &ManifestFile{} 111 if len(contents) > 0 { 112 if err = json.Unmarshal(contents, manifestFile); err != nil { 113 return source, true, fmt.Errorf("Unable to parse content from %s: %s", p.config.Filename, err) 114 } 115 } 116 117 // If -force is set and we are not on same run, truncate the file. Otherwise 118 // we will continue to add new builds to the existing manifest file. 119 if p.config.PackerForce && os.Getenv("PACKER_RUN_UUID") != manifestFile.LastRunUUID { 120 manifestFile = &ManifestFile{} 121 } 122 123 // Add the current artifact to the manifest file 124 manifestFile.Builds = append(manifestFile.Builds, *artifact) 125 manifestFile.LastRunUUID = os.Getenv("PACKER_RUN_UUID") 126 127 // Write JSON to disk 128 if out, err := json.MarshalIndent(manifestFile, "", " "); err == nil { 129 if err = ioutil.WriteFile(p.config.Filename, out, 0664); err != nil { 130 return source, true, fmt.Errorf("Unable to write %s: %s", p.config.Filename, err) 131 } 132 } else { 133 return source, true, fmt.Errorf("Unable to marshal JSON %s", err) 134 } 135 136 return source, true, nil 137 }