packer-cn/builder/azure/chroot/builder.go

623 lines
23 KiB
Go
Raw Normal View History

2019-09-26 14:44:35 -04:00
//go:generate struct-markdown
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 05:25:56 -05:00
//go:generate mapstructure-to-hcl2 -type Config
2019-09-26 14:44:35 -04:00
2019-10-11 11:32:02 -04:00
// Package chroot is able to create an Azure managed image without requiring the
// launch of a new virtual machine for every build. It does this by attaching and
2019-09-26 14:44:35 -04:00
// mounting the root disk and chrooting into that directory.
// It then creates a managed image from that attached disk.
2019-05-16 20:24:53 -04:00
package chroot
import (
"context"
"errors"
2019-05-27 02:20:11 -04:00
"fmt"
"log"
2019-05-16 20:24:53 -04:00
"runtime"
2019-06-03 01:27:33 -04:00
"strings"
2019-05-16 20:24:53 -04:00
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 05:25:56 -05:00
"github.com/hashicorp/hcl/v2/hcldec"
2019-05-16 20:24:53 -04:00
azcommon "github.com/hashicorp/packer/builder/azure/common"
2019-05-27 02:20:11 -04:00
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/packer-plugin-sdk/chroot"
"github.com/hashicorp/packer/packer-plugin-sdk/common"
2020-11-17 19:31:03 -05:00
"github.com/hashicorp/packer/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate"
2019-05-31 16:02:25 -04:00
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
2019-09-10 08:48:55 -04:00
"github.com/Azure/go-autorest/autorest/azure"
"github.com/mitchellh/mapstructure"
2019-05-16 20:24:53 -04:00
)
2020-03-25 17:55:37 -04:00
// BuilderID is the unique ID for this builder
const BuilderID = "azure.chroot"
2019-10-07 15:20:08 -04:00
2019-09-26 14:44:35 -04:00
// Config is the configuration that is chained through the steps and settable
// from the template.
2019-05-16 20:24:53 -04:00
type Config struct {
common.PackerConfig `mapstructure:",squash"`
2019-05-31 18:15:56 -04:00
ClientConfig client.Config `mapstructure:",squash"`
2019-09-26 14:44:35 -04:00
// When set to `true`, starts with an empty, unpartitioned disk. Defaults to `false`.
2019-09-26 18:17:07 -04:00
FromScratch bool `mapstructure:"from_scratch"`
// One of the following can be used as a source for an image:
// - a shared image version resource ID
// - a managed disk resource ID
// - a publisher:offer:sku:version specifier for plaform image sources.
2019-09-26 18:17:07 -04:00
Source string `mapstructure:"source" required:"true"`
sourceType sourceType
2019-05-27 02:20:11 -04:00
2019-09-26 14:44:35 -04:00
// How to run shell commands. This may be useful to set environment variables or perhaps run
// a command with sudo or so on. This is a configuration template where the `.Command` variable
// is replaced with the command to be run. Defaults to `{{.Command}}`.
2019-09-26 18:17:07 -04:00
CommandWrapper string `mapstructure:"command_wrapper"`
// A series of commands to execute after attaching the root volume and before mounting the chroot.
// This is not required unless using `from_scratch`. If so, this should include any partitioning
2019-09-26 14:44:35 -04:00
// and filesystem creation commands. The path to the device is provided by `{{.Device}}`.
2019-09-26 18:17:07 -04:00
PreMountCommands []string `mapstructure:"pre_mount_commands"`
// Options to supply the `mount` command when mounting devices. Each option will be prefixed with
2019-09-26 14:44:35 -04:00
// `-o` and supplied to the `mount` command ran by Packer. Because this command is ran in a shell,
// user discretion is advised. See this manual page for the `mount` command for valid file system specific options.
2019-09-26 18:17:07 -04:00
MountOptions []string `mapstructure:"mount_options"`
2019-09-26 14:44:35 -04:00
// The partition number containing the / partition. By default this is the first partition of the volume.
2019-09-26 18:17:07 -04:00
MountPartition string `mapstructure:"mount_partition"`
2019-09-26 14:44:35 -04:00
// The path where the volume will be mounted. This is where the chroot environment will be. This defaults
2019-09-26 18:17:07 -04:00
// to `/mnt/packer-amazon-chroot-volumes/{{.Device}}`. This is a configuration template where the `.Device`
2019-09-26 14:44:35 -04:00
// variable is replaced with the name of the device where the volume is attached.
2019-09-26 18:17:07 -04:00
MountPath string `mapstructure:"mount_path"`
2019-09-26 14:44:35 -04:00
// As `pre_mount_commands`, but the commands are executed after mounting the root device and before the
// extra mount and copy steps. The device and mount path are provided by `{{.Device}}` and `{{.MountPath}}`.
2019-09-26 18:17:07 -04:00
PostMountCommands []string `mapstructure:"post_mount_commands"`
// This is a list of devices to mount into the chroot environment. This configuration parameter requires
2019-09-26 14:44:35 -04:00
// some additional documentation which is in the "Chroot Mounts" section below. Please read that section
// for more information on how to use this.
2019-09-26 18:17:07 -04:00
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
// Paths to files on the running Azure instance that will be copied into the chroot environment prior to
2019-09-26 14:44:35 -04:00
// provisioning. Defaults to `/etc/resolv.conf` so that DNS lookups work. Pass an empty list to skip copying
// `/etc/resolv.conf`. You may need to do this if you're building an image that uses systemd.
2019-09-26 18:17:07 -04:00
CopyFiles []string `mapstructure:"copy_files"`
2019-09-26 14:44:35 -04:00
// Try to resize the OS disk to this size on the first copy. Disks can only be englarged. If not specified,
// the disk will keep its original size. Required when using `from_scratch`
2019-09-26 18:17:07 -04:00
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb"`
2019-09-26 14:44:35 -04:00
// The [storage SKU](https://docs.microsoft.com/en-us/rest/api/compute/disks/createorupdate#diskstorageaccounttypes)
// to use for the OS Disk. Defaults to `Standard_LRS`.
2019-05-31 16:02:25 -04:00
OSDiskStorageAccountType string `mapstructure:"os_disk_storage_account_type"`
2019-09-26 14:44:35 -04:00
// The [cache type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#cachingtypes)
// specified in the resulting image and for attaching it to the Packer VM. Defaults to `ReadOnly`
2019-09-26 18:17:07 -04:00
OSDiskCacheType string `mapstructure:"os_disk_cache_type"`
// The [storage SKU](https://docs.microsoft.com/en-us/rest/api/compute/disks/createorupdate#diskstorageaccounttypes)
// to use for datadisks. Defaults to `Standard_LRS`.
DataDiskStorageAccountType string `mapstructure:"data_disk_storage_account_type"`
// The [cache type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#cachingtypes)
// specified in the resulting image and for attaching it to the Packer VM. Defaults to `ReadOnly`
DataDiskCacheType string `mapstructure:"data_disk_cache_type"`
2020-04-07 18:42:41 -04:00
// The [Hyper-V generation type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#hypervgenerationtypes) for Managed Image output.
// Defaults to `V1`.
ImageHyperVGeneration string `mapstructure:"image_hyperv_generation"`
// The id of the temporary OS disk that will be created. Will be generated if not set.
2020-04-07 18:42:41 -04:00
TemporaryOSDiskID string `mapstructure:"temporary_os_disk_id"`
2019-05-31 16:02:25 -04:00
2020-05-29 00:15:53 -04:00
// The id of the temporary OS disk snapshot that will be created. Will be generated if not set.
2020-03-25 17:55:37 -04:00
TemporaryOSDiskSnapshotID string `mapstructure:"temporary_os_disk_snapshot_id"`
// The prefix for the resource ids of the temporary data disks that will be created. The disks will be suffixed with a number. Will be generated if not set.
TemporaryDataDiskIDPrefix string `mapstructure:"temporary_data_disk_id_prefix"`
// The prefix for the resource ids of the temporary data disk snapshots that will be created. The snapshots will be suffixed with a number. Will be generated if not set.
TemporaryDataDiskSnapshotIDPrefix string `mapstructure:"temporary_data_disk_snapshot_id"`
2020-04-07 18:42:41 -04:00
// If set to `true`, leaves the temporary disks and snapshots behind in the Packer VM resource group. Defaults to `false`
SkipCleanup bool `mapstructure:"skip_cleanup"`
2020-03-25 17:55:37 -04:00
// The managed image to create using this build.
ImageResourceID string `mapstructure:"image_resource_id"`
// The shared image to create using this build.
SharedImageGalleryDestination SharedImageGalleryDestination `mapstructure:"shared_image_destination"`
2019-05-16 20:24:53 -04:00
ctx interpolate.Context
}
2019-09-10 08:48:55 -04:00
type sourceType string
const (
sourcePlatformImage sourceType = "PlatformImage"
sourceDisk sourceType = "Disk"
sourceSharedImage sourceType = "SharedImage"
2019-09-10 08:48:55 -04:00
)
2019-09-26 18:17:07 -04:00
// GetContext implements ContextProvider to allow steps to use the config context
2019-09-26 14:44:35 -04:00
// for template interpolation
2019-06-03 01:27:33 -04:00
func (c *Config) GetContext() interpolate.Context {
return c.ctx
}
2019-05-16 20:24:53 -04:00
type Builder struct {
config Config
runner multistep.Runner
}
// verify interface implementation
2020-12-01 16:42:11 -05:00
var _ packersdk.Builder = &Builder{}
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 05:25:56 -05:00
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
2019-05-16 20:24:53 -04:00
b.config.ctx.Funcs = azcommon.TemplateFuncs
2019-10-04 14:59:11 -04:00
b.config.ctx.Funcs["vm"] = CreateVMMetadataTemplateFunc()
2020-03-25 17:55:37 -04:00
md := &mapstructure.Metadata{}
2019-05-16 20:24:53 -04:00
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderID,
2019-05-16 20:24:53 -04:00
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
2019-05-31 14:49:35 -04:00
// these fields are interpolated in the steps,
// when more information is available
"command_wrapper",
"post_mount_commands",
"pre_mount_commands",
"mount_path",
2019-05-16 20:24:53 -04:00
},
},
2020-03-25 17:55:37 -04:00
Metadata: md,
2019-05-16 20:24:53 -04:00
}, raws...)
2019-10-04 14:59:11 -04:00
if err != nil {
return nil, nil, err
2019-10-04 14:59:11 -04:00
}
2019-05-16 20:24:53 -04:00
var errs *packersdk.MultiError
2019-06-03 04:33:31 -04:00
var warns []string
2019-05-31 16:02:25 -04:00
// Defaults
2019-05-31 18:15:56 -04:00
err = b.config.ClientConfig.SetDefaultValues()
if err != nil {
return nil, nil, err
2019-05-31 18:15:56 -04:00
}
2019-05-31 16:02:25 -04:00
if b.config.ChrootMounts == nil {
b.config.ChrootMounts = make([][]string, 0)
}
if len(b.config.ChrootMounts) == 0 {
b.config.ChrootMounts = [][]string{
{"proc", "proc", "/proc"},
{"sysfs", "sysfs", "/sys"},
{"bind", "/dev", "/dev"},
{"devpts", "devpts", "/dev/pts"},
{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
}
}
// set default copy file if we're not giving our own
if b.config.CopyFiles == nil {
if !b.config.FromScratch {
b.config.CopyFiles = []string{"/etc/resolv.conf"}
}
}
if b.config.CommandWrapper == "" {
b.config.CommandWrapper = "{{.Command}}"
}
if b.config.MountPath == "" {
2019-05-31 18:15:56 -04:00
b.config.MountPath = "/mnt/packer-azure-chroot-disks/{{.Device}}"
2019-05-31 16:02:25 -04:00
}
if b.config.MountPartition == "" {
b.config.MountPartition = "1"
}
2020-03-25 17:55:37 -04:00
if b.config.TemporaryOSDiskID == "" {
if def, err := interpolate.Render(
"/subscriptions/{{ vm `subscription_id` }}/resourceGroups/{{ vm `resource_group` }}/providers/Microsoft.Compute/disks/PackerTemp-osdisk-{{timestamp}}",
&b.config.ctx); err == nil {
b.config.TemporaryOSDiskID = def
} else {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary disk id: %s", err))
2020-03-25 17:55:37 -04:00
}
}
2019-06-03 04:33:31 -04:00
2020-03-25 17:55:37 -04:00
if b.config.TemporaryOSDiskSnapshotID == "" {
if def, err := interpolate.Render(
"/subscriptions/{{ vm `subscription_id` }}/resourceGroups/{{ vm `resource_group` }}/providers/Microsoft.Compute/snapshots/PackerTemp-osdisk-snapshot-{{timestamp}}",
&b.config.ctx); err == nil {
b.config.TemporaryOSDiskSnapshotID = def
2019-06-03 04:33:31 -04:00
} else {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary snapshot id: %s", err))
2019-06-03 04:33:31 -04:00
}
2019-06-03 01:27:33 -04:00
}
if b.config.TemporaryDataDiskIDPrefix == "" {
if def, err := interpolate.Render(
"/subscriptions/{{ vm `subscription_id` }}/resourceGroups/{{ vm `resource_group` }}/providers/Microsoft.Compute/disks/PackerTemp-datadisk-{{timestamp}}-",
&b.config.ctx); err == nil {
b.config.TemporaryDataDiskIDPrefix = def
} else {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary data disk id prefix: %s", err))
}
}
if b.config.TemporaryDataDiskSnapshotIDPrefix == "" {
if def, err := interpolate.Render(
"/subscriptions/{{ vm `subscription_id` }}/resourceGroups/{{ vm `resource_group` }}/providers/Microsoft.Compute/snapshots/PackerTemp-datadisk-snapshot-{{timestamp}}-",
&b.config.ctx); err == nil {
b.config.TemporaryDataDiskSnapshotIDPrefix = def
} else {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary data disk snapshot id prefix: %s", err))
}
}
2019-05-27 02:20:11 -04:00
if b.config.OSDiskStorageAccountType == "" {
b.config.OSDiskStorageAccountType = string(compute.PremiumLRS)
}
2019-05-31 16:02:25 -04:00
if b.config.OSDiskCacheType == "" {
b.config.OSDiskCacheType = string(compute.CachingTypesReadOnly)
}
if b.config.DataDiskStorageAccountType == "" {
b.config.DataDiskStorageAccountType = string(compute.PremiumLRS)
}
if b.config.DataDiskCacheType == "" {
b.config.DataDiskCacheType = string(compute.CachingTypesReadOnly)
}
2019-06-03 01:27:33 -04:00
if b.config.ImageHyperVGeneration == "" {
2019-10-03 00:45:39 -04:00
b.config.ImageHyperVGeneration = string(compute.V1)
2019-05-31 18:15:56 -04:00
}
2019-05-16 20:24:53 -04:00
// checks, accumulate any errors or warnings
2019-05-27 02:20:11 -04:00
if b.config.FromScratch {
2019-06-03 04:33:31 -04:00
if b.config.Source != "" {
errs = packersdk.MultiErrorAppend(
2019-06-03 04:33:31 -04:00
errs, errors.New("source cannot be specified when building from_scratch"))
}
2019-05-27 02:20:11 -04:00
if b.config.OSDiskSizeGB == 0 {
errs = packersdk.MultiErrorAppend(
2019-05-31 18:15:56 -04:00
errs, errors.New("os_disk_size_gb is required with from_scratch"))
2019-05-27 02:20:11 -04:00
}
if len(b.config.PreMountCommands) == 0 {
errs = packersdk.MultiErrorAppend(
2019-05-31 14:49:35 -04:00
errs, errors.New("pre_mount_commands is required with from_scratch"))
}
2019-05-31 18:15:56 -04:00
} else {
2019-06-03 04:33:31 -04:00
if _, err := client.ParsePlatformImageURN(b.config.Source); err == nil {
log.Println("Source is platform image:", b.config.Source)
2019-09-10 08:48:55 -04:00
b.config.sourceType = sourcePlatformImage
} else if id, err := client.ParseResourceID(b.config.Source); err == nil &&
strings.EqualFold(id.Provider, "Microsoft.Compute") &&
strings.EqualFold(id.ResourceType.String(), "disks") {
2019-09-10 08:48:55 -04:00
log.Println("Source is a disk resource ID:", b.config.Source)
b.config.sourceType = sourceDisk
} else if id, err := client.ParseResourceID(b.config.Source); err == nil &&
strings.EqualFold(id.Provider, "Microsoft.Compute") &&
strings.EqualFold(id.ResourceType.String(), "galleries/images/versions") {
log.Println("Source is a shared image ID:", b.config.Source)
b.config.sourceType = sourceSharedImage
2019-06-03 04:33:31 -04:00
} else {
errs = packersdk.MultiErrorAppend(
2019-09-10 08:48:55 -04:00
errs, fmt.Errorf("source: %q is not a valid platform image specifier, nor is it a disk resource ID", b.config.Source))
2019-06-03 04:33:31 -04:00
}
2019-05-27 02:20:11 -04:00
}
2019-05-31 18:15:56 -04:00
if err := checkDiskCacheType(b.config.OSDiskCacheType); err != nil {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("os_disk_cache_type: %v", err))
2019-05-16 20:24:53 -04:00
}
2019-06-03 01:27:33 -04:00
2019-05-31 18:15:56 -04:00
if err := checkStorageAccountType(b.config.OSDiskStorageAccountType); err != nil {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("os_disk_storage_account_type: %v", err))
2019-05-31 18:15:56 -04:00
}
if err := checkDiskCacheType(b.config.DataDiskCacheType); err != nil {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("data_disk_cache_type: %v", err))
}
if err := checkStorageAccountType(b.config.DataDiskStorageAccountType); err != nil {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("data_disk_storage_account_type: %v", err))
}
2020-03-25 17:55:37 -04:00
if b.config.ImageResourceID != "" {
2019-06-03 01:27:33 -04:00
r, err := azure.ParseResourceID(b.config.ImageResourceID)
if err != nil ||
!strings.EqualFold(r.Provider, "Microsoft.Compute") ||
!strings.EqualFold(r.ResourceType, "images") {
errs = packersdk.MultiErrorAppend(fmt.Errorf(
2019-06-03 01:27:33 -04:00
"image_resource_id: %q is not a valid image resource id", b.config.ImageResourceID))
}
}
2020-03-25 17:55:37 -04:00
if azcommon.StringsContains(md.Keys, "shared_image_destination") {
e, w := b.config.SharedImageGalleryDestination.Validate("shared_image_destination")
if len(e) > 0 {
errs = packersdk.MultiErrorAppend(errs, e...)
2020-03-25 17:55:37 -04:00
}
if len(w) > 0 {
warns = append(warns, w...)
}
}
if !azcommon.StringsContains(md.Keys, "shared_image_destination") && b.config.ImageResourceID == "" {
errs = packersdk.MultiErrorAppend(errs, errors.New("image_resource_id or shared_image_destination is required"))
2020-03-25 17:55:37 -04:00
}
2019-06-03 01:27:33 -04:00
if err := checkHyperVGeneration(b.config.ImageHyperVGeneration); err != nil {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("image_hyperv_generation: %v", err))
2019-06-03 01:27:33 -04:00
}
2019-05-31 18:15:56 -04:00
if errs != nil {
return nil, warns, errs
2019-05-31 18:15:56 -04:00
}
2020-11-19 17:03:11 -05:00
packersdk.LogSecretFilter.Set(b.config.ClientConfig.ClientSecret, b.config.ClientConfig.ClientJWT)
return nil, warns, nil
2019-05-31 18:15:56 -04:00
}
func checkDiskCacheType(s string) interface{} {
for _, v := range compute.PossibleCachingTypesValues() {
if compute.CachingTypes(s) == v {
return nil
}
}
2019-06-03 01:27:33 -04:00
return fmt.Errorf("%q is not a valid value %v",
2019-05-31 18:15:56 -04:00
s, compute.PossibleCachingTypesValues())
}
func checkStorageAccountType(s string) interface{} {
2019-06-02 15:29:45 -04:00
for _, v := range compute.PossibleDiskStorageAccountTypesValues() {
if compute.DiskStorageAccountTypes(s) == v {
2019-05-31 18:15:56 -04:00
return nil
}
}
2019-06-03 01:27:33 -04:00
return fmt.Errorf("%q is not a valid value %v",
2019-06-02 15:29:45 -04:00
s, compute.PossibleDiskStorageAccountTypesValues())
2019-05-16 20:24:53 -04:00
}
2019-06-03 01:27:33 -04:00
func checkHyperVGeneration(s string) interface{} {
for _, v := range compute.PossibleHyperVGenerationValues() {
if compute.HyperVGeneration(s) == v {
return nil
}
}
return fmt.Errorf("%q is not a valid value %v",
s, compute.PossibleHyperVGenerationValues())
}
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
2020-08-02 00:09:43 -04:00
switch runtime.GOOS {
case "linux", "freebsd":
break
default:
return nil, errors.New("the azure-chroot builder only works on Linux and FreeBSD environments")
2019-05-16 20:24:53 -04:00
}
2019-05-31 18:15:56 -04:00
err := b.config.ClientConfig.FillParameters()
if err != nil {
return nil, fmt.Errorf("error setting Azure client defaults: %v", err)
}
azcli, err := client.New(b.config.ClientConfig, ui.Say)
if err != nil {
return nil, fmt.Errorf("error creating Azure client: %v", err)
}
2019-05-27 02:20:11 -04:00
wrappedCommand := func(command string) (string, error) {
ictx := b.config.ctx
ictx.Data = &struct{ Command string }{Command: command}
return interpolate.Render(b.config.CommandWrapper, &ictx)
}
2019-05-16 20:24:53 -04:00
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("hook", hook)
state.Put("ui", ui)
2019-05-31 16:02:25 -04:00
state.Put("azureclient", azcli)
state.Put("wrappedCommand", common.CommandWrapper(wrappedCommand))
2019-05-16 20:24:53 -04:00
2019-05-27 02:20:11 -04:00
info, err := azcli.MetadataClient().GetComputeInfo()
if err != nil {
log.Printf("MetadataClient().GetComputeInfo(): error: %+v", err)
err := fmt.Errorf(
"Error retrieving information ARM resource ID and location" +
"of the VM that Packer is running on.\n" +
"Please verify that Packer is running on a proper Azure VM.")
ui.Error(err.Error())
return nil, err
}
state.Put("instance", info)
// Build the step array from the config
steps := buildsteps(b.config, info)
// Run!
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// Build the artifact and return it
artifact := &azcommon.Artifact{
2020-03-25 17:55:37 -04:00
BuilderIdValue: BuilderID,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
2020-04-09 16:54:50 -04:00
AzureClientSet: azcli,
}
2020-03-25 17:55:37 -04:00
if b.config.ImageResourceID != "" {
2020-04-09 16:54:50 -04:00
artifact.Resources = append(artifact.Resources, b.config.ImageResourceID)
2020-03-25 17:55:37 -04:00
}
if e, _ := b.config.SharedImageGalleryDestination.Validate(""); len(e) == 0 {
2020-04-09 16:54:50 -04:00
artifact.Resources = append(artifact.Resources, b.config.SharedImageGalleryDestination.ResourceID(info.SubscriptionID))
}
if b.config.SkipCleanup {
if d, ok := state.GetOk(stateBagKey_Diskset); ok {
for _, disk := range d.(Diskset) {
artifact.Resources = append(artifact.Resources, disk.String())
}
2020-04-09 16:54:50 -04:00
}
if d, ok := state.GetOk(stateBagKey_Snapshotset); ok {
for _, snapshot := range d.(Diskset) {
artifact.Resources = append(artifact.Resources, snapshot.String())
}
2020-04-09 16:54:50 -04:00
}
2020-03-25 17:55:37 -04:00
}
return artifact, nil
}
func buildsteps(config Config, info *client.ComputeInfo) []multistep.Step {
2019-05-16 20:24:53 -04:00
// Build the steps
var steps []multistep.Step
addSteps := func(s ...multistep.Step) { // convenience function
2020-03-25 17:55:37 -04:00
steps = append(steps, s...)
}
e, _ := config.SharedImageGalleryDestination.Validate("")
hasValidSharedImage := len(e) == 0
if hasValidSharedImage {
// validate destination early
addSteps(
&StepVerifySharedImageDestination{
Image: config.SharedImageGalleryDestination,
Location: info.Location,
},
)
}
2019-05-16 20:24:53 -04:00
if config.FromScratch {
addSteps(&StepCreateNewDiskset{
OSDiskID: config.TemporaryOSDiskID,
OSDiskSizeGB: config.OSDiskSizeGB,
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
HyperVGeneration: config.ImageHyperVGeneration,
Location: info.Location})
2019-06-03 04:33:31 -04:00
} else {
switch config.sourceType {
2019-09-10 08:48:55 -04:00
case sourcePlatformImage:
if pi, err := client.ParsePlatformImageURN(config.Source); err == nil {
2019-09-10 08:48:55 -04:00
if strings.EqualFold(pi.Version, "latest") {
2020-03-25 17:55:37 -04:00
addSteps(
&StepResolvePlatformImageVersion{
PlatformImage: pi,
Location: info.Location,
})
2019-06-03 04:33:31 -04:00
}
2020-03-25 17:55:37 -04:00
addSteps(
&StepCreateNewDiskset{
OSDiskID: config.TemporaryOSDiskID,
OSDiskSizeGB: config.OSDiskSizeGB,
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
HyperVGeneration: config.ImageHyperVGeneration,
Location: info.Location,
SourcePlatformImage: pi,
2019-09-10 08:48:55 -04:00
2020-03-25 17:53:16 -04:00
SkipCleanup: config.SkipCleanup,
2019-09-10 08:48:55 -04:00
})
} else {
2020-03-25 17:55:37 -04:00
panic("Couldn't parse platfrom image urn: " + config.Source + " err: " + err.Error())
2019-06-03 04:33:31 -04:00
}
2020-03-25 17:55:37 -04:00
2019-09-10 08:48:55 -04:00
case sourceDisk:
2020-03-25 17:55:37 -04:00
addSteps(
2019-09-26 18:17:07 -04:00
&StepVerifySourceDisk{
SourceDiskResourceID: config.Source,
2019-09-26 18:17:07 -04:00
Location: info.Location,
},
&StepCreateNewDiskset{
OSDiskID: config.TemporaryOSDiskID,
OSDiskSizeGB: config.OSDiskSizeGB,
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
HyperVGeneration: config.ImageHyperVGeneration,
SourceOSDiskResourceID: config.Source,
Location: info.Location,
2019-06-03 19:06:19 -04:00
2020-03-25 17:53:16 -04:00
SkipCleanup: config.SkipCleanup,
2019-06-03 04:33:31 -04:00
})
2020-03-25 17:55:37 -04:00
case sourceSharedImage:
addSteps(
&StepVerifySharedImageSource{
SharedImageID: config.Source,
SubscriptionID: info.SubscriptionID,
Location: info.Location,
},
&StepCreateNewDiskset{
OSDiskID: config.TemporaryOSDiskID,
DataDiskIDPrefix: config.TemporaryDataDiskIDPrefix,
OSDiskSizeGB: config.OSDiskSizeGB,
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
DataDiskStorageAccountType: config.DataDiskStorageAccountType,
SourceImageResourceID: config.Source,
Location: info.Location,
SkipCleanup: config.SkipCleanup,
})
2019-09-10 08:48:55 -04:00
default:
panic(fmt.Errorf("Unknown source type: %+q", config.sourceType))
2019-06-03 04:33:31 -04:00
}
2019-05-27 02:20:11 -04:00
}
2020-03-25 17:55:37 -04:00
addSteps(
2019-05-31 16:02:25 -04:00
&StepAttachDisk{}, // uses os_disk_resource_id and sets 'device' in stateBag
2019-05-31 14:39:43 -04:00
&chroot.StepPreMountCommands{
Commands: config.PreMountCommands,
},
2019-05-31 14:39:43 -04:00
&StepMountDevice{
MountOptions: config.MountOptions,
MountPartition: config.MountPartition,
MountPath: config.MountPath,
2019-05-31 14:39:43 -04:00
},
&chroot.StepPostMountCommands{
Commands: config.PostMountCommands,
2019-05-31 14:39:43 -04:00
},
2019-05-31 14:49:35 -04:00
&chroot.StepMountExtra{
ChrootMounts: config.ChrootMounts,
2019-05-31 14:49:35 -04:00
},
&chroot.StepCopyFiles{
Files: config.CopyFiles,
},
&chroot.StepChrootProvision{},
2019-05-31 15:01:47 -04:00
&chroot.StepEarlyCleanup{},
2020-03-25 17:55:37 -04:00
)
if config.ImageResourceID != "" {
addSteps(&StepCreateImage{
ImageResourceID: config.ImageResourceID,
2019-09-26 14:44:35 -04:00
ImageOSState: string(compute.Generalized),
OSDiskCacheType: config.OSDiskCacheType,
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
2019-06-03 01:27:33 -04:00
Location: info.Location,
2020-03-25 17:55:37 -04:00
})
}
if hasValidSharedImage {
addSteps(
&StepCreateSnapshotset{
OSDiskSnapshotID: config.TemporaryOSDiskSnapshotID,
DataDiskSnapshotIDPrefix: config.TemporaryDataDiskSnapshotIDPrefix,
Location: info.Location,
SkipCleanup: config.SkipCleanup,
2020-03-25 17:55:37 -04:00
},
&StepCreateSharedImageVersion{
Destination: config.SharedImageGalleryDestination,
OSDiskCacheType: config.OSDiskCacheType,
Location: info.Location,
},
)
}
return steps
2019-05-16 20:24:53 -04:00
}