Add implementation for disk as source
This commit is contained in:
parent
eff3f2bdcf
commit
27a5bfe11c
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue