github.com/hashicorp/packer@v1.14.3/website/content/docs/plugins/creation/custom-builders.mdx (about)

     1  ---
     2  description: |
     3    You can create custom builders for Packer that extend building functionality. Learn how write custom builders using the Packer plugin interface.
     4  page_title: Create custom builders
     5  ---
     6  
     7  # Create Custom Builders
     8  
     9  Packer builders are responsible for creating a virtual machine, setting the virtual machine up for provisioning, and then turning that provisioned virtual machine into a machine image. We officially maintain and distribute several builders, including builders to create images on Amazon EC2, VMware, Google
    10  Compute Engine, and many more. Refer to the [Builders](/packer/docs/builders) documentation for details.
    11  
    12  This page explains how to use the Packer plugin interface to write custom builders. If you want your builder to support HashiCorp Cloud Platform (HCP) Packer, you should also review the [HCP Packer Support](/packer/docs/plugins/creation/hcp-support) documentation.
    13  
    14  ~> **Warning:** This is an advanced topic that requires strong knowledge of Packer and Packer plugins.
    15  
    16  ## Before You Begin
    17  
    18  We recommend reviewing the following resources before you begin development:
    19  
    20  - [Developing Plugins - Overview](/packer/docs/plugins/creation)
    21  - The [Go](https://go.dev/) language. You must write custom plugins in Go, so this guide assumes you are familiar with the language.
    22  
    23  ## The Interface
    24  
    25  To create your own builder, you must create a struct that implements the
    26  [`packer.Builder`](https://pkg.go.dev/github.com/hashicorp/packer-plugin-sdk/packer#Builder) interface. It is reproduced below for reference.
    27  
    28  ```go
    29  type Builder interface {
    30    ConfigSpec() hcldec.ObjectSpec
    31    Prepare(...interface{}) ([]string, []string, error)
    32    Run(context.Context, ui Ui, hook Hook) (Artifact, error)
    33  }
    34  ```
    35  
    36  ### The "ConfigSpec" Method
    37  
    38  This method returns a hcldec.ObjectSpec, which is a spec necessary for using
    39  HCL2 templates with Packer. For information on how to use and implement this
    40  function, check our
    41  [object spec docs](/packer/guides/hcl/component-object-spec)
    42  
    43  ### The "Prepare" Method
    44  
    45  The `Prepare` method for each builder will be called by the Packer core
    46  at the beginning of the build. Its purpose is to parse and validate the
    47  configuration template provided to Packer with `packer build your_packer_template.json`, but not to execute API calls or begin creating any
    48  resources or artifacts.
    49  
    50  The configuration from your Packer template is passed into the Prepare() method
    51  as an array of `interface{}` types, but is generally `map[string]interface{}`.
    52  The Prepare method is responsible for translating this configuration into an
    53  internal structure, validating it, and returning any errors.
    54  
    55  If multiple parameters are passed into Prepare(), they should be merged together
    56  into the final configuration, with later parameters overwriting any previous
    57  configuration. The exact semantics of the merge are left to the builder author.
    58  
    59  We recommend that you use the
    60  [mapstructure](https://godoc.org/github.com/mitchellh/mapstructure) library to
    61  decode the `interface{}` into a meaningful structure. Mapstructure will take an
    62  `interface{}` and decode it into an arbitrarily complex struct. If there are any
    63  errors, it generates very human friendly errors that can be returned directly
    64  from the prepare method. You can find many usage examples of this library within
    65  the Prepare() methods of HashiCorp-maintained Packer plugins.
    66  
    67  While Packer does not actively enforce this, **no side effects** should occur
    68  from running the `Prepare` method. Specifically: don't create files, don't
    69  launch virtual machines, etc. Prepare's purpose is solely to load the
    70  configuration from the template into a format usable by your builder, to
    71  validate that configuration, and to apply necessary defaults to that
    72  configuration.
    73  
    74  In addition to the configuration provided in the Packer template, Packer will
    75  also supply a [common.PackerConfig](https://github.com/hashicorp/packer-plugin-sdk/blob/8a28198491f70deca3824ce452adf6f9bd507880/common/packer_config.go#L44)
    76  containing meta-information such as the build name, builder type, core version,
    77  etc, and coded into a `map[string]interface{}`. One important piece of
    78  meta information in this map is the `packer.DebugConfigKey` set to boolean
    79  `true` if debug mode is enabled for the build. If this is set to true, then the
    80  builder should enable a debug mode which assists builder developers and
    81  advanced users to introspect what is going on during a build. During debug
    82  builds, parallelism is strictly disabled, so it is safe to request input from
    83  stdin and so on.
    84  
    85  Prepare() returns an array of strings and an error. The array of strings is a
    86  special list of keys for variables created at runtime which your builder will
    87  make accessible to provisioners using the generatedData mechanism (see below for
    88  more details) An example could be an instance ID for the cloud instance created
    89  by your builder. If you do not plan to make use of the generatedData feature,
    90  just return an empty list. The error should be used if there is something wrong
    91  with the user-provided configuration and the build should not proceed.
    92  
    93  ### The "Run" Method
    94  
    95  `Run` is executed, often in parallel for multiple builders, to actually build
    96  the machine, provision it, and create the resulting machine image, which is
    97  returned as an implementation of the `packer.Artifact` interface.
    98  
    99  The `Run` method takes three parameters. The context.Context used to cancel the
   100  build. The `packer.Ui` object is used to send output to the console.
   101  `packer.Hook` is used to execute hooks, which are covered in more detail in the
   102  Provisioning section below.
   103  
   104  Because builder runs are typically a complex set of many steps, the
   105  packer-plugin-sdk contains a
   106  [multistep](https://godoc.org/github.com/hashicorp/packer-plugin-sdk/multistep)
   107  module. Multistep allows you to separate your build logic into multiple distinct
   108  "steps" with separate run and cleanup phases, and run them in order. It
   109  supports cancellation mid-step, pausing between steps when debugging, the CLI's
   110  on-error flag, and more. All of the HashiCorp maintained builders make use of
   111  this module, and while it is not required for builder implementation, it will
   112  help you create your builder in a way that matches user and Packer Core
   113  assumptions. The SDK also provides a number of "helper" generic steps that may
   114  prevent you from having to re-implement work that has already been done by the
   115  HashiCorp maintainers. Examples include sending boot commands, connecting to
   116  SSH, and creating virtual CDs to mount on your VM. Take a look at the
   117  [communicator](https://github.com/hashicorp/packer-plugin-sdk/tree/main/communicator)
   118  and
   119  [multistep/commonsteps](https://github.com/hashicorp/packer-plugin-sdk/tree/main/multistep/commonsteps)
   120  modules in the SDK to see what tools are available to you.
   121  
   122  Finally, `Run` should return an implementation of `packer.Artifact`. More
   123  details on creating a `packer.Artifact` are covered in the artifact section
   124  below. If something goes wrong during the build that prevents an artifact from
   125  being correctly created, `Run` should return an error and a nil artifact. Note
   126  that your builder is allowed to produce no artifact and no error, although this
   127  is a rare use case.
   128  
   129  ### Cancellation
   130  
   131  #### With the "Cancel" Method ( for plugins for Packer < v1.3 )
   132  
   133  The `Cancel` method can be called at any time and requests cancellation of any
   134  builder run in progress. This method should block until the run actually stops.
   135  Note that the Cancel method will not be called by Packer versions >= 1.4.0.
   136  
   137  #### Context cancellation ( from Packer v1.4 )
   138  
   139  The `<-ctx.Done()` can unblock at any time and signifies request for
   140  cancellation of any builder run in progress.
   141  
   142  Cancels are most commonly triggered by external interrupts, such as the user
   143  pressing `Ctrl-C`. Packer will only exit once all the builders clean up, so it
   144  is important that you architect your builder in a way that it is quick to
   145  respond to these cancellations and clean up after itself. If your builder makes
   146  a long-running call, you should consider the possibility that a user may cancel
   147  the build during that call, and make sure that such a cancellation is not
   148  blocked.
   149  
   150  ## Creating an Artifact
   151  
   152  The `Run` method is expected to return an implementation of the
   153  `packer.Artifact` interface. Each builder must create its own implementation of
   154  this interface.
   155  
   156  Most of the pieces of an artifact should be fairly self-explanatory by reading
   157  the [packer.Artifact interface
   158  documentation](https://godoc.org/github.com/hashicorp/packer-plugin-sdk/packer#Artifact).
   159  
   160  However one part of an artifact that may be confusing is the `BuilderId` method.
   161  This method must return an absolutely unique ID for the builder. In general, a
   162  reasonable ID would be the github username or organization that created the
   163  builder, followed by the platform it is building for. For example, the builder
   164  ID of the VMware builder is "hashicorp.vmware".
   165  
   166  Post-processors use the builder ID value in order to make some assumptions
   167  about the artifact results and to determine whether they are even able to run
   168  against a given artifact, so it is important that this ID never changes once
   169  the builder is published.
   170  
   171  The builder ID for each builder is included on the associated documentation page.
   172  
   173  ## Provisioning
   174  
   175  Packer has built-in support for provisioning using the Provisioner plugins. But
   176  builders themselves, rather than the Packer core, must determine when to invoke
   177  the provisioners since only the builder knows when the machine is running and
   178  ready for communication.
   179  
   180  When the machine is ready to be provisioned, run the `packer.HookProvision`
   181  hook, making sure the communicator is not nil, since this is required for
   182  provisioners. An example of calling the hook is shown below:
   183  
   184  ```go
   185  hook.Run(context.Context, packer.HookProvision, ui, comm, nil)
   186  ```
   187  
   188  At this point, Packer will run the provisioners and no additional work is
   189  necessary.
   190  
   191  If you are using the multistep tooling, the Packer plugin SDK contains a
   192  generic StepProvision which handles execution the provision hook for you and
   193  automatically supplies any custom builder generatedData you would like to
   194  provide to provisioners (see below for more details on generatedData.)
   195  
   196  ## Template Engine
   197  
   198  ~> Note: In HCL2 the JSON template engine and generated data is slowly going to
   199  be deprecated in favor of using HCL2 objects, we will keep on supporting those
   200  but it is recommended to avoid using them if you can.
   201  
   202  ### Build variables
   203  
   204  Packer JSON makes it possible to provide custom template engine variables to be
   205  shared with provisioners and post-processors using the `build` function.
   206  JSON template `build` docs are [here](/packer/docs/templates/legacy_json_templates/engine#build)
   207  and HCL template build docs are [here](/packer/docs/templates/hcl_templates/contextual-variables#build-variables).
   208  
   209  As of Packer v1.5.0, builder Prepare() methods return a list of custom variables
   210  which we call `generated data`. We use that list of variables to generate a
   211  custom placeholder map per builder that combines custom variables with the
   212  placeholder map of default build variables created by Packer. Here's an example
   213  snippet telling packer what will be made available by the builder:
   214  
   215  ```go
   216  func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
   217      // ...
   218  
   219      generatedData := []string{"SourceImageName"}
   220      return generatedData, warns, nil
   221  }
   222  ```
   223  
   224  When a user provides a Packer template that uses a `build` function, Packer
   225  validates that the key in the `build` function exists for a given builder using
   226  this generatedData array. If it does not exist, then Packer validation will
   227  fail.
   228  
   229  Once the placeholder is set, it's necessary to pass the variables' real values
   230  when calling the provisioner. This can be done as the example below:
   231  
   232  ```go
   233  func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
   234      // ...
   235  
   236      // Create map of custom variable
   237      generatedData := map[string]interface{}{"SourceImageName": "the source image name value"}
   238      // Pass map to provisioner
   239      hook.Run(context.Context, packer.HookProvision, ui, comm, generatedData)
   240  
   241      // ...
   242  }
   243  ```
   244  
   245  In order to make these same variables and the Packer default ones also available
   246  to post-processors, your builder will need to add them to its Artifact.
   247  This can be done by adding an attribute of type `map[string]interface{}` to the
   248  Artifact and putting the generated data in it. The post-processor will access
   249  this data later via the Artifact's `State` method.
   250  
   251  The Artifact code should be implemented similar to the below:
   252  
   253  ```go
   254  type Artifact struct {
   255  	// ...
   256  
   257  	// StateData should store data such as GeneratedData
   258  	// to be shared with post-processors
   259  	StateData map[string]interface{}
   260  }
   261  
   262  // ...
   263  
   264  func (a *Artifact) State(name string) interface{} {
   265  	return a.StateData[name]
   266  }
   267  
   268  // ...
   269  ```
   270  
   271  The builder should return the above Artifact containing the generated data and
   272  the code should be similar to the example snippet below:
   273  
   274  ```go
   275  func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
   276      // ...
   277  
   278      return &Artifact{
   279                  // ...
   280                  StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
   281              }, nil
   282  }
   283  ```
   284  
   285  The code above assigns the `generated_data` state to the `StateData` map with
   286  the key `generated_data`.
   287  
   288  Here some example of how this data will be used by post-processors:
   289  
   290  ```go
   291  func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, source packer.Artifact) (packer.Artifact, bool, bool, error) {
   292      generatedData := source.State("generated_data")
   293  
   294      // generatedData will then be used for interpolation
   295  
   296      // ...
   297  }
   298  ```
   299  
   300  ## Putting it all together
   301  
   302  This page has focused up until now on the implementation details for the Builder
   303  interface. You will need to create a server and store the builder in a binary in
   304  order to make it available to the Packer core as a plugin. We have created a
   305  [scaffolding](https://github.com/hashicorp/packer-plugin-scaffolding/blob/main/builder/scaffolding/builder.go)
   306  repo to give you an idea of the relationship between the builder
   307  implementation and the server implementation within a repository, and then read
   308  [basics of how Plugins work](/packer/docs/plugins/install), which breaks down all the
   309  server details.