2019-09-26 14:44:35 -04:00
//go:generate struct-markdown
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
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"
2019-05-16 20:24:53 -04:00
"github.com/hashicorp/packer/common"
2019-10-24 00:21:08 -04:00
"github.com/hashicorp/packer/common/chroot"
2020-11-11 18:04:28 -05:00
"github.com/hashicorp/packer/common/commonsteps"
2019-05-16 20:24:53 -04:00
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
2020-11-11 13:21:37 -05:00
"github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate"
2019-05-31 16:02:25 -04:00
2020-04-23 05:03:17 -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"
2020-04-23 05:03:17 -04:00
"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" `
2020-04-23 05:03:17 -04:00
// 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-05-28 20:40:44 -04:00
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" `
2020-04-29 16:28:10 -04:00
// 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" `
2020-04-15 17:23:50 -04:00
// 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" `
2020-04-15 17:23:50 -04:00
// 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"
2020-04-23 05:03:17 -04:00
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
}
2020-03-25 14:23:10 -04:00
// verify interface implementation
var _ packer . Builder = & Builder { }
2019-12-17 05:25:56 -05:00
func ( b * Builder ) ConfigSpec ( ) hcldec . ObjectSpec { return b . config . FlatMapstructure ( ) . HCL2Spec ( ) }
2019-12-17 00:23:05 -05:00
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 {
2020-10-09 20:01:55 -04:00
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 {
2019-12-17 00:23:05 -05:00
return nil , nil , err
2019-10-04 14:59:11 -04:00
}
2019-05-16 20:24:53 -04:00
2019-06-03 04:33:31 -04:00
var errs * packer . MultiError
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 {
2019-12-17 00:23:05 -05:00
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 = packer . MultiErrorAppend ( errs , fmt . Errorf ( "unable to render temporary disk id: %s" , err ) )
}
}
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 {
2020-03-25 17:55:37 -04:00
errs = packer . 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
}
2020-04-15 17:23:50 -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 = packer . 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 = packer . 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 )
}
2020-04-29 16:28:10 -04:00
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 = packer . MultiErrorAppend (
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 = packer . 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
}
2019-05-28 20:40:44 -04:00
if len ( b . config . PreMountCommands ) == 0 {
errs = packer . MultiErrorAppend (
2019-05-31 14:49:35 -04:00
errs , errors . New ( "pre_mount_commands is required with from_scratch" ) )
2019-05-28 20:40:44 -04:00
}
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
2020-04-23 05:03:17 -04:00
} 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
2020-04-23 05:03:17 -04:00
} 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 = packer . 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 = packer . 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 = packer . MultiErrorAppend ( errs , fmt . Errorf ( "os_disk_storage_account_type: %v" , err ) )
}
2020-04-29 16:28:10 -04:00
if err := checkDiskCacheType ( b . config . DataDiskCacheType ) ; err != nil {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "data_disk_cache_type: %v" , err ) )
}
if err := checkStorageAccountType ( b . config . DataDiskStorageAccountType ) ; err != nil {
errs = packer . 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 = packer . MultiErrorAppend ( fmt . Errorf (
"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 = packer . MultiErrorAppend ( errs , e ... )
}
if len ( w ) > 0 {
warns = append ( warns , w ... )
}
}
if ! azcommon . StringsContains ( md . Keys , "shared_image_destination" ) && b . config . ImageResourceID == "" {
errs = packer . MultiErrorAppend ( errs , errors . New ( "image_resource_id or shared_image_destination is required" ) )
}
2019-06-03 01:27:33 -04:00
if err := checkHyperVGeneration ( b . config . ImageHyperVGeneration ) ; err != nil {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "image_hyperv_generation: %v" , err ) )
}
2019-05-31 18:15:56 -04:00
if errs != nil {
2019-12-17 00:23:05 -05:00
return nil , warns , errs
2019-05-31 18:15:56 -04:00
}
packer . LogSecretFilter . Set ( b . config . ClientConfig . ClientSecret , b . config . ClientConfig . ClientJWT )
2019-12-17 00:23:05 -05:00
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 ( ) )
}
2019-05-16 20:24:53 -04:00
func ( b * Builder ) Run ( ctx context . Context , ui packer . Ui , hook packer . Hook ) ( packer . 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
2019-05-28 20:40:44 -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 )
2019-10-23 23:57:17 -04:00
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 )
2020-03-25 14:23:10 -04:00
// Build the step array from the config
steps := buildsteps ( b . config , info )
// Run!
2020-11-11 18:04:28 -05:00
b . runner = commonsteps . NewRunner ( steps , b . config . PackerConfig , ui )
2020-03-25 14:23:10 -04:00
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 ,
2020-03-25 14:23:10 -04:00
StateData : map [ string ] interface { } { "generated_data" : state . Get ( "generated_data" ) } ,
2020-04-09 16:54:50 -04:00
AzureClientSet : azcli ,
2020-03-25 14:23:10 -04:00
}
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 {
2020-04-15 18:58:06 -04:00
if d , ok := state . GetOk ( stateBagKey_Diskset ) ; ok {
2020-04-29 18:24:49 -04:00
for _ , disk := range d . ( Diskset ) {
2020-04-15 18:58:06 -04:00
artifact . Resources = append ( artifact . Resources , disk . String ( ) )
}
2020-04-09 16:54:50 -04:00
}
2020-04-29 14:28:52 -04:00
if d , ok := state . GetOk ( stateBagKey_Snapshotset ) ; ok {
2020-04-29 18:24:49 -04:00
for _ , snapshot := range d . ( Diskset ) {
2020-04-29 14:28:52 -04:00
artifact . Resources = append ( artifact . Resources , snapshot . String ( ) )
}
2020-04-09 16:54:50 -04:00
}
2020-03-25 17:55:37 -04:00
}
2020-03-25 14:23:10 -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
2020-04-23 05:03:17 -04:00
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
2020-03-25 14:23:10 -04:00
if config . FromScratch {
2020-04-15 17:46:46 -04:00
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 {
2020-03-25 14:23:10 -04:00
switch config . sourceType {
2019-09-10 08:48:55 -04:00
case sourcePlatformImage :
2020-03-25 14:23:10 -04:00
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 (
2020-04-15 17:46:46 -04:00
& 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 {
2020-03-25 14:23:10 -04:00
SourceDiskResourceID : config . Source ,
2019-09-26 18:17:07 -04:00
Location : info . Location ,
} ,
2020-04-15 17:46:46 -04:00
& 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
2020-04-23 05:03:17 -04:00
case sourceSharedImage :
addSteps (
& StepVerifySharedImageSource {
SharedImageID : config . Source ,
SubscriptionID : info . SubscriptionID ,
Location : info . Location ,
} ,
2020-04-15 17:46:46 -04:00
& StepCreateNewDiskset {
2020-04-29 16:28:10 -04:00
OSDiskID : config . TemporaryOSDiskID ,
2020-04-29 18:10:20 -04:00
DataDiskIDPrefix : config . TemporaryDataDiskIDPrefix ,
2020-04-29 16:28:10 -04:00
OSDiskSizeGB : config . OSDiskSizeGB ,
OSDiskStorageAccountType : config . OSDiskStorageAccountType ,
DataDiskStorageAccountType : config . DataDiskStorageAccountType ,
SourceImageResourceID : config . Source ,
Location : info . Location ,
2020-04-23 05:03:17 -04:00
SkipCleanup : config . SkipCleanup ,
} )
2019-09-10 08:48:55 -04:00
default :
2020-03-25 14:23:10 -04:00
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 {
2020-03-25 14:23:10 -04:00
Commands : config . PreMountCommands ,
2019-05-28 20:40:44 -04:00
} ,
2019-05-31 14:39:43 -04:00
& StepMountDevice {
2020-03-25 14:23:10 -04:00
MountOptions : config . MountOptions ,
MountPartition : config . MountPartition ,
MountPath : config . MountPath ,
2019-05-31 14:39:43 -04:00
} ,
& chroot . StepPostMountCommands {
2020-03-25 14:23:10 -04:00
Commands : config . PostMountCommands ,
2019-05-31 14:39:43 -04:00
} ,
2019-05-31 14:49:35 -04:00
& chroot . StepMountExtra {
2020-03-25 14:23:10 -04:00
ChrootMounts : config . ChrootMounts ,
2019-05-31 14:49:35 -04:00
} ,
2019-05-31 14:55:58 -04:00
& chroot . StepCopyFiles {
2020-03-25 14:23:10 -04:00
Files : config . CopyFiles ,
2019-05-31 14:55:58 -04:00
} ,
& 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 {
2020-03-25 14:23:10 -04:00
ImageResourceID : config . ImageResourceID ,
2019-09-26 14:44:35 -04:00
ImageOSState : string ( compute . Generalized ) ,
2020-03-25 14:23:10 -04:00
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 (
2020-04-29 14:28:52 -04:00
& 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 ,
} ,
)
}
2019-05-28 20:40:44 -04:00
2020-03-25 14:23:10 -04:00
return steps
2019-05-16 20:24:53 -04:00
}