github.com/raghuse92/packer@v1.3.2/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/hashicorp/packer/common" 13 "github.com/hashicorp/packer/helper/config" 14 "github.com/hashicorp/packer/packer" 15 "github.com/hashicorp/packer/template/interpolate" 16 ) 17 18 type Config struct { 19 common.PackerConfig `mapstructure:",squash"` 20 21 OutputPath string `mapstructure:"output"` 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.OutputPath == "" { 48 p.config.OutputPath = "packer-manifest.json" 49 } 50 51 if err = interpolate.Validate(p.config.OutputPath, &p.config.ctx); err != nil { 52 return fmt.Errorf("Error parsing target template: %s", err) 53 } 54 55 return nil 56 } 57 58 func (p *PostProcessor) PostProcess(ui packer.Ui, source packer.Artifact) (packer.Artifact, bool, error) { 59 artifact := &Artifact{} 60 61 var err error 62 var fi os.FileInfo 63 64 // Create the current artifact. 65 for _, name := range source.Files() { 66 af := ArtifactFile{} 67 if fi, err = os.Stat(name); err == nil { 68 af.Size = fi.Size() 69 } 70 if p.config.StripPath { 71 af.Name = filepath.Base(name) 72 } else { 73 af.Name = name 74 } 75 artifact.ArtifactFiles = append(artifact.ArtifactFiles, af) 76 } 77 artifact.ArtifactId = source.Id() 78 artifact.BuilderType = p.config.PackerBuilderType 79 artifact.BuildName = p.config.PackerBuildName 80 artifact.BuildTime = time.Now().Unix() 81 // Since each post-processor runs in a different process we need a way to 82 // coordinate between various post-processors in a single packer run. We do 83 // this by setting a UUID per run and tracking this in the manifest file. 84 // When we detect that the UUID in the file is the same, we know that we are 85 // part of the same run and we simply add our data to the list. If the UUID 86 // is different we will check the -force flag and decide whether to truncate 87 // the file before we proceed. 88 artifact.PackerRunUUID = os.Getenv("PACKER_RUN_UUID") 89 90 // Create a lock file with exclusive access. If this fails we will retry 91 // after a delay. 92 lockFilename := p.config.OutputPath + ".lock" 93 for i := 0; i < 3; i++ { 94 // The file should not be locked for very long so we'll keep this short. 95 time.Sleep((time.Duration(i) * 200 * time.Millisecond)) 96 _, err = os.OpenFile(lockFilename, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) 97 if err == nil { 98 break 99 } 100 log.Printf("Error locking manifest file for reading and writing. Will sleep and retry. %s", err) 101 } 102 defer os.Remove(lockFilename) 103 104 // Read the current manifest file from disk 105 contents := []byte{} 106 if contents, err = ioutil.ReadFile(p.config.OutputPath); err != nil && !os.IsNotExist(err) { 107 return source, true, fmt.Errorf("Unable to open %s for reading: %s", p.config.OutputPath, err) 108 } 109 110 // Parse the manifest file JSON, if we have one 111 manifestFile := &ManifestFile{} 112 if len(contents) > 0 { 113 if err = json.Unmarshal(contents, manifestFile); err != nil { 114 return source, true, fmt.Errorf("Unable to parse content from %s: %s", p.config.OutputPath, err) 115 } 116 } 117 118 // If -force is set and we are not on same run, truncate the file. Otherwise 119 // we will continue to add new builds to the existing manifest file. 120 if p.config.PackerForce && os.Getenv("PACKER_RUN_UUID") != manifestFile.LastRunUUID { 121 manifestFile = &ManifestFile{} 122 } 123 124 // Add the current artifact to the manifest file 125 manifestFile.Builds = append(manifestFile.Builds, *artifact) 126 manifestFile.LastRunUUID = os.Getenv("PACKER_RUN_UUID") 127 128 // Write JSON to disk 129 if out, err := json.MarshalIndent(manifestFile, "", " "); err == nil { 130 if err = ioutil.WriteFile(p.config.OutputPath, out, 0664); err != nil { 131 return source, true, fmt.Errorf("Unable to write %s: %s", p.config.OutputPath, err) 132 } 133 } else { 134 return source, true, fmt.Errorf("Unable to marshal JSON %s", err) 135 } 136 137 return source, true, nil 138 }