hcp: generate fingerprints on each new build

Fingerprints are how we link a packer build to an iteration on HCP.
These are computed automatically from the Git SHA in the current state,
and are unique to the bucket/iteration.

The main problem with this approach is that while sound in theory, it
quickly falls apart when users want to run the same build configuration
twice, but expect a new image to be created.

With the current model, this fails, as the iteration with the current
SHA already exists.

While this is solvable through environment variables, or by committing a
change to the repository, we think this is not clear enough, and causes
an extra step to what should otherwise be a simple process.

Therefore, to lower the barrier of entry into HCP, we change this
behaviour with this commit.

Now, fingerprints are randomly generated ULIDs instead of a git SHA, and
a new one is always generated, unless one is already specified in the
environment.

This makes continuation of an existing iteration a conscious choice
rather than something automatic, and virtually eliminates conflicts such
as the ones described above.
test-prepare
Lucas Bajolet 3 years ago committed by Lucas Bajolet
parent 73482bb636
commit ec1d2e68f5

@ -3,6 +3,15 @@
### IMPROVEMENTS:
* bump github.com/hashicorp/hcp-sdk-go from 0.28.0 to 0.29.0.
[GH-12163](https://github.com/hashicorp/packer/pull/12163)
* hcp: iteration fingerprints used to be computed from the Git SHA of the repository
where the template is located when running packer build. This changes with this
release, and now fingerprints are automatically generated as a ULID. This implies
that continuing an existing iteration will require users to define the
fingerprint in the environment manually in order to adopt this behaviour,
otherwise, by default, a new iteration will be created. This does not impact
workflows where the fingerprint was defined through the
HCP_PACKER_ITERATION_FINGERPRINT environment variable, and these builds will work
exactly as they did before.
### BUG FIXES:
* core/hcl2: Templates with build blocks referencing an unknown source block

@ -90,12 +90,14 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
return ret
}
hcpRegistry, diags := registry.New(packerStarter)
hcpRegistry, diags := registry.New(packerStarter, c.Ui)
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
}
defer hcpRegistry.IterationStatusSummary()
err := hcpRegistry.PopulateIteration(buildCtx)
if err != nil {
return writeDiags(c.Ui, nil, hcl.Diagnostics{

@ -3,6 +3,7 @@ package registry
import (
"context"
"fmt"
"log"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models"
@ -17,6 +18,7 @@ import (
type HCLMetadataRegistry struct {
configuration *hcl2template.PackerConfig
bucket *Bucket
ui sdkpacker.Ui
}
const (
@ -42,6 +44,13 @@ func (h *HCLMetadataRegistry) PopulateIteration(ctx context.Context) error {
h.configuration.HCPVars["iterationID"] = cty.StringVal(iterationID)
sha, err := getGitSHA(h.configuration.Basedir)
if err != nil {
log.Printf("failed to get GIT SHA from environment, won't set as build labels")
} else {
h.bucket.Iteration.AddSHAToBuildLabels(sha)
}
return nil
}
@ -70,7 +79,12 @@ func (h *HCLMetadataRegistry) CompleteBuild(
return h.bucket.completeBuild(ctx, name, artifacts, buildErr)
}
func NewHCLMetadataRegistry(config *hcl2template.PackerConfig) (*HCLMetadataRegistry, hcl.Diagnostics) {
// IterationStatusSummary prints a status report in the UI if the iteration is not yet done
func (h *HCLMetadataRegistry) IterationStatusSummary() {
h.bucket.Iteration.iterationStatusSummary(h.ui)
}
func NewHCLMetadataRegistry(config *hcl2template.PackerConfig, ui sdkpacker.Ui) (*HCLMetadataRegistry, hcl.Diagnostics) {
var diags hcl.Diagnostics
if len(config.Builds) > 1 {
diags = append(diags, &hcl.Diagnostic{
@ -127,9 +141,12 @@ func NewHCLMetadataRegistry(config *hcl2template.PackerConfig) (*HCLMetadataRegi
bucket.RegisterBuildForComponent(source.String())
}
ui.Say(fmt.Sprintf("Tracking build on HCP Packer with fingerprint %q", bucket.Iteration.Fingerprint))
return &HCLMetadataRegistry{
configuration: config,
bucket: bucket,
ui: ui,
}, nil
}

@ -3,6 +3,7 @@ package registry
import (
"fmt"
git "github.com/go-git/go-git/v5"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/internal/hcp/env"
@ -87,9 +88,15 @@ func createConfiguredBucket(templateDir string, opts ...bucketConfigurationOpts)
})
}
err := bucket.Iteration.Initialize(IterationOptions{
TemplateBaseDir: templateDir,
})
err := bucket.Iteration.Initialize()
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Failed to initialize iteration",
Detail: fmt.Sprintf("The iteration failed to be initialized for bucket %q: %s",
bucket.Slug, err),
})
}
if err != nil {
diags = append(diags, &hcl.Diagnostic{
@ -109,3 +116,35 @@ func withPackerEnvConfiguration(bucket *Bucket) hcl.Diagnostics {
return nil
}
// getGitSHA returns the HEAD commit for some template dir defined in baseDir.
// If the base directory is not under version control an error is returned.
func getGitSHA(baseDir string) (string, error) {
r, err := git.PlainOpenWithOptions(baseDir, &git.PlainOpenOptions{
DetectDotGit: true,
})
if err != nil {
return "", fmt.Errorf("Packer could not read the fingerprint from git.")
}
// The config can be used to retrieve user identity. for example,
// c.User.Email. Leaving in but commented because I'm not sure we care
// about this identity right now. - Megan
//
// c, err := r.ConfigScoped(config.GlobalScope)
// if err != nil {
// return "", fmt.Errorf("Error setting git scope", err)
// }
ref, err := r.Head()
if err != nil {
// If we get there, we're in a Git dir, but HEAD cannot be read.
//
// This may happen when there's no commit in the git dir.
return "", fmt.Errorf("Packer could not read a git SHA in directory %q: %s", baseDir, err)
}
// log.Printf("Author: %v, Commit: %v\n", c.User.Email, ref.Hash())
return ref.Hash().String(), nil
}

@ -3,6 +3,7 @@ package registry
import (
"context"
"fmt"
"log"
"path/filepath"
"github.com/hashicorp/hcl/v2"
@ -15,9 +16,10 @@ import (
type JSONMetadataRegistry struct {
configuration *packer.Core
bucket *Bucket
ui sdkpacker.Ui
}
func NewJSONMetadataRegistry(config *packer.Core) (*JSONMetadataRegistry, hcl.Diagnostics) {
func NewJSONMetadataRegistry(config *packer.Core, ui sdkpacker.Ui) (*JSONMetadataRegistry, hcl.Diagnostics) {
bucket, diags := createConfiguredBucket(
filepath.Dir(config.Template.Path),
withPackerEnvConfiguration,
@ -41,9 +43,12 @@ func NewJSONMetadataRegistry(config *packer.Core) (*JSONMetadataRegistry, hcl.Di
bucket.RegisterBuildForComponent(buildName)
}
ui.Say(fmt.Sprintf("Tracking build on HCP Packer with fingerprint %q", bucket.Iteration.Fingerprint))
return &JSONMetadataRegistry{
configuration: config,
bucket: bucket,
ui: ui,
}, nil
}
@ -63,6 +68,13 @@ func (h *JSONMetadataRegistry) PopulateIteration(ctx context.Context) error {
return err
}
sha, err := getGitSHA(h.configuration.Template.Path)
if err != nil {
log.Printf("failed to get GIT SHA from environment, won't set as build labels")
} else {
h.bucket.Iteration.AddSHAToBuildLabels(sha)
}
return nil
}
@ -80,3 +92,8 @@ func (h *JSONMetadataRegistry) CompleteBuild(
) ([]sdkpacker.Artifact, error) {
return h.bucket.completeBuild(ctx, build.Name(), artifacts, buildErr)
}
// IterationStatusSummary prints a status report in the UI if the iteration is not yet done
func (h *JSONMetadataRegistry) IterationStatusSummary() {
h.bucket.Iteration.iterationStatusSummary(h.ui)
}

@ -25,3 +25,5 @@ func (r nullRegistry) CompleteBuild(
) ([]sdkpacker.Artifact, error) {
return artifacts, nil
}
func (r nullRegistry) IterationStatusSummary() {}

@ -12,15 +12,15 @@ import (
// Registry is an entity capable to orchestrate a Packer build and upload metadata to HCP
type Registry interface {
//Configure(packer.Handler)
PopulateIteration(context.Context) error
StartBuild(context.Context, sdkpacker.Build) error
CompleteBuild(ctx context.Context, build sdkpacker.Build, artifacts []sdkpacker.Artifact, buildErr error) ([]sdkpacker.Artifact, error)
IterationStatusSummary()
}
// New instanciates the appropriate registry for the Packer configuration template type.
// A nullRegistry is returned for non-HCP Packer registry enabled templates.
func New(cfg packer.Handler) (Registry, hcl.Diagnostics) {
func New(cfg packer.Handler, ui sdkpacker.Ui) (Registry, hcl.Diagnostics) {
if !IsHCPEnabled(cfg) {
return &nullRegistry{}, nil
}
@ -28,9 +28,9 @@ func New(cfg packer.Handler) (Registry, hcl.Diagnostics) {
switch config := cfg.(type) {
case *hcl2template.PackerConfig:
// Maybe rename to what it represents....
return NewHCLMetadataRegistry(config)
return NewHCLMetadataRegistry(config, ui)
case *packer.Core:
return NewJSONMetadataRegistry(config)
return NewJSONMetadataRegistry(config, ui)
}
return nil, hcl.Diagnostics{

@ -9,7 +9,6 @@ import (
)
func TestInitialize_NewBucketNewIteration(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := api.NewMockPackerClientService()
b := &Bucket{
@ -20,7 +19,7 @@ func TestInitialize_NewBucketNewIteration(t *testing.T) {
}
b.Iteration = NewIteration()
err := b.Iteration.Initialize(IterationOptions{})
err := b.Iteration.Initialize()
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -62,7 +61,6 @@ func TestInitialize_NewBucketNewIteration(t *testing.T) {
}
func TestInitialize_UnsetTemplateTypeError(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testunsettemplate")
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
@ -74,7 +72,7 @@ func TestInitialize_UnsetTemplateTypeError(t *testing.T) {
}
b.Iteration = NewIteration()
err := b.Iteration.Initialize(IterationOptions{})
err := b.Iteration.Initialize()
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -88,7 +86,6 @@ func TestInitialize_UnsetTemplateTypeError(t *testing.T) {
}
func TestInitialize_ExistingBucketNewIteration(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
@ -100,7 +97,7 @@ func TestInitialize_ExistingBucketNewIteration(t *testing.T) {
}
b.Iteration = NewIteration()
err := b.Iteration.Initialize(IterationOptions{})
err := b.Iteration.Initialize()
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -142,7 +139,6 @@ func TestInitialize_ExistingBucketNewIteration(t *testing.T) {
}
func TestInitialize_ExistingBucketExistingIteration(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
mockService.IterationAlreadyExist = true
@ -155,7 +151,7 @@ func TestInitialize_ExistingBucketExistingIteration(t *testing.T) {
}
b.Iteration = NewIteration()
err := b.Iteration.Initialize(IterationOptions{})
err := b.Iteration.Initialize()
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -212,7 +208,6 @@ func TestInitialize_ExistingBucketExistingIteration(t *testing.T) {
}
func TestInitialize_ExistingBucketCompleteIteration(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
mockService.IterationAlreadyExist = true
@ -227,7 +222,7 @@ func TestInitialize_ExistingBucketCompleteIteration(t *testing.T) {
}
b.Iteration = NewIteration()
err := b.Iteration.Initialize(IterationOptions{})
err := b.Iteration.Initialize()
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -258,7 +253,6 @@ func TestInitialize_ExistingBucketCompleteIteration(t *testing.T) {
}
func TestUpdateBuildStatus(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
mockService.IterationAlreadyExist = true
@ -271,7 +265,7 @@ func TestUpdateBuildStatus(t *testing.T) {
}
b.Iteration = NewIteration()
err := b.Iteration.Initialize(IterationOptions{})
err := b.Iteration.Initialize()
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -312,7 +306,6 @@ func TestUpdateBuildStatus(t *testing.T) {
}
func TestUpdateBuildStatus_DONENoImages(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
mockService.IterationAlreadyExist = true
@ -325,7 +318,7 @@ func TestUpdateBuildStatus_DONENoImages(t *testing.T) {
}
b.Iteration = NewIteration()
err := b.Iteration.Initialize(IterationOptions{})
err := b.Iteration.Initialize()
if err != nil {
t.Errorf("unexpected failure: %v", err)
}

@ -10,11 +10,9 @@ import (
)
func createInitialTestBucket(t testing.TB) *Bucket {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "no-fingerprint-here")
t.Helper()
bucket := NewBucketWithIteration()
err := bucket.Iteration.Initialize(IterationOptions{})
err := bucket.Iteration.Initialize()
if err != nil {
t.Errorf("failed to initialize Bucket: %s", err)
return nil
@ -289,7 +287,7 @@ func TestBucket_PopulateIteration(t *testing.T) {
mockService.BuildAlreadyDone = tt.buildCompleted
bucket := NewBucketWithIteration()
err := bucket.Iteration.Initialize(IterationOptions{})
err := bucket.Iteration.Initialize()
if err != nil {
t.Fatalf("failed when calling NewBucketWithIteration: %s", err)
}

@ -3,12 +3,17 @@ package registry
import (
"errors"
"fmt"
"math/rand"
"os"
"strings"
"sync"
"time"
git "github.com/go-git/go-git/v5"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models"
sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer"
registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image"
"github.com/hashicorp/packer/internal/hcp/env"
"github.com/oklog/ulid"
)
type Iteration struct {
@ -34,68 +39,28 @@ func NewIteration() *Iteration {
}
// Initialize prepares the iteration to be used with an active HCP Packer registry bucket.
func (i *Iteration) Initialize(opts IterationOptions) error {
func (i *Iteration) Initialize() error {
if i == nil {
return errors.New("Unexpected call to initialize for a nil Iteration")
}
// By default we try to load a Fingerprint from the environment variable.
// If no variable is defined we should try to load a fingerprint from Git, or other VCS.
// If no variable is defined we generate a new fingerprint.
i.Fingerprint = os.Getenv(env.HCPPackerBuildFingerprint)
if i.Fingerprint != "" {
return nil
}
fp, err := GetGitFingerprint(opts)
fp, err := ulid.New(ulid.Now(), ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0))
if err != nil {
return err
return fmt.Errorf("Failed to generate a fingerprint: %s", err)
}
i.Fingerprint = fp
i.Fingerprint = fp.String()
return nil
}
// GetGitFingerprint returns the HEAD commit for some template dir defined in opt.TemplateBaseDir.
// If the base directory is not under version control an error is returned.
func GetGitFingerprint(opts IterationOptions) (string, error) {
r, err := git.PlainOpenWithOptions(opts.TemplateBaseDir, &git.PlainOpenOptions{
DetectDotGit: true,
})
if err != nil {
return "", fmt.Errorf("Packer could not read the fingerprint from git." +
"\n\nIf your Packer template is not within a git managed directory, " +
"you can set the HCP_PACKER_BUILD_FINGERPRINT environment variable. " +
"The fingerprint must be less than 32 characters and can contain letters and numbers.")
}
// The config can be used to retrieve user identity. for example,
// c.User.Email. Leaving in but commented because I'm not sure we care
// about this identity right now. - Megan
//
// c, err := r.ConfigScoped(config.GlobalScope)
// if err != nil {
// return "", fmt.Errorf("Error setting git scope", err)
// }
ref, err := r.Head()
if err != nil {
// If we get there, we're in a Git dir, but HEAD cannot be read.
//
// This may happen when there's no commit in the git dir.
return "", fmt.Errorf("Packer could not read a git SHA in directory %q.\n"+
"This may happen if your template is in a git repository without any "+
"commits. You can either add a commit in this directory, or set the "+
"HCP_PACKER_BUILD_FINGERPRINT environment variable. The fingerprint "+
"must be less than 32 characters and can contain letters and numbers.\n"+
"Error: %s", opts.TemplateBaseDir, err)
}
// log.Printf("Author: %v, Commit: %v\n", c.User.Email, ref.Hash())
return ref.Hash().String(), nil
}
//StoreBuild stores a build for buildName to an active iteration.
func (i *Iteration) StoreBuild(buildName string, build *Build) {
i.builds.Store(buildName, build)
@ -151,3 +116,59 @@ func (i *Iteration) AddLabelsToBuild(buildName string, data map[string]string) e
i.StoreBuild(buildName, build)
return nil
}
// AddSHAToBuildLabels adds the Git SHA for the current iteration (if set) as a label for all the builds of the iteration
func (i *Iteration) AddSHAToBuildLabels(sha string) {
i.builds.Range(func(_, v any) bool {
b, ok := v.(*Build)
if !ok {
return true
}
b.MergeLabels(map[string]string{
"git_sha": sha,
})
return true
})
}
// RemainingBuilds returns the list of builds that are not in a DONE status
func (i *Iteration) RemainingBuilds() []*Build {
var todo []*Build
i.builds.Range(func(k, v any) bool {
build, ok := v.(*Build)
if !ok {
// Unlikely since the builds map contains only Build instances
return true
}
if build.Status != models.HashicorpCloudPackerBuildStatusDONE {
todo = append(todo, build)
}
return true
})
return todo
}
func (i *Iteration) iterationStatusSummary(ui sdkpacker.Ui) {
rem := i.RemainingBuilds()
if rem == nil {
return
}
buf := &strings.Builder{}
buf.WriteString(fmt.Sprintf(
"\nIteration %q is not complete, the following builds are not done:\n\n",
i.Fingerprint))
for _, b := range rem {
buf.WriteString(fmt.Sprintf("* %q: %s\n", b.ComponentType, b.Status))
}
buf.WriteString("\nYou may resume work on this iteration in further Packer builds by defining the following variable in your environment:\n")
buf.WriteString(fmt.Sprintf("HCP_PACKER_BUILD_FINGERPRINT=%q", i.Fingerprint))
ui.Say(buf.String())
}

@ -12,7 +12,6 @@ func TestIteration_Initialize(t *testing.T) {
var tc = []struct {
name string
fingerprint string
opts IterationOptions
setupFn func(t *testing.T)
errorExpected bool
}{
@ -26,9 +25,6 @@ func TestIteration_Initialize(t *testing.T) {
{
name: "using git fingerprint",
fingerprint: "4ec004e18e977a5b8a3a28f4b24279b6993d7e7c",
opts: IterationOptions{
TemplateBaseDir: tempdir("4ec004e18e"),
},
setupFn: func(t *testing.T) {
//nolint:errcheck
git.PlainClone(tempdir("4ec004e18e"), false, &git.CloneOptions{
@ -37,33 +33,16 @@ func TestIteration_Initialize(t *testing.T) {
Depth: 1,
})
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "4ec004e18e977a5b8a3a28f4b24279b6993d7e7c")
t.Cleanup(func() {
//nolint:errcheck
os.RemoveAll(tempdir("4ec004e18e"))
})
},
},
{
name: "using empty git directory",
opts: IterationOptions{
TemplateBaseDir: tempdir("empty-init"),
},
setupFn: func(t *testing.T) {
//nolint:errcheck
git.PlainInit(tempdir("empty-init"), false)
t.Cleanup(func() {
//nolint:errcheck
os.RemoveAll(tempdir("empty-init"))
})
},
errorExpected: true,
},
{
name: "using no fingerprint in clean directory",
opts: IterationOptions{
TemplateBaseDir: "/dev/null",
},
errorExpected: true,
},
}
@ -75,7 +54,7 @@ func TestIteration_Initialize(t *testing.T) {
}
i := NewIteration()
err := i.Initialize(tt.opts)
err := i.Initialize()
if tt.errorExpected {
t.Logf("%v", err)
if err == nil {
@ -92,7 +71,7 @@ func TestIteration_Initialize(t *testing.T) {
t.Errorf("expected %q to return with no error, but it %v", tt.name, err)
}
if i.Fingerprint != tt.fingerprint {
if tt.fingerprint != "" && i.Fingerprint != tt.fingerprint {
t.Errorf("%q failed to load the expected fingerprint %q, but got %q", tt.name, tt.fingerprint, i.Fingerprint)
}

@ -29,7 +29,7 @@ You must set the following environment variables to enable Packer to push metada
You can set these additional environment variables to control how metadata is pushed to the registry.
- `HCP_PACKER_BUILD_FINGERPRINT` - A unique identifier assigned to each build. HCP Packer uses this identifier to determine if metadata for a build on the registry is complete. By default, HCP Packer uses the HEAD Git SHA for the template file. If the template is not version controlled, you must set this environment variable to a unique value before running `packer build`.
- `HCP_PACKER_BUILD_FINGERPRINT` - A unique identifier assigned to each iteration. To reuse a fingerprint that is associated with an existing incomplete iteration you must set this environment variable. Refer to [Iteration Fingerprinting](#iteration-fingerprinting) for usage details.
- `HCP_PACKER_REGISTRY` - When set, Packer does not push image metadata to HCP Packer from an otherwise configured template. Allowed values are [0|OFF].
@ -41,4 +41,21 @@ The `hcp_packer_registry` block is only available for HCL2 Packer templates. The
Refer to [`hcp_packer_registry`](docs/templates/hcl_templates/blocks/build/hcp_packer_registry) for a full list of configuration arguments. Refer to [Custom Configuration](https://cloud.hashicorp.com/docs/packer/store-image-metadata/packer-template-configuration#custom-configuration) in the HCP Packer documentation for information and examples about how to customize image metadata.
### Iteration Fingerprinting
Packer uses a unique fingerprint for tracking the completion of builds associated to an iteration. By default a fingerprint is automatically generated by Packer during each invocation of `packer build`, unless a fingerprint is manually provided via the `HCP_PACKER_BUILD_FINGERPRINT` environment variable.
#### Fingerprints and Incomplete Iterations
When you build a template with Packer, there's always a chance that it does not succeed because of a network issue, a provisioning failure, or some upstream error. When that happens, Packer will output the generated fingerprint associated with the incomplete iteration so that you can resume building that iteration using the `HCP_PACKER_BUILD_FINGERPRINT` environment variable; an iteration can be resumed until it is marked as complete. This environment variable is necessary for resuming an incomplete iteration, otherwise Packer will create a new iteration for the build.
There are two alternatives for when and how to set your own fingerprint:
* You can set it prior to invoking `packer build` for the first time on this template. This will require the fingerprint to be unique, otherwise Packer will attempt to continue the iteration with the same fingerprint.
* You can invoke `packer build` on the template, and if it fails, you can then get the fingerprint from the output of the command and set it for subsequent runs, Packer will then continue building this iteration.
The first alternative is recommended for CI environments, as you can use environment variables from the CI to generate a unique, deterministic, fingerprint, and then re-use this in case the step fails for any reason. This will let you continue building the same iteration, rather than creating a new one on each invocation.
The second alternative is good for local builds, as you can interact with the build environment directly, and therefore can decide if you want to continue building an iteration by setting the fingerprint provided by Packer in case of failure.
Please note that in all cases, an iteration can only be continued if it has not completed yet. Once an iteration is complete, it cannot be modified, and you will have to create a new one.

Loading…
Cancel
Save