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  }