From d796edc783f346b2532c23f284af991e90e12b05 Mon Sep 17 00:00:00 2001 From: Patrick Double Date: Wed, 8 Aug 2018 10:04:28 -0500 Subject: [PATCH 1/2] Add to vagrant post-processor support for Azure --- builder/azure/arm/artifact.go | 13 ++- builder/azure/arm/artifact_test.go | 28 ++++-- builder/azure/arm/builder.go | 11 ++- packer/artifact_mock.go | 9 +- post-processor/vagrant/azure.go | 67 +++++++++++++ post-processor/vagrant/azure_test.go | 94 +++++++++++++++++++ post-processor/vagrant/post-processor.go | 3 + .../docs/post-processors/vagrant.html.md | 2 + 8 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 post-processor/vagrant/azure.go create mode 100644 post-processor/vagrant/azure_test.go diff --git a/builder/azure/arm/artifact.go b/builder/azure/arm/artifact.go index 8e42fa43d..f7e63d6c4 100644 --- a/builder/azure/arm/artifact.go +++ b/builder/azure/arm/artifact.go @@ -18,6 +18,9 @@ type AdditionalDiskArtifact struct { } type Artifact struct { + // OS type: Linux, Windows + OSType string + // VHD StorageAccountLocation string OSDiskUri string @@ -29,20 +32,23 @@ type Artifact struct { ManagedImageResourceGroupName string ManagedImageName string ManagedImageLocation string + ManagedImageId string // Additional Disks AdditionalDisks *[]AdditionalDiskArtifact } -func NewManagedImageArtifact(resourceGroup, name, location string) (*Artifact, error) { +func NewManagedImageArtifact(osType, resourceGroup, name, location, id string) (*Artifact, error) { return &Artifact{ ManagedImageResourceGroupName: resourceGroup, ManagedImageName: name, ManagedImageLocation: location, + ManagedImageId: id, + OSType: osType, }, nil } -func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string) (*Artifact, error) { +func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string, osType string) (*Artifact, error) { if template == nil { return nil, fmt.Errorf("nil capture template") } @@ -76,6 +82,7 @@ func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string) } return &Artifact{ + OSType: osType, OSDiskUri: vhdUri.String(), OSDiskUriReadOnlySas: getSasUrl(getStorageUrlPath(vhdUri)), TemplateUri: templateUri.String(), @@ -142,9 +149,11 @@ func (a *Artifact) String() string { var buf bytes.Buffer buf.WriteString(fmt.Sprintf("%s:\n\n", a.BuilderId())) + buf.WriteString(fmt.Sprintf("OSType: %s\n", a.OSType)) if a.isManagedImage() { buf.WriteString(fmt.Sprintf("ManagedImageResourceGroupName: %s\n", a.ManagedImageResourceGroupName)) buf.WriteString(fmt.Sprintf("ManagedImageName: %s\n", a.ManagedImageName)) + buf.WriteString(fmt.Sprintf("ManagedImageId: %s\n", a.ManagedImageId)) buf.WriteString(fmt.Sprintf("ManagedImageLocation: %s\n", a.ManagedImageLocation)) } else { buf.WriteString(fmt.Sprintf("StorageAccountLocation: %s\n", a.StorageAccountLocation)) diff --git a/builder/azure/arm/artifact_test.go b/builder/azure/arm/artifact_test.go index a86a6353e..a65427897 100644 --- a/builder/azure/arm/artifact_test.go +++ b/builder/azure/arm/artifact_test.go @@ -28,7 +28,7 @@ func TestArtifactId(t *testing.T) { }, } - artifact, err := NewArtifact(&template, getFakeSasUrl) + artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux") if err != nil { t.Fatalf("err=%s", err) } @@ -59,7 +59,7 @@ func TestArtifactString(t *testing.T) { }, } - artifact, err := NewArtifact(&template, getFakeSasUrl) + artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux") if err != nil { t.Fatalf("err=%s", err) } @@ -80,6 +80,9 @@ func TestArtifactString(t *testing.T) { if !strings.Contains(testSubject, "StorageAccountLocation: southcentralus") { t.Errorf("Expected String() output to contain StorageAccountLocation") } + if !strings.Contains(testSubject, "OSType: Linux") { + t.Errorf("Expected String() output to contain OSType") + } } func TestAdditionalDiskArtifactString(t *testing.T) { @@ -107,7 +110,7 @@ func TestAdditionalDiskArtifactString(t *testing.T) { }, } - artifact, err := NewArtifact(&template, getFakeSasUrl) + artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux") if err != nil { t.Fatalf("err=%s", err) } @@ -128,6 +131,9 @@ func TestAdditionalDiskArtifactString(t *testing.T) { if !strings.Contains(testSubject, "StorageAccountLocation: southcentralus") { t.Errorf("Expected String() output to contain StorageAccountLocation") } + if !strings.Contains(testSubject, "OSType: Linux") { + t.Errorf("Expected String() output to contain OSType") + } if !strings.Contains(testSubject, "AdditionalDiskUri (datadisk-1): https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-1.4085bb15-3644-4641-b9cd-f575918640b4.vhd") { t.Errorf("Expected String() output to contain AdditionalDiskUri") } @@ -154,7 +160,7 @@ func TestArtifactProperties(t *testing.T) { }, } - testSubject, err := NewArtifact(&template, getFakeSasUrl) + testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux") if err != nil { t.Fatalf("err=%s", err) } @@ -174,6 +180,9 @@ func TestArtifactProperties(t *testing.T) { if testSubject.StorageAccountLocation != "southcentralus" { t.Errorf("Expected StorageAccountLocation to be 'southcentral', but got %s", testSubject.StorageAccountLocation) } + if testSubject.OSType != "Linux" { + t.Errorf("Expected OSType to be 'Linux', but got %s", testSubject.OSType) + } } func TestAdditionalDiskArtifactProperties(t *testing.T) { @@ -201,7 +210,7 @@ func TestAdditionalDiskArtifactProperties(t *testing.T) { }, } - testSubject, err := NewArtifact(&template, getFakeSasUrl) + testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux") if err != nil { t.Fatalf("err=%s", err) } @@ -221,6 +230,9 @@ func TestAdditionalDiskArtifactProperties(t *testing.T) { if testSubject.StorageAccountLocation != "southcentralus" { t.Errorf("Expected StorageAccountLocation to be 'southcentral', but got %s", testSubject.StorageAccountLocation) } + if testSubject.OSType != "Linux" { + t.Errorf("Expected OSType to be 'Linux', but got %s", testSubject.OSType) + } if testSubject.AdditionalDisks == nil { t.Errorf("Expected AdditionalDisks to be not nil") } @@ -253,7 +265,7 @@ func TestArtifactOverHyphenatedCaptureUri(t *testing.T) { }, } - testSubject, err := NewArtifact(&template, getFakeSasUrl) + testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux") if err != nil { t.Fatalf("err=%s", err) } @@ -266,7 +278,7 @@ func TestArtifactOverHyphenatedCaptureUri(t *testing.T) { func TestArtifactRejectMalformedTemplates(t *testing.T) { template := CaptureTemplate{} - _, err := NewArtifact(&template, getFakeSasUrl) + _, err := NewArtifact(&template, getFakeSasUrl, "Linux") if err == nil { t.Fatalf("Expected artifact creation to fail, but it succeeded.") } @@ -289,7 +301,7 @@ func TestArtifactRejectMalformedStorageUri(t *testing.T) { }, } - _, err := NewArtifact(&template, getFakeSasUrl) + _, err := NewArtifact(&template, getFakeSasUrl, "Linux") if err == nil { t.Fatalf("Expected artifact creation to fail, but it succeeded.") } diff --git a/builder/azure/arm/builder.go b/builder/azure/arm/builder.go index 23d3d2181..03c6801cf 100644 --- a/builder/azure/arm/builder.go +++ b/builder/azure/arm/builder.go @@ -254,8 +254,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, errors.New("Build was halted.") } + osType := "Linux" + if b.config.OSType == constants.Target_Windows { + osType = "Windows" + } + if b.config.isManagedImage() { - return NewManagedImageArtifact(b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation) + managedImageID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/images/%s", b.config.SubscriptionID, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName) + return NewManagedImageArtifact(osType, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation, managedImageID) } else if template, ok := b.stateBag.GetOk(constants.ArmCaptureTemplate); ok { return NewArtifact( template.(*CaptureTemplate), @@ -266,7 +272,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe options.Expiry = time.Now().AddDate(0, 1, 0).UTC() // one month sasUrl, _ := blob.GetSASURI(options) return sasUrl - }) + }, + osType) } return &Artifact{}, nil diff --git a/packer/artifact_mock.go b/packer/artifact_mock.go index 18f4e562f..737032447 100644 --- a/packer/artifact_mock.go +++ b/packer/artifact_mock.go @@ -7,6 +7,7 @@ type MockArtifact struct { IdValue string StateValues map[string]interface{} DestroyCalled bool + StringValue string } func (a *MockArtifact) BuilderId() string { @@ -34,8 +35,12 @@ func (a *MockArtifact) Id() string { return id } -func (*MockArtifact) String() string { - return "string" +func (a *MockArtifact) String() string { + str := a.StringValue + if str == "" { + str = "string" + } + return str } func (a *MockArtifact) State(name string) interface{} { diff --git a/post-processor/vagrant/azure.go b/post-processor/vagrant/azure.go new file mode 100644 index 000000000..031e70259 --- /dev/null +++ b/post-processor/vagrant/azure.go @@ -0,0 +1,67 @@ +package vagrant + +import ( + "fmt" + "strings" + + "github.com/hashicorp/packer/packer" +) + +type AzureProvider struct{} + +func (p *AzureProvider) KeepInputArtifact() bool { + return true +} + +func (p *AzureProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) { + // Create the metadata + metadata = map[string]interface{}{"provider": "azure"} + + var AzureImageProps map[string]string + AzureImageProps = make(map[string]string) + + // HACK(double16): It appears we can not access the Azure Artifact directly, so parse String() + artifactString := artifact.String() + ui.Message(fmt.Sprintf("artifact string: '%s'", artifactString)) + lines := strings.Split(artifactString, "\n") + for l := 0; l < len(lines); l++ { + split := strings.Split(lines[l], ": ") + if len(split) > 1 { + AzureImageProps[strings.TrimSpace(split[0])] = strings.TrimSpace(split[1]) + } + } + ui.Message(fmt.Sprintf("artifact string parsed: %+v", AzureImageProps)) + + if AzureImageProps["ManagedImageId"] != "" { + vagrantfile = fmt.Sprintf(managedImageVagrantfile, AzureImageProps["ManagedImageLocation"], AzureImageProps["ManagedImageId"]) + } else if AzureImageProps["OSDiskUri"] != "" { + vagrantfile = fmt.Sprintf(vhdVagrantfile, AzureImageProps["StorageAccountLocation"], AzureImageProps["OSDiskUri"], AzureImageProps["OSType"]) + } else { + err = fmt.Errorf("No managed image nor VHD URI found in artifact: %s", artifactString) + return + } + return +} + +var managedImageVagrantfile = ` +Vagrant.configure("2") do |config| + config.vm.provider :azure do |azure, override| + azure.location = "%s" + azure.vm_managed_image_id = "%s" + override.winrm.transport = :ssl + override.winrm.port = 5986 + end +end +` + +var vhdVagrantfile = ` +Vagrant.configure("2") do |config| + config.vm.provider :azure do |azure, override| + azure.location = "%s" + azure.vm_vhd_uri = "%s" + azure.vm_operating_system = "%s" + override.winrm.transport = :ssl + override.winrm.port = 5986 + end +end +` diff --git a/post-processor/vagrant/azure_test.go b/post-processor/vagrant/azure_test.go new file mode 100644 index 000000000..7521f4405 --- /dev/null +++ b/post-processor/vagrant/azure_test.go @@ -0,0 +1,94 @@ +package vagrant + +import ( + "strings" + "testing" + + "github.com/hashicorp/packer/packer" +) + +func TestAzureProvider_impl(t *testing.T) { + var _ Provider = new(AzureProvider) +} + +func TestAzureProvider_KeepInputArtifact(t *testing.T) { + p := new(AzureProvider) + + if !p.KeepInputArtifact() { + t.Fatal("should keep input artifact") + } +} + +func TestAzureProvider_ManagedImage(t *testing.T) { + p := new(AzureProvider) + ui := testUi() + artifact := &packer.MockArtifact{ + StringValue: `Azure.ResourceManagement.VMImage: + +OSType: Linux +ManagedImageResourceGroupName: packerruns +ManagedImageName: packer-1533651633 +ManagedImageId: /subscriptions/e6229913-d9c3-4ddd-99a4-9e1ef3beaa1b/resourceGroups/packerruns/providers/Microsoft.Compute/images/packer-1533675589 +ManagedImageLocation: westus`, + } + + vagrantfile, _, err := p.Process(ui, artifact, "foo") + if err != nil { + t.Fatalf("should not have error: %s", err) + } + result := `azure.location = "westus"` + if !strings.Contains(vagrantfile, result) { + t.Fatalf("wrong substitution: %s", vagrantfile) + } + result = `azure.vm_managed_image_id = "/subscriptions/e6229913-d9c3-4ddd-99a4-9e1ef3beaa1b/resourceGroups/packerruns/providers/Microsoft.Compute/images/packer-1533675589"` + if !strings.Contains(vagrantfile, result) { + t.Fatalf("wrong substitution: %s", vagrantfile) + } + // DO NOT set resource group in Vagrantfile, it should be separate from the image + result = `azure.resource_group_name` + if strings.Contains(vagrantfile, result) { + t.Fatalf("wrong substitution: %s", vagrantfile) + } + result = `azure.vm_operating_system` + if strings.Contains(vagrantfile, result) { + t.Fatalf("wrong substitution: %s", vagrantfile) + } +} + +func TestAzureProvider_VHD(t *testing.T) { + p := new(AzureProvider) + ui := testUi() + artifact := &packer.MockArtifact{ + IdValue: "https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd", + StringValue: `Azure.ResourceManagement.VMImage: + + OSType: Linux + StorageAccountLocation: westus + OSDiskUri: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd + OSDiskUriReadOnlySas: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd?se=2018-09-07T18%3A36%3A34Z&sig=xUiFvwAviPYoP%2Bc91vErqvwYR1eK4x%2BAx7YLMe84zzU%3D&sp=r&sr=b&sv=2016-05-31 + TemplateUri: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.96ed2120-591d-4900-95b0-ee8e985f2213.json + TemplateUriReadOnlySas: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.96ed2120-591d-4900-95b0-ee8e985f2213.json?se=2018-09-07T18%3A36%3A34Z&sig=lDxePyAUCZbfkB5ddiofimXfwk5INn%2F9E2BsnqIKC9Q%3D&sp=r&sr=b&sv=2016-05-31`, + } + + vagrantfile, _, err := p.Process(ui, artifact, "foo") + if err != nil { + t.Fatalf("should not have error: %s", err) + } + result := `azure.location = "westus"` + if !strings.Contains(vagrantfile, result) { + t.Fatalf("wrong substitution: %s", vagrantfile) + } + result = `azure.vm_vhd_uri = "https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd"` + if !strings.Contains(vagrantfile, result) { + t.Fatalf("wrong substitution: %s", vagrantfile) + } + result = `azure.vm_operating_system = "Linux"` + if !strings.Contains(vagrantfile, result) { + t.Fatalf("wrong substitution: %s", vagrantfile) + } + // DO NOT set resource group in Vagrantfile, it should be separate from the image + result = `azure.resource_group_name` + if strings.Contains(vagrantfile, result) { + t.Fatalf("wrong substitution: %s", vagrantfile) + } +} diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 1aab0ca23..5ea07f3a3 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -31,6 +31,7 @@ var builtins = map[string]string{ "MSOpenTech.hyperv": "hyperv", "transcend.qemu": "libvirt", "ustream.lxc": "lxc", + "Azure.ResourceManagement.VMImage": "azure", "packer.post-processor.docker-import": "docker", "packer.post-processor.docker-tag": "docker", "packer.post-processor.docker-push": "docker", @@ -244,6 +245,8 @@ func providerForName(name string) Provider { return new(GoogleProvider) case "lxc": return new(LXCProvider) + case "azure": + return new(AzureProvider) case "docker": return new(DockerProvider) default: diff --git a/website/source/docs/post-processors/vagrant.html.md b/website/source/docs/post-processors/vagrant.html.md index bc6e581bd..260339e7f 100644 --- a/website/source/docs/post-processors/vagrant.html.md +++ b/website/source/docs/post-processors/vagrant.html.md @@ -33,6 +33,7 @@ providers. - AWS - DigitalOcean - Google +- Azure - Hyper-V - LXC - Parallels @@ -108,6 +109,7 @@ The available provider names are: - `aws` - `digitalocean` - `google` +- `azure` - `hyperv` - `parallels` - `libvirt` From 2868971a9ba7dd13162a0720eea64ed4e34b6329 Mon Sep 17 00:00:00 2001 From: Patrick Double Date: Thu, 9 Aug 2018 07:14:14 -0500 Subject: [PATCH 2/2] Fixes per code review --- builder/azure/arm/builder.go | 9 ++------- website/source/docs/post-processors/vagrant.html.md | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/builder/azure/arm/builder.go b/builder/azure/arm/builder.go index 03c6801cf..ed42f2ac3 100644 --- a/builder/azure/arm/builder.go +++ b/builder/azure/arm/builder.go @@ -254,14 +254,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, errors.New("Build was halted.") } - osType := "Linux" - if b.config.OSType == constants.Target_Windows { - osType = "Windows" - } - if b.config.isManagedImage() { managedImageID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/images/%s", b.config.SubscriptionID, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName) - return NewManagedImageArtifact(osType, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation, managedImageID) + return NewManagedImageArtifact(b.config.OSType, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation, managedImageID) } else if template, ok := b.stateBag.GetOk(constants.ArmCaptureTemplate); ok { return NewArtifact( template.(*CaptureTemplate), @@ -273,7 +268,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe sasUrl, _ := blob.GetSASURI(options) return sasUrl }, - osType) + b.config.OSType) } return &Artifact{}, nil diff --git a/website/source/docs/post-processors/vagrant.html.md b/website/source/docs/post-processors/vagrant.html.md index 260339e7f..632c080f5 100644 --- a/website/source/docs/post-processors/vagrant.html.md +++ b/website/source/docs/post-processors/vagrant.html.md @@ -31,8 +31,8 @@ Currently, the Vagrant post-processor can create boxes for the following providers. - AWS +- Azure - DigitalOcean -- Google - Azure - Hyper-V - LXC @@ -107,9 +107,9 @@ where it will be set to 0. The available provider names are: - `aws` +- `azure` - `digitalocean` - `google` -- `azure` - `hyperv` - `parallels` - `libvirt`