Packer
Custom Post-Processors
Packer post-processors transform one artifact into another. For example, a post-processor might compress or upload files.
In the compression example, the transformation would be taking an artifact with a set of files, compressing those files, and returning a new artifact with only a single file (the compressed archive). For the upload example, the transformation would be taking an artifact with some set of files, uploading those files, and returning an artifact with a single ID: the URL of the upload.
Post-processor plugins implement the packer.PostProcessor
interface and are
served using the plugin.ServePostProcessor
function.
This page explains how to implement and serve custom post-processors. If you want your post-processor to support HashiCorp Cloud Platform (HCP) Packer, you should also review the HCP Packer Support documentation.
Warning: This is an advanced topic that requires strong knowledge of Packer and Packer plugins.
Before You Begin
We recommend reviewing the following resources before you begin development:
- Developing Plugins - Overview
- The Go language. You must write custom plugins in Go, so this guide assumes you are familiar with the language.
The Interface
The interface that must be implemented for a post-processor is the
packer.PostProcessor
interface. It is reproduced below for reference. The
actual interface in the source code contains some basic documentation as well
explaining what each method should do.
type PostProcessor interface {
ConfigSpec() hcldec.ObjectSpec
Configure(interface{}) error
PostProcess(context.Context, Ui, Artifact) (a Artifact, keep, mustKeep bool, err error)
}
The "ConfigSpec" Method
This method returns a hcldec.ObjectSpec, which is a spec necessary for using HCL2 templates with Packer. For information on how to use and implement this function, check our object spec docs
The "Configure" Method
The Configure
method for each post-processor is called early in the build
process to configure the post-processor. The configuration is passed in as a
raw interface{}
. The configure method is responsible for translating this
configuration into an internal structure, validating it, and returning any
errors.
For decoding the interface{}
into a meaningful structure, the
mapstructure library is
recommended. Mapstructure will take an interface{}
and decode it into an
arbitrarily complex struct. If there are any errors, it generates very
human-friendly errors that can be returned directly from the configure method.
While it is not actively enforced, no side effects should occur from
running the Configure
method. Specifically, don't create files, don't create
network connections, etc. Configure's purpose is solely to setup internal state
and validate the configuration as much as possible.
Configure
being run is not an indication that PostProcess
will ever run.
For example, packer validate
will run Configure
to verify the configuration
validates, but will never actually run the build.
The "PostProcess" Method
The PostProcess
method is where the real work goes. PostProcess is
responsible for taking one packer.Artifact
implementation, and transforming
it into another.
A PostProcess
call can be cancelled at any moment. Cancellation is triggered
when the done chan of the context struct (<-ctx.Done()
) unblocks .
When we say "transform," we don't mean actually modifying the existing
packer.Artifact
value itself. We mean taking the contents of the artifact and
creating a new artifact from that. For example, if we were creating a
"compress" post-processor that is responsible for compressing files, the
transformation would be taking the Files()
from the original artifact,
compressing them, and creating a new artifact with a single file: the
compressed archive.
The result signature of this method is (Artifact, bool, bool, error)
. Each
return value is explained below:
Artifact
- The newly created artifact if no errors occurred.bool
- If keep true, the input artifact will forcefully be kept. By default, Packer typically deletes all input artifacts, since the user doesn't generally want intermediary artifacts. However, some post-processors depend on the previous artifact existing. If this istrue
, it forces packer to keep the artifact around.bool
- If forceOverride is true, then any user input for keep_input_artifact is ignored and the artifact is either kept or discarded according to the value set inkeep
.error
- Non-nil if there was an error in any way. If this is the case, the other two return values are ignored.