Add implementation for disk as source
This commit is contained in:
parent
eff3f2bdcf
commit
27a5bfe11c
|
@ -4,8 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Azure/go-autorest/autorest/azure"
|
|
||||||
"github.com/Azure/go-autorest/autorest/to"
|
|
||||||
"log"
|
"log"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,6 +18,8 @@ import (
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
|
"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 {
|
type Config struct {
|
||||||
|
@ -29,6 +29,7 @@ type Config struct {
|
||||||
|
|
||||||
FromScratch bool `mapstructure:"from_scratch"`
|
FromScratch bool `mapstructure:"from_scratch"`
|
||||||
Source string `mapstructure:"source"`
|
Source string `mapstructure:"source"`
|
||||||
|
sourceType sourceType
|
||||||
|
|
||||||
CommandWrapper string `mapstructure:"command_wrapper"`
|
CommandWrapper string `mapstructure:"command_wrapper"`
|
||||||
PreMountCommands []string `mapstructure:"pre_mount_commands"`
|
PreMountCommands []string `mapstructure:"pre_mount_commands"`
|
||||||
|
@ -52,6 +53,13 @@ type Config struct {
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sourceType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
sourcePlatformImage sourceType = "PlatformImage"
|
||||||
|
sourceDisk sourceType = "Disk"
|
||||||
|
)
|
||||||
|
|
||||||
func (c *Config) GetContext() interpolate.Context {
|
func (c *Config) GetContext() interpolate.Context {
|
||||||
return c.ctx
|
return c.ctx
|
||||||
}
|
}
|
||||||
|
@ -163,9 +171,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
} else {
|
} else {
|
||||||
if _, err := client.ParsePlatformImageURN(b.config.Source); err == nil {
|
if _, err := client.ParsePlatformImageURN(b.config.Source); err == nil {
|
||||||
log.Println("Source is platform image:", b.config.Source)
|
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 {
|
} else {
|
||||||
errs = packer.MultiErrorAppend(
|
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,
|
Location: info.Location,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if pi, err := client.ParsePlatformImageURN(b.config.Source); err == nil {
|
switch b.config.sourceType {
|
||||||
if strings.EqualFold(pi.Version, "latest") {
|
case sourcePlatformImage:
|
||||||
|
|
||||||
vmi, err := azcli.VirtualMachineImagesClient().GetLatest(ctx, pi.Publisher, pi.Offer, pi.Sku, info.Location)
|
if pi, err := client.ParsePlatformImageURN(b.config.Source); err == nil {
|
||||||
if err != nil {
|
if strings.EqualFold(pi.Version, "latest") {
|
||||||
return nil, fmt.Errorf("error retieving latest version of %q: %v", b.config.Source, err)
|
|
||||||
|
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)
|
steps = append(steps,
|
||||||
log.Println("Resolved latest version of source image:", pi.Version)
|
&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,
|
steps = append(steps,
|
||||||
&StepCreateNewDisk{
|
&StepCreateNewDisk{
|
||||||
SubscriptionID: info.SubscriptionID,
|
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,
|
DiskSizeGB: b.config.OSDiskSizeGB,
|
||||||
DiskStorageAccountType: b.config.OSDiskStorageAccountType,
|
DiskStorageAccountType: b.config.OSDiskStorageAccountType,
|
||||||
HyperVGeneration: b.config.ImageHyperVGeneration,
|
HyperVGeneration: b.config.ImageHyperVGeneration,
|
||||||
Location: info.Location,
|
SourceDiskResourceID: b.config.Source,
|
||||||
PlatformImage: pi,
|
//todo(paulmey) validate that source disk is in same location as VM
|
||||||
|
|
||||||
SkipCleanup: b.config.OSDiskSkipCleanup,
|
SkipCleanup: b.config.OSDiskSkipCleanup,
|
||||||
})
|
})
|
||||||
} else {
|
default:
|
||||||
panic("Unknown image source: " + b.config.Source)
|
panic(fmt.Errorf("Unknown source type: %+q", b.config.sourceType))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,11 @@ type StepCreateNewDisk struct {
|
||||||
DiskSizeGB int32 // optional, ignored if 0
|
DiskSizeGB int32 // optional, ignored if 0
|
||||||
DiskStorageAccountType string // from compute.DiskStorageAccountTypes
|
DiskStorageAccountType string // from compute.DiskStorageAccountTypes
|
||||||
HyperVGeneration string
|
HyperVGeneration string
|
||||||
Location string
|
|
||||||
PlatformImage *client.PlatformImage
|
Location string
|
||||||
|
PlatformImage *client.PlatformImage
|
||||||
|
|
||||||
|
SourceDiskResourceID string
|
||||||
|
|
||||||
SkipCleanup bool
|
SkipCleanup bool
|
||||||
}
|
}
|
||||||
|
@ -55,7 +58,10 @@ func (s StepCreateNewDisk) Run(ctx context.Context, state multistep.StateBag) mu
|
||||||
disk.DiskProperties.DiskSizeGB = to.Int32Ptr(s.DiskSizeGB)
|
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
|
disk.CreationData.CreateOption = compute.Empty
|
||||||
} else {
|
} else {
|
||||||
disk.CreationData.CreateOption = compute.FromImage
|
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