Adrien Delorme 0785c2f6fc
build using HCL2 (#8423)
This follows #8232 which added the code to generate the code required to parse
HCL files for each packer component.

All old config files of packer will keep on working the same. Packer takes one
argument. When a directory is passed, all files in the folder with a name
ending with  “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format.
When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed
using the HCL2 format. For every other case; the old packer style will be used.

## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files

I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields

## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file.

  This is a breaking change for packer plugins.

a packer component can be a: builder/provisioner/post-processor

each component interface now gets a `ConfigSpec() hcldec.ObjectSpec`
which allows packer to tell what is the layout of the hcl2 config meant
to configure that specific component.

This ObjectSpec is sent through the wire (RPC) and a cty.Value is now
sent through the already existing configuration entrypoints:

 Provisioner.Prepare(raws ...interface{}) error
 Builder.Prepare(raws ...interface{}) ([]string, error)
 PostProcessor.Configure(raws ...interface{}) error

close #1768


Example hcl files:

```hcl
// file amazon-ebs-kms-key/run.pkr.hcl
build {
    sources = [
        "source.amazon-ebs.first",
    ]

    provisioner "shell" {
        inline = [
            "sleep 5"
        ]
    }

    post-processor "shell-local" {
        inline = [
            "sleep 5"
        ]
    }
}

// amazon-ebs-kms-key/source.pkr.hcl

source "amazon-ebs" "first" {

    ami_name = "hcl2-test"
    region = "us-east-1"
    instance_type = "t2.micro"

    kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c"
    encrypt_boot = true
    source_ami_filter {
        filters {
          virtualization-type = "hvm"
          name =  "amzn-ami-hvm-????.??.?.????????-x86_64-gp2"
          root-device-type = "ebs"
        }
        most_recent = true
        owners = ["amazon"]
    }
    launch_block_device_mappings {
        device_name = "/dev/xvda"
        volume_size = 20
        volume_type = "gp2"
        delete_on_termination = "true"
    }
    launch_block_device_mappings {
        device_name = "/dev/xvdf"
        volume_size = 500
        volume_type = "gp2"
        delete_on_termination = true
        encrypted = true
    }

    ami_regions = ["eu-central-1"]
    run_tags {
        Name = "packer-solr-something"
        stack-name = "DevOps Tools"
    }
    
    communicator = "ssh"
    ssh_pty = true
    ssh_username = "ec2-user"
    associate_public_ip_address = true
}
```
2019-12-17 11:25:56 +01:00

316 lines
12 KiB
Go

//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config
package cloudstack
import (
"errors"
"fmt"
"os"
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
// Config holds all the details needed to configure the builder.
type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
// The CloudStack API endpoint we will connect to. It can
// also be specified via environment variable CLOUDSTACK_API_URL, if set.
APIURL string `mapstructure:"api_url" required:"true"`
// The API key used to sign all API requests. It can also
// be specified via environment variable CLOUDSTACK_API_KEY, if set.
APIKey string `mapstructure:"api_key" required:"true"`
// The secret key used to sign all API requests. It
// can also be specified via environment variable CLOUDSTACK_SECRET_KEY, if
// set.
SecretKey string `mapstructure:"secret_key" required:"true"`
// The time duration to wait for async calls to
// finish. Defaults to 30m.
AsyncTimeout time.Duration `mapstructure:"async_timeout" required:"false"`
// Some cloud providers only allow HTTP GET calls
// to their CloudStack API. If using such a provider, you need to set this to
// true in order for the provider to only make GET calls and no POST calls.
HTTPGetOnly bool `mapstructure:"http_get_only" required:"false"`
// Set to true to skip SSL verification.
// Defaults to false.
SSLNoVerify bool `mapstructure:"ssl_no_verify" required:"false"`
// List of CIDR's that will have access to the new
// instance. This is needed in order for any provisioners to be able to
// connect to the instance. Defaults to [ "0.0.0.0/0" ]. Only required when
// use_local_ip_address is false.
CIDRList []string `mapstructure:"cidr_list" required:"false"`
// If true a temporary security group
// will be created which allows traffic towards the instance from the
// cidr_list. This option will be ignored if security_groups is also
// defined. Requires expunge set to true. Defaults to false.
CreateSecurityGroup bool `mapstructure:"create_security_group" required:"false"`
// The name or ID of the disk offering used for the
// instance. This option is only available (and also required) when using
// source_iso.
DiskOffering string `mapstructure:"disk_offering" required:"false"`
// The size (in GB) of the root disk of the new
// instance. This option is only available when using source_template.
DiskSize int64 `mapstructure:"disk_size" required:"false"`
// If `true` make a call to the CloudStack API, after loading image to
// cache, requesting to check and detach ISO file (if any) currently
// attached to a virtual machine. Defaults to `false`. This option is only
// available when using `source_iso`.
EjectISO bool `mapstructure:"eject_iso"`
// Configure the duration time to wait, making sure virtual machine is able
// to finish installing OS before it ejects safely. Requires `eject_iso`
// set to `true` and this option is only available when using `source_iso`.
EjectISODelay time.Duration `mapstructure:"eject_iso_delay"`
// Set to true to expunge the instance when it is
// destroyed. Defaults to false.
Expunge bool `mapstructure:"expunge" required:"false"`
// The target hypervisor (e.g. XenServer, KVM) for
// the new template. This option is required when using source_iso.
Hypervisor string `mapstructure:"hypervisor" required:"false"`
// The name of the instance. Defaults to
// "packer-UUID" where UUID is dynamically generated.
InstanceName string `mapstructure:"instance_name" required:"false"`
// The display name of the instance. Defaults to "Created by Packer".
InstanceDisplayName string `mapstructure:"instance_display_name" required:"false"`
// The name or ID of the network to connect the instance
// to.
Network string `mapstructure:"network" required:"true"`
// The name or ID of the project to deploy the instance
// to.
Project string `mapstructure:"project" required:"false"`
// The public IP address or it's ID used for
// connecting any provisioners to. If not provided, a temporary public IP
// address will be associated and released during the Packer run.
PublicIPAddress string `mapstructure:"public_ip_address" required:"false"`
// The fixed port you want to configure in the port
// forwarding rule. Set this attribute if you do not want to use the a random
// public port.
PublicPort int `mapstructure:"public_port" required:"false"`
// A list of security group IDs or
// names to associate the instance with.
SecurityGroups []string `mapstructure:"security_groups" required:"false"`
// The name or ID of the service offering used
// for the instance.
ServiceOffering string `mapstructure:"service_offering" required:"true"`
// Set to true to prevent network
// ACLs or firewall rules creation. Defaults to false.
PreventFirewallChanges bool `mapstructure:"prevent_firewall_changes" required:"false"`
// The name or ID of an ISO that will be mounted
// before booting the instance. This option is mutually exclusive with
// source_template. When using source_iso, both disk_offering and
// hypervisor are required.
SourceISO string `mapstructure:"source_iso" required:"true"`
// The name or ID of the template used as base
// template for the instance. This option is mutually exclusive with
// source_iso.
SourceTemplate string `mapstructure:"source_template" required:"true"`
// The name of the temporary SSH key pair
// to generate. By default, Packer generates a name that looks like
// packer_<UUID>, where <UUID> is a 36 character unique identifier.
TemporaryKeypairName string `mapstructure:"temporary_keypair_name" required:"false"`
// Set to true to indicate that the
// provisioners should connect to the local IP address of the instance.
UseLocalIPAddress bool `mapstructure:"use_local_ip_address" required:"false"`
// User data to launch with the instance. This is a
// template engine see User Data bellow for
// more details. Packer will not automatically wait for a user script to
// finish before shutting down the instance this must be handled in a
// provisioner.
UserData string `mapstructure:"user_data" required:"false"`
// Path to a file that will be used for the user
// data when launching the instance. This file will be parsed as a template
// engine see User Data bellow for more
// details.
UserDataFile string `mapstructure:"user_data_file" required:"false"`
// The name or ID of the zone where the instance will be
// created.
Zone string `mapstructure:"zone" required:"true"`
// The name of the new template. Defaults to
// "packer-{{timestamp}}" where timestamp will be the current time.
TemplateName string `mapstructure:"template_name" required:"false"`
// The display text of the new template.
// Defaults to the template_name.
TemplateDisplayText string `mapstructure:"template_display_text" required:"false"`
// The name or ID of the template OS for the new
// template that will be created.
TemplateOS string `mapstructure:"template_os" required:"true"`
// Set to true to indicate that the template
// is featured. Defaults to false.
TemplateFeatured bool `mapstructure:"template_featured" required:"false"`
// Set to true to indicate that the template
// is available for all accounts. Defaults to false.
TemplatePublic bool `mapstructure:"template_public" required:"false"`
// Set to true to indicate the
// template should be password enabled. Defaults to false.
TemplatePasswordEnabled bool `mapstructure:"template_password_enabled" required:"false"`
// Set to true to indicate the template
// requires hardware-assisted virtualization. Defaults to false.
TemplateRequiresHVM bool `mapstructure:"template_requires_hvm" required:"false"`
// Set to true to indicate that the template
// contains tools to support dynamic scaling of VM cpu/memory. Defaults to
// false.
TemplateScalable bool `mapstructure:"template_scalable" required:"false"`
//
TemplateTag string `mapstructure:"template_tag"`
Tags map[string]string `mapstructure:"tags"`
ctx interpolate.Context
}
// NewConfig parses and validates the given config.
func (c *Config) Prepare(raws ...interface{}) error {
err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"user_data",
},
},
}, raws...)
if err != nil {
return err
}
var errs *packer.MultiError
// Set some defaults.
if c.APIURL == "" {
// Default to environment variable for api_url, if it exists
c.APIURL = os.Getenv("CLOUDSTACK_API_URL")
}
if c.APIKey == "" {
// Default to environment variable for api_key, if it exists
c.APIKey = os.Getenv("CLOUDSTACK_API_KEY")
}
if c.SecretKey == "" {
// Default to environment variable for secret_key, if it exists
c.SecretKey = os.Getenv("CLOUDSTACK_SECRET_KEY")
}
if c.AsyncTimeout == 0 {
c.AsyncTimeout = 30 * time.Minute
}
if len(c.CIDRList) == 0 {
c.CIDRList = []string{"0.0.0.0/0"}
}
if c.InstanceName == "" {
c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
}
if c.InstanceDisplayName == "" {
c.InstanceDisplayName = "Created by Packer"
}
if c.TemplateName == "" {
name, err := interpolate.Render("packer-{{timestamp}}", nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Unable to parse template name: %s ", err))
}
c.TemplateName = name
}
if c.TemplateDisplayText == "" {
c.TemplateDisplayText = c.TemplateName
}
// If we are not given an explicit keypair, ssh_password or ssh_private_key_file,
// then create a temporary one, but only if the temporary_keypair_name has not
// been provided.
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" {
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
// Process required parameters.
if c.APIURL == "" {
errs = packer.MultiErrorAppend(errs, errors.New("a api_url must be specified"))
}
if c.APIKey == "" {
errs = packer.MultiErrorAppend(errs, errors.New("a api_key must be specified"))
}
if c.SecretKey == "" {
errs = packer.MultiErrorAppend(errs, errors.New("a secret_key must be specified"))
}
if c.Network == "" {
errs = packer.MultiErrorAppend(errs, errors.New("a network must be specified"))
}
if c.CreateSecurityGroup && !c.Expunge {
errs = packer.MultiErrorAppend(errs, errors.New("auto creating a temporary security group requires expunge"))
}
if c.ServiceOffering == "" {
errs = packer.MultiErrorAppend(errs, errors.New("a service_offering must be specified"))
}
if c.SourceISO == "" && c.SourceTemplate == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("either source_iso or source_template must be specified"))
}
if c.SourceISO != "" && c.SourceTemplate != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("only one of source_iso or source_template can be specified"))
}
if c.SourceISO != "" && c.DiskOffering == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("a disk_offering must be specified when using source_iso"))
}
if c.SourceISO != "" && c.Hypervisor == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("a hypervisor must be specified when using source_iso"))
}
if c.TemplateOS == "" {
errs = packer.MultiErrorAppend(errs, errors.New("a template_os must be specified"))
}
if c.UserData != "" && c.UserDataFile != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("only one of user_data or user_data_file can be specified"))
}
if c.UserDataFile != "" {
if _, err := os.Stat(c.UserDataFile); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
}
}
if c.Zone == "" {
errs = packer.MultiErrorAppend(errs, errors.New("a zone must be specified"))
}
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
// Check for errors and return if we have any.
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}