diff --git a/builder/azure/chroot/builder.go b/builder/azure/chroot/builder.go index b2ac901f9..abc75d677 100644 --- a/builder/azure/chroot/builder.go +++ b/builder/azure/chroot/builder.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/to" "log" "runtime" "strings" @@ -26,7 +27,8 @@ type Config struct { ClientConfig client.Config `mapstructure:",squash"` - FromScratch bool `mapstructure:"from_scratch"` + FromScratch bool `mapstructure:"from_scratch"` + Source string `mapstructure:"source"` CommandWrapper string `mapstructure:"command_wrapper"` PreMountCommands []string `mapstructure:"pre_mount_commands"` @@ -75,6 +77,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) + var errs *packer.MultiError + var warns []string + // Defaults err = b.config.ClientConfig.SetDefaultValues() if err != nil { @@ -115,7 +120,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.TemporaryOSDiskName == "" { - b.config.TemporaryOSDiskName = "PackerTemp-{{timestamp}}" + + if def, err := interpolate.Render("PackerTemp-{{timestamp}}", &b.config.ctx); err == nil { + b.config.TemporaryOSDiskName = def + } else { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary disk name: %s", err)) + } } if b.config.OSDiskStorageAccountType == "" { @@ -135,10 +145,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } // checks, accumulate any errors or warnings - var errs *packer.MultiError - var warns []string if b.config.FromScratch { + if b.config.Source != "" { + errs = packer.MultiErrorAppend( + errs, errors.New("source cannot be specified when building from_scratch")) + } if b.config.OSDiskSizeGB == 0 { errs = packer.MultiErrorAppend( errs, errors.New("os_disk_size_gb is required with from_scratch")) @@ -148,7 +160,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs, errors.New("pre_mount_commands is required with from_scratch")) } } else { - errs = packer.MultiErrorAppend(errors.New("only 'from_scratch'=true is supported right now")) + if _, err := client.ParsePlatformImageURN(b.config.Source); err == nil { + log.Println("Source is platform image:", b.config.Source) + } else { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("source: %q is not a valid platform image specifier", b.config.Source)) + } } if err := checkDiskCacheType(b.config.OSDiskCacheType); err != nil { @@ -271,10 +288,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Build the steps var steps []multistep.Step - if !b.config.FromScratch { - panic("Only from_scratch is currently implemented") - // create disk from PIR / managed image (warn on non-linux images) - } else { + if b.config.FromScratch { steps = append(steps, &StepCreateNewDisk{ SubscriptionID: info.SubscriptionID, @@ -285,6 +299,31 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack HyperVGeneration: b.config.ImageHyperVGeneration, Location: info.Location, }) + } else { + if pi, err := client.ParsePlatformImageURN(b.config.Source); err == nil { + if strings.EqualFold(pi.Version, "latest") { + + vmi, err := azcli.VirtualMachineImagesClient().GetLatest(ctx, pi.Publisher, pi.Offer, pi.Sku, info.Location) + if err != nil { + return nil, fmt.Errorf("error retieving latest version of %q: %v", b.config.Source, err) + } + pi.Version = to.String(vmi.Name) + log.Println("Resolved latest version of source image:", pi.Version) + } + steps = append(steps, + &StepCreateNewDisk{ + SubscriptionID: info.SubscriptionID, + ResourceGroup: info.ResourceGroupName, + DiskName: b.config.TemporaryOSDiskName, + DiskSizeGB: b.config.OSDiskSizeGB, + DiskStorageAccountType: b.config.OSDiskStorageAccountType, + HyperVGeneration: b.config.ImageHyperVGeneration, + Location: info.Location, + PlatformImage: pi, + }) + } else { + panic("Unknown image source: " + b.config.Source) + } } steps = append(steps, diff --git a/builder/azure/chroot/step_create_image.go b/builder/azure/chroot/step_create_image.go index bd869470c..4ae90d2fe 100644 --- a/builder/azure/chroot/step_create_image.go +++ b/builder/azure/chroot/step_create_image.go @@ -51,9 +51,12 @@ func (s *StepCreateImage) Run(ctx context.Context, state multistep.StateBag) mul StorageProfile: &compute.ImageStorageProfile{ OsDisk: &compute.ImageOSDisk{ OsState: compute.OperatingSystemStateTypes(s.ImageOSState), + OsType: compute.Linux, ManagedDisk: &compute.SubResource{ ID: &diskResourceID, }, + StorageAccountType: compute.StorageAccountTypes(s.OSDiskStorageAccountType), + Caching: compute.CachingTypes(s.OSDiskCacheType), }, // DataDisks: nil, // ZoneResilient: nil, diff --git a/builder/azure/chroot/step_create_new_disk.go b/builder/azure/chroot/step_create_new_disk.go index 8187bb7dc..6873c827a 100644 --- a/builder/azure/chroot/step_create_new_disk.go +++ b/builder/azure/chroot/step_create_new_disk.go @@ -20,6 +20,7 @@ type StepCreateNewDisk struct { DiskStorageAccountType string // from compute.DiskStorageAccountTypes HyperVGeneration string Location string + PlatformImage *client.PlatformImage } func (s StepCreateNewDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -42,10 +43,8 @@ func (s StepCreateNewDisk) Run(ctx context.Context, state multistep.StateBag) mu DiskProperties: &compute.DiskProperties{ OsType: "Linux", HyperVGeneration: compute.HyperVGeneration(s.HyperVGeneration), - CreationData: &compute.CreationData{ - CreateOption: compute.Empty, - }, - DiskSizeGB: to.Int32Ptr(s.DiskSizeGB), + CreationData: &compute.CreationData{}, + DiskSizeGB: to.Int32Ptr(s.DiskSizeGB), }, //Tags: map[string]*string{ } @@ -54,6 +53,17 @@ func (s StepCreateNewDisk) Run(ctx context.Context, state multistep.StateBag) mu disk.DiskProperties.DiskSizeGB = to.Int32Ptr(s.DiskSizeGB) } + if s.PlatformImage == nil { + disk.CreationData.CreateOption = compute.Empty + } else { + disk.CreationData.CreateOption = compute.FromImage + disk.CreationData.ImageReference = &compute.ImageDiskReference{ + ID: to.StringPtr(fmt.Sprintf( + "/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions/%s", + s.SubscriptionID, s.Location, s.PlatformImage.Publisher, s.PlatformImage.Offer, s.PlatformImage.Sku, s.PlatformImage.Version)), + } + } + f, err := azcli.DisksClient().CreateOrUpdate(ctx, s.ResourceGroup, s.DiskName, disk) if err == nil { err = f.WaitForCompletionRef(ctx, azcli.PollClient())