Add implementation for disk as source

This commit is contained in:
Paul Meyer 2019-09-10 12:48:55 +00:00
parent eff3f2bdcf
commit 27a5bfe11c
4 changed files with 171 additions and 17 deletions

View File

@ -4,8 +4,6 @@ import (
"context"
"errors"
"fmt"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"log"
"runtime"
"strings"
@ -20,6 +18,8 @@ import (
"github.com/hashicorp/packer/template/interpolate"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
)
type Config struct {
@ -29,6 +29,7 @@ type Config struct {
FromScratch bool `mapstructure:"from_scratch"`
Source string `mapstructure:"source"`
sourceType sourceType
CommandWrapper string `mapstructure:"command_wrapper"`
PreMountCommands []string `mapstructure:"pre_mount_commands"`
@ -52,6 +53,13 @@ type Config struct {
ctx interpolate.Context
}
type sourceType string
const (
sourcePlatformImage sourceType = "PlatformImage"
sourceDisk sourceType = "Disk"
)
func (c *Config) GetContext() interpolate.Context {
return c.ctx
}
@ -163,9 +171,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
} else {
if _, err := client.ParsePlatformImageURN(b.config.Source); err == nil {
log.Println("Source is platform image:", b.config.Source)
b.config.sourceType = sourcePlatformImage
} else if id, err := azure.ParseResourceID(b.config.Source); err == nil &&
strings.EqualFold(id.Provider, "Microsoft.Compute") && strings.EqualFold(id.ResourceType, "disks") {
log.Println("Source is a disk resource ID:", b.config.Source)
b.config.sourceType = sourceDisk
} else {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("source: %q is not a valid platform image specifier", b.config.Source))
errs, fmt.Errorf("source: %q is not a valid platform image specifier, nor is it a disk resource ID", b.config.Source))
}
}
@ -301,16 +314,36 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Location: info.Location,
})
} else {
if pi, err := client.ParsePlatformImageURN(b.config.Source); err == nil {
if strings.EqualFold(pi.Version, "latest") {
switch b.config.sourceType {
case sourcePlatformImage:
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)
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)
}
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,
SkipCleanup: b.config.OSDiskSkipCleanup,
})
} else {
panic("Unknown image source: " + b.config.Source)
}
case sourceDisk:
steps = append(steps,
&StepCreateNewDisk{
SubscriptionID: info.SubscriptionID,
@ -319,13 +352,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
DiskSizeGB: b.config.OSDiskSizeGB,
DiskStorageAccountType: b.config.OSDiskStorageAccountType,
HyperVGeneration: b.config.ImageHyperVGeneration,
Location: info.Location,
PlatformImage: pi,
SourceDiskResourceID: b.config.Source,
//todo(paulmey) validate that source disk is in same location as VM
SkipCleanup: b.config.OSDiskSkipCleanup,
})
} else {
panic("Unknown image source: " + b.config.Source)
default:
panic(fmt.Errorf("Unknown source type: %+q", b.config.sourceType))
}
}

View File

@ -19,8 +19,11 @@ type StepCreateNewDisk struct {
DiskSizeGB int32 // optional, ignored if 0
DiskStorageAccountType string // from compute.DiskStorageAccountTypes
HyperVGeneration string
Location string
PlatformImage *client.PlatformImage
Location string
PlatformImage *client.PlatformImage
SourceDiskResourceID string
SkipCleanup bool
}
@ -55,7 +58,10 @@ func (s StepCreateNewDisk) Run(ctx context.Context, state multistep.StateBag) mu
disk.DiskProperties.DiskSizeGB = to.Int32Ptr(s.DiskSizeGB)
}
if s.PlatformImage == nil {
if s.SourceDiskResourceID != "" {
disk.CreationData.CreateOption = compute.Copy
disk.CreationData.SourceResourceID = to.StringPtr(s.SourceDiskResourceID)
} else if s.PlatformImage == nil {
disk.CreationData.CreateOption = compute.Empty
} else {
disk.CreationData.CreateOption = compute.FromImage

View File

@ -0,0 +1,69 @@
package chroot
import (
"context"
"io/ioutil"
"net/http"
"regexp"
"testing"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
"github.com/Azure/go-autorest/autorest"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func Test_StepCreateNewDisk_FromDisk(t *testing.T) {
sut := StepCreateNewDisk{
SubscriptionID: "SubscriptionID",
ResourceGroup: "ResourceGroupName",
DiskName: "TemporaryOSDiskName",
DiskSizeGB: 42,
DiskStorageAccountType: string(compute.PremiumLRS),
HyperVGeneration: string(compute.V1),
Location: "westus",
SourceDiskResourceID: "SourceDisk",
}
expected := regexp.MustCompile(`[\s\n]`).ReplaceAllString(`
{
"location": "westus",
"properties": {
"osType": "Linux",
"hyperVGeneration": "V1",
"creationData": {
"createOption": "Copy",
"sourceResourceId": "SourceDisk"
},
"diskSizeGB": 42
},
"sku": {
"name": "Premium_LRS"
}
}`, "")
m := compute.NewDisksClient("subscriptionId")
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
b, _ := ioutil.ReadAll(r.Body)
if string(b) != expected {
t.Fatalf("expected body to be %q, but got %q", expected, string(b))
}
return &http.Response{
Request: r,
StatusCode: 200,
}, nil
})
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
DisksClientMock: m,
})
state.Put("ui", packer.TestUi(t))
r := sut.Run(context.TODO(), state)
if r != multistep.ActionContinue {
t.Fatal("Run failed")
}
}

View File

@ -0,0 +1,46 @@
package client
import (
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute/computeapi"
"github.com/Azure/go-autorest/autorest"
)
// AzureClientSetMock provides a generic mock for AzureClientSet
type AzureClientSetMock struct {
DisksClientMock computeapi.DisksClientAPI
ImagesClientMock computeapi.ImagesClientAPI
VirtualMachineImagesClientMock VirtualMachineImagesClientAPI
VirtualMachinesClientMock computeapi.VirtualMachinesClientAPI
PollClientMock autorest.Client
MetadataClientMock MetadataClientAPI
}
// DisksClient returns a DisksClientAPI
func (m *AzureClientSetMock) DisksClient() computeapi.DisksClientAPI {
return m.DisksClientMock
}
// ImagesClient returns a ImagesClientAPI
func (m *AzureClientSetMock) ImagesClient() computeapi.ImagesClientAPI {
return m.ImagesClientMock
}
// VirtualMachineImagesClient returns a VirtualMachineImagesClientAPI
func (m *AzureClientSetMock) VirtualMachineImagesClient() VirtualMachineImagesClientAPI {
return m.VirtualMachineImagesClientMock
}
// VirtualMachinesClient returns a VirtualMachinesClientAPI
func (m *AzureClientSetMock) VirtualMachinesClient() computeapi.VirtualMachinesClientAPI {
return m.VirtualMachinesClientMock
}
// PollClient returns an autorest Client that can be used for polling async requests
func (m *AzureClientSetMock) PollClient() autorest.Client {
return m.PollClientMock
}
// MetadataClient returns a MetadataClientAPI
func (m *AzureClientSetMock) MetadataClient() MetadataClientAPI {
return m.MetadataClientMock
}