From c6b995a34d4da8f18a2fd49ae8d92163211ac903 Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Wed, 29 Apr 2020 18:28:52 +0000 Subject: [PATCH] create snapshots for all disks in a diskset --- builder/azure/chroot/builder.go | 15 +- builder/azure/chroot/const.go | 4 +- .../step_create_shared_image_version.go | 2 +- .../step_create_shared_image_version_test.go | 6 +- builder/azure/chroot/step_create_snapshot.go | 126 ---------- .../azure/chroot/step_create_snapshot_test.go | 216 ---------------- .../azure/chroot/step_create_snapshotset.go | 126 ++++++++++ .../chroot/step_create_snapshotset_test.go | 230 ++++++++++++++++++ 8 files changed, 371 insertions(+), 354 deletions(-) delete mode 100644 builder/azure/chroot/step_create_snapshot.go delete mode 100644 builder/azure/chroot/step_create_snapshot_test.go create mode 100644 builder/azure/chroot/step_create_snapshotset.go create mode 100644 builder/azure/chroot/step_create_snapshotset_test.go diff --git a/builder/azure/chroot/builder.go b/builder/azure/chroot/builder.go index 4295d19e4..c335ef087 100644 --- a/builder/azure/chroot/builder.go +++ b/builder/azure/chroot/builder.go @@ -437,8 +437,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack artifact.Resources = append(artifact.Resources, disk.String()) } } - if d, ok := state.GetOk(stateBagKey_OSDiskSnapshotResourceID); ok { - artifact.Resources = append(artifact.Resources, d.(string)) + if d, ok := state.GetOk(stateBagKey_Snapshotset); ok { + for _, snapshot := range d.([]client.Resource) { + artifact.Resources = append(artifact.Resources, snapshot.String()) + } } } @@ -570,10 +572,11 @@ func buildsteps(config Config, info *client.ComputeInfo) []multistep.Step { } if hasValidSharedImage { addSteps( - &StepCreateSnapshot{ - ResourceID: config.TemporaryOSDiskSnapshotID, - Location: info.Location, - SkipCleanup: config.SkipCleanup, + &StepCreateSnapshotset{ + OSDiskSnapshotID: config.TemporaryOSDiskSnapshotID, + DataDiskSnapshotIDPrefix: config.TemporaryDataDiskSnapshotIDPrefix, + Location: info.Location, + SkipCleanup: config.SkipCleanup, }, &StepCreateSharedImageVersion{ Destination: config.SharedImageGalleryDestination, diff --git a/builder/azure/chroot/const.go b/builder/azure/chroot/const.go index dc3b86409..e99cf6284 100644 --- a/builder/azure/chroot/const.go +++ b/builder/azure/chroot/const.go @@ -1,6 +1,6 @@ package chroot const ( - stateBagKey_Diskset = "diskset" - stateBagKey_OSDiskSnapshotResourceID = "os_disk_snapshot_resource_id" + stateBagKey_Diskset = "diskset" + stateBagKey_Snapshotset = "snapshotset" ) diff --git a/builder/azure/chroot/step_create_shared_image_version.go b/builder/azure/chroot/step_create_shared_image_version.go index 88da684b4..83543f1e6 100644 --- a/builder/azure/chroot/step_create_shared_image_version.go +++ b/builder/azure/chroot/step_create_shared_image_version.go @@ -22,7 +22,7 @@ type StepCreateSharedImageVersion struct { func (s *StepCreateSharedImageVersion) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { azcli := state.Get("azureclient").(client.AzureClientSet) ui := state.Get("ui").(packer.Ui) - osDiskSnapshotResourceID := state.Get(stateBagKey_OSDiskSnapshotResourceID).(string) + osDiskSnapshotResourceID := state.Get(stateBagKey_Snapshotset).(Diskset)[-1].String() ui.Say(fmt.Sprintf("Creating image version %s\n using %s for os disk.", s.Destination.ResourceID(azcli.SubscriptionID()), diff --git a/builder/azure/chroot/step_create_shared_image_version_test.go b/builder/azure/chroot/step_create_shared_image_version_test.go index 5721a978e..09a750c48 100644 --- a/builder/azure/chroot/step_create_shared_image_version_test.go +++ b/builder/azure/chroot/step_create_shared_image_version_test.go @@ -37,7 +37,7 @@ func TestStepCreateSharedImageVersion_Run(t *testing.T) { ImageName: "ImageName", ImageVersion: "0.1.2", TargetRegions: []TargetRegion{ - TargetRegion{ + { Name: "region1", ReplicaCount: 5, StorageAccountType: "Standard_ZRS", @@ -63,7 +63,7 @@ func TestStepCreateSharedImageVersion_Run(t *testing.T) { "storageProfile": { "osDiskImage": { "source": { - "id": "osdisksnapshotresourceid" + "id": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/snapshots/snapshot1" } } } @@ -94,7 +94,7 @@ func TestStepCreateSharedImageVersion_Run(t *testing.T) { GalleryImageVersionsClientMock: m, }) state.Put("ui", packer.TestUi(t)) - state.Put(stateBagKey_OSDiskSnapshotResourceID, "osdisksnapshotresourceid") + state.Put(stateBagKey_Snapshotset, diskset("/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/snapshots/snapshot1")) t.Run(tt.name, func(t *testing.T) { s := &StepCreateSharedImageVersion{ diff --git a/builder/azure/chroot/step_create_snapshot.go b/builder/azure/chroot/step_create_snapshot.go deleted file mode 100644 index 31ac4d566..000000000 --- a/builder/azure/chroot/step_create_snapshot.go +++ /dev/null @@ -1,126 +0,0 @@ -package chroot - -import ( - "context" - "fmt" - "log" - "strings" - "time" - - "github.com/hashicorp/packer/builder/azure/common/client" - "github.com/hashicorp/packer/helper/multistep" - "github.com/hashicorp/packer/packer" - - "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute" - "github.com/Azure/go-autorest/autorest/azure" - "github.com/Azure/go-autorest/autorest/to" -) - -var _ multistep.Step = &StepCreateSnapshot{} - -type StepCreateSnapshot struct { - ResourceID string - Location string - - SkipCleanup bool - - subscriptionID, resourceGroup, snapshotName string -} - -func parseSnapshotResourceID(resourceID string) (subscriptionID, resourceGroup, snapshotName string, err error) { - r, err := azure.ParseResourceID(resourceID) - if err != nil { - return "", "", "", err - } - - if !strings.EqualFold(r.Provider, "Microsoft.Compute") || - !strings.EqualFold(r.ResourceType, "snapshots") { - return "", "", "", fmt.Errorf("Resource %q is not of type Microsoft.Compute/snapshots", resourceID) - } - - return r.SubscriptionID, r.ResourceGroup, r.ResourceName, nil -} - -func (s *StepCreateSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - azcli := state.Get("azureclient").(client.AzureClientSet) - ui := state.Get("ui").(packer.Ui) - diskset := state.Get(stateBagKey_Diskset).(Diskset) - osDiskResourceID := diskset.OS().String() - - state.Put(stateBagKey_OSDiskSnapshotResourceID, s.ResourceID) - ui.Say(fmt.Sprintf("Creating snapshot '%s'", s.ResourceID)) - - var err error - s.subscriptionID, s.resourceGroup, s.snapshotName, err = parseSnapshotResourceID(s.ResourceID) - if err != nil { - log.Printf("StepCreateSnapshot.Run: error: %+v", err) - err := fmt.Errorf( - "error parsing resource id '%s': %v", s.ResourceID, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - snapshot := compute.Snapshot{ - Location: to.StringPtr(s.Location), - SnapshotProperties: &compute.SnapshotProperties{ - CreationData: &compute.CreationData{ - CreateOption: compute.Copy, - SourceResourceID: to.StringPtr(osDiskResourceID), - }, - Incremental: to.BoolPtr(false), - }, - } - - f, err := azcli.SnapshotsClient().CreateOrUpdate(ctx, s.resourceGroup, s.snapshotName, snapshot) - if err == nil { - pollClient := azcli.PollClient() - pollClient.PollingDelay = 2 * time.Second - ctx, cancel := context.WithTimeout(ctx, time.Hour*12) - defer cancel() - err = f.WaitForCompletionRef(ctx, pollClient) - } - if err != nil { - log.Printf("StepCreateSnapshot.Run: error: %+v", err) - err := fmt.Errorf( - "error creating snapshot '%s': %v", s.ResourceID, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - return multistep.ActionContinue -} - -func (s *StepCreateSnapshot) Cleanup(state multistep.StateBag) { - if !s.SkipCleanup { - azcli := state.Get("azureclient").(client.AzureClientSet) - ui := state.Get("ui").(packer.Ui) - - ui.Say(fmt.Sprintf("Removing any active SAS for snapshot %q", s.ResourceID)) - { - f, err := azcli.SnapshotsClient().RevokeAccess(context.TODO(), s.resourceGroup, s.snapshotName) - if err == nil { - log.Printf("StepCreateSnapshot.Cleanup: removing SAS...") - err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient()) - } - if err != nil { - log.Printf("StepCreateSnapshot.Cleanup: error: %+v", err) - ui.Error(fmt.Sprintf("error deleting snapshot '%s': %v.", s.ResourceID, err)) - } - } - - ui.Say(fmt.Sprintf("Deleting snapshot %q", s.ResourceID)) - { - f, err := azcli.SnapshotsClient().Delete(context.TODO(), s.resourceGroup, s.snapshotName) - if err == nil { - log.Printf("StepCreateSnapshot.Cleanup: deleting snapshot...") - err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient()) - } - if err != nil { - log.Printf("StepCreateSnapshot.Cleanup: error: %+v", err) - ui.Error(fmt.Sprintf("error deleting snapshot '%s': %v.", s.ResourceID, err)) - } - } - } -} diff --git a/builder/azure/chroot/step_create_snapshot_test.go b/builder/azure/chroot/step_create_snapshot_test.go deleted file mode 100644 index 4821e6bf2..000000000 --- a/builder/azure/chroot/step_create_snapshot_test.go +++ /dev/null @@ -1,216 +0,0 @@ -package chroot - -import ( - "context" - "io/ioutil" - "net/http" - "reflect" - "regexp" - "strings" - "testing" - - "github.com/hashicorp/packer/builder/azure/common/client" - "github.com/hashicorp/packer/helper/multistep" - "github.com/hashicorp/packer/packer" - - "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute" - "github.com/Azure/go-autorest/autorest" -) - -func Test_parseSnapshotResourceID(t *testing.T) { - - tests := []struct { - name string - resourceID string - wantSubscriptionID string - wantResourceGroup string - wantSnapshotName string - wantErr bool - }{ - { - name: "happy path", - resourceID: "/subscriptions/1234/resourceGroups/rg/providers/microsoft.compute/snapshots/disksnapshot1", - wantErr: false, - wantSubscriptionID: "1234", - wantResourceGroup: "rg", - wantSnapshotName: "disksnapshot1", - }, - { - name: "error: nonsense", - resourceID: "nonsense", - wantErr: true, - }, - { - name: "error: other resource type", - resourceID: "/subscriptions/1234/resourceGroups/rg/providers/microsoft.compute/disks/disksnapshot1", - wantErr: true, - }, - { - name: "error: no name", - resourceID: "/subscriptions/1234/resourceGroups/rg/providers/microsoft.compute/snapshots", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotSubscriptionID, gotResourceGroup, gotSnapshotName, err := parseSnapshotResourceID(tt.resourceID) - if (err != nil) != tt.wantErr { - t.Errorf("parseSnapshotResourceID() error = %v, wantErr %v", err, tt.wantErr) - return - } - if gotSubscriptionID != tt.wantSubscriptionID { - t.Errorf("parseSnapshotResourceID() gotSubscriptionID = %v, want %v", gotSubscriptionID, tt.wantSubscriptionID) - } - if gotResourceGroup != tt.wantResourceGroup { - t.Errorf("parseSnapshotResourceID() gotResourceGroup = %v, want %v", gotResourceGroup, tt.wantResourceGroup) - } - if gotSnapshotName != tt.wantSnapshotName { - t.Errorf("parseSnapshotResourceID() gotSnapshotName = %v, want %v", gotSnapshotName, tt.wantSnapshotName) - } - }) - } -} - -func TestStepCreateSnapshot_Run(t *testing.T) { - type fields struct { - ResourceID string - Location string - } - tests := []struct { - name string - fields fields - want multistep.StepAction - wantSnapshotID string - expectedPutBody string - }{ - { - name: "happy path", - fields: fields{ - ResourceID: "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/snap1", - Location: "region1", - }, - expectedPutBody: `{ - "location": "region1", - "properties": { - "creationData": { - "createOption": "Copy", - "sourceResourceId": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1" - }, - "incremental": false - } - }`, - }, - { - name: "invalid ResourceID", - fields: fields{ - ResourceID: "notaresourceid", - Location: "region1", - }, - want: multistep.ActionHalt, - }, - } - for _, tt := range tests { - expectedPutBody := regexp.MustCompile(`[\s\n]`).ReplaceAllString(tt.expectedPutBody, "") - - m := compute.NewSnapshotsClient("subscriptionId") - m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) { - if r.Method != "PUT" { - t.Fatal("Expected only a PUT call") - } - b, _ := ioutil.ReadAll(r.Body) - if string(b) != expectedPutBody { - t.Errorf("expected body to be %v, but got %v", expectedPutBody, string(b)) - } - return &http.Response{ - Request: r, - StatusCode: 200, - }, nil - }) - - state := new(multistep.BasicStateBag) - state.Put("azureclient", &client.AzureClientSetMock{ - SnapshotsClientMock: m, - }) - state.Put("ui", packer.TestUi(t)) - state.Put(stateBagKey_Diskset, diskset("/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1")) - - t.Run(tt.name, func(t *testing.T) { - s := &StepCreateSnapshot{ - ResourceID: tt.fields.ResourceID, - Location: tt.fields.Location, - } - if got := s.Run(context.TODO(), state); !reflect.DeepEqual(got, tt.want) { - t.Errorf("StepCreateSnapshot.Run() = %v, want %v", got, tt.want) - } - - if tt.wantSnapshotID != "" { - got := state.Get(stateBagKey_OSDiskSnapshotResourceID).(string) - if !strings.EqualFold(got, tt.wantSnapshotID) { - t.Errorf("OSDiskSnapshotResourceID = %v, want %v", got, tt.wantSnapshotID) - } - } - - }) - } -} - -func TestStepCreateSnapshot_Cleanup_skipped(t *testing.T) { - m := compute.NewSnapshotsClient("subscriptionId") - m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) { - t.Fatalf("clean up should be skipped, did not expect HTTP calls") - return nil, nil - }) - - state := new(multistep.BasicStateBag) - state.Put("azureclient", &client.AzureClientSetMock{ - SnapshotsClientMock: m, - }) - state.Put("ui", packer.TestUi(t)) - - s := &StepCreateSnapshot{ - SkipCleanup: true, - } - s.Cleanup(state) -} - -func TestStepCreateSnapshot_Cleanup(t *testing.T) { - m := compute.NewSnapshotsClient("subscriptionId") - { - expectedCalls := []string{ - "POST /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/snap1/endGetAccess", - "DELETE /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/snap1", - } - i := 0 - m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) { - want := expectedCalls[i] - got := r.Method + " " + r.URL.Path - if want != got { - t.Errorf("unexpected HTTP call: %v, wanted %v", got, want) - return &http.Response{ - Request: r, - StatusCode: 599, // 500 is retried - }, nil - } - i++ - return &http.Response{ - Request: r, - StatusCode: 200, - }, nil - }) - } - state := new(multistep.BasicStateBag) - state.Put("azureclient", &client.AzureClientSetMock{ - SnapshotsClientMock: m, - }) - state.Put("ui", packer.TestUi(t)) - - s := &StepCreateSnapshot{ - ResourceID: "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/snap1", - Location: "region1", - SkipCleanup: false, - resourceGroup: "rg", - snapshotName: "snap1", - subscriptionID: "1234", - } - s.Cleanup(state) -} diff --git a/builder/azure/chroot/step_create_snapshotset.go b/builder/azure/chroot/step_create_snapshotset.go new file mode 100644 index 000000000..a03fbe5c9 --- /dev/null +++ b/builder/azure/chroot/step_create_snapshotset.go @@ -0,0 +1,126 @@ +package chroot + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/hashicorp/packer/builder/azure/common/client" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute" + "github.com/Azure/go-autorest/autorest/to" +) + +var _ multistep.Step = &StepCreateSnapshotset{} + +type StepCreateSnapshotset struct { + OSDiskSnapshotID string + DataDiskSnapshotIDPrefix string + Location string + + SkipCleanup bool + + snapshots Diskset +} + +func (s *StepCreateSnapshotset) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + azcli := state.Get("azureclient").(client.AzureClientSet) + ui := state.Get("ui").(packer.Ui) + diskset := state.Get(stateBagKey_Diskset).(Diskset) + + s.snapshots = make(Diskset) + + errorMessage := func(format string, params ...interface{}) multistep.StepAction { + err := fmt.Errorf("StepCreateSnapshotset.Run: error: "+format, params...) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + for lun, resource := range diskset { + snapshotID := fmt.Sprintf("%s%d", s.DataDiskSnapshotIDPrefix, lun) + if lun == -1 { + snapshotID = s.OSDiskSnapshotID + } + ssr, err := client.ParseResourceID(snapshotID) + if err != nil { + errorMessage("Could not create a valid resource id, tried %q: %v", snapshotID, err) + } + if !strings.EqualFold(ssr.Provider, "Microsoft.Compute") || + !strings.EqualFold(ssr.ResourceType.String(), "snapshots") { + return errorMessage("Resource %q is not of type Microsoft.Compute/snapshots", snapshotID) + } + s.snapshots[lun] = ssr + state.Put(stateBagKey_Snapshotset, s.snapshots) + + ui.Say(fmt.Sprintf("Creating snapshot %q", ssr)) + + snapshot := compute.Snapshot{ + Location: to.StringPtr(s.Location), + SnapshotProperties: &compute.SnapshotProperties{ + CreationData: &compute.CreationData{ + CreateOption: compute.Copy, + SourceResourceID: to.StringPtr(resource.String()), + }, + Incremental: to.BoolPtr(false), + }, + } + + f, err := azcli.SnapshotsClient().CreateOrUpdate(ctx, ssr.ResourceGroup, ssr.ResourceName.String(), snapshot) + if err != nil { + return errorMessage("error initiating snapshot %q: %v", ssr, err) + } + + pollClient := azcli.PollClient() + pollClient.PollingDelay = 2 * time.Second + ctx, cancel := context.WithTimeout(ctx, time.Hour*12) + defer cancel() + err = f.WaitForCompletionRef(ctx, pollClient) + + if err != nil { + return errorMessage("error creating snapshot '%s': %v", s.OSDiskSnapshotID, err) + } + } + + return multistep.ActionContinue +} + +func (s *StepCreateSnapshotset) Cleanup(state multistep.StateBag) { + if !s.SkipCleanup { + azcli := state.Get("azureclient").(client.AzureClientSet) + ui := state.Get("ui").(packer.Ui) + + for _, resource := range s.snapshots { + + ui.Say(fmt.Sprintf("Removing any active SAS for snapshot %q", resource)) + { + f, err := azcli.SnapshotsClient().RevokeAccess(context.TODO(), resource.ResourceGroup, resource.ResourceName.String()) + if err == nil { + log.Printf("StepCreateSnapshotset.Cleanup: removing SAS...") + err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient()) + } + if err != nil { + log.Printf("StepCreateSnapshotset.Cleanup: error: %+v", err) + ui.Error(fmt.Sprintf("error deleting snapshot %q: %v.", resource, err)) + } + } + + ui.Say(fmt.Sprintf("Deleting snapshot %q", resource)) + { + f, err := azcli.SnapshotsClient().Delete(context.TODO(), resource.ResourceGroup, resource.ResourceName.String()) + if err == nil { + log.Printf("StepCreateSnapshotset.Cleanup: deleting snapshot...") + err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient()) + } + if err != nil { + log.Printf("StepCreateSnapshotset.Cleanup: error: %+v", err) + ui.Error(fmt.Sprintf("error deleting snapshot %q: %v.", resource, err)) + } + } + } + } +} diff --git a/builder/azure/chroot/step_create_snapshotset_test.go b/builder/azure/chroot/step_create_snapshotset_test.go new file mode 100644 index 000000000..c7baf74e5 --- /dev/null +++ b/builder/azure/chroot/step_create_snapshotset_test.go @@ -0,0 +1,230 @@ +package chroot + +import ( + "context" + "io/ioutil" + "net/http" + "reflect" + "regexp" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/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 TestStepCreateSnapshot_Run(t *testing.T) { + type fields struct { + OSDiskSnapshotID string + DataDiskSnapshotIDPrefix string + Location string + } + tests := []struct { + name string + fields fields + diskset Diskset + want multistep.StepAction + wantSnapshotset Diskset + expectedPutBody string + }{ + { + name: "happy path", + fields: fields{ + OSDiskSnapshotID: "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/osdisk-snap", + Location: "region1", + }, + diskset: diskset("/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1"), + expectedPutBody: `{ + "location": "region1", + "properties": { + "creationData": { + "createOption": "Copy", + "sourceResourceId": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1" + }, + "incremental": false + } + }`, + wantSnapshotset: diskset("/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/osdisk-snap"), + }, + { + name: "multi disk", + fields: fields{ + OSDiskSnapshotID: "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/osdisk-snap", + DataDiskSnapshotIDPrefix: "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datadisk-snap", + Location: "region1", + }, + diskset: diskset( + "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/osdisk", + "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk1", + "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk2", + "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk3"), + wantSnapshotset: diskset( + "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/osdisk-snap", + "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datadisk-snap0", + "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datadisk-snap1", + "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datadisk-snap2", + ), + }, + { + name: "invalid ResourceID", + fields: fields{ + OSDiskSnapshotID: "notaresourceid", + Location: "region1", + }, + diskset: diskset("/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1"), + want: multistep.ActionHalt, + }, + } + for _, tt := range tests { + expectedPutBody := regexp.MustCompile(`[\s\n]`).ReplaceAllString(tt.expectedPutBody, "") + + m := compute.NewSnapshotsClient("subscriptionId") + m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) { + if r.Method != "PUT" { + t.Fatal("Expected only a PUT call") + } + if expectedPutBody != "" { + b, _ := ioutil.ReadAll(r.Body) + if string(b) != expectedPutBody { + t.Errorf("expected body to be %v, but got %v", expectedPutBody, string(b)) + } + } + return &http.Response{ + Request: r, + StatusCode: 200, + }, nil + }) + + state := new(multistep.BasicStateBag) + state.Put("azureclient", &client.AzureClientSetMock{ + SnapshotsClientMock: m, + }) + state.Put("ui", packer.TestUi(t)) + state.Put(stateBagKey_Diskset, tt.diskset) + + t.Run(tt.name, func(t *testing.T) { + s := &StepCreateSnapshotset{ + OSDiskSnapshotID: tt.fields.OSDiskSnapshotID, + DataDiskSnapshotIDPrefix: tt.fields.DataDiskSnapshotIDPrefix, + Location: tt.fields.Location, + } + if got := s.Run(context.TODO(), state); !reflect.DeepEqual(got, tt.want) { + t.Errorf("StepCreateSnapshot.Run() = %v, want %v", got, tt.want) + } + + if len(tt.wantSnapshotset) > 0 { + got := state.Get(stateBagKey_Snapshotset).(Diskset) + if !reflect.DeepEqual(got, tt.wantSnapshotset) { + t.Errorf("Snapshotset = %v, want %v", got, tt.wantSnapshotset) + } + } + + }) + } +} + +func TestStepCreateSnapshot_Cleanup_skipped(t *testing.T) { + m := compute.NewSnapshotsClient("subscriptionId") + m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) { + t.Fatalf("clean up should be skipped, did not expect HTTP calls") + return nil, nil + }) + + state := new(multistep.BasicStateBag) + state.Put("azureclient", &client.AzureClientSetMock{ + SnapshotsClientMock: m, + }) + state.Put("ui", packer.TestUi(t)) + + s := &StepCreateSnapshotset{ + SkipCleanup: true, + } + s.Cleanup(state) +} + +func TestStepCreateSnapshot_Cleanup(t *testing.T) { + m := compute.NewSnapshotsClient("subscriptionId") + { + expectedCalls := []string{ + "POST /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/ossnap/endGetAccess", + "DELETE /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/ossnap", + "POST /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap1/endGetAccess", + "DELETE /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap1", + "POST /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap2/endGetAccess", + "DELETE /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap2", + } + + m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) { + got := r.Method + " " + r.URL.Path + found := false + for i, call := range expectedCalls { + if call == got { + // swap i with last and drop last + expectedCalls[i] = expectedCalls[len(expectedCalls)-1] + expectedCalls = expectedCalls[:len(expectedCalls)-1] + found = true + break + } + } + if !found { + t.Errorf("unexpected HTTP call: %v, wanted one of %q", got, expectedCalls) + return &http.Response{ + Request: r, + StatusCode: 599, // 500 is retried + }, nil + } + return &http.Response{ + Request: r, + StatusCode: 200, + }, nil + }) + } + state := new(multistep.BasicStateBag) + state.Put("azureclient", &client.AzureClientSetMock{ + SnapshotsClientMock: m, + }) + state.Put("ui", packer.TestUi(t)) + + s := &StepCreateSnapshotset{ + SkipCleanup: false, + snapshots: diskset( + "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/ossnap", + "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap1", + "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap2"), + } + s.Cleanup(state) +} + +func TestStepCreateSnapshotset_Cleanup(t *testing.T) { + type fields struct { + OSDiskSnapshotID string + DataDiskSnapshotIDPrefix string + Location string + SkipCleanup bool + snapshots Diskset + } + type args struct { + state multistep.StateBag + } + tests := []struct { + name string + fields fields + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &StepCreateSnapshotset{ + OSDiskSnapshotID: tt.fields.OSDiskSnapshotID, + DataDiskSnapshotIDPrefix: tt.fields.DataDiskSnapshotIDPrefix, + Location: tt.fields.Location, + SkipCleanup: tt.fields.SkipCleanup, + snapshots: tt.fields.snapshots, + } + s.Cleanup(tt.args.state) + }) + } +}