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  }