github.com/hashicorp/packer@v1.14.3/post-processor/checksum/post-processor.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  //go:generate packer-sdc mapstructure-to-hcl2 -type Config
     5  
     6  package checksum
     7  
     8  import (
     9  	"context"
    10  	"crypto/md5"
    11  	"crypto/sha1"
    12  	"crypto/sha256"
    13  	"crypto/sha512"
    14  	"fmt"
    15  	"hash"
    16  	"io"
    17  	"os"
    18  	"path/filepath"
    19  
    20  	"github.com/hashicorp/hcl/v2/hcldec"
    21  	"github.com/hashicorp/packer-plugin-sdk/common"
    22  	packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
    23  	"github.com/hashicorp/packer-plugin-sdk/template/config"
    24  	"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
    25  )
    26  
    27  type Config struct {
    28  	common.PackerConfig `mapstructure:",squash"`
    29  
    30  	ChecksumTypes []string `mapstructure:"checksum_types"`
    31  	OutputPath    string   `mapstructure:"output"`
    32  	ctx           interpolate.Context
    33  }
    34  
    35  type PostProcessor struct {
    36  	config Config
    37  }
    38  
    39  func getHash(t string) hash.Hash {
    40  	var h hash.Hash
    41  	switch t {
    42  	case "md5":
    43  		h = md5.New()
    44  	case "sha1":
    45  		h = sha1.New()
    46  	case "sha224":
    47  		h = sha256.New224()
    48  	case "sha256":
    49  		h = sha256.New()
    50  	case "sha384":
    51  		h = sha512.New384()
    52  	case "sha512":
    53  		h = sha512.New()
    54  	}
    55  	return h
    56  }
    57  
    58  func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
    59  
    60  func (p *PostProcessor) Configure(raws ...interface{}) error {
    61  	err := config.Decode(&p.config, &config.DecodeOpts{
    62  		PluginType:         "checksum",
    63  		Interpolate:        true,
    64  		InterpolateContext: &p.config.ctx,
    65  		InterpolateFilter: &interpolate.RenderFilter{
    66  			Exclude: []string{"output"},
    67  		},
    68  	}, raws...)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	errs := new(packersdk.MultiError)
    73  
    74  	if p.config.ChecksumTypes == nil {
    75  		p.config.ChecksumTypes = []string{"md5"}
    76  	}
    77  
    78  	for _, k := range p.config.ChecksumTypes {
    79  		if h := getHash(k); h == nil {
    80  			errs = packersdk.MultiErrorAppend(errs,
    81  				fmt.Errorf("Unrecognized checksum type: %s", k))
    82  		}
    83  	}
    84  
    85  	if p.config.OutputPath == "" {
    86  		p.config.OutputPath = "packer_{{.BuildName}}_{{.BuilderType}}_{{.ChecksumType}}.checksum"
    87  	}
    88  
    89  	if err = interpolate.Validate(p.config.OutputPath, &p.config.ctx); err != nil {
    90  		errs = packersdk.MultiErrorAppend(
    91  			errs, fmt.Errorf("Error parsing target template: %s", err))
    92  	}
    93  
    94  	if len(errs.Errors) > 0 {
    95  		return errs
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifact packersdk.Artifact) (packersdk.Artifact, bool, bool, error) {
   102  	files := artifact.Files()
   103  	var h hash.Hash
   104  
   105  	var generatedData map[interface{}]interface{}
   106  	stateData := artifact.State("generated_data")
   107  	if stateData != nil {
   108  		// Make sure it's not a nil map so we can assign to it later.
   109  		generatedData = stateData.(map[interface{}]interface{})
   110  	}
   111  	// If stateData has a nil map generatedData will be nil
   112  	// and we need to make sure it's not
   113  	if generatedData == nil {
   114  		generatedData = make(map[interface{}]interface{})
   115  	}
   116  	generatedData["BuildName"] = p.config.PackerBuildName
   117  	generatedData["BuilderType"] = p.config.PackerBuilderType
   118  
   119  	newartifact := NewArtifact(artifact.Files())
   120  
   121  	for _, ct := range p.config.ChecksumTypes {
   122  		h = getHash(ct)
   123  		generatedData["ChecksumType"] = ct
   124  		p.config.ctx.Data = generatedData
   125  
   126  		for _, art := range files {
   127  			checksumFile, err := interpolate.Render(p.config.OutputPath, &p.config.ctx)
   128  			if err != nil {
   129  				return nil, false, true, err
   130  			}
   131  
   132  			if _, err := os.Stat(checksumFile); err != nil {
   133  				newartifact.files = append(newartifact.files, checksumFile)
   134  			}
   135  			if err := os.MkdirAll(filepath.Dir(checksumFile), os.FileMode(0755)); err != nil {
   136  				return nil, false, true, fmt.Errorf("unable to create dir: %s", err.Error())
   137  			}
   138  			fw, err := os.OpenFile(checksumFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0644))
   139  			if err != nil {
   140  				return nil, false, true, fmt.Errorf("unable to create file %s: %s", checksumFile, err.Error())
   141  			}
   142  			fr, err := os.Open(art)
   143  			if err != nil {
   144  				fw.Close()
   145  				return nil, false, true, fmt.Errorf("unable to open file %s: %s", art, err.Error())
   146  			}
   147  
   148  			if _, err = io.Copy(h, fr); err != nil {
   149  				fr.Close()
   150  				fw.Close()
   151  				return nil, false, true, fmt.Errorf("unable to compute %s hash for %s", ct, art)
   152  			}
   153  			fr.Close()
   154  			_, _ = fw.WriteString(fmt.Sprintf("%x\t%s\n", h.Sum(nil), filepath.Base(art)))
   155  			fw.Close()
   156  			h.Reset()
   157  		}
   158  	}
   159  
   160  	// sets keep and forceOverride to true because we don't want to accidentally
   161  	// delete the very artifact we're checksumming.
   162  	return newartifact, true, true, nil
   163  }