refresh builder docs
This commit is contained in:
parent
51d45ab77f
commit
a34a33296a
|
@ -10,23 +10,26 @@ sidebar_title: Custom Builders
|
||||||
|
|
||||||
Packer Builders are the components of Packer responsible for creating a
|
Packer Builders are the components of Packer responsible for creating a
|
||||||
machine, bringing it to a point where it can be provisioned, and then turning
|
machine, bringing it to a point where it can be provisioned, and then turning
|
||||||
that provisioned machine into some sort of machine image. Several builders are
|
that provisioned machine into a machine image. Several builders are
|
||||||
officially distributed with Packer itself, such as the AMI builder, the VMware
|
officially maintained and distributed by the HashiCorp Packer team -- these
|
||||||
builder, etc. However, it is possible to write custom builders using the Packer
|
builders include builders for creating images on Amazon EC2, VMWare, Google
|
||||||
plugin interface, and this page documents how to do that.
|
Compute Engine, and many more. It is also possible to write custom builders
|
||||||
|
using the Packer plugin interface, and this page documents how to do that.
|
||||||
|
|
||||||
Prior to reading this page, it is assumed you have read the page on [plugin
|
Prior to reading this page, you should read the page on [plugin development
|
||||||
development basics](/docs/extending/plugins).
|
basics](/docs/extending/plugins).
|
||||||
|
|
||||||
~> **Warning!** This is an advanced topic. If you're new to Packer, we
|
~> **Warning!** This is an advanced topic. If you're new to Packer, we
|
||||||
recommend getting a bit more comfortable before you dive into writing plugins.
|
recommend getting comfortable with using Packer and its officially maintained
|
||||||
|
plugins before before you dive into writing plugins of your own.
|
||||||
|
|
||||||
|
Custom plugins are written in [golang](https://golang.org/), so this guide
|
||||||
|
assumes that you have some familiarity with that programming language.
|
||||||
|
|
||||||
## The Interface
|
## The Interface
|
||||||
|
|
||||||
The interface that must be implemented for a builder is the `packer.Builder`
|
To create your own builder, you must create a struct that implements the
|
||||||
interface. It is reproduced below for reference. The actual interface in the
|
`packer.Builder` interface. It is reproduced below for reference.
|
||||||
source code contains some basic documentation as well explaining what each
|
|
||||||
method should do.
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Builder interface {
|
type Builder interface {
|
||||||
|
@ -45,72 +48,100 @@ function, check our
|
||||||
|
|
||||||
### The "Prepare" Method
|
### The "Prepare" Method
|
||||||
|
|
||||||
The `Prepare` method for each builder is called prior to any runs with the
|
The `Prepare` method for each builder will be called by the Packer core
|
||||||
configuration that was given in the template. This is passed in as an array of
|
at the beginning of the build. Its purpose is to parse and validate the
|
||||||
`interface{}` types, but is generally `map[string]interface{}`. The prepare
|
configuration template provided to Packer with `packer build
|
||||||
method is responsible for translating this configuration into an internal
|
your_packer_template.json`, but not to execute API calls or begin creating any
|
||||||
structure, validating it, and returning any errors.
|
resources or artifacts.
|
||||||
|
|
||||||
For multiple parameters, they should be merged together into the final
|
The configuration from your Packer template is passed into the Prepare() method
|
||||||
configuration, with later parameters overwriting any previous configuration.
|
as an array of `interface{}` types, but is generally `map[string]interface{}`.
|
||||||
The exact semantics of the merge are left to the builder author.
|
The Prepare 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
|
If multiple parameters are passed into Prepare(), they should be merged together
|
||||||
[mapstructure](https://godoc.org/github.com/mitchellh/mapstructure) library is
|
into the final configuration, with later parameters overwriting any previous
|
||||||
recommended. Mapstructure will take an `interface{}` and decode it into an
|
configuration. The exact semantics of the merge are left to the builder author.
|
||||||
arbitrarily complex struct. If there are any errors, it generates very human
|
|
||||||
friendly errors that can be returned directly from the prepare method.
|
|
||||||
|
|
||||||
While it is not actively enforced, **no side effects** should occur from
|
We recommend that you use the
|
||||||
running the `Prepare` method. Specifically, don't create files, don't launch
|
[mapstructure](https://godoc.org/github.com/mitchellh/mapstructure) library to
|
||||||
virtual machines, etc. Prepare's purpose is solely to configure the builder and
|
decode the interface{} into a meaningful structure. Mapstructure will take an
|
||||||
validate the configuration.
|
`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 prepare method. You can find many usage examples of this library within
|
||||||
|
the Prepare() methods of HashiCorp-maintained Packer plugins.
|
||||||
|
|
||||||
In addition to normal configuration, Packer will inject a
|
While Packer does not actively enforce this, **no side effects** should occur
|
||||||
`map[string]interface{}` with a key of `packer.DebugConfigKey` set to boolean
|
from running the `Prepare` method. Specifically: don't create files, don't
|
||||||
|
launch virtual machines, etc. Prepare's purpose is solely to load the
|
||||||
|
configuration from the template into a format usable by your builder, to
|
||||||
|
validate that configuration, and to apply necessary defaults to that
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
In addition to the configuration provided in the Packer template, Packer will
|
||||||
|
also supply a [common.PackerConfig](https://github.com/hashicorp/packer-plugin-sdk/blob/8a28198491f70deca3824ce452adf6f9bd507880/common/packer_config.go#L44)
|
||||||
|
containing meta-information such as the build name, builder type, core version,
|
||||||
|
etc, and coded into a `map[string]interface{}`. One important piece of
|
||||||
|
meta information in this map is the `packer.DebugConfigKey` set to boolean
|
||||||
`true` if debug mode is enabled for the build. If this is set to true, then the
|
`true` if debug mode is enabled for the build. If this is set to true, then the
|
||||||
builder should enable a debug mode which assists builder developers and
|
builder should enable a debug mode which assists builder developers and
|
||||||
advanced users to introspect what is going on during a build. During debug
|
advanced users to introspect what is going on during a build. During debug
|
||||||
builds, parallelism is strictly disabled, so it is safe to request input from
|
builds, parallelism is strictly disabled, so it is safe to request input from
|
||||||
stdin and so on.
|
stdin and so on.
|
||||||
|
|
||||||
|
Prepare() returns an array of strings and an error. The array of strings is a
|
||||||
|
special list of keys for variables created at runtime which your builder will
|
||||||
|
make accessible to provisioners using the generatedData mechanism (see below for
|
||||||
|
more details) An example could be an instance ID for the cloud instance created
|
||||||
|
by your builder. If you do not plan to make use of the generatedData feature,
|
||||||
|
just return an empty list. The error should be used if there is something wrong
|
||||||
|
with the user-provided configuration and the build should not proceed.
|
||||||
|
|
||||||
### The "Run" Method
|
### The "Run" Method
|
||||||
|
|
||||||
`Run` is where all the interesting stuff happens. Run is executed, often in
|
`Run` is executed, often in parallel for multiple builders, to actually build
|
||||||
parallel for multiple builders, to actually build the machine, provision it,
|
the machine, provision it, and create the resulting machine image, which is
|
||||||
and create the resulting machine image, which is returned as an implementation
|
returned as an implementation of the `packer.Artifact` interface.
|
||||||
of the `packer.Artifact` interface.
|
|
||||||
|
|
||||||
The `Run` method takes three parameters. These are all very useful. The
|
The `Run` method takes three parameters. The context.Context used to cancel the
|
||||||
`packer.Ui` object is used to send output to the console. `packer.Hook` is used
|
build. The `packer.Ui` object is used to send output to the console.
|
||||||
to execute hooks, which are covered in more detail in the hook section below.
|
`packer.Hook` is used to execute hooks, which are covered in more detail in the
|
||||||
And `packer.Cache` is used to store files between multiple Packer runs, and is
|
Provisioning section below.
|
||||||
covered in more detail in the cache section below.
|
|
||||||
|
|
||||||
Because builder runs are typically a complex set of many steps, the
|
Because builder runs are typically a complex set of many steps, the
|
||||||
|
packer-plugin-sdk contains a
|
||||||
[multistep](https://godoc.org/github.com/hashicorp/packer-plugin-sdk/multistep)
|
[multistep](https://godoc.org/github.com/hashicorp/packer-plugin-sdk/multistep)
|
||||||
package is recommended to bring order to the complexity. Multistep is a library
|
module. Multistep allows you to separate your build logic into multiple distinct
|
||||||
which allows you to separate your logic into multiple distinct "steps" and
|
"steps" with separate run and cleanup phases, and run them in order. It
|
||||||
string them together. It fully supports cancellation mid-step and so on. Please
|
supports cancellation mid-step, pausing between steps when debugging, the CLI's
|
||||||
check it out, it is how the built-in builders are all implemented.
|
on-error flag, and more. All of the HashiCorp maintained builders make use of
|
||||||
|
this module, and while it is not required for builder implementation, it will
|
||||||
|
help you create your builder in a way that matches user and Packer Core
|
||||||
|
assumptions. The SDK also provides a number of "helper" generic steps that may
|
||||||
|
prevent you from having to re-implement work that has already been done by the
|
||||||
|
HashiCorp maintainers. Examples include sending boot commands, connecting to
|
||||||
|
SSH, and creating virtual CDs to mount on your VM. Take a look at the
|
||||||
|
[communicator](https://github.com/hashicorp/packer-plugin-sdk/tree/main/communicator)
|
||||||
|
and
|
||||||
|
[multistep/commonsteps](https://github.com/hashicorp/packer-plugin-sdk/tree/main/multistep/commonsteps)
|
||||||
|
modules in the SDK to see what tools are available to you.
|
||||||
|
|
||||||
Finally, as a result of `Run`, an implementation of `packer.Artifact` should be
|
Finally, `Run` should return an implementation of `packer.Artifact`. More
|
||||||
returned. More details on creating a `packer.Artifact` are covered in the
|
details on creating a `packer.Artifact` are covered in the artifact section
|
||||||
artifact section below. If something goes wrong during the build, an error can
|
below. If something goes wrong during the build that prevents an artifact from
|
||||||
be returned, as well. Note that it is perfectly fine to produce no artifact and
|
being correctly created, `Run` should return an error and a nil artifact. Note
|
||||||
no error, although this is rare.
|
that your builder is allowed to produce no artifact and no error, although this
|
||||||
|
is a rare use case.
|
||||||
|
|
||||||
### Cancellation
|
### Cancellation
|
||||||
|
|
||||||
The `Run` method is often run in parallel.
|
#### With the "Cancel" Method ( for plugins for Packer < v1.3 )
|
||||||
|
|
||||||
#### With the "Cancel" Method ( up until packer 1.3 )
|
|
||||||
|
|
||||||
The `Cancel` method can be called at any time and requests cancellation of any
|
The `Cancel` method can be called at any time and requests cancellation of any
|
||||||
builder run in progress. This method should block until the run actually stops.
|
builder run in progress. This method should block until the run actually stops.
|
||||||
Not that the Cancel method will no longer be called since packer 1.4.0.
|
Not that the Cancel method will not be called by Packer versions >= 1.4.0.
|
||||||
|
|
||||||
#### Context cancellation ( from packer 1.4 )
|
#### Context cancellation ( from Packer v1.4 )
|
||||||
|
|
||||||
The `<-ctx.Done()` can unblock at any time and signifies request for
|
The `<-ctx.Done()` can unblock at any time and signifies request for
|
||||||
cancellation of any builder run in progress.
|
cancellation of any builder run in progress.
|
||||||
|
@ -118,32 +149,36 @@ cancellation of any builder run in progress.
|
||||||
Cancels are most commonly triggered by external interrupts, such as the user
|
Cancels are most commonly triggered by external interrupts, such as the user
|
||||||
pressing `Ctrl-C`. Packer will only exit once all the builders clean up, so it
|
pressing `Ctrl-C`. Packer will only exit once all the builders clean up, so it
|
||||||
is important that you architect your builder in a way that it is quick to
|
is important that you architect your builder in a way that it is quick to
|
||||||
respond to these cancellations and clean up after itself.
|
respond to these cancellations and clean up after itself. If your builder makes
|
||||||
|
a long-running call, you should consider the possibility that a user may cancel
|
||||||
|
the build during that call, and make sure that such a cancellation is not
|
||||||
|
blocked.
|
||||||
|
|
||||||
## Creating an Artifact
|
## Creating an Artifact
|
||||||
|
|
||||||
The `Run` method is expected to return an implementation of the
|
The `Run` method is expected to return an implementation of the
|
||||||
`packer.Artifact` interface. Each builder must create their own implementation.
|
`packer.Artifact` interface. Each builder must create its own implementation of
|
||||||
The interface has ample documentation to help you get started.
|
this interface.
|
||||||
|
|
||||||
The only part of an artifact that may be confusing is the `BuilderId` method.
|
Most of the pieces of an artifact should be fairly self-explanatory by reading
|
||||||
This method must return an absolutely unique ID for the builder. In general, I
|
the [packer.Artifact interface
|
||||||
follow the practice of making the ID contain my GitHub username and then the
|
documentation](https://godoc.org/github.com/hashicorp/packer-plugin-sdk/packer#Artifact).
|
||||||
platform it is building for. For example, the builder ID of the VMware builder
|
|
||||||
is "hashicorp.vmware" or something similar.
|
However one part of an artifact that may be confusing is the `BuilderId` method.
|
||||||
|
This method must return an absolutely unique ID for the builder. In general, a
|
||||||
|
reasonable ID would be the github username or organization that created the
|
||||||
|
builder, followed by the platform it is building for. For example, the builder
|
||||||
|
ID of the VMware builder is "hashicorp.vmware".
|
||||||
|
|
||||||
Post-processors use the builder ID value in order to make some assumptions
|
Post-processors use the builder ID value in order to make some assumptions
|
||||||
about the artifact results, so it is important it never changes.
|
about the artifact results, so it is important it never changes.
|
||||||
|
|
||||||
Other than the builder ID, the rest should be self-explanatory by reading the
|
|
||||||
[packer.Artifact interface
|
|
||||||
documentation](https://godoc.org/github.com/hashicorp/packer-plugin-sdk/packer#Artifact).
|
|
||||||
|
|
||||||
## Provisioning
|
## Provisioning
|
||||||
|
|
||||||
Packer has built-in support for provisioning, but the moment when provisioning
|
Packer has built-in support for provisioning using the Provisioner plugins. But
|
||||||
runs must be invoked by the builder itself, since only the builder knows when
|
builders themselves, rather than the Packer core, must determine when to invoke
|
||||||
the machine is running and ready for communication.
|
the provisioners since only the builder knows when the machine is running and
|
||||||
|
ready for communication.
|
||||||
|
|
||||||
When the machine is ready to be provisioned, run the `packer.HookProvision`
|
When the machine is ready to be provisioned, run the `packer.HookProvision`
|
||||||
hook, making sure the communicator is not nil, since this is required for
|
hook, making sure the communicator is not nil, since this is required for
|
||||||
|
@ -156,22 +191,25 @@ hook.Run(context.Context, packer.HookProvision, ui, comm, nil)
|
||||||
At this point, Packer will run the provisioners and no additional work is
|
At this point, Packer will run the provisioners and no additional work is
|
||||||
necessary.
|
necessary.
|
||||||
|
|
||||||
-> **Note:** Hooks are still undergoing thought around their general design
|
If you are using the multistep tooling, the Packer plugin SDK contains a
|
||||||
and will likely change in a future version. They aren't fully "baked" yet, so
|
generic StepProvision which handles execution the provision hook for you and
|
||||||
they aren't documented here other than to tell you how to hook in provisioners.
|
automatically supplies any custom builder generatedData you would like to
|
||||||
|
provide to procisioners (see below for more details on generatedData.)
|
||||||
|
|
||||||
## Template Engine
|
## Template Engine
|
||||||
|
|
||||||
### Build variables
|
### Build variables
|
||||||
|
|
||||||
Packer makes it possible to provide custom template engine variables to be shared with
|
Packer makes it possible to provide custom template engine variables to be
|
||||||
provisioners and post-processors using the `build` function.
|
shared with provisioners and post-processors using the `build` function.
|
||||||
|
Json template `build` docs are [here](https://www.packer.io/docs/templates/engine#build)
|
||||||
|
and HCL template build docs are [here](https://www.packer.io/docs/from-1.5/contextual-variables#build-variables).
|
||||||
|
|
||||||
Part of the builder interface changes made in 1.5.0 was to make builder Prepare() methods
|
As of Packer v1.5.0, builder Prepare() methods return a list of custom variables
|
||||||
return a list of custom variables which we call `generated data`.
|
which we call `generated data`. We use that list of variables to generate a
|
||||||
We use that list of variables to generate a custom placeholder map per builder that
|
custom placeholder map per builder that combines custom variables with the
|
||||||
combines custom variables with the placeholder map of default build variables created by Packer.
|
placeholder map of default build variables created by Packer. Here's an example
|
||||||
Here's an example snippet telling packer what will be made available by the builder:
|
snippet telling packer what will be made available by the builder:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
|
@ -182,11 +220,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Returning the custom variable name(s) within the `generated_data` placeholder is necessary
|
When a user provides a Packer template that uses a `build` function, Packer
|
||||||
for the template containing the build variable(s) to validate.
|
validates that the key in the `build` function exists for a given builder using
|
||||||
|
this generatedData array. If it does not exist, then Packer validation will
|
||||||
|
fail.
|
||||||
|
|
||||||
Once the placeholder is set, it's necessary to pass the variables' real values when calling
|
Once the placeholder is set, it's necessary to pass the variables' real values
|
||||||
the provisioner. This can be done as the example below:
|
when calling the provisioner. This can be done as the example below:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||||
|
@ -201,10 +241,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In order to make these same variables and the Packer default ones also available to post-processor,
|
In order to make these same variables and the Packer default ones also available
|
||||||
it is necessary to add them to the Artifact returned by the builder. This can be done by adding an attribute of type
|
to post-processors, your builder will need to add them to its Artifact.
|
||||||
`map[string]interface{}` to the Artifact and putting the generated data in it. The post-processor
|
This can be done by adding an attribute of type `map[string]interface{}` to the
|
||||||
will access this data later via the Artifact's `State` method.
|
Artifact and putting the generated data in it. The post-processor will access
|
||||||
|
this data later via the Artifact's `State` method.
|
||||||
|
|
||||||
The Artifact code should be implemented similar to the below:
|
The Artifact code should be implemented similar to the below:
|
||||||
|
|
||||||
|
@ -226,8 +267,8 @@ func (a *Artifact) State(name string) interface{} {
|
||||||
// ...
|
// ...
|
||||||
```
|
```
|
||||||
|
|
||||||
The builder should return the above Artifact containing the generated data and the code should be similar
|
The builder should return the above Artifact containing the generated data and
|
||||||
to the example snippet below:
|
the code should be similar to the example snippet below:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||||
|
@ -240,7 +281,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The code above assigns the `generated_data` state to the `StateData` map with the key `generated_data`.
|
The code above assigns the `generated_data` state to the `StateData` map with
|
||||||
|
the key `generated_data`.
|
||||||
|
|
||||||
Here some example of how this data will be used by post-processors:
|
Here some example of how this data will be used by post-processors:
|
||||||
|
|
||||||
|
@ -254,4 +296,13 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, source pa
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To know more about the template engine build function, please refer to the [template engine docs](/docs/templates/engine).
|
## Putting it all together
|
||||||
|
|
||||||
|
This page has focused up until now on the implementation details for the Builder
|
||||||
|
interface. You will need to create a server and store the builder in a binary in
|
||||||
|
order to make it available to the Packer core as a plugin. We have created a
|
||||||
|
[scaffolding](https://github.com/hashicorp/packer-plugin-scaffolding/blob/main/builder/scaffolding/builder.go)
|
||||||
|
repo to give you an idea of the relationship between the builder
|
||||||
|
implementation and the server implementation within a repository, and then read
|
||||||
|
[basics of how Plugins work](/docs/extending/plugins), which breaks down all the
|
||||||
|
server details.
|
Loading…
Reference in New Issue