Compare commits
2 Commits
master
...
packer-plu
Author | SHA1 | Date |
---|---|---|
Wilken Rivera | 4a091b7bd3 | |
Wilken Rivera | c56677d399 |
|
@ -1,5 +0,0 @@
|
|||
bug:
|
||||
- 'panic:'
|
||||
crash:
|
||||
- 'panic:'
|
||||
|
|
@ -50,14 +50,6 @@ async function checkPluginDocs() {
|
|||
);
|
||||
}
|
||||
}
|
||||
// Validate that local zip files are not used in production
|
||||
if (typeof pluginEntry.zipFile !== "undefined") {
|
||||
throw new Error(
|
||||
`Local ZIP file being used for "${
|
||||
title || pluginEntry.path || repo
|
||||
}". The zipFile option should only be used for local development. Please omit the zipFile attribute and ensure the plugin entry points to a remote repository.`
|
||||
);
|
||||
}
|
||||
// Attempt to fetch plugin docs files
|
||||
const docsMdxFiles = await fetchPluginDocs({ repo, tag: version });
|
||||
const mdxFilesByComponent = docsMdxFiles.reduce((acc, mdxFile) => {
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
name: Issue Comment Created Triage
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
issue_comment_triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-ecosystem/action-remove-labels@v1
|
||||
with:
|
||||
github_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
labels: |
|
||||
stale
|
||||
waiting-reply
|
|
@ -1,16 +0,0 @@
|
|||
name: Issue Opened Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
issue_triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: github/issue-labeler@v2
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
configuration-path: .github/labeler-issue-triage.yml
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
name: 'Lock Threads'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '50 1 * * *'
|
||||
|
||||
# Only 50 issues will be handled during a given run.
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-comment: >
|
||||
I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.
|
||||
|
||||
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
|
||||
issue-lock-inactive-days: '30'
|
||||
# Issues older than 180 days ago should be ignored
|
||||
issue-exclude-created-before: '2020-11-01'
|
||||
pr-lock-comment: >
|
||||
I'm going to lock this pull request because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.
|
||||
|
||||
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
|
||||
pr-lock-inactive-days: '30'
|
||||
# Issues older than 180 days ago should be ignored
|
||||
pr-exclude-created-before: '2020-11-01'
|
||||
|
|
@ -1,3 +1,28 @@
|
|||
|
||||
behavior "regexp_issue_labeler" "panic_label" {
|
||||
regexp = "panic:"
|
||||
labels = ["crash", "bug"]
|
||||
}
|
||||
|
||||
behavior "remove_labels_on_reply" "remove_stale" {
|
||||
labels = ["waiting-reply", "stale"]
|
||||
only_non_maintainers = true
|
||||
}
|
||||
|
||||
poll "closed_issue_locker" "locker" {
|
||||
schedule = "0 50 1 * * *"
|
||||
closed_for = "720h" # 30 days
|
||||
max_issues = 500
|
||||
sleep_between_issues = "5s"
|
||||
no_comment_if_no_activity_for = "4320h" # 180 days
|
||||
|
||||
message = <<-EOF
|
||||
I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.
|
||||
|
||||
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
|
||||
EOF
|
||||
}
|
||||
|
||||
poll "label_issue_migrater" "remote_plugin_migrater" {
|
||||
schedule = "0 20 * * * *"
|
||||
new_owner = "hashicorp"
|
||||
|
|
95
CHANGELOG.md
95
CHANGELOG.md
|
@ -1,100 +1,5 @@
|
|||
## 1.7.3 (Upcoming)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
|
||||
Major refactor: Extracted a majority of HashiCorp-maintained and community plugins from the Packer Core repository. They now live in their own multi-component plugin repositiores. The following repositories have been created, and their components have been deleted from the "github.com/hashicorp/packer" repository.
|
||||
|
||||
* "github.com/hashicorp/packer-plugin-alicloud" [GH-10932]
|
||||
* "github.com/hashicorp/packer-plugin-amazon" [GH-10800]
|
||||
* "github.com/hashicorp/packer-plugin-ansible" [GH-10912]
|
||||
* "github.com/hashicorp/packer-plugin-azure" [GH-10979]
|
||||
* "github.com/hashicorp/packer-plugin-chef" [GH-10921]
|
||||
* "github.com/hashicorp/packer-plugin-cloudstack" [GH-10934]
|
||||
* "github.com/hashicorp/packer-plugin-converge" [GH-10956]
|
||||
* "github.com/hashicorp/packer-plugin-digitalocean" [GH-10961]
|
||||
* "github.com/hashicorp/packer-plugin-docker" [GH-10695]
|
||||
* "github.com/hashicorp/packer-plugin-googlecompute" [GH-10890]
|
||||
* "github.com/hashicorp/packer-plugin-hcloud" [GH-10966]
|
||||
* "github.com/hashicorp/packer-plugin-hyperone" [GH-10949]
|
||||
* "github.com/hashicorp/packer-plugin-hyperv" [GH-10949]
|
||||
* "github.com/hashicorp/packer-plugin-inspec"
|
||||
* "github.com/hashicorp/packer-plugin-ionos-cloud"
|
||||
* "github.com/hashicorp/packer-plugin-jdcloud" [GH-10946]
|
||||
* "github.com/hashicorp/packer-plugin-linode" [GH-10947]
|
||||
* "github.com/hashicorp/packer-plugin-lxc" [GH-10965]
|
||||
* "github.com/hashicorp/packer-plugin-lxd" [GH-10965]
|
||||
* "github.com/hashicorp/packer-plugin-ncloud" [GH-10937]
|
||||
* "github.com/hashicorp/packer-plugin-openstack" [GH-10933]
|
||||
* "github.com/hashicorp/packer-plugin-oracle" [GH-10962]
|
||||
* "github.com/hashicorp/packer-plugin-outscale" [GH-10941]
|
||||
* "github.com/hashicorp/packer-plugin-parallels" [GH-10936]
|
||||
* "github.com/hashicorp/packer-plugin-proxmox" [GH-10930]
|
||||
* "github.com/hashicorp/packer-plugin-puppet" [GH-10943]
|
||||
* "github.com/hashicorp/packer-plugin-qemu" [GH-10929]
|
||||
* "github.com/hashicorp/packer-plugin-salt"
|
||||
* "github.com/hashicorp/packer-plugin-scaleway" [GH-10939]
|
||||
* "github.com/hashicorp/packer-plugin-tencentcloud" [GH-10967]
|
||||
* "github.com/hashicorp/packer-plugin-triton" [GH-10963]
|
||||
* "github.com/hashicorp/packer-plugin-ucloud" [GH-10953]
|
||||
* "github.com/hashicorp/packer-plugin-vagrant" [GH-10960]
|
||||
* "github.com/hashicorp/packer-plugin-virtualbox" [GH-10910]
|
||||
* "github.com/hashicorp/packer-plugin-vmware" [GH-10920]
|
||||
* "github.com/hashicorp/packer-plugin-vsphere" [GH-10896]
|
||||
* "github.com/hashicorp/packer-plugin-yandex" [GH-10970]
|
||||
|
||||
_this will not be a backwards-breaking change in v1.7.3_ because the extracted
|
||||
components are being vendored back into Packer. However, we encourage users to
|
||||
begin using `packer init` to download and install plugins to get the latest
|
||||
updates to each plugin, and to prepare for Packer v2.0 when we will stop
|
||||
vendoring the above plugins into the main Packer binary. The following
|
||||
components will not be removed from the main packer binary:
|
||||
|
||||
* `null` builder
|
||||
* `file` builder
|
||||
* `breakpoint` provisioner
|
||||
|
||||
* `file` provisioner
|
||||
* `powershell` provisioner
|
||||
* `shell` provisioner
|
||||
* `shell-local` provisioner
|
||||
* `sleep` provisioner
|
||||
* `windows-restart` provisioner
|
||||
* `windows-shell` provisioner
|
||||
|
||||
* `artifice` post-processor
|
||||
* `checksum` post-processor
|
||||
* `compress` post-processor
|
||||
* `manifest` post-processor
|
||||
* `shell-local` post-processor
|
||||
|
||||
### Bug Fixes:
|
||||
* builder/azure: Add `keep_os_disk` parameter to control OS disk deletion
|
||||
[GH-10045]
|
||||
* builder/azure: Stop SIG timout from being overridden by PollingDuration
|
||||
[GH-10816]
|
||||
* builder/azure: Support shared image gallery storage account type [GH-10863]
|
||||
* builder/proxmox: Proxmox builder use ipv4 address instead of always ipv6.
|
||||
[GH-10858]
|
||||
* core/hcl: Fix Invalid provisioner pause_before panic [GH-10978]
|
||||
* core: HCL "index" function now actually returns the index of the element
|
||||
[GH-11008]
|
||||
* core: Implemented DEFAULT_NAME handling for datasource plugins [GH-11026]
|
||||
|
||||
### Enhancements:
|
||||
|
||||
* builder/azure: Added custom nicname and osdiskname [GH-10938]
|
||||
* builder/azure: Add support for shared image gallery storage account type
|
||||
[GH-10863]
|
||||
* builder/digitalocean: support ecdsa, ed25519, dsa temporary key types.
|
||||
[GH-10856]
|
||||
* builder/ncloud: Support ncloud vpc version [GH-10870]
|
||||
* post-processor/compress: Add bzip2 support to post-processor [GH-10867]
|
||||
* post-processor/googlecompute-import: Add Image Storage Locations field
|
||||
[GH-10864]
|
||||
* Removed the golang "vendor" directory in favor of go modules. This should not
|
||||
affect end users. [GH-10916]
|
||||
|
||||
## 1.7.2 (April 05, 2021)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,16 @@
|
|||
Here's a list of things we like to get done in no particular order:
|
||||
|
||||
- [ ] Blob/image copy post-processor
|
||||
- [ ] Blob/image rename post-processor
|
||||
- [ ] SSH to private ip through subnet
|
||||
- [ ] chroot builder
|
||||
- [ ] support cross-storage account image source (i.e. pre-build blob copy)
|
||||
- [ ] look up object id when using device code (graph api /me ?)
|
||||
- [ ] device flow support for Windows
|
||||
- [x] look up tenant id in all cases (see device flow code)
|
||||
- [ ] look up resource group of storage account
|
||||
- [ ] include all _data_ disks in artifact too
|
||||
- [ ] windows sysprep provisioner (since it seems to generate a certain issue volume)
|
||||
- [ ] allow arbitrary json patching for deployment document
|
||||
- [ ] tag all resources with user-supplied tag
|
||||
- [ ] managed disk support
|
|
@ -0,0 +1,118 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminPassword": "[parameters('adminPassword')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2015-06-15",
|
||||
"location": "[resourceGroup().location]",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "ignore",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "ignore",
|
||||
"virtualNetworkResourceGroup": "ignore",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
BuilderId = "Azure.ResourceManagement.VMImage"
|
||||
)
|
||||
|
||||
type AdditionalDiskArtifact struct {
|
||||
AdditionalDiskUri string
|
||||
AdditionalDiskUriReadOnlySas string
|
||||
}
|
||||
|
||||
type Artifact struct {
|
||||
// OS type: Linux, Windows
|
||||
OSType string
|
||||
|
||||
// VHD
|
||||
StorageAccountLocation string
|
||||
OSDiskUri string
|
||||
TemplateUri string
|
||||
OSDiskUriReadOnlySas string
|
||||
TemplateUriReadOnlySas string
|
||||
|
||||
// Managed Image
|
||||
ManagedImageResourceGroupName string
|
||||
ManagedImageName string
|
||||
ManagedImageLocation string
|
||||
ManagedImageId string
|
||||
ManagedImageOSDiskSnapshotName string
|
||||
ManagedImageDataDiskSnapshotPrefix string
|
||||
// ARM resource id for Shared Image Gallery
|
||||
ManagedImageSharedImageGalleryId string
|
||||
|
||||
// Additional Disks
|
||||
AdditionalDisks *[]AdditionalDiskArtifact
|
||||
|
||||
// StateData should store data such as GeneratedData
|
||||
// to be shared with post-processors
|
||||
StateData map[string]interface{}
|
||||
}
|
||||
|
||||
func NewManagedImageArtifact(osType, resourceGroup, name, location, id, osDiskSnapshotName, dataDiskSnapshotPrefix string, generatedData map[string]interface{}) (*Artifact, error) {
|
||||
return &Artifact{
|
||||
ManagedImageResourceGroupName: resourceGroup,
|
||||
ManagedImageName: name,
|
||||
ManagedImageLocation: location,
|
||||
ManagedImageId: id,
|
||||
OSType: osType,
|
||||
ManagedImageOSDiskSnapshotName: osDiskSnapshotName,
|
||||
ManagedImageDataDiskSnapshotPrefix: dataDiskSnapshotPrefix,
|
||||
StateData: generatedData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewManagedImageArtifactWithSIGAsDestination(osType, resourceGroup, name, location, id, osDiskSnapshotName, dataDiskSnapshotPrefix, destinationSharedImageGalleryId string, generatedData map[string]interface{}) (*Artifact, error) {
|
||||
return &Artifact{
|
||||
ManagedImageResourceGroupName: resourceGroup,
|
||||
ManagedImageName: name,
|
||||
ManagedImageLocation: location,
|
||||
ManagedImageId: id,
|
||||
OSType: osType,
|
||||
ManagedImageOSDiskSnapshotName: osDiskSnapshotName,
|
||||
ManagedImageDataDiskSnapshotPrefix: dataDiskSnapshotPrefix,
|
||||
ManagedImageSharedImageGalleryId: destinationSharedImageGalleryId,
|
||||
StateData: generatedData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewArtifact(template *CaptureTemplate, getSasUrl func(name string) string, osType string, generatedData map[string]interface{}) (*Artifact, error) {
|
||||
if template == nil {
|
||||
return nil, fmt.Errorf("nil capture template")
|
||||
}
|
||||
|
||||
if len(template.Resources) != 1 {
|
||||
return nil, fmt.Errorf("malformed capture template, expected one resource")
|
||||
}
|
||||
|
||||
vhdUri, err := url.Parse(template.Resources[0].Properties.StorageProfile.OSDisk.Image.Uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
templateUri, err := storageUriToTemplateUri(vhdUri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var additional_disks *[]AdditionalDiskArtifact
|
||||
if template.Resources[0].Properties.StorageProfile.DataDisks != nil {
|
||||
data_disks := make([]AdditionalDiskArtifact, len(template.Resources[0].Properties.StorageProfile.DataDisks))
|
||||
for i, additionaldisk := range template.Resources[0].Properties.StorageProfile.DataDisks {
|
||||
additionalVhdUri, err := url.Parse(additionaldisk.Image.Uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data_disks[i].AdditionalDiskUri = additionalVhdUri.String()
|
||||
data_disks[i].AdditionalDiskUriReadOnlySas = getSasUrl(getStorageUrlPath(additionalVhdUri))
|
||||
}
|
||||
additional_disks = &data_disks
|
||||
}
|
||||
|
||||
return &Artifact{
|
||||
OSType: osType,
|
||||
OSDiskUri: vhdUri.String(),
|
||||
OSDiskUriReadOnlySas: getSasUrl(getStorageUrlPath(vhdUri)),
|
||||
TemplateUri: templateUri.String(),
|
||||
TemplateUriReadOnlySas: getSasUrl(getStorageUrlPath(templateUri)),
|
||||
|
||||
AdditionalDisks: additional_disks,
|
||||
|
||||
StorageAccountLocation: template.Resources[0].Location,
|
||||
|
||||
StateData: generatedData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getStorageUrlPath(u *url.URL) string {
|
||||
parts := strings.Split(u.Path, "/")
|
||||
return strings.Join(parts[3:], "/")
|
||||
}
|
||||
|
||||
func storageUriToTemplateUri(su *url.URL) (*url.URL, error) {
|
||||
// packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd -> 4085bb15-3644-4641-b9cd-f575918640b4
|
||||
filename := path.Base(su.Path)
|
||||
parts := strings.Split(filename, ".")
|
||||
|
||||
if len(parts) < 3 {
|
||||
return nil, fmt.Errorf("malformed URL")
|
||||
}
|
||||
|
||||
// packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd -> packer
|
||||
prefixParts := strings.Split(parts[0], "-")
|
||||
prefix := strings.Join(prefixParts[:len(prefixParts)-1], "-")
|
||||
|
||||
templateFilename := fmt.Sprintf("%s-vmTemplate.%s.json", prefix, parts[1])
|
||||
|
||||
// https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd"
|
||||
// ->
|
||||
// https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json"
|
||||
return url.Parse(strings.Replace(su.String(), filename, templateFilename, 1))
|
||||
}
|
||||
|
||||
func (a *Artifact) isManagedImage() bool {
|
||||
return a.ManagedImageResourceGroupName != ""
|
||||
}
|
||||
|
||||
func (*Artifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (*Artifact) Files() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
if a.OSDiskUri != "" {
|
||||
return a.OSDiskUri
|
||||
}
|
||||
return a.ManagedImageId
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
if _, ok := a.StateData[name]; ok {
|
||||
return a.StateData[name]
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "atlas.artifact.metadata":
|
||||
return a.stateAtlasMetadata()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
if a.ManagedImageOSDiskSnapshotName != "" {
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageOSDiskSnapshotName: %s\n", a.ManagedImageOSDiskSnapshotName))
|
||||
}
|
||||
if a.ManagedImageDataDiskSnapshotPrefix != "" {
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageDataDiskSnapshotPrefix: %s\n", a.ManagedImageDataDiskSnapshotPrefix))
|
||||
}
|
||||
if a.ManagedImageSharedImageGalleryId != "" {
|
||||
buf.WriteString(fmt.Sprintf("ManagedImageSharedImageGalleryId: %s\n", a.ManagedImageSharedImageGalleryId))
|
||||
}
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf("StorageAccountLocation: %s\n", a.StorageAccountLocation))
|
||||
buf.WriteString(fmt.Sprintf("OSDiskUri: %s\n", a.OSDiskUri))
|
||||
buf.WriteString(fmt.Sprintf("OSDiskUriReadOnlySas: %s\n", a.OSDiskUriReadOnlySas))
|
||||
buf.WriteString(fmt.Sprintf("TemplateUri: %s\n", a.TemplateUri))
|
||||
buf.WriteString(fmt.Sprintf("TemplateUriReadOnlySas: %s\n", a.TemplateUriReadOnlySas))
|
||||
if a.AdditionalDisks != nil {
|
||||
for i, additionaldisk := range *a.AdditionalDisks {
|
||||
buf.WriteString(fmt.Sprintf("AdditionalDiskUri (datadisk-%d): %s\n", i+1, additionaldisk.AdditionalDiskUri))
|
||||
buf.WriteString(fmt.Sprintf("AdditionalDiskUriReadOnlySas (datadisk-%d): %s\n", i+1, additionaldisk.AdditionalDiskUriReadOnlySas))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (*Artifact) Destroy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) stateAtlasMetadata() interface{} {
|
||||
metadata := make(map[string]string)
|
||||
metadata["StorageAccountLocation"] = a.StorageAccountLocation
|
||||
metadata["OSDiskUri"] = a.OSDiskUri
|
||||
metadata["OSDiskUriReadOnlySas"] = a.OSDiskUriReadOnlySas
|
||||
metadata["TemplateUri"] = a.TemplateUri
|
||||
metadata["TemplateUriReadOnlySas"] = a.TemplateUriReadOnlySas
|
||||
|
||||
return metadata
|
||||
}
|
|
@ -0,0 +1,429 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getFakeSasUrl(name string) string {
|
||||
return fmt.Sprintf("SAS-%s", name)
|
||||
}
|
||||
|
||||
func generatedData() map[string]interface{} {
|
||||
return make(map[string]interface{})
|
||||
}
|
||||
|
||||
func TestArtifactIdVHD(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
Image: CaptureUri{
|
||||
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Location: "southcentralus",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux", generatedData())
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd"
|
||||
|
||||
result := artifact.Id()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImage(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "fakeDataDiskSnapshotPrefix", generatedData())
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: fakeResourceGroup
|
||||
ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImageWithoutOSDiskSnapshotName(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "", "fakeDataDiskSnapshotPrefix", generatedData())
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: fakeResourceGroup
|
||||
ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImageWithoutDataDiskSnapshotPrefix(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifact("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "", generatedData())
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: fakeResourceGroup
|
||||
ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIDManagedImageWithSharedImageGalleryId(t *testing.T) {
|
||||
artifact, err := NewManagedImageArtifactWithSIGAsDestination("Linux", "fakeResourceGroup", "fakeName", "fakeLocation", "fakeID", "fakeOsDiskSnapshotName", "fakeDataDiskSnapshotPrefix", "fakeSharedImageGallery", generatedData())
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
expected := `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: fakeResourceGroup
|
||||
ManagedImageName: fakeName
|
||||
ManagedImageId: fakeID
|
||||
ManagedImageLocation: fakeLocation
|
||||
ManagedImageOSDiskSnapshotName: fakeOsDiskSnapshotName
|
||||
ManagedImageDataDiskSnapshotPrefix: fakeDataDiskSnapshotPrefix
|
||||
ManagedImageSharedImageGalleryId: fakeSharedImageGallery
|
||||
`
|
||||
|
||||
result := artifact.String()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactString(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
Image: CaptureUri{
|
||||
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Location: "southcentralus",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux", generatedData())
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
testSubject := artifact.String()
|
||||
if !strings.Contains(testSubject, "OSDiskUri: https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd") {
|
||||
t.Errorf("Expected String() output to contain OSDiskUri")
|
||||
}
|
||||
if !strings.Contains(testSubject, "OSDiskUriReadOnlySas: SAS-Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd") {
|
||||
t.Errorf("Expected String() output to contain OSDiskUriReadOnlySas")
|
||||
}
|
||||
if !strings.Contains(testSubject, "TemplateUri: https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json") {
|
||||
t.Errorf("Expected String() output to contain TemplateUri")
|
||||
}
|
||||
if !strings.Contains(testSubject, "TemplateUriReadOnlySas: SAS-Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json") {
|
||||
t.Errorf("Expected String() output to contain TemplateUriReadOnlySas")
|
||||
}
|
||||
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) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
Image: CaptureUri{
|
||||
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
|
||||
},
|
||||
},
|
||||
DataDisks: []CaptureDisk{
|
||||
{
|
||||
Image: CaptureUri{
|
||||
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-1.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Location: "southcentralus",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
artifact, err := NewArtifact(&template, getFakeSasUrl, "Linux", generatedData())
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
testSubject := artifact.String()
|
||||
if !strings.Contains(testSubject, "OSDiskUri: https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd") {
|
||||
t.Errorf("Expected String() output to contain OSDiskUri")
|
||||
}
|
||||
if !strings.Contains(testSubject, "OSDiskUriReadOnlySas: SAS-Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd") {
|
||||
t.Errorf("Expected String() output to contain OSDiskUriReadOnlySas")
|
||||
}
|
||||
if !strings.Contains(testSubject, "TemplateUri: https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json") {
|
||||
t.Errorf("Expected String() output to contain TemplateUri")
|
||||
}
|
||||
if !strings.Contains(testSubject, "TemplateUriReadOnlySas: SAS-Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json") {
|
||||
t.Errorf("Expected String() output to contain TemplateUriReadOnlySas")
|
||||
}
|
||||
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")
|
||||
}
|
||||
if !strings.Contains(testSubject, "AdditionalDiskUriReadOnlySas (datadisk-1): SAS-Images/images/packer-datadisk-1.4085bb15-3644-4641-b9cd-f575918640b4.vhd") {
|
||||
t.Errorf("Expected String() output to contain AdditionalDiskUriReadOnlySas")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactProperties(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
Image: CaptureUri{
|
||||
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Location: "southcentralus",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux", generatedData())
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
if testSubject.OSDiskUri != "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd" {
|
||||
t.Errorf("Expected template to be 'https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd', but got %s", testSubject.OSDiskUri)
|
||||
}
|
||||
if testSubject.OSDiskUriReadOnlySas != "SAS-Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd" {
|
||||
t.Errorf("Expected template to be 'SAS-Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd', but got %s", testSubject.OSDiskUriReadOnlySas)
|
||||
}
|
||||
if testSubject.TemplateUri != "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json" {
|
||||
t.Errorf("Expected template to be 'https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json', but got %s", testSubject.TemplateUri)
|
||||
}
|
||||
if testSubject.TemplateUriReadOnlySas != "SAS-Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json" {
|
||||
t.Errorf("Expected template to be 'SAS-Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json', but got %s", testSubject.TemplateUriReadOnlySas)
|
||||
}
|
||||
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) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
Image: CaptureUri{
|
||||
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
|
||||
},
|
||||
},
|
||||
DataDisks: []CaptureDisk{
|
||||
{
|
||||
Image: CaptureUri{
|
||||
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-1.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Location: "southcentralus",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux", generatedData())
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
if testSubject.OSDiskUri != "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd" {
|
||||
t.Errorf("Expected template to be 'https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd', but got %s", testSubject.OSDiskUri)
|
||||
}
|
||||
if testSubject.OSDiskUriReadOnlySas != "SAS-Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd" {
|
||||
t.Errorf("Expected template to be 'SAS-Images/images/packer-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd', but got %s", testSubject.OSDiskUriReadOnlySas)
|
||||
}
|
||||
if testSubject.TemplateUri != "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json" {
|
||||
t.Errorf("Expected template to be 'https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json', but got %s", testSubject.TemplateUri)
|
||||
}
|
||||
if testSubject.TemplateUriReadOnlySas != "SAS-Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json" {
|
||||
t.Errorf("Expected template to be 'SAS-Images/images/packer-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json', but got %s", testSubject.TemplateUriReadOnlySas)
|
||||
}
|
||||
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")
|
||||
}
|
||||
if len(*testSubject.AdditionalDisks) != 1 {
|
||||
t.Errorf("Expected AdditionalDisks to have one additional disk, but got %d", len(*testSubject.AdditionalDisks))
|
||||
}
|
||||
if (*testSubject.AdditionalDisks)[0].AdditionalDiskUri != "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-1.4085bb15-3644-4641-b9cd-f575918640b4.vhd" {
|
||||
t.Errorf("Expected additional disk uri to be 'https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-1.4085bb15-3644-4641-b9cd-f575918640b4.vhd', but got %s", (*testSubject.AdditionalDisks)[0].AdditionalDiskUri)
|
||||
}
|
||||
if (*testSubject.AdditionalDisks)[0].AdditionalDiskUriReadOnlySas != "SAS-Images/images/packer-datadisk-1.4085bb15-3644-4641-b9cd-f575918640b4.vhd" {
|
||||
t.Errorf("Expected additional disk sas to be 'SAS-Images/images/packer-datadisk-1.4085bb15-3644-4641-b9cd-f575918640b4.vhd', but got %s", (*testSubject.AdditionalDisks)[0].AdditionalDiskUriReadOnlySas)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactOverHyphenatedCaptureUri(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
Image: CaptureUri{
|
||||
Uri: "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/pac-ker-osDisk.4085bb15-3644-4641-b9cd-f575918640b4.vhd",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Location: "southcentralus",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testSubject, err := NewArtifact(&template, getFakeSasUrl, "Linux", generatedData())
|
||||
if err != nil {
|
||||
t.Fatalf("err=%s", err)
|
||||
}
|
||||
|
||||
if testSubject.TemplateUri != "https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/pac-ker-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json" {
|
||||
t.Errorf("Expected template to be 'https://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/pac-ker-vmTemplate.4085bb15-3644-4641-b9cd-f575918640b4.json', but got %s", testSubject.TemplateUri)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactRejectMalformedTemplates(t *testing.T) {
|
||||
template := CaptureTemplate{}
|
||||
|
||||
_, err := NewArtifact(&template, getFakeSasUrl, "Linux", generatedData())
|
||||
if err == nil {
|
||||
t.Fatalf("Expected artifact creation to fail, but it succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactRejectMalformedStorageUri(t *testing.T) {
|
||||
template := CaptureTemplate{
|
||||
Resources: []CaptureResources{
|
||||
{
|
||||
Properties: CaptureProperties{
|
||||
StorageProfile: CaptureStorageProfile{
|
||||
OSDisk: CaptureDisk{
|
||||
Image: CaptureUri{
|
||||
Uri: "bark",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := NewArtifact(&template, getFakeSasUrl, "Linux", generatedData())
|
||||
if err == nil {
|
||||
t.Fatalf("Expected artifact creation to fail, but it succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactState_StateData(t *testing.T) {
|
||||
expectedData := "this is the data"
|
||||
artifact := &Artifact{
|
||||
StateData: map[string]interface{}{"state_data": expectedData},
|
||||
}
|
||||
|
||||
// Valid state
|
||||
result := artifact.State("state_data")
|
||||
if result != expectedData {
|
||||
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
|
||||
}
|
||||
|
||||
// Invalid state
|
||||
result = artifact.State("invalid_key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for invalid state data name")
|
||||
}
|
||||
|
||||
// Nil StateData should not fail and should return nil
|
||||
artifact = &Artifact{}
|
||||
result = artifact.State("key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for nil StateData")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,311 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
newCompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault"
|
||||
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-01-01/network"
|
||||
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources"
|
||||
armStorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage"
|
||||
"github.com/Azure/azure-sdk-for-go/storage"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/hashicorp/packer-plugin-sdk/useragent"
|
||||
"github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/version"
|
||||
)
|
||||
|
||||
const (
|
||||
EnvPackerLogAzureMaxLen = "PACKER_LOG_AZURE_MAXLEN"
|
||||
)
|
||||
|
||||
type AzureClient struct {
|
||||
storage.BlobStorageClient
|
||||
resources.DeploymentsClient
|
||||
resources.DeploymentOperationsClient
|
||||
resources.GroupsClient
|
||||
network.PublicIPAddressesClient
|
||||
network.InterfacesClient
|
||||
network.SubnetsClient
|
||||
network.VirtualNetworksClient
|
||||
network.SecurityGroupsClient
|
||||
compute.ImagesClient
|
||||
compute.VirtualMachinesClient
|
||||
common.VaultClient
|
||||
armStorage.AccountsClient
|
||||
compute.DisksClient
|
||||
compute.SnapshotsClient
|
||||
newCompute.GalleryImageVersionsClient
|
||||
newCompute.GalleryImagesClient
|
||||
|
||||
InspectorMaxLength int
|
||||
Template *CaptureTemplate
|
||||
LastError azureErrorResponse
|
||||
VaultClientDelete keyvault.VaultsClient
|
||||
}
|
||||
|
||||
func getCaptureResponse(body string) *CaptureTemplate {
|
||||
var operation CaptureOperation
|
||||
err := json.Unmarshal([]byte(body), &operation)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if operation.Properties != nil && operation.Properties.Output != nil {
|
||||
return operation.Properties.Output
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HACK(chrboum): This method is a hack. It was written to work around this issue
|
||||
// (https://github.com/Azure/azure-sdk-for-go/issues/307) and to an extent this
|
||||
// issue (https://github.com/Azure/azure-rest-api-specs/issues/188).
|
||||
//
|
||||
// Capturing a VM is a long running operation that requires polling. There are
|
||||
// couple different forms of polling, and the end result of a poll operation is
|
||||
// discarded by the SDK. It is expected that any discarded data can be re-fetched,
|
||||
// so discarding it has minimal impact. Unfortunately, there is no way to re-fetch
|
||||
// the template returned by a capture call that I am aware of.
|
||||
//
|
||||
// If the second issue were fixed the VM ID would be included when GET'ing a VM. The
|
||||
// VM ID could be used to locate the captured VHD, and captured template.
|
||||
// Unfortunately, the VM ID is not included so this method cannot be used either.
|
||||
//
|
||||
// This code captures the template and saves it to the client (the AzureClient type).
|
||||
// It expects that the capture API is called only once, or rather you only care that the
|
||||
// last call's value is important because subsequent requests are not persisted. There
|
||||
// is no care given to multiple threads writing this value because for our use case
|
||||
// it does not matter.
|
||||
func templateCapture(client *AzureClient) autorest.RespondDecorator {
|
||||
return func(r autorest.Responder) autorest.Responder {
|
||||
return autorest.ResponderFunc(func(resp *http.Response) error {
|
||||
body, bodyString := handleBody(resp.Body, math.MaxInt64)
|
||||
resp.Body = body
|
||||
|
||||
captureTemplate := getCaptureResponse(bodyString)
|
||||
if captureTemplate != nil {
|
||||
client.Template = captureTemplate
|
||||
}
|
||||
|
||||
return r.Respond(resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func errorCapture(client *AzureClient) autorest.RespondDecorator {
|
||||
return func(r autorest.Responder) autorest.Responder {
|
||||
return autorest.ResponderFunc(func(resp *http.Response) error {
|
||||
body, bodyString := handleBody(resp.Body, math.MaxInt64)
|
||||
resp.Body = body
|
||||
|
||||
errorResponse := newAzureErrorResponse(bodyString)
|
||||
if errorResponse != nil {
|
||||
client.LastError = *errorResponse
|
||||
}
|
||||
|
||||
return r.Respond(resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WAITING(chrboum): I have logged https://github.com/Azure/azure-sdk-for-go/issues/311 to get this
|
||||
// method included in the SDK. It has been accepted, and I'll cut over to the official way
|
||||
// once it ships.
|
||||
func byConcatDecorators(decorators ...autorest.RespondDecorator) autorest.RespondDecorator {
|
||||
return func(r autorest.Responder) autorest.Responder {
|
||||
return autorest.DecorateResponder(r, decorators...)
|
||||
}
|
||||
}
|
||||
|
||||
func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storageAccountName string,
|
||||
cloud *azure.Environment, sharedGalleryTimeout time.Duration, pollingDuration time.Duration,
|
||||
servicePrincipalToken, servicePrincipalTokenVault *adal.ServicePrincipalToken) (*AzureClient, error) {
|
||||
|
||||
var azureClient = &AzureClient{}
|
||||
|
||||
maxlen := getInspectorMaxLength()
|
||||
|
||||
azureClient.DeploymentsClient = resources.NewDeploymentsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.DeploymentsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.DeploymentsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.DeploymentsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.DeploymentsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.DeploymentsClient.UserAgent)
|
||||
azureClient.DeploymentsClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.DeploymentOperationsClient = resources.NewDeploymentOperationsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.DeploymentOperationsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.DeploymentOperationsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.DeploymentOperationsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.DeploymentOperationsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.DeploymentOperationsClient.UserAgent)
|
||||
azureClient.DeploymentOperationsClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.DisksClient = compute.NewDisksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.DisksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.DisksClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.DisksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.DisksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.DisksClient.UserAgent)
|
||||
azureClient.DisksClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.GroupsClient = resources.NewGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.GroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.GroupsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.GroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.GroupsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.GroupsClient.UserAgent)
|
||||
azureClient.GroupsClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.ImagesClient = compute.NewImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.ImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.ImagesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.ImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.ImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.ImagesClient.UserAgent)
|
||||
azureClient.ImagesClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.InterfacesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.InterfacesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.InterfacesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.InterfacesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.InterfacesClient.UserAgent)
|
||||
azureClient.InterfacesClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.SubnetsClient = network.NewSubnetsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.SubnetsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.SubnetsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.SubnetsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.SubnetsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.SubnetsClient.UserAgent)
|
||||
azureClient.SubnetsClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.VirtualNetworksClient = network.NewVirtualNetworksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.VirtualNetworksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.VirtualNetworksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.VirtualNetworksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.VirtualNetworksClient.UserAgent)
|
||||
azureClient.VirtualNetworksClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.SecurityGroupsClient = network.NewSecurityGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.SecurityGroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.SecurityGroupsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.SecurityGroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.SecurityGroupsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.SecurityGroupsClient.UserAgent)
|
||||
|
||||
azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.PublicIPAddressesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.PublicIPAddressesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.PublicIPAddressesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.PublicIPAddressesClient.UserAgent)
|
||||
azureClient.PublicIPAddressesClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.VirtualMachinesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient), errorCapture(azureClient))
|
||||
azureClient.VirtualMachinesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.VirtualMachinesClient.UserAgent)
|
||||
azureClient.VirtualMachinesClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.SnapshotsClient = compute.NewSnapshotsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.SnapshotsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.SnapshotsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.SnapshotsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.SnapshotsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.SnapshotsClient.UserAgent)
|
||||
azureClient.SnapshotsClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.AccountsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.AccountsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.AccountsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.AccountsClient.UserAgent)
|
||||
azureClient.AccountsClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
azureClient.GalleryImageVersionsClient = newCompute.NewGalleryImageVersionsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.GalleryImageVersionsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.GalleryImageVersionsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.GalleryImageVersionsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.GalleryImageVersionsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.GalleryImageVersionsClient.UserAgent)
|
||||
azureClient.GalleryImageVersionsClient.Client.PollingDuration = sharedGalleryTimeout
|
||||
if sigSubscriptionID != "" {
|
||||
azureClient.GalleryImageVersionsClient.SubscriptionID = sigSubscriptionID
|
||||
}
|
||||
|
||||
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.GalleryImagesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.GalleryImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.GalleryImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.GalleryImagesClient.UserAgent)
|
||||
azureClient.GalleryImagesClient.Client.PollingDuration = pollingDuration
|
||||
if sigSubscriptionID != "" {
|
||||
azureClient.GalleryImagesClient.SubscriptionID = sigSubscriptionID
|
||||
}
|
||||
|
||||
keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
azureClient.VaultClient = common.NewVaultClient(*keyVaultURL)
|
||||
azureClient.VaultClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalTokenVault)
|
||||
azureClient.VaultClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.VaultClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.VaultClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.VaultClient.UserAgent)
|
||||
azureClient.VaultClient.Client.PollingDuration = pollingDuration
|
||||
|
||||
// This client is different than the above because it manages the vault
|
||||
// itself rather than the contents of the vault.
|
||||
azureClient.VaultClientDelete = keyvault.NewVaultsClient(subscriptionID)
|
||||
azureClient.VaultClientDelete.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.VaultClientDelete.RequestInspector = withInspection(maxlen)
|
||||
azureClient.VaultClientDelete.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.VaultClientDelete.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.VaultClientDelete.UserAgent)
|
||||
azureClient.VaultClientDelete.Client.PollingDuration = pollingDuration
|
||||
|
||||
// If this is a managed disk build, this should be ignored.
|
||||
if resourceGroupName != "" && storageAccountName != "" {
|
||||
accountKeys, err := azureClient.AccountsClient.ListKeys(context.TODO(), resourceGroupName, storageAccountName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storageClient, err := storage.NewClient(
|
||||
storageAccountName,
|
||||
*(*accountKeys.Keys)[0].Value,
|
||||
cloud.StorageEndpointSuffix,
|
||||
storage.DefaultAPIVersion,
|
||||
true /*useHttps*/)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
azureClient.BlobStorageClient = storageClient.GetBlobService()
|
||||
}
|
||||
|
||||
return azureClient, nil
|
||||
}
|
||||
|
||||
func getInspectorMaxLength() int64 {
|
||||
value, ok := os.LookupEnv(EnvPackerLogAzureMaxLen)
|
||||
if !ok {
|
||||
return math.MaxInt64
|
||||
}
|
||||
|
||||
i, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
if i < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type azureErrorDetails struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Details []azureErrorDetails `json:"details"`
|
||||
}
|
||||
|
||||
type azureErrorResponse struct {
|
||||
ErrorDetails azureErrorDetails `json:"error"`
|
||||
}
|
||||
|
||||
func newAzureErrorResponse(s string) *azureErrorResponse {
|
||||
var errorResponse azureErrorResponse
|
||||
err := json.Unmarshal([]byte(s), &errorResponse)
|
||||
if err == nil {
|
||||
return &errorResponse
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *azureErrorDetails) isEmpty() bool {
|
||||
return e.Code == ""
|
||||
}
|
||||
|
||||
func (e *azureErrorResponse) isEmpty() bool {
|
||||
return e.ErrorDetails.isEmpty()
|
||||
}
|
||||
|
||||
func (e *azureErrorResponse) Error() string {
|
||||
var buf bytes.Buffer
|
||||
//buf.WriteString("-=-=- ERROR -=-=-")
|
||||
formatAzureErrorResponse(e.ErrorDetails, &buf, "")
|
||||
//buf.WriteString("-=-=- ERROR -=-=-")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// format a Azure Error Response by recursing through the JSON structure.
|
||||
//
|
||||
// Errors may contain nested errors, which are JSON documents that have been
|
||||
// serialized and escaped. Keep following this nesting all the way down...
|
||||
func formatAzureErrorResponse(error azureErrorDetails, buf *bytes.Buffer, indent string) {
|
||||
if error.isEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf("ERROR: %s-> %s : %s\n", indent, error.Code, error.Message))
|
||||
for _, x := range error.Details {
|
||||
newIndent := fmt.Sprintf("%s ", indent)
|
||||
|
||||
var aer azureErrorResponse
|
||||
err := json.Unmarshal([]byte(x.Message), &aer)
|
||||
if err == nil {
|
||||
buf.WriteString(fmt.Sprintf("ERROR: %s-> %s\n", newIndent, x.Code))
|
||||
formatAzureErrorResponse(aer.ErrorDetails, buf, newIndent)
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf("ERROR: %s-> %s : %s\n", newIndent, x.Code, x.Message))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
ERROR: -> DeploymentFailed : At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.
|
||||
ERROR: -> BadRequest
|
||||
ERROR: -> InvalidRequestFormat : Cannot parse the request.
|
||||
ERROR: -> InvalidJson : Error converting value "playground" to type 'Microsoft.WindowsAzure.Networking.Nrp.Frontend.Contract.Csm.Public.IpAllocationMethod'. Path 'properties.publicIPAllocationMethod', line 1, position 130.
|
|
@ -0,0 +1 @@
|
|||
ERROR: -> ResourceNotFound : The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found.
|
|
@ -0,0 +1,77 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
approvaltests "github.com/approvals/go-approval-tests"
|
||||
"github.com/hashicorp/packer-plugin-sdk/json"
|
||||
)
|
||||
|
||||
const AzureErrorSimple = `{"error":{"code":"ResourceNotFound","message":"The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found."}}`
|
||||
const AzureErrorNested = `{"status":"Failed","error":{"code":"DeploymentFailed","message":"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.","details":[{"code":"BadRequest","message":"{\r\n \"error\": {\r\n \"code\": \"InvalidRequestFormat\",\r\n \"message\": \"Cannot parse the request.\",\r\n \"details\": [\r\n {\r\n \"code\": \"InvalidJson\",\r\n \"message\": \"Error converting value \\\"playground\\\" to type 'Microsoft.WindowsAzure.Networking.Nrp.Frontend.Contract.Csm.Public.IpAllocationMethod'. Path 'properties.publicIPAllocationMethod', line 1, position 130.\"\r\n }\r\n ]\r\n }\r\n}"}]}}`
|
||||
|
||||
func TestAzureErrorSimpleShouldUnmarshal(t *testing.T) {
|
||||
var azureErrorResponse azureErrorResponse
|
||||
err := json.Unmarshal([]byte(AzureErrorSimple), &azureErrorResponse)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if azureErrorResponse.ErrorDetails.Code != "ResourceNotFound" {
|
||||
t.Errorf("Error.Code")
|
||||
}
|
||||
if azureErrorResponse.ErrorDetails.Message != "The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found." {
|
||||
t.Errorf("Error.Message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAzureErrorNestedShouldUnmarshal(t *testing.T) {
|
||||
var azureError azureErrorResponse
|
||||
err := json.Unmarshal([]byte(AzureErrorNested), &azureError)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if azureError.ErrorDetails.Code != "DeploymentFailed" {
|
||||
t.Errorf("Error.Code")
|
||||
}
|
||||
if !strings.HasPrefix(azureError.ErrorDetails.Message, "At least one resource deployment operation failed") {
|
||||
t.Errorf("Error.Message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAzureErrorEmptyShouldFormat(t *testing.T) {
|
||||
var aer azureErrorResponse
|
||||
s := aer.Error()
|
||||
|
||||
if s != "" {
|
||||
t.Fatalf("Expected \"\", but got %s", aer.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAzureErrorSimpleShouldFormat(t *testing.T) {
|
||||
var azureErrorResponse azureErrorResponse
|
||||
err := json.Unmarshal([]byte(AzureErrorSimple), &azureErrorResponse)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyString(t, azureErrorResponse.Error())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAzureErrorNestedShouldFormat(t *testing.T) {
|
||||
var azureErrorResponse azureErrorResponse
|
||||
err := json.Unmarshal([]byte(AzureErrorNested), &azureErrorResponse)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyString(t, azureErrorResponse.Error())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,477 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
armstorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage"
|
||||
"github.com/Azure/azure-sdk-for-go/storage"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
packerAzureCommon "github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/builder/azure/common/lin"
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
stateBag multistep.StateBag
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultSasBlobContainer = "system/Microsoft.Compute"
|
||||
DefaultSecretName = "packerKeyVaultSecret"
|
||||
)
|
||||
|
||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
warnings, errs := b.config.Prepare(raws...)
|
||||
if errs != nil {
|
||||
return nil, warnings, errs
|
||||
}
|
||||
|
||||
b.stateBag = new(multistep.BasicStateBag)
|
||||
b.configureStateBag(b.stateBag)
|
||||
b.setTemplateParameters(b.stateBag)
|
||||
b.setImageParameters(b.stateBag)
|
||||
|
||||
return nil, warnings, errs
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
||||
|
||||
ui.Say("Running builder ...")
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// FillParameters function captures authType and sets defaults.
|
||||
err := b.config.ClientConfig.FillParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//When running Packer on an Azure instance using Managed Identity, FillParameters will update SubscriptionID from the instance
|
||||
// so lets make sure to update our state bag with the valid subscriptionID.
|
||||
if b.config.isManagedImage() && b.config.SharedGalleryDestination.SigDestinationGalleryName != "" {
|
||||
b.stateBag.Put(constants.ArmManagedImageSubscription, b.config.ClientConfig.SubscriptionID)
|
||||
}
|
||||
|
||||
log.Print(":: Configuration")
|
||||
packerAzureCommon.DumpConfig(&b.config, func(s string) { log.Print(s) })
|
||||
|
||||
b.stateBag.Put("hook", hook)
|
||||
b.stateBag.Put(constants.Ui, ui)
|
||||
|
||||
spnCloud, spnKeyVault, err := b.getServicePrincipalTokens(ui.Say)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ui.Message("Creating Azure Resource Manager (ARM) client ...")
|
||||
azureClient, err := NewAzureClient(
|
||||
b.config.ClientConfig.SubscriptionID,
|
||||
b.config.SharedGalleryDestination.SigDestinationSubscription,
|
||||
b.config.ResourceGroupName,
|
||||
b.config.StorageAccount,
|
||||
b.config.ClientConfig.CloudEnvironment(),
|
||||
b.config.SharedGalleryTimeout,
|
||||
b.config.PollingDurationTimeout,
|
||||
spnCloud,
|
||||
spnKeyVault)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolver := newResourceResolver(azureClient)
|
||||
if err := resolver.Resolve(&b.config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.config.ClientConfig.ObjectID == "" {
|
||||
b.config.ClientConfig.ObjectID = getObjectIdFromToken(ui, spnCloud)
|
||||
} else {
|
||||
ui.Message("You have provided Object_ID which is no longer needed, azure packer builder determines this dynamically from the authentication token")
|
||||
}
|
||||
|
||||
if b.config.ClientConfig.ObjectID == "" && b.config.OSType != constants.Target_Linux {
|
||||
return nil, fmt.Errorf("could not determine the ObjectID for the user, which is required for Windows builds")
|
||||
}
|
||||
|
||||
if b.config.isManagedImage() {
|
||||
_, err := azureClient.GroupsClient.Get(ctx, b.config.ManagedImageResourceGroupName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot locate the managed image resource group %s.", b.config.ManagedImageResourceGroupName)
|
||||
}
|
||||
|
||||
// If a managed image already exists it cannot be overwritten.
|
||||
_, err = azureClient.ImagesClient.Get(ctx, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, "")
|
||||
if err == nil {
|
||||
if b.config.PackerForce {
|
||||
ui.Say(fmt.Sprintf("the managed image named %s already exists, but deleting it due to -force flag", b.config.ManagedImageName))
|
||||
f, err := azureClient.ImagesClient.Delete(ctx, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, azureClient.ImagesClient.Client)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete the managed image named %s : %s", b.config.ManagedImageName, azureClient.LastError.Error())
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("the managed image named %s already exists in the resource group %s, use the -force option to automatically delete it.", b.config.ManagedImageName, b.config.ManagedImageResourceGroupName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// User is not using Managed Images to build, warning message here that this path is being deprecated
|
||||
ui.Error("Warning: You are using Azure Packer Builder to create VHDs which is being deprecated, consider using Managed Images. Learn more https://www.packer.io/docs/builders/azure/arm#azure-arm-builder-specific-options")
|
||||
}
|
||||
|
||||
if b.config.BuildResourceGroupName != "" {
|
||||
group, err := azureClient.GroupsClient.Get(ctx, b.config.BuildResourceGroupName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot locate the existing build resource resource group %s.", b.config.BuildResourceGroupName)
|
||||
}
|
||||
|
||||
b.config.Location = *group.Location
|
||||
}
|
||||
|
||||
b.config.validateLocationZoneResiliency(ui.Say)
|
||||
|
||||
if b.config.StorageAccount != "" {
|
||||
account, err := b.getBlobAccount(ctx, azureClient, b.config.ResourceGroupName, b.config.StorageAccount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.config.storageAccountBlobEndpoint = *account.AccountProperties.PrimaryEndpoints.Blob
|
||||
|
||||
if !equalLocation(*account.Location, b.config.Location) {
|
||||
return nil, fmt.Errorf("The storage account is located in %s, but the build will take place in %s. The locations must be identical", *account.Location, b.config.Location)
|
||||
}
|
||||
}
|
||||
|
||||
endpointConnectType := PublicEndpoint
|
||||
if b.isPublicPrivateNetworkCommunication() && b.isPrivateNetworkCommunication() {
|
||||
endpointConnectType = PublicEndpointInPrivateNetwork
|
||||
} else if b.isPrivateNetworkCommunication() {
|
||||
endpointConnectType = PrivateEndpoint
|
||||
}
|
||||
|
||||
b.setRuntimeParameters(b.stateBag)
|
||||
b.setTemplateParameters(b.stateBag)
|
||||
b.setImageParameters(b.stateBag)
|
||||
|
||||
deploymentName := b.stateBag.Get(constants.ArmDeploymentName).(string)
|
||||
|
||||
// For Managed Images, validate that Shared Gallery Image exists before publishing to SIG
|
||||
if b.config.isManagedImage() && b.config.SharedGalleryDestination.SigDestinationGalleryName != "" {
|
||||
_, err = azureClient.GalleryImagesClient.Get(ctx, b.config.SharedGalleryDestination.SigDestinationResourceGroup, b.config.SharedGalleryDestination.SigDestinationGalleryName, b.config.SharedGalleryDestination.SigDestinationImageName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the Shared Gallery Image to which to publish the managed image version to does not exist in the resource group %s", b.config.SharedGalleryDestination.SigDestinationResourceGroup)
|
||||
}
|
||||
// SIG requires that replication regions include the region in which the Managed Image resides
|
||||
managedImageLocation := normalizeAzureRegion(b.stateBag.Get(constants.ArmLocation).(string))
|
||||
foundMandatoryReplicationRegion := false
|
||||
var normalizedReplicationRegions []string
|
||||
for _, region := range b.config.SharedGalleryDestination.SigDestinationReplicationRegions {
|
||||
// change region to lower-case and strip spaces
|
||||
normalizedRegion := normalizeAzureRegion(region)
|
||||
normalizedReplicationRegions = append(normalizedReplicationRegions, normalizedRegion)
|
||||
if strings.EqualFold(normalizedRegion, managedImageLocation) {
|
||||
foundMandatoryReplicationRegion = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if foundMandatoryReplicationRegion == false {
|
||||
b.config.SharedGalleryDestination.SigDestinationReplicationRegions = append(normalizedReplicationRegions, managedImageLocation)
|
||||
}
|
||||
b.stateBag.Put(constants.ArmManagedImageSharedGalleryReplicationRegions, b.config.SharedGalleryDestination.SigDestinationReplicationRegions)
|
||||
}
|
||||
|
||||
var steps []multistep.Step
|
||||
if b.config.OSType == constants.Target_Linux {
|
||||
steps = []multistep.Step{
|
||||
NewStepCreateResourceGroup(azureClient, ui),
|
||||
NewStepValidateTemplate(azureClient, ui, &b.config, GetVirtualMachineDeployment),
|
||||
NewStepDeployTemplate(azureClient, ui, &b.config, deploymentName, GetVirtualMachineDeployment),
|
||||
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
|
||||
&communicator.StepConnectSSH{
|
||||
Config: &b.config.Comm,
|
||||
Host: lin.SSHHost,
|
||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&commonsteps.StepProvision{},
|
||||
&commonsteps.StepCleanupTempKeys{
|
||||
Comm: &b.config.Comm,
|
||||
},
|
||||
NewStepGetOSDisk(azureClient, ui),
|
||||
NewStepGetAdditionalDisks(azureClient, ui),
|
||||
NewStepPowerOffCompute(azureClient, ui),
|
||||
NewStepSnapshotOSDisk(azureClient, ui, &b.config),
|
||||
NewStepSnapshotDataDisks(azureClient, ui, &b.config),
|
||||
NewStepCaptureImage(azureClient, ui),
|
||||
NewStepPublishToSharedImageGallery(azureClient, ui, &b.config),
|
||||
}
|
||||
} else if b.config.OSType == constants.Target_Windows {
|
||||
steps = []multistep.Step{
|
||||
NewStepCreateResourceGroup(azureClient, ui),
|
||||
}
|
||||
if b.config.BuildKeyVaultName == "" {
|
||||
keyVaultDeploymentName := b.stateBag.Get(constants.ArmKeyVaultDeploymentName).(string)
|
||||
steps = append(steps,
|
||||
NewStepValidateTemplate(azureClient, ui, &b.config, GetKeyVaultDeployment),
|
||||
NewStepDeployTemplate(azureClient, ui, &b.config, keyVaultDeploymentName, GetKeyVaultDeployment),
|
||||
)
|
||||
} else {
|
||||
steps = append(steps, NewStepCertificateInKeyVault(&azureClient.VaultClient, ui, &b.config))
|
||||
}
|
||||
steps = append(steps,
|
||||
NewStepGetCertificate(azureClient, ui),
|
||||
NewStepSetCertificate(&b.config, ui),
|
||||
NewStepValidateTemplate(azureClient, ui, &b.config, GetVirtualMachineDeployment),
|
||||
NewStepDeployTemplate(azureClient, ui, &b.config, deploymentName, GetVirtualMachineDeployment),
|
||||
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
|
||||
&communicator.StepConnectWinRM{
|
||||
Config: &b.config.Comm,
|
||||
Host: func(stateBag multistep.StateBag) (string, error) {
|
||||
return stateBag.Get(constants.SSHHost).(string), nil
|
||||
},
|
||||
WinRMConfig: func(multistep.StateBag) (*communicator.WinRMConfig, error) {
|
||||
return &communicator.WinRMConfig{
|
||||
Username: b.config.UserName,
|
||||
Password: b.config.Password,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
&commonsteps.StepProvision{},
|
||||
NewStepGetOSDisk(azureClient, ui),
|
||||
NewStepGetAdditionalDisks(azureClient, ui),
|
||||
NewStepPowerOffCompute(azureClient, ui),
|
||||
NewStepSnapshotOSDisk(azureClient, ui, &b.config),
|
||||
NewStepSnapshotDataDisks(azureClient, ui, &b.config),
|
||||
NewStepCaptureImage(azureClient, ui),
|
||||
NewStepPublishToSharedImageGallery(azureClient, ui, &b.config),
|
||||
)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Builder does not support the os_type '%s'", b.config.OSType)
|
||||
}
|
||||
|
||||
if b.config.PackerDebug {
|
||||
ui.Message(fmt.Sprintf("temp admin user: '%s'", b.config.UserName))
|
||||
ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password))
|
||||
|
||||
if len(b.config.Comm.SSHPrivateKey) != 0 {
|
||||
debugKeyPath := fmt.Sprintf("%s-%s.pem", b.config.PackerBuildName, b.config.tmpComputeName)
|
||||
ui.Message(fmt.Sprintf("temp ssh key: %s", debugKeyPath))
|
||||
|
||||
b.writeSSHPrivateKey(ui, debugKeyPath)
|
||||
}
|
||||
}
|
||||
|
||||
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(ctx, b.stateBag)
|
||||
|
||||
// Report any errors.
|
||||
if rawErr, ok := b.stateBag.GetOk(constants.Error); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// If we were interrupted or cancelled, then just exit.
|
||||
if _, ok := b.stateBag.GetOk(multistep.StateCancelled); ok {
|
||||
return nil, errors.New("Build was cancelled.")
|
||||
}
|
||||
|
||||
if _, ok := b.stateBag.GetOk(multistep.StateHalted); ok {
|
||||
return nil, errors.New("Build was halted.")
|
||||
}
|
||||
|
||||
generatedData := map[string]interface{}{"generated_data": b.stateBag.Get("generated_data")}
|
||||
if b.config.isManagedImage() {
|
||||
managedImageID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/images/%s",
|
||||
b.config.ClientConfig.SubscriptionID, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName)
|
||||
if b.config.SharedGalleryDestination.SigDestinationGalleryName != "" {
|
||||
return NewManagedImageArtifactWithSIGAsDestination(b.config.OSType,
|
||||
b.config.ManagedImageResourceGroupName,
|
||||
b.config.ManagedImageName,
|
||||
b.config.Location,
|
||||
managedImageID,
|
||||
b.config.ManagedImageOSDiskSnapshotName,
|
||||
b.config.ManagedImageDataDiskSnapshotPrefix,
|
||||
b.stateBag.Get(constants.ArmManagedImageSharedGalleryId).(string),
|
||||
generatedData)
|
||||
}
|
||||
return NewManagedImageArtifact(b.config.OSType,
|
||||
b.config.ManagedImageResourceGroupName,
|
||||
b.config.ManagedImageName,
|
||||
b.config.Location,
|
||||
managedImageID,
|
||||
b.config.ManagedImageOSDiskSnapshotName,
|
||||
b.config.ManagedImageDataDiskSnapshotPrefix,
|
||||
generatedData)
|
||||
} else if template, ok := b.stateBag.GetOk(constants.ArmCaptureTemplate); ok {
|
||||
return NewArtifact(
|
||||
template.(*CaptureTemplate),
|
||||
func(name string) string {
|
||||
blob := azureClient.BlobStorageClient.GetContainerReference(DefaultSasBlobContainer).GetBlobReference(name)
|
||||
options := storage.BlobSASOptions{}
|
||||
options.BlobServiceSASPermissions.Read = true
|
||||
options.Expiry = time.Now().AddDate(0, 1, 0).UTC() // one month
|
||||
sasUrl, _ := blob.GetSASURI(options)
|
||||
return sasUrl
|
||||
},
|
||||
b.config.OSType,
|
||||
generatedData)
|
||||
}
|
||||
|
||||
return &Artifact{
|
||||
StateData: generatedData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Builder) writeSSHPrivateKey(ui packersdk.Ui, debugKeyPath string) {
|
||||
f, err := os.Create(debugKeyPath)
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Error saving debug key: %s", err))
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Write the key out
|
||||
if _, err := f.Write(b.config.Comm.SSHPrivateKey); err != nil {
|
||||
ui.Say(fmt.Sprintf("Error saving debug key: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Chmod it so that it is SSH ready
|
||||
if runtime.GOOS != "windows" {
|
||||
if err := f.Chmod(0600); err != nil {
|
||||
ui.Say(fmt.Sprintf("Error setting permissions of debug key: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) isPublicPrivateNetworkCommunication() bool {
|
||||
return DefaultPrivateVirtualNetworkWithPublicIp != b.config.PrivateVirtualNetworkWithPublicIp
|
||||
}
|
||||
|
||||
func (b *Builder) isPrivateNetworkCommunication() bool {
|
||||
return b.config.VirtualNetworkName != ""
|
||||
}
|
||||
|
||||
func equalLocation(location1, location2 string) bool {
|
||||
return strings.EqualFold(canonicalizeLocation(location1), canonicalizeLocation(location2))
|
||||
}
|
||||
|
||||
func canonicalizeLocation(location string) string {
|
||||
return strings.Replace(location, " ", "", -1)
|
||||
}
|
||||
|
||||
func (b *Builder) getBlobAccount(ctx context.Context, client *AzureClient, resourceGroupName string, storageAccountName string) (*armstorage.Account, error) {
|
||||
account, err := client.AccountsClient.GetProperties(ctx, resourceGroupName, storageAccountName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &account, err
|
||||
}
|
||||
|
||||
func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
|
||||
stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
|
||||
|
||||
stateBag.Put(constants.ArmTags, packerAzureCommon.MapToAzureTags(b.config.AzureTags))
|
||||
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
|
||||
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
|
||||
|
||||
if b.config.OSType == constants.Target_Windows && b.config.BuildKeyVaultName == "" {
|
||||
stateBag.Put(constants.ArmKeyVaultDeploymentName, fmt.Sprintf("kv%s", b.config.tmpDeploymentName))
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
|
||||
stateBag.Put(constants.ArmIsExistingKeyVault, false)
|
||||
if b.config.BuildKeyVaultName != "" {
|
||||
stateBag.Put(constants.ArmKeyVaultName, b.config.BuildKeyVaultName)
|
||||
b.config.tmpKeyVaultName = b.config.BuildKeyVaultName
|
||||
stateBag.Put(constants.ArmIsExistingKeyVault, true)
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmNicName, b.config.tmpNicName)
|
||||
stateBag.Put(constants.ArmPublicIPAddressName, b.config.tmpPublicIPAddressName)
|
||||
stateBag.Put(constants.ArmResourceGroupName, b.config.BuildResourceGroupName)
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
|
||||
|
||||
if b.config.tmpResourceGroupName != "" {
|
||||
stateBag.Put(constants.ArmResourceGroupName, b.config.tmpResourceGroupName)
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
|
||||
|
||||
if b.config.BuildResourceGroupName != "" {
|
||||
stateBag.Put(constants.ArmDoubleResourceGroupNameSet, true)
|
||||
}
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmStorageAccountName, b.config.StorageAccount)
|
||||
stateBag.Put(constants.ArmIsManagedImage, b.config.isManagedImage())
|
||||
stateBag.Put(constants.ArmManagedImageResourceGroupName, b.config.ManagedImageResourceGroupName)
|
||||
stateBag.Put(constants.ArmManagedImageName, b.config.ManagedImageName)
|
||||
stateBag.Put(constants.ArmManagedImageOSDiskSnapshotName, b.config.ManagedImageOSDiskSnapshotName)
|
||||
stateBag.Put(constants.ArmManagedImageDataDiskSnapshotPrefix, b.config.ManagedImageDataDiskSnapshotPrefix)
|
||||
stateBag.Put(constants.ArmAsyncResourceGroupDelete, b.config.AsyncResourceGroupDelete)
|
||||
|
||||
if b.config.isManagedImage() && b.config.SharedGalleryDestination.SigDestinationGalleryName != "" {
|
||||
stateBag.Put(constants.ArmManagedImageSigPublishResourceGroup, b.config.SharedGalleryDestination.SigDestinationResourceGroup)
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryName, b.config.SharedGalleryDestination.SigDestinationGalleryName)
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryImageName, b.config.SharedGalleryDestination.SigDestinationImageName)
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersion, b.config.SharedGalleryDestination.SigDestinationImageVersion)
|
||||
stateBag.Put(constants.ArmManagedImageSubscription, b.config.ClientConfig.SubscriptionID)
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersionEndOfLifeDate, b.config.SharedGalleryImageVersionEndOfLifeDate)
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersionReplicaCount, b.config.SharedGalleryImageVersionReplicaCount)
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersionExcludeFromLatest, b.config.SharedGalleryImageVersionExcludeFromLatest)
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters that are only known at runtime after querying Azure.
|
||||
func (b *Builder) setRuntimeParameters(stateBag multistep.StateBag) {
|
||||
stateBag.Put(constants.ArmLocation, b.config.Location)
|
||||
}
|
||||
|
||||
func (b *Builder) setTemplateParameters(stateBag multistep.StateBag) {
|
||||
stateBag.Put(constants.ArmVirtualMachineCaptureParameters, b.config.toVirtualMachineCaptureParameters())
|
||||
}
|
||||
|
||||
func (b *Builder) setImageParameters(stateBag multistep.StateBag) {
|
||||
stateBag.Put(constants.ArmImageParameters, b.config.toImageParameters())
|
||||
}
|
||||
|
||||
func (b *Builder) getServicePrincipalTokens(say func(string)) (*adal.ServicePrincipalToken, *adal.ServicePrincipalToken, error) {
|
||||
return b.config.ClientConfig.GetServicePrincipalTokens(say)
|
||||
}
|
||||
|
||||
func getObjectIdFromToken(ui packersdk.Ui, token *adal.ServicePrincipalToken) string {
|
||||
claims := jwt.MapClaims{}
|
||||
var p jwt.Parser
|
||||
|
||||
var err error
|
||||
|
||||
_, _, err = p.ParseUnverified(token.OAuthToken(), claims)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Failed to parse the token,Error: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
|
||||
oid, _ := claims["oid"].(string)
|
||||
return oid
|
||||
}
|
||||
|
||||
func normalizeAzureRegion(name string) string {
|
||||
return strings.ToLower(strings.Replace(name, " ", "", -1))
|
||||
}
|
|
@ -0,0 +1,513 @@
|
|||
package arm
|
||||
|
||||
// these tests require the following variables to be set,
|
||||
// although some test will only use a subset:
|
||||
//
|
||||
// * ARM_CLIENT_ID
|
||||
// * ARM_CLIENT_SECRET
|
||||
// * ARM_SUBSCRIPTION_ID
|
||||
// * ARM_STORAGE_ACCOUNT
|
||||
//
|
||||
// The subscription in question should have a resource group
|
||||
// called "packer-acceptance-test" in "South Central US" region. The
|
||||
// storage account referred to in the above variable should
|
||||
// be inside this resource group and in "South Central US" as well.
|
||||
//
|
||||
// In addition, the PACKER_ACC variable should also be set to
|
||||
// a non-empty value to enable Packer acceptance tests and the
|
||||
// options "-v -timeout 90m" should be provided to the test
|
||||
// command, e.g.:
|
||||
// go test -v -timeout 90m -run TestBuilderAcc_.*
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Windows(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskWindows,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Windows_Build_Resource_Group(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskWindowsBuildResourceGroup,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Windows_Build_Resource_Group_Additional_Disk(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionalDisk,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Windows_DeviceLogin(t *testing.T) {
|
||||
if os.Getenv(DeviceLoginAcceptanceTest) == "" {
|
||||
t.Skip(fmt.Sprintf(
|
||||
"Device Login Acceptance tests skipped unless env '%s' set, as its requires manual step during execution",
|
||||
DeviceLoginAcceptanceTest))
|
||||
return
|
||||
}
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskWindowsDeviceLogin,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Linux(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskLinux,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Linux_DeviceLogin(t *testing.T) {
|
||||
if os.Getenv(DeviceLoginAcceptanceTest) == "" {
|
||||
t.Skip(fmt.Sprintf(
|
||||
"Device Login Acceptance tests skipped unless env '%s' set, as its requires manual step during execution",
|
||||
DeviceLoginAcceptanceTest))
|
||||
return
|
||||
}
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskLinuxDeviceLogin,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ManagedDisk_Linux_AzureCLI(t *testing.T) {
|
||||
if os.Getenv("AZURE_CLI_AUTH") == "" {
|
||||
t.Skip("Azure CLI Acceptance tests skipped unless env 'AZURE_CLI_AUTH' is set, and an active `az login` session has been established")
|
||||
return
|
||||
}
|
||||
|
||||
var b Builder
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAuthPreCheck(t) },
|
||||
Builder: &b,
|
||||
Template: testBuilderAccManagedDiskLinuxAzureCLI,
|
||||
Check: func([]packersdk.Artifact) error {
|
||||
checkTemporaryGroupDeleted(t, &b)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_Blob_Windows(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccBlobWindows,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_Blob_Linux(t *testing.T) {
|
||||
var b Builder
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAuthPreCheck(t) },
|
||||
Builder: &b,
|
||||
Template: testBuilderAccBlobLinux,
|
||||
Check: func([]packersdk.Artifact) error {
|
||||
checkUnmanagedVHDDeleted(t, &b)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccPreCheck(*testing.T) {}
|
||||
|
||||
func testAuthPreCheck(t *testing.T) {
|
||||
_, err := auth.NewAuthorizerFromEnvironment()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to auth to azure: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkTemporaryGroupDeleted(t *testing.T, b *Builder) {
|
||||
ui := testUi()
|
||||
|
||||
spnCloud, spnKeyVault, err := b.getServicePrincipalTokens(ui.Say)
|
||||
if err != nil {
|
||||
t.Fatalf("failed getting azure tokens: %s", err)
|
||||
}
|
||||
|
||||
ui.Message("Creating test Azure Resource Manager (ARM) client ...")
|
||||
azureClient, err := NewAzureClient(
|
||||
b.config.ClientConfig.SubscriptionID,
|
||||
b.config.SharedGalleryDestination.SigDestinationSubscription,
|
||||
b.config.ResourceGroupName,
|
||||
b.config.StorageAccount,
|
||||
b.config.ClientConfig.CloudEnvironment(),
|
||||
b.config.SharedGalleryTimeout,
|
||||
b.config.PollingDurationTimeout,
|
||||
spnCloud,
|
||||
spnKeyVault)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create azure client: %s", err)
|
||||
}
|
||||
|
||||
// Validate resource group has been deleted
|
||||
_, err = azureClient.GroupsClient.Get(context.Background(), b.config.tmpResourceGroupName)
|
||||
if err == nil || !resourceNotFound(err) {
|
||||
t.Fatalf("failed validating resource group deletion: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkUnmanagedVHDDeleted(t *testing.T, b *Builder) {
|
||||
ui := testUi()
|
||||
|
||||
spnCloud, spnKeyVault, err := b.getServicePrincipalTokens(ui.Say)
|
||||
if err != nil {
|
||||
t.Fatalf("failed getting azure tokens: %s", err)
|
||||
}
|
||||
|
||||
azureClient, err := NewAzureClient(
|
||||
b.config.ClientConfig.SubscriptionID,
|
||||
b.config.SharedGalleryDestination.SigDestinationSubscription,
|
||||
b.config.ResourceGroupName,
|
||||
b.config.StorageAccount,
|
||||
b.config.ClientConfig.CloudEnvironment(),
|
||||
b.config.SharedGalleryTimeout,
|
||||
b.config.PollingDurationTimeout,
|
||||
spnCloud,
|
||||
spnKeyVault)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create azure client: %s", err)
|
||||
}
|
||||
|
||||
// validate temporary os blob was deleted
|
||||
blob := azureClient.BlobStorageClient.GetContainerReference("images").GetBlobReference(b.config.tmpOSDiskName)
|
||||
_, err = blob.BreakLease(nil)
|
||||
if err != nil && !strings.Contains(err.Error(), "BlobNotFound") {
|
||||
t.Fatalf("failed validating deletion of unmanaged vhd: %s", err)
|
||||
}
|
||||
|
||||
// Validate resource group has been deleted
|
||||
_, err = azureClient.GroupsClient.Get(context.Background(), b.config.tmpResourceGroupName)
|
||||
if err == nil || !resourceNotFound(err) {
|
||||
t.Fatalf("failed validating resource group deletion: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func resourceNotFound(err error) bool {
|
||||
derr := autorest.DetailedError{}
|
||||
return errors.As(err, &derr) && derr.StatusCode == 404
|
||||
}
|
||||
|
||||
func testUi() *packersdk.BasicUi {
|
||||
return &packersdk.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
ErrorWriter: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
const testBuilderAccManagedDiskWindows = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindows-{{timestamp}}",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
"image_offer": "WindowsServer",
|
||||
"image_sku": "2012-R2-Datacenter",
|
||||
|
||||
"communicator": "winrm",
|
||||
"winrm_use_ssl": "true",
|
||||
"winrm_insecure": "true",
|
||||
"winrm_timeout": "3m",
|
||||
"winrm_username": "packer",
|
||||
"async_resourcegroup_delete": "true",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"build_resource_group_name" : "packer-acceptance-test",
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroup-{{timestamp}}",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
"image_offer": "WindowsServer",
|
||||
"image_sku": "2012-R2-Datacenter",
|
||||
|
||||
"communicator": "winrm",
|
||||
"winrm_use_ssl": "true",
|
||||
"winrm_insecure": "true",
|
||||
"winrm_timeout": "3m",
|
||||
"winrm_username": "packer",
|
||||
"async_resourcegroup_delete": "true",
|
||||
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionalDisk = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"build_resource_group_name" : "packer-acceptance-test",
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionDisk-{{timestamp}}",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
"image_offer": "WindowsServer",
|
||||
"image_sku": "2012-R2-Datacenter",
|
||||
|
||||
"communicator": "winrm",
|
||||
"winrm_use_ssl": "true",
|
||||
"winrm_insecure": "true",
|
||||
"winrm_timeout": "3m",
|
||||
"winrm_username": "packer",
|
||||
"async_resourcegroup_delete": "true",
|
||||
|
||||
"vm_size": "Standard_DS2_v2",
|
||||
"disk_additional_size": [10,15]
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskWindowsDeviceLogin = `
|
||||
{
|
||||
"variables": {
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindowsDeviceLogin-{{timestamp}}",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
"image_offer": "WindowsServer",
|
||||
"image_sku": "2012-R2-Datacenter",
|
||||
|
||||
"communicator": "winrm",
|
||||
"winrm_use_ssl": "true",
|
||||
"winrm_insecure": "true",
|
||||
"winrm_timeout": "3m",
|
||||
"winrm_username": "packer",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskLinux = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskLinux-{{timestamp}}",
|
||||
|
||||
"os_type": "Linux",
|
||||
"image_publisher": "Canonical",
|
||||
"image_offer": "UbuntuServer",
|
||||
"image_sku": "16.04-LTS",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2",
|
||||
"azure_tags": {
|
||||
"env": "testing",
|
||||
"builder": "packer"
|
||||
}
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskLinuxDeviceLogin = `
|
||||
{
|
||||
"variables": {
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskLinuxDeviceLogin-{{timestamp}}",
|
||||
|
||||
"os_type": "Linux",
|
||||
"image_publisher": "Canonical",
|
||||
"image_offer": "UbuntuServer",
|
||||
"image_sku": "16.04-LTS",
|
||||
"async_resourcegroup_delete": "true",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccBlobWindows = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}",
|
||||
"storage_account": "{{env ` + "`ARM_STORAGE_ACCOUNT`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"storage_account": "{{user ` + "`storage_account`" + `}}",
|
||||
"resource_group_name": "packer-acceptance-test",
|
||||
"capture_container_name": "test",
|
||||
"capture_name_prefix": "testBuilderAccBlobWin",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
"image_offer": "WindowsServer",
|
||||
"image_sku": "2012-R2-Datacenter",
|
||||
|
||||
"communicator": "winrm",
|
||||
"winrm_use_ssl": "true",
|
||||
"winrm_insecure": "true",
|
||||
"winrm_timeout": "3m",
|
||||
"winrm_username": "packer",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccBlobLinux = `
|
||||
{
|
||||
"variables": {
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}",
|
||||
"storage_account": "{{env ` + "`ARM_STORAGE_ACCOUNT`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
|
||||
"storage_account": "{{user ` + "`storage_account`" + `}}",
|
||||
"resource_group_name": "packer-acceptance-test",
|
||||
"capture_container_name": "test",
|
||||
"capture_name_prefix": "testBuilderAccBlobLinux",
|
||||
|
||||
"os_type": "Linux",
|
||||
"image_publisher": "Canonical",
|
||||
"image_offer": "UbuntuServer",
|
||||
"image_sku": "16.04-LTS",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskLinuxAzureCLI = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
|
||||
"use_azure_cli_auth": true,
|
||||
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskLinuxAzureCLI-{{timestamp}}",
|
||||
"temp_resource_group_name": "packer-acceptance-test-managed-cli",
|
||||
|
||||
"os_type": "Linux",
|
||||
"image_publisher": "Canonical",
|
||||
"image_offer": "UbuntuServer",
|
||||
"image_sku": "16.04-LTS",
|
||||
|
||||
"location": "South Central US",
|
||||
"vm_size": "Standard_DS2_v2",
|
||||
"azure_tags": {
|
||||
"env": "testing",
|
||||
"builder": "packer"
|
||||
}
|
||||
}]
|
||||
}
|
||||
`
|
|
@ -0,0 +1,67 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
|
||||
var testSubject Builder
|
||||
_, _, err := testSubject.Prepare(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to prepare: %s", err)
|
||||
}
|
||||
|
||||
var expectedStateBagKeys = []string{
|
||||
constants.AuthorizedKey,
|
||||
|
||||
constants.ArmTags,
|
||||
constants.ArmComputeName,
|
||||
constants.ArmDeploymentName,
|
||||
constants.ArmNicName,
|
||||
constants.ArmResourceGroupName,
|
||||
constants.ArmStorageAccountName,
|
||||
constants.ArmVirtualMachineCaptureParameters,
|
||||
constants.ArmPublicIPAddressName,
|
||||
constants.ArmAsyncResourceGroupDelete,
|
||||
}
|
||||
|
||||
for _, v := range expectedStateBagKeys {
|
||||
if _, ok := testSubject.stateBag.GetOk(v); ok == false {
|
||||
t.Errorf("Expected the builder's state bag to contain '%s', but it did not.", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateBagShouldPoluateExpectedTags(t *testing.T) {
|
||||
var testSubject Builder
|
||||
|
||||
expectedTags := map[string]string{
|
||||
"env": "test",
|
||||
"builder": "packer",
|
||||
}
|
||||
armConfig := getArmBuilderConfiguration()
|
||||
armConfig["azure_tags"] = expectedTags
|
||||
|
||||
_, _, err := testSubject.Prepare(armConfig, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to prepare: %s", err)
|
||||
}
|
||||
|
||||
tags, ok := testSubject.stateBag.Get(constants.ArmTags).(map[string]*string)
|
||||
if !ok {
|
||||
t.Errorf("Expected the builder's state bag to contain tags of type %T, but didn't.", testSubject.config.AzureTags)
|
||||
}
|
||||
|
||||
if len(tags) != len(expectedTags) {
|
||||
t.Errorf("expect tags from state to be the same length as tags from config")
|
||||
}
|
||||
|
||||
for k, v := range tags {
|
||||
if expectedTags[k] != *v {
|
||||
t.Errorf("expect tag value of %s to be %s, but got %s", k, expectedTags[k], *v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package arm
|
||||
|
||||
type CaptureTemplateParameter struct {
|
||||
Type string `json:"type"`
|
||||
DefaultValue string `json:"defaultValue,omitempty"`
|
||||
}
|
||||
|
||||
type CaptureHardwareProfile struct {
|
||||
VMSize string `json:"vmSize"`
|
||||
}
|
||||
|
||||
type CaptureUri struct {
|
||||
Uri string `json:"uri"`
|
||||
}
|
||||
|
||||
type CaptureDisk struct {
|
||||
OSType string `json:"osType"`
|
||||
Name string `json:"name"`
|
||||
Image CaptureUri `json:"image"`
|
||||
Vhd CaptureUri `json:"vhd"`
|
||||
CreateOption string `json:"createOption"`
|
||||
Caching string `json:"caching"`
|
||||
}
|
||||
|
||||
type CaptureStorageProfile struct {
|
||||
OSDisk CaptureDisk `json:"osDisk"`
|
||||
DataDisks []CaptureDisk `json:"dataDisks"`
|
||||
}
|
||||
|
||||
type CaptureOSProfile struct {
|
||||
ComputerName string `json:"computerName"`
|
||||
AdminUsername string `json:"adminUsername"`
|
||||
AdminPassword string `json:"adminPassword"`
|
||||
}
|
||||
|
||||
type CaptureNetworkInterface struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
type CaptureNetworkProfile struct {
|
||||
NetworkInterfaces []CaptureNetworkInterface `json:"networkInterfaces"`
|
||||
}
|
||||
|
||||
type CaptureBootDiagnostics struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type CaptureDiagnosticProfile struct {
|
||||
BootDiagnostics CaptureBootDiagnostics `json:"bootDiagnostics"`
|
||||
}
|
||||
|
||||
type CaptureProperties struct {
|
||||
HardwareProfile CaptureHardwareProfile `json:"hardwareProfile"`
|
||||
StorageProfile CaptureStorageProfile `json:"storageProfile"`
|
||||
OSProfile CaptureOSProfile `json:"osProfile"`
|
||||
NetworkProfile CaptureNetworkProfile `json:"networkProfile"`
|
||||
DiagnosticsProfile CaptureDiagnosticProfile `json:"diagnosticsProfile"`
|
||||
ProvisioningState int `json:"provisioningState"`
|
||||
}
|
||||
|
||||
type CaptureResources struct {
|
||||
ApiVersion string `json:"apiVersion"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Location string `json:"location"`
|
||||
Properties CaptureProperties `json:"properties"`
|
||||
}
|
||||
|
||||
type CaptureTemplate struct {
|
||||
Schema string `json:"$schema"`
|
||||
ContentVersion string `json:"contentVersion"`
|
||||
Parameters map[string]CaptureTemplateParameter `json:"parameters"`
|
||||
Resources []CaptureResources `json:"resources"`
|
||||
}
|
||||
|
||||
type CaptureOperationProperties struct {
|
||||
Output *CaptureTemplate `json:"output"`
|
||||
}
|
||||
|
||||
type CaptureOperation struct {
|
||||
OperationId string `json:"operationId"`
|
||||
Status string `json:"status"`
|
||||
Properties *CaptureOperationProperties `json:"properties"`
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var captureTemplate01 = `{
|
||||
"operationId": "ac1c7c38-a591-41b3-89bd-ea39fceace1b",
|
||||
"status": "Succeeded",
|
||||
"startTime": "2016-04-04T21:07:25.2900874+00:00",
|
||||
"endTime": "2016-04-04T21:07:26.4776321+00:00",
|
||||
"properties": {
|
||||
"output": {
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/VM_IP.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string",
|
||||
"defaultValue": "Standard_A2"
|
||||
},
|
||||
"adminUserName": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminPassword": {
|
||||
"type": "securestring"
|
||||
},
|
||||
"networkInterfaceId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2015-06-15",
|
||||
"properties": {
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"storageProfile": {
|
||||
"osDisk": {
|
||||
"osType": "Linux",
|
||||
"name": "packer-osDisk.32118633-6dc9-449f-83b6-a7d2983bec14.vhd",
|
||||
"createOption": "FromImage",
|
||||
"image": {
|
||||
"uri": "http://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.32118633-6dc9-449f-83b6-a7d2983bec14.vhd"
|
||||
},
|
||||
"vhd": {
|
||||
"uri": "http://storage.blob.core.windows.net/vmcontainerce1a1b75-f480-47cb-8e6e-55142e4a5f68/osDisk.ce1a1b75-f480-47cb-8e6e-55142e4a5f68.vhd"
|
||||
},
|
||||
"caching": "ReadWrite"
|
||||
},
|
||||
"dataDisks": [
|
||||
{
|
||||
"lun": 0,
|
||||
"name": "packer-datadisk-0.32118633-6dc9-449f-83b6-a7d2983bec14.vhd",
|
||||
"createOption": "Empty",
|
||||
"image": {
|
||||
"uri": "http://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-0.32118633-6dc9-449f-83b6-a7d2983bec14.vhd"
|
||||
},
|
||||
"vhd": {
|
||||
"uri": "http://storage.blob.core.windows.net/vmcontainerce1a1b75-f480-47cb-8e6e-55142e4a5f68/datadisk-0.ce1a1b75-f480-47cb-8e6e-55142e4a5f68.vhd"
|
||||
},
|
||||
"caching": "ReadWrite"
|
||||
},
|
||||
{
|
||||
"lun": 1,
|
||||
"name": "packer-datadisk-1.32118633-6dc9-449f-83b6-a7d2983bec14.vhd",
|
||||
"createOption": "Empty",
|
||||
"image": {
|
||||
"uri": "http://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-1.32118633-6dc9-449f-83b6-a7d2983bec14.vhd"
|
||||
},
|
||||
"vhd": {
|
||||
"uri": "http://storage.blob.core.windows.net/vmcontainerce1a1b75-f480-47cb-8e6e-55142e4a5f68/datadisk-1.ce1a1b75-f480-47cb-8e6e-55142e4a5f68.vhd"
|
||||
},
|
||||
"caching": "ReadWrite"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"adminPassword": "[parameters('adminPassword')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[parameters('networkInterfaceId')]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"provisioningState": 0
|
||||
},
|
||||
"name": "[parameters('vmName')]",
|
||||
"type": "Microsoft.Compute/virtualMachines",
|
||||
"location": "southcentralus"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
var captureTemplate02 = `{
|
||||
"operationId": "ac1c7c38-a591-41b3-89bd-ea39fceace1b",
|
||||
"status": "Succeeded",
|
||||
"startTime": "2016-04-04T21:07:25.2900874+00:00",
|
||||
"endTime": "2016-04-04T21:07:26.4776321+00:00"
|
||||
}`
|
||||
|
||||
func TestCaptureParseJson(t *testing.T) {
|
||||
var operation CaptureOperation
|
||||
err := json.Unmarshal([]byte(captureTemplate01), &operation)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to the sample capture operation: %s", err)
|
||||
}
|
||||
|
||||
testSubject := operation.Properties.Output
|
||||
if testSubject.Schema != "http://schema.management.azure.com/schemas/2014-04-01-preview/VM_IP.json" {
|
||||
t.Errorf("Schema's value was unexpected: %s", testSubject.Schema)
|
||||
}
|
||||
if testSubject.ContentVersion != "1.0.0.0" {
|
||||
t.Errorf("ContentVersion's value was unexpected: %s", testSubject.ContentVersion)
|
||||
}
|
||||
|
||||
// == Parameters ====================================
|
||||
if len(testSubject.Parameters) != 5 {
|
||||
t.Fatalf("expected parameters to have 5 keys, but got %d", len(testSubject.Parameters))
|
||||
}
|
||||
if _, ok := testSubject.Parameters["vmName"]; !ok {
|
||||
t.Errorf("Parameters['vmName'] was an expected parameters, but it did not exist")
|
||||
}
|
||||
if testSubject.Parameters["vmName"].Type != "string" {
|
||||
t.Errorf("Parameters['vmName'].Type == 'string', but got '%s'", testSubject.Parameters["vmName"].Type)
|
||||
}
|
||||
if _, ok := testSubject.Parameters["vmSize"]; !ok {
|
||||
t.Errorf("Parameters['vmSize'] was an expected parameters, but it did not exist")
|
||||
}
|
||||
if testSubject.Parameters["vmSize"].Type != "string" {
|
||||
t.Errorf("Parameters['vmSize'].Type == 'string', but got '%s'", testSubject.Parameters["vmSize"])
|
||||
}
|
||||
if testSubject.Parameters["vmSize"].DefaultValue != "Standard_A2" {
|
||||
t.Errorf("Parameters['vmSize'].DefaultValue == 'string', but got '%s'", testSubject.Parameters["vmSize"].DefaultValue)
|
||||
}
|
||||
|
||||
// == Resources =====================================
|
||||
if len(testSubject.Resources) != 1 {
|
||||
t.Fatalf("expected resources to have length 1, but got %d", len(testSubject.Resources))
|
||||
}
|
||||
if testSubject.Resources[0].Name != "[parameters('vmName')]" {
|
||||
t.Errorf("Resources[0].Name's value was unexpected: %s", testSubject.Resources[0].Name)
|
||||
}
|
||||
if testSubject.Resources[0].Type != "Microsoft.Compute/virtualMachines" {
|
||||
t.Errorf("Resources[0].Type's value was unexpected: %s", testSubject.Resources[0].Type)
|
||||
}
|
||||
if testSubject.Resources[0].Location != "southcentralus" {
|
||||
t.Errorf("Resources[0].Location's value was unexpected: %s", testSubject.Resources[0].Location)
|
||||
}
|
||||
|
||||
// == Resources/Properties =====================================
|
||||
if testSubject.Resources[0].Properties.ProvisioningState != 0 {
|
||||
t.Errorf("Resources[0].Properties.ProvisioningState's value was unexpected: %d", testSubject.Resources[0].Properties.ProvisioningState)
|
||||
}
|
||||
|
||||
// == Resources/Properties/HardwareProfile ======================
|
||||
hardwareProfile := testSubject.Resources[0].Properties.HardwareProfile
|
||||
if hardwareProfile.VMSize != "[parameters('vmSize')]" {
|
||||
t.Errorf("Resources[0].Properties.HardwareProfile.VMSize's value was unexpected: %s", hardwareProfile.VMSize)
|
||||
}
|
||||
|
||||
// == Resources/Properties/StorageProfile/OSDisk ================
|
||||
osDisk := testSubject.Resources[0].Properties.StorageProfile.OSDisk
|
||||
if osDisk.OSType != "Linux" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.OSDisk's value was unexpected: %s", osDisk.OSType)
|
||||
}
|
||||
if osDisk.Name != "packer-osDisk.32118633-6dc9-449f-83b6-a7d2983bec14.vhd" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.Name's value was unexpected: %s", osDisk.Name)
|
||||
}
|
||||
if osDisk.CreateOption != "FromImage" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.CreateOption's value was unexpected: %s", osDisk.CreateOption)
|
||||
}
|
||||
if osDisk.Image.Uri != "http://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.32118633-6dc9-449f-83b6-a7d2983bec14.vhd" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.Image.Uri's value was unexpected: %s", osDisk.Image.Uri)
|
||||
}
|
||||
if osDisk.Vhd.Uri != "http://storage.blob.core.windows.net/vmcontainerce1a1b75-f480-47cb-8e6e-55142e4a5f68/osDisk.ce1a1b75-f480-47cb-8e6e-55142e4a5f68.vhd" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.Vhd.Uri's value was unexpected: %s", osDisk.Vhd.Uri)
|
||||
}
|
||||
if osDisk.Caching != "ReadWrite" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.OSDisk.Caching's value was unexpected: %s", osDisk.Caching)
|
||||
}
|
||||
|
||||
// == Resources/Properties/StorageProfile/DataDisks ================
|
||||
dataDisks := testSubject.Resources[0].Properties.StorageProfile.DataDisks
|
||||
if len(dataDisks) != 2 {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.DataDisks, 2 disks expected but was: %d", len(dataDisks))
|
||||
}
|
||||
if dataDisks[0].Name != "packer-datadisk-0.32118633-6dc9-449f-83b6-a7d2983bec14.vhd" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.dataDisks[0].Name's value was unexpected: %s", dataDisks[0].Name)
|
||||
}
|
||||
if dataDisks[0].CreateOption != "Empty" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.dataDisks[0].CreateOption's value was unexpected: %s", dataDisks[0].CreateOption)
|
||||
}
|
||||
if dataDisks[0].Image.Uri != "http://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-0.32118633-6dc9-449f-83b6-a7d2983bec14.vhd" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.dataDisks[0].Image.Uri's value was unexpected: %s", dataDisks[0].Image.Uri)
|
||||
}
|
||||
if dataDisks[0].Vhd.Uri != "http://storage.blob.core.windows.net/vmcontainerce1a1b75-f480-47cb-8e6e-55142e4a5f68/datadisk-0.ce1a1b75-f480-47cb-8e6e-55142e4a5f68.vhd" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.dataDisks[0].Vhd.Uri's value was unexpected: %s", dataDisks[0].Vhd.Uri)
|
||||
}
|
||||
if dataDisks[0].Caching != "ReadWrite" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.dataDisks[0].Caching's value was unexpected: %s", dataDisks[0].Caching)
|
||||
}
|
||||
if dataDisks[1].Name != "packer-datadisk-1.32118633-6dc9-449f-83b6-a7d2983bec14.vhd" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.dataDisks[1].Name's value was unexpected: %s", dataDisks[1].Name)
|
||||
}
|
||||
if dataDisks[1].CreateOption != "Empty" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.dataDisks[1].CreateOption's value was unexpected: %s", dataDisks[1].CreateOption)
|
||||
}
|
||||
if dataDisks[1].Image.Uri != "http://storage.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-datadisk-1.32118633-6dc9-449f-83b6-a7d2983bec14.vhd" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.dataDisks[1].Image.Uri's value was unexpected: %s", dataDisks[1].Image.Uri)
|
||||
}
|
||||
if dataDisks[1].Vhd.Uri != "http://storage.blob.core.windows.net/vmcontainerce1a1b75-f480-47cb-8e6e-55142e4a5f68/datadisk-1.ce1a1b75-f480-47cb-8e6e-55142e4a5f68.vhd" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.dataDisks[1].Vhd.Uri's value was unexpected: %s", dataDisks[1].Vhd.Uri)
|
||||
}
|
||||
if dataDisks[1].Caching != "ReadWrite" {
|
||||
t.Errorf("Resources[0].Properties.StorageProfile.dataDisks[1].Caching's value was unexpected: %s", dataDisks[1].Caching)
|
||||
}
|
||||
|
||||
// == Resources/Properties/OSProfile ============================
|
||||
osProfile := testSubject.Resources[0].Properties.OSProfile
|
||||
if osProfile.AdminPassword != "[parameters('adminPassword')]" {
|
||||
t.Errorf("Resources[0].Properties.OSProfile.AdminPassword's value was unexpected: %s", osProfile.AdminPassword)
|
||||
}
|
||||
if osProfile.AdminUsername != "[parameters('adminUsername')]" {
|
||||
t.Errorf("Resources[0].Properties.OSProfile.AdminUsername's value was unexpected: %s", osProfile.AdminUsername)
|
||||
}
|
||||
if osProfile.ComputerName != "[parameters('vmName')]" {
|
||||
t.Errorf("Resources[0].Properties.OSProfile.ComputerName's value was unexpected: %s", osProfile.ComputerName)
|
||||
}
|
||||
|
||||
// == Resources/Properties/NetworkProfile =======================
|
||||
networkProfile := testSubject.Resources[0].Properties.NetworkProfile
|
||||
if len(networkProfile.NetworkInterfaces) != 1 {
|
||||
t.Errorf("Count of Resources[0].Properties.NetworkProfile.NetworkInterfaces was expected to be 1, but go %d", len(networkProfile.NetworkInterfaces))
|
||||
}
|
||||
if networkProfile.NetworkInterfaces[0].Id != "[parameters('networkInterfaceId')]" {
|
||||
t.Errorf("Resources[0].Properties.NetworkProfile.NetworkInterfaces[0].Id's value was unexpected: %s", networkProfile.NetworkInterfaces[0].Id)
|
||||
}
|
||||
|
||||
// == Resources/Properties/DiagnosticsProfile ===================
|
||||
diagnosticsProfile := testSubject.Resources[0].Properties.DiagnosticsProfile
|
||||
if diagnosticsProfile.BootDiagnostics.Enabled != false {
|
||||
t.Errorf("Resources[0].Properties.DiagnosticsProfile.BootDiagnostics.Enabled's value was unexpected: %t", diagnosticsProfile.BootDiagnostics.Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaptureEmptyOperationJson(t *testing.T) {
|
||||
var operation CaptureOperation
|
||||
err := json.Unmarshal([]byte(captureTemplate02), &operation)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to the sample capture operation: %s", err)
|
||||
}
|
||||
|
||||
if operation.Properties != nil {
|
||||
t.Errorf("JSON contained no properties, but value was not nil: %+v", operation.Properties)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,357 @@
|
|||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
|
||||
package arm
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
||||
CloudEnvironmentName *string `mapstructure:"cloud_environment_name" required:"false" cty:"cloud_environment_name" hcl:"cloud_environment_name"`
|
||||
ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"`
|
||||
ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"`
|
||||
ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"`
|
||||
ClientCertExpireTimeout *string `mapstructure:"client_cert_token_timeout" required:"false" cty:"client_cert_token_timeout" hcl:"client_cert_token_timeout"`
|
||||
ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"`
|
||||
ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"`
|
||||
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
|
||||
SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"`
|
||||
UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"`
|
||||
UserAssignedManagedIdentities []string `mapstructure:"user_assigned_managed_identities" required:"false" cty:"user_assigned_managed_identities" hcl:"user_assigned_managed_identities"`
|
||||
CaptureNamePrefix *string `mapstructure:"capture_name_prefix" cty:"capture_name_prefix" hcl:"capture_name_prefix"`
|
||||
CaptureContainerName *string `mapstructure:"capture_container_name" cty:"capture_container_name" hcl:"capture_container_name"`
|
||||
SharedGallery *FlatSharedImageGallery `mapstructure:"shared_image_gallery" required:"false" cty:"shared_image_gallery" hcl:"shared_image_gallery"`
|
||||
SharedGalleryDestination *FlatSharedImageGalleryDestination `mapstructure:"shared_image_gallery_destination" cty:"shared_image_gallery_destination" hcl:"shared_image_gallery_destination"`
|
||||
SharedGalleryTimeout *string `mapstructure:"shared_image_gallery_timeout" cty:"shared_image_gallery_timeout" hcl:"shared_image_gallery_timeout"`
|
||||
SharedGalleryImageVersionEndOfLifeDate *string `mapstructure:"shared_gallery_image_version_end_of_life_date" required:"false" cty:"shared_gallery_image_version_end_of_life_date" hcl:"shared_gallery_image_version_end_of_life_date"`
|
||||
SharedGalleryImageVersionReplicaCount *int32 `mapstructure:"shared_image_gallery_replica_count" required:"false" cty:"shared_image_gallery_replica_count" hcl:"shared_image_gallery_replica_count"`
|
||||
SharedGalleryImageVersionExcludeFromLatest *bool `mapstructure:"shared_gallery_image_version_exclude_from_latest" required:"false" cty:"shared_gallery_image_version_exclude_from_latest" hcl:"shared_gallery_image_version_exclude_from_latest"`
|
||||
ImagePublisher *string `mapstructure:"image_publisher" required:"true" cty:"image_publisher" hcl:"image_publisher"`
|
||||
ImageOffer *string `mapstructure:"image_offer" required:"true" cty:"image_offer" hcl:"image_offer"`
|
||||
ImageSku *string `mapstructure:"image_sku" required:"true" cty:"image_sku" hcl:"image_sku"`
|
||||
ImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version" hcl:"image_version"`
|
||||
ImageUrl *string `mapstructure:"image_url" required:"true" cty:"image_url" hcl:"image_url"`
|
||||
CustomManagedImageName *string `mapstructure:"custom_managed_image_name" required:"true" cty:"custom_managed_image_name" hcl:"custom_managed_image_name"`
|
||||
CustomManagedImageResourceGroupName *string `mapstructure:"custom_managed_image_resource_group_name" required:"true" cty:"custom_managed_image_resource_group_name" hcl:"custom_managed_image_resource_group_name"`
|
||||
Location *string `mapstructure:"location" cty:"location" hcl:"location"`
|
||||
VMSize *string `mapstructure:"vm_size" required:"false" cty:"vm_size" hcl:"vm_size"`
|
||||
ManagedImageResourceGroupName *string `mapstructure:"managed_image_resource_group_name" cty:"managed_image_resource_group_name" hcl:"managed_image_resource_group_name"`
|
||||
ManagedImageName *string `mapstructure:"managed_image_name" cty:"managed_image_name" hcl:"managed_image_name"`
|
||||
ManagedImageStorageAccountType *string `mapstructure:"managed_image_storage_account_type" required:"false" cty:"managed_image_storage_account_type" hcl:"managed_image_storage_account_type"`
|
||||
ManagedImageOSDiskSnapshotName *string `mapstructure:"managed_image_os_disk_snapshot_name" required:"false" cty:"managed_image_os_disk_snapshot_name" hcl:"managed_image_os_disk_snapshot_name"`
|
||||
ManagedImageDataDiskSnapshotPrefix *string `mapstructure:"managed_image_data_disk_snapshot_prefix" required:"false" cty:"managed_image_data_disk_snapshot_prefix" hcl:"managed_image_data_disk_snapshot_prefix"`
|
||||
ManagedImageZoneResilient *bool `mapstructure:"managed_image_zone_resilient" required:"false" cty:"managed_image_zone_resilient" hcl:"managed_image_zone_resilient"`
|
||||
AzureTags map[string]string `mapstructure:"azure_tags" required:"false" cty:"azure_tags" hcl:"azure_tags"`
|
||||
AzureTag []config.FlatNameValue `mapstructure:"azure_tag" required:"false" cty:"azure_tag" hcl:"azure_tag"`
|
||||
ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name" hcl:"resource_group_name"`
|
||||
StorageAccount *string `mapstructure:"storage_account" cty:"storage_account" hcl:"storage_account"`
|
||||
TempComputeName *string `mapstructure:"temp_compute_name" required:"false" cty:"temp_compute_name" hcl:"temp_compute_name"`
|
||||
TempNicName *string `mapstructure:"temp_nic_name" required:"false" cty:"temp_nic_name" hcl:"temp_nic_name"`
|
||||
TempResourceGroupName *string `mapstructure:"temp_resource_group_name" cty:"temp_resource_group_name" hcl:"temp_resource_group_name"`
|
||||
BuildResourceGroupName *string `mapstructure:"build_resource_group_name" cty:"build_resource_group_name" hcl:"build_resource_group_name"`
|
||||
BuildKeyVaultName *string `mapstructure:"build_key_vault_name" cty:"build_key_vault_name" hcl:"build_key_vault_name"`
|
||||
BuildKeyVaultSKU *string `mapstructure:"build_key_vault_sku" cty:"build_key_vault_sku" hcl:"build_key_vault_sku"`
|
||||
PrivateVirtualNetworkWithPublicIp *bool `mapstructure:"private_virtual_network_with_public_ip" required:"false" cty:"private_virtual_network_with_public_ip" hcl:"private_virtual_network_with_public_ip"`
|
||||
VirtualNetworkName *string `mapstructure:"virtual_network_name" required:"false" cty:"virtual_network_name" hcl:"virtual_network_name"`
|
||||
VirtualNetworkSubnetName *string `mapstructure:"virtual_network_subnet_name" required:"false" cty:"virtual_network_subnet_name" hcl:"virtual_network_subnet_name"`
|
||||
VirtualNetworkResourceGroupName *string `mapstructure:"virtual_network_resource_group_name" required:"false" cty:"virtual_network_resource_group_name" hcl:"virtual_network_resource_group_name"`
|
||||
CustomDataFile *string `mapstructure:"custom_data_file" required:"false" cty:"custom_data_file" hcl:"custom_data_file"`
|
||||
PlanInfo *FlatPlanInformation `mapstructure:"plan_info" required:"false" cty:"plan_info" hcl:"plan_info"`
|
||||
PollingDurationTimeout *string `mapstructure:"polling_duration_timeout" required:"false" cty:"polling_duration_timeout" hcl:"polling_duration_timeout"`
|
||||
OSType *string `mapstructure:"os_type" required:"false" cty:"os_type" hcl:"os_type"`
|
||||
TempOSDiskName *string `mapstructure:"temp_os_disk_name" required:"false" cty:"temp_os_disk_name" hcl:"temp_os_disk_name"`
|
||||
OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" required:"false" cty:"os_disk_size_gb" hcl:"os_disk_size_gb"`
|
||||
AdditionalDiskSize []int32 `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
|
||||
DiskCachingType *string `mapstructure:"disk_caching_type" required:"false" cty:"disk_caching_type" hcl:"disk_caching_type"`
|
||||
AllowedInboundIpAddresses []string `mapstructure:"allowed_inbound_ip_addresses" cty:"allowed_inbound_ip_addresses" hcl:"allowed_inbound_ip_addresses"`
|
||||
BootDiagSTGAccount *string `mapstructure:"boot_diag_storage_account" required:"false" cty:"boot_diag_storage_account" hcl:"boot_diag_storage_account"`
|
||||
CustomResourcePrefix *string `mapstructure:"custom_resource_build_prefix" required:"false" cty:"custom_resource_build_prefix" hcl:"custom_resource_build_prefix"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
||||
AsyncResourceGroupDelete *bool `mapstructure:"async_resourcegroup_delete" required:"false" cty:"async_resourcegroup_delete" hcl:"async_resourcegroup_delete"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||
"cloud_environment_name": &hcldec.AttrSpec{Name: "cloud_environment_name", Type: cty.String, Required: false},
|
||||
"client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false},
|
||||
"client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false},
|
||||
"client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false},
|
||||
"client_cert_token_timeout": &hcldec.AttrSpec{Name: "client_cert_token_timeout", Type: cty.String, Required: false},
|
||||
"client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false},
|
||||
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
|
||||
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
|
||||
"subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false},
|
||||
"use_azure_cli_auth": &hcldec.AttrSpec{Name: "use_azure_cli_auth", Type: cty.Bool, Required: false},
|
||||
"user_assigned_managed_identities": &hcldec.AttrSpec{Name: "user_assigned_managed_identities", Type: cty.List(cty.String), Required: false},
|
||||
"capture_name_prefix": &hcldec.AttrSpec{Name: "capture_name_prefix", Type: cty.String, Required: false},
|
||||
"capture_container_name": &hcldec.AttrSpec{Name: "capture_container_name", Type: cty.String, Required: false},
|
||||
"shared_image_gallery": &hcldec.BlockSpec{TypeName: "shared_image_gallery", Nested: hcldec.ObjectSpec((*FlatSharedImageGallery)(nil).HCL2Spec())},
|
||||
"shared_image_gallery_destination": &hcldec.BlockSpec{TypeName: "shared_image_gallery_destination", Nested: hcldec.ObjectSpec((*FlatSharedImageGalleryDestination)(nil).HCL2Spec())},
|
||||
"shared_image_gallery_timeout": &hcldec.AttrSpec{Name: "shared_image_gallery_timeout", Type: cty.String, Required: false},
|
||||
"shared_gallery_image_version_end_of_life_date": &hcldec.AttrSpec{Name: "shared_gallery_image_version_end_of_life_date", Type: cty.String, Required: false},
|
||||
"shared_image_gallery_replica_count": &hcldec.AttrSpec{Name: "shared_image_gallery_replica_count", Type: cty.Number, Required: false},
|
||||
"shared_gallery_image_version_exclude_from_latest": &hcldec.AttrSpec{Name: "shared_gallery_image_version_exclude_from_latest", Type: cty.Bool, Required: false},
|
||||
"image_publisher": &hcldec.AttrSpec{Name: "image_publisher", Type: cty.String, Required: false},
|
||||
"image_offer": &hcldec.AttrSpec{Name: "image_offer", Type: cty.String, Required: false},
|
||||
"image_sku": &hcldec.AttrSpec{Name: "image_sku", Type: cty.String, Required: false},
|
||||
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
|
||||
"image_url": &hcldec.AttrSpec{Name: "image_url", Type: cty.String, Required: false},
|
||||
"custom_managed_image_name": &hcldec.AttrSpec{Name: "custom_managed_image_name", Type: cty.String, Required: false},
|
||||
"custom_managed_image_resource_group_name": &hcldec.AttrSpec{Name: "custom_managed_image_resource_group_name", Type: cty.String, Required: false},
|
||||
"location": &hcldec.AttrSpec{Name: "location", Type: cty.String, Required: false},
|
||||
"vm_size": &hcldec.AttrSpec{Name: "vm_size", Type: cty.String, Required: false},
|
||||
"managed_image_resource_group_name": &hcldec.AttrSpec{Name: "managed_image_resource_group_name", Type: cty.String, Required: false},
|
||||
"managed_image_name": &hcldec.AttrSpec{Name: "managed_image_name", Type: cty.String, Required: false},
|
||||
"managed_image_storage_account_type": &hcldec.AttrSpec{Name: "managed_image_storage_account_type", Type: cty.String, Required: false},
|
||||
"managed_image_os_disk_snapshot_name": &hcldec.AttrSpec{Name: "managed_image_os_disk_snapshot_name", Type: cty.String, Required: false},
|
||||
"managed_image_data_disk_snapshot_prefix": &hcldec.AttrSpec{Name: "managed_image_data_disk_snapshot_prefix", Type: cty.String, Required: false},
|
||||
"managed_image_zone_resilient": &hcldec.AttrSpec{Name: "managed_image_zone_resilient", Type: cty.Bool, Required: false},
|
||||
"azure_tags": &hcldec.AttrSpec{Name: "azure_tags", Type: cty.Map(cty.String), Required: false},
|
||||
"azure_tag": &hcldec.BlockListSpec{TypeName: "azure_tag", Nested: hcldec.ObjectSpec((*config.FlatNameValue)(nil).HCL2Spec())},
|
||||
"resource_group_name": &hcldec.AttrSpec{Name: "resource_group_name", Type: cty.String, Required: false},
|
||||
"storage_account": &hcldec.AttrSpec{Name: "storage_account", Type: cty.String, Required: false},
|
||||
"temp_compute_name": &hcldec.AttrSpec{Name: "temp_compute_name", Type: cty.String, Required: false},
|
||||
"temp_nic_name": &hcldec.AttrSpec{Name: "temp_nic_name", Type: cty.String, Required: false},
|
||||
"temp_resource_group_name": &hcldec.AttrSpec{Name: "temp_resource_group_name", Type: cty.String, Required: false},
|
||||
"build_resource_group_name": &hcldec.AttrSpec{Name: "build_resource_group_name", Type: cty.String, Required: false},
|
||||
"build_key_vault_name": &hcldec.AttrSpec{Name: "build_key_vault_name", Type: cty.String, Required: false},
|
||||
"build_key_vault_sku": &hcldec.AttrSpec{Name: "build_key_vault_sku", Type: cty.String, Required: false},
|
||||
"private_virtual_network_with_public_ip": &hcldec.AttrSpec{Name: "private_virtual_network_with_public_ip", Type: cty.Bool, Required: false},
|
||||
"virtual_network_name": &hcldec.AttrSpec{Name: "virtual_network_name", Type: cty.String, Required: false},
|
||||
"virtual_network_subnet_name": &hcldec.AttrSpec{Name: "virtual_network_subnet_name", Type: cty.String, Required: false},
|
||||
"virtual_network_resource_group_name": &hcldec.AttrSpec{Name: "virtual_network_resource_group_name", Type: cty.String, Required: false},
|
||||
"custom_data_file": &hcldec.AttrSpec{Name: "custom_data_file", Type: cty.String, Required: false},
|
||||
"plan_info": &hcldec.BlockSpec{TypeName: "plan_info", Nested: hcldec.ObjectSpec((*FlatPlanInformation)(nil).HCL2Spec())},
|
||||
"polling_duration_timeout": &hcldec.AttrSpec{Name: "polling_duration_timeout", Type: cty.String, Required: false},
|
||||
"os_type": &hcldec.AttrSpec{Name: "os_type", Type: cty.String, Required: false},
|
||||
"temp_os_disk_name": &hcldec.AttrSpec{Name: "temp_os_disk_name", Type: cty.String, Required: false},
|
||||
"os_disk_size_gb": &hcldec.AttrSpec{Name: "os_disk_size_gb", Type: cty.Number, Required: false},
|
||||
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
|
||||
"disk_caching_type": &hcldec.AttrSpec{Name: "disk_caching_type", Type: cty.String, Required: false},
|
||||
"allowed_inbound_ip_addresses": &hcldec.AttrSpec{Name: "allowed_inbound_ip_addresses", Type: cty.List(cty.String), Required: false},
|
||||
"boot_diag_storage_account": &hcldec.AttrSpec{Name: "boot_diag_storage_account", Type: cty.String, Required: false},
|
||||
"custom_resource_build_prefix": &hcldec.AttrSpec{Name: "custom_resource_build_prefix", Type: cty.String, Required: false},
|
||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
||||
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||
"async_resourcegroup_delete": &hcldec.AttrSpec{Name: "async_resourcegroup_delete", Type: cty.Bool, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatPlanInformation is an auto-generated flat version of PlanInformation.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatPlanInformation struct {
|
||||
PlanName *string `mapstructure:"plan_name" cty:"plan_name" hcl:"plan_name"`
|
||||
PlanProduct *string `mapstructure:"plan_product" cty:"plan_product" hcl:"plan_product"`
|
||||
PlanPublisher *string `mapstructure:"plan_publisher" cty:"plan_publisher" hcl:"plan_publisher"`
|
||||
PlanPromotionCode *string `mapstructure:"plan_promotion_code" cty:"plan_promotion_code" hcl:"plan_promotion_code"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatPlanInformation.
|
||||
// FlatPlanInformation is an auto-generated flat version of PlanInformation.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*PlanInformation) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatPlanInformation)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a PlanInformation.
|
||||
// This spec is used by HCL to read the fields of PlanInformation.
|
||||
// The decoded values from this spec will then be applied to a FlatPlanInformation.
|
||||
func (*FlatPlanInformation) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"plan_name": &hcldec.AttrSpec{Name: "plan_name", Type: cty.String, Required: false},
|
||||
"plan_product": &hcldec.AttrSpec{Name: "plan_product", Type: cty.String, Required: false},
|
||||
"plan_publisher": &hcldec.AttrSpec{Name: "plan_publisher", Type: cty.String, Required: false},
|
||||
"plan_promotion_code": &hcldec.AttrSpec{Name: "plan_promotion_code", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatSharedImageGallery is an auto-generated flat version of SharedImageGallery.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatSharedImageGallery struct {
|
||||
Subscription *string `mapstructure:"subscription" cty:"subscription" hcl:"subscription"`
|
||||
ResourceGroup *string `mapstructure:"resource_group" cty:"resource_group" hcl:"resource_group"`
|
||||
GalleryName *string `mapstructure:"gallery_name" cty:"gallery_name" hcl:"gallery_name"`
|
||||
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
||||
ImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version" hcl:"image_version"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatSharedImageGallery.
|
||||
// FlatSharedImageGallery is an auto-generated flat version of SharedImageGallery.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*SharedImageGallery) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatSharedImageGallery)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a SharedImageGallery.
|
||||
// This spec is used by HCL to read the fields of SharedImageGallery.
|
||||
// The decoded values from this spec will then be applied to a FlatSharedImageGallery.
|
||||
func (*FlatSharedImageGallery) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"subscription": &hcldec.AttrSpec{Name: "subscription", Type: cty.String, Required: false},
|
||||
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
|
||||
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
|
||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatSharedImageGalleryDestination struct {
|
||||
SigDestinationSubscription *string `mapstructure:"subscription" cty:"subscription" hcl:"subscription"`
|
||||
SigDestinationResourceGroup *string `mapstructure:"resource_group" cty:"resource_group" hcl:"resource_group"`
|
||||
SigDestinationGalleryName *string `mapstructure:"gallery_name" cty:"gallery_name" hcl:"gallery_name"`
|
||||
SigDestinationImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
||||
SigDestinationImageVersion *string `mapstructure:"image_version" cty:"image_version" hcl:"image_version"`
|
||||
SigDestinationReplicationRegions []string `mapstructure:"replication_regions" cty:"replication_regions" hcl:"replication_regions"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatSharedImageGalleryDestination.
|
||||
// FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*SharedImageGalleryDestination) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatSharedImageGalleryDestination)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a SharedImageGalleryDestination.
|
||||
// This spec is used by HCL to read the fields of SharedImageGalleryDestination.
|
||||
// The decoded values from this spec will then be applied to a FlatSharedImageGalleryDestination.
|
||||
func (*FlatSharedImageGalleryDestination) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"subscription": &hcldec.AttrSpec{Name: "subscription", Type: cty.String, Required: false},
|
||||
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
|
||||
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
|
||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
|
||||
"replication_regions": &hcldec.AttrSpec{Name: "replication_regions", Type: cty.List(cty.String), Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,71 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/hashicorp/packer/builder/azure/common/logutil"
|
||||
)
|
||||
|
||||
func chop(data []byte, maxlen int64) string {
|
||||
s := string(data)
|
||||
if int64(len(s)) > maxlen {
|
||||
s = s[:maxlen] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func handleBody(body io.ReadCloser, maxlen int64) (io.ReadCloser, string) {
|
||||
if body == nil {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
defer body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
return ioutil.NopCloser(bytes.NewReader(b)), chop(b, maxlen)
|
||||
}
|
||||
|
||||
func withInspection(maxlen int64) autorest.PrepareDecorator {
|
||||
return func(p autorest.Preparer) autorest.Preparer {
|
||||
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
body, bodyString := handleBody(r.Body, maxlen)
|
||||
r.Body = body
|
||||
|
||||
log.Print("Azure request", logutil.Fields{
|
||||
"method": r.Method,
|
||||
"request": r.URL.String(),
|
||||
"body": bodyString,
|
||||
})
|
||||
return p.Prepare(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func byInspecting(maxlen int64) autorest.RespondDecorator {
|
||||
return func(r autorest.Responder) autorest.Responder {
|
||||
return autorest.ResponderFunc(func(resp *http.Response) error {
|
||||
body, bodyString := handleBody(resp.Body, maxlen)
|
||||
resp.Body = body
|
||||
|
||||
log.Print("Azure response", logutil.Fields{
|
||||
"status": resp.Status,
|
||||
"method": resp.Request.Method,
|
||||
"request": resp.Request.URL.String(),
|
||||
"x-ms-request-id": azure.ExtractRequestID(resp),
|
||||
"body": bodyString,
|
||||
})
|
||||
return r.Respond(resp)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const (
|
||||
KeySize = 2048
|
||||
)
|
||||
|
||||
type OpenSshKeyPair struct {
|
||||
privateKey *rsa.PrivateKey
|
||||
publicKey ssh.PublicKey
|
||||
}
|
||||
|
||||
func NewOpenSshKeyPair() (*OpenSshKeyPair, error) {
|
||||
return NewOpenSshKeyPairWithSize(KeySize)
|
||||
}
|
||||
|
||||
func NewOpenSshKeyPairWithSize(keySize int) (*OpenSshKeyPair, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &OpenSshKeyPair{
|
||||
privateKey: privateKey,
|
||||
publicKey: publicKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *OpenSshKeyPair) AuthorizedKey() string {
|
||||
return fmt.Sprintf("%s %s packer Azure Deployment%s",
|
||||
s.publicKey.Type(),
|
||||
base64.StdEncoding.EncodeToString(s.publicKey.Marshal()),
|
||||
time.Now().Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func (s *OpenSshKeyPair) PrivateKey() []byte {
|
||||
privateKey := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(s.privateKey),
|
||||
})
|
||||
|
||||
return privateKey
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestFart(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestAuthorizedKeyShouldParse(t *testing.T) {
|
||||
testSubject, err := NewOpenSshKeyPairWithSize(512)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a new OpenSSH key pair, err=%s.", err)
|
||||
}
|
||||
|
||||
authorizedKey := testSubject.AuthorizedKey()
|
||||
|
||||
_, _, _, _, err = ssh.ParseAuthorizedKey([]byte(authorizedKey))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse the authorized key, err=%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivateKeyShouldParse(t *testing.T) {
|
||||
testSubject, err := NewOpenSshKeyPairWithSize(512)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a new OpenSSH key pair, err=%s.", err)
|
||||
}
|
||||
|
||||
_, err = ssh.ParsePrivateKey([]byte(testSubject.PrivateKey()))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse the private key, err=%s\n", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package arm
|
||||
|
||||
// Code to resolve resources that are required by the API. These resources
|
||||
// can most likely be resolved without asking the user, thereby reducing the
|
||||
// amount of configuration they need to provide.
|
||||
//
|
||||
// Resource resolver differs from config retriever because resource resolver
|
||||
// requires a client to communicate with the Azure API. A config retriever is
|
||||
// used to determine values without use of a client.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
)
|
||||
|
||||
type resourceResolver struct {
|
||||
client *AzureClient
|
||||
findVirtualNetworkResourceGroup func(*AzureClient, string) (string, error)
|
||||
findVirtualNetworkSubnet func(*AzureClient, string, string) (string, error)
|
||||
}
|
||||
|
||||
func newResourceResolver(client *AzureClient) *resourceResolver {
|
||||
return &resourceResolver{
|
||||
client: client,
|
||||
findVirtualNetworkResourceGroup: findVirtualNetworkResourceGroup,
|
||||
findVirtualNetworkSubnet: findVirtualNetworkSubnet,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *resourceResolver) Resolve(c *Config) error {
|
||||
if s.shouldResolveResourceGroup(c) {
|
||||
resourceGroupName, err := s.findVirtualNetworkResourceGroup(s.client, c.VirtualNetworkName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subnetName, err := s.findVirtualNetworkSubnet(s.client, resourceGroupName, c.VirtualNetworkName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.VirtualNetworkResourceGroupName = resourceGroupName
|
||||
c.VirtualNetworkSubnetName = subnetName
|
||||
}
|
||||
|
||||
if s.shouldResolveManagedImageName(c) {
|
||||
image, err := findManagedImageByName(s.client, c.CustomManagedImageName, c.CustomManagedImageResourceGroupName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.customManagedImageID = *image.ID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *resourceResolver) shouldResolveResourceGroup(c *Config) bool {
|
||||
return c.VirtualNetworkName != "" && c.VirtualNetworkResourceGroupName == ""
|
||||
}
|
||||
|
||||
func (s *resourceResolver) shouldResolveManagedImageName(c *Config) bool {
|
||||
return c.CustomManagedImageName != ""
|
||||
}
|
||||
|
||||
func getResourceGroupNameFromId(id string) string {
|
||||
// "/subscriptions/3f499422-dd76-4114-8859-86d526c9deb6/resourceGroups/packer-Resource-Group-yylnwsl30j/providers/...
|
||||
xs := strings.Split(id, "/")
|
||||
return xs[4]
|
||||
}
|
||||
|
||||
func findManagedImageByName(client *AzureClient, name, resourceGroupName string) (*compute.Image, error) {
|
||||
images, err := client.ImagesClient.ListByResourceGroupComplete(context.TODO(), resourceGroupName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for images.NotDone() {
|
||||
image := images.Value()
|
||||
if strings.EqualFold(name, *image.Name) {
|
||||
return &image, nil
|
||||
}
|
||||
if err = images.Next(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Cannot find an image named '%s' in the resource group '%s'", name, resourceGroupName)
|
||||
}
|
||||
|
||||
func findVirtualNetworkResourceGroup(client *AzureClient, name string) (string, error) {
|
||||
virtualNetworks, err := client.VirtualNetworksClient.ListAllComplete(context.TODO())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resourceGroupNames := make([]string, 0)
|
||||
for virtualNetworks.NotDone() {
|
||||
virtualNetwork := virtualNetworks.Value()
|
||||
if strings.EqualFold(name, *virtualNetwork.Name) {
|
||||
rgn := getResourceGroupNameFromId(*virtualNetwork.ID)
|
||||
resourceGroupNames = append(resourceGroupNames, rgn)
|
||||
}
|
||||
if err = virtualNetworks.Next(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if len(resourceGroupNames) == 0 {
|
||||
return "", fmt.Errorf("Cannot find a resource group with a virtual network called %q", name)
|
||||
}
|
||||
|
||||
if len(resourceGroupNames) > 1 {
|
||||
return "", fmt.Errorf("Found multiple resource groups with a virtual network called %q, please use virtual_network_subnet_name and virtual_network_resource_group_name to disambiguate", name)
|
||||
}
|
||||
|
||||
return resourceGroupNames[0], nil
|
||||
}
|
||||
|
||||
func findVirtualNetworkSubnet(client *AzureClient, resourceGroupName string, name string) (string, error) {
|
||||
subnets, err := client.SubnetsClient.List(context.TODO(), resourceGroupName, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
subnetList := subnets.Values() // only first page of subnets, but only interested in ==0 or >1
|
||||
|
||||
if len(subnetList) == 0 {
|
||||
return "", fmt.Errorf("Cannot find a subnet in the resource group %q associated with the virtual network called %q", resourceGroupName, name)
|
||||
}
|
||||
|
||||
if len(subnetList) > 1 {
|
||||
return "", fmt.Errorf("Found multiple subnets in the resource group %q associated with the virtual network called %q, please use virtual_network_subnet_name and virtual_network_resource_group_name to disambiguate", resourceGroupName, name)
|
||||
}
|
||||
|
||||
subnet := subnetList[0]
|
||||
return *subnet.Name, nil
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResourceResolverIgnoresEmptyVirtualNetworkName(t *testing.T) {
|
||||
var c Config
|
||||
c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
if c.VirtualNetworkName != "" {
|
||||
t.Fatalf("Expected VirtualNetworkName to be empty by default")
|
||||
}
|
||||
|
||||
sut := newTestResourceResolver()
|
||||
sut.findVirtualNetworkResourceGroup = nil // assert that this is not even called
|
||||
sut.Resolve(&c)
|
||||
|
||||
if c.VirtualNetworkName != "" {
|
||||
t.Fatalf("Expected VirtualNetworkName to be empty")
|
||||
}
|
||||
if c.VirtualNetworkResourceGroupName != "" {
|
||||
t.Fatalf("Expected VirtualNetworkResourceGroupName to be empty")
|
||||
}
|
||||
}
|
||||
|
||||
// If the user fully specified the virtual network name and resource group then
|
||||
// there is no need to do a lookup.
|
||||
func TestResourceResolverIgnoresSetVirtualNetwork(t *testing.T) {
|
||||
var c Config
|
||||
c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
c.VirtualNetworkName = "--virtual-network-name--"
|
||||
c.VirtualNetworkResourceGroupName = "--virtual-network-resource-group-name--"
|
||||
c.VirtualNetworkSubnetName = "--virtual-network-subnet-name--"
|
||||
|
||||
sut := newTestResourceResolver()
|
||||
sut.findVirtualNetworkResourceGroup = nil // assert that this is not even called
|
||||
sut.findVirtualNetworkSubnet = nil // assert that this is not even called
|
||||
sut.Resolve(&c)
|
||||
|
||||
if c.VirtualNetworkName != "--virtual-network-name--" {
|
||||
t.Fatalf("Expected VirtualNetworkName to be --virtual-network-name--")
|
||||
}
|
||||
if c.VirtualNetworkResourceGroupName != "--virtual-network-resource-group-name--" {
|
||||
t.Fatalf("Expected VirtualNetworkResourceGroupName to be --virtual-network-resource-group-name--")
|
||||
}
|
||||
if c.VirtualNetworkSubnetName != "--virtual-network-subnet-name--" {
|
||||
t.Fatalf("Expected VirtualNetworkSubnetName to be --virtual-network-subnet-name--")
|
||||
}
|
||||
}
|
||||
|
||||
// If the user set virtual network name then the code should resolve virtual network
|
||||
// resource group name.
|
||||
func TestResourceResolverSetVirtualNetworkResourceGroupName(t *testing.T) {
|
||||
var c Config
|
||||
c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
c.VirtualNetworkName = "--virtual-network-name--"
|
||||
|
||||
sut := newTestResourceResolver()
|
||||
sut.Resolve(&c)
|
||||
|
||||
if c.VirtualNetworkResourceGroupName != "findVirtualNetworkResourceGroup is mocked" {
|
||||
t.Fatalf("Expected VirtualNetworkResourceGroupName to be 'findVirtualNetworkResourceGroup is mocked'")
|
||||
}
|
||||
if c.VirtualNetworkSubnetName != "findVirtualNetworkSubnet is mocked" {
|
||||
t.Fatalf("Expected findVirtualNetworkSubnet to be 'findVirtualNetworkSubnet is mocked'")
|
||||
}
|
||||
}
|
||||
|
||||
func newTestResourceResolver() resourceResolver {
|
||||
return resourceResolver{
|
||||
client: nil,
|
||||
findVirtualNetworkResourceGroup: func(*AzureClient, string) (string, error) {
|
||||
return "findVirtualNetworkResourceGroup is mocked", nil
|
||||
},
|
||||
findVirtualNetworkSubnet: func(*AzureClient, string, string) (string, error) {
|
||||
return "findVirtualNetworkSubnet is mocked", nil
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func processStepResult(
|
||||
err error, sayError func(error), state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
sayError(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepCaptureImage struct {
|
||||
client *AzureClient
|
||||
generalizeVM func(resourceGroupName, computeName string) error
|
||||
captureVhd func(ctx context.Context, resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters) error
|
||||
captureManagedImage func(ctx context.Context, resourceGroupName string, computeName string, parameters *compute.Image) error
|
||||
get func(client *AzureClient) *CaptureTemplate
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepCaptureImage(client *AzureClient, ui packersdk.Ui) *StepCaptureImage {
|
||||
var step = &StepCaptureImage{
|
||||
client: client,
|
||||
get: func(client *AzureClient) *CaptureTemplate {
|
||||
return client.Template
|
||||
},
|
||||
say: func(message string) {
|
||||
ui.Say(message)
|
||||
},
|
||||
error: func(e error) {
|
||||
ui.Error(e.Error())
|
||||
},
|
||||
}
|
||||
|
||||
step.generalizeVM = step.generalize
|
||||
step.captureVhd = step.captureImage
|
||||
step.captureManagedImage = step.captureImageFromVM
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCaptureImage) generalize(resourceGroupName string, computeName string) error {
|
||||
_, err := s.client.Generalize(context.TODO(), resourceGroupName, computeName)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepCaptureImage) captureImageFromVM(ctx context.Context, resourceGroupName string, imageName string, image *compute.Image) error {
|
||||
f, err := s.client.ImagesClient.CreateOrUpdate(ctx, resourceGroupName, imageName, *image)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return f.WaitForCompletionRef(ctx, s.client.ImagesClient.Client)
|
||||
}
|
||||
|
||||
func (s *StepCaptureImage) captureImage(ctx context.Context, resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters) error {
|
||||
f, err := s.client.VirtualMachinesClient.Capture(ctx, resourceGroupName, computeName, *parameters)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return f.WaitForCompletionRef(ctx, s.client.VirtualMachinesClient.Client)
|
||||
}
|
||||
|
||||
func (s *StepCaptureImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Capturing image ...")
|
||||
|
||||
var computeName = state.Get(constants.ArmComputeName).(string)
|
||||
var location = state.Get(constants.ArmLocation).(string)
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var vmCaptureParameters = state.Get(constants.ArmVirtualMachineCaptureParameters).(*compute.VirtualMachineCaptureParameters)
|
||||
var imageParameters = state.Get(constants.ArmImageParameters).(*compute.Image)
|
||||
|
||||
var isManagedImage = state.Get(constants.ArmIsManagedImage).(bool)
|
||||
var targetManagedImageResourceGroupName = state.Get(constants.ArmManagedImageResourceGroupName).(string)
|
||||
var targetManagedImageName = state.Get(constants.ArmManagedImageName).(string)
|
||||
var targetManagedImageLocation = state.Get(constants.ArmLocation).(string)
|
||||
|
||||
s.say(fmt.Sprintf(" -> Compute ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> Compute Name : '%s'", computeName))
|
||||
s.say(fmt.Sprintf(" -> Compute Location : '%s'", location))
|
||||
|
||||
err := s.generalizeVM(resourceGroupName, computeName)
|
||||
|
||||
if err == nil {
|
||||
if isManagedImage {
|
||||
s.say(fmt.Sprintf(" -> Image ResourceGroupName : '%s'", targetManagedImageResourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> Image Name : '%s'", targetManagedImageName))
|
||||
s.say(fmt.Sprintf(" -> Image Location : '%s'", targetManagedImageLocation))
|
||||
err = s.captureManagedImage(ctx, targetManagedImageResourceGroupName, targetManagedImageName, imageParameters)
|
||||
} else {
|
||||
err = s.captureVhd(ctx, resourceGroupName, computeName, vmCaptureParameters)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// HACK(chrboum): I do not like this. The capture method should be returning this value
|
||||
// instead having to pass in another lambda.
|
||||
//
|
||||
// Having to resort to capturing the template via an inspector is hack, and once I can
|
||||
// resolve that I can cleanup this code too. See the comments in azure_client.go for more
|
||||
// details.
|
||||
// [paulmey]: autorest.Future now has access to the last http.Response, but I'm not sure if
|
||||
// the body is still accessible.
|
||||
template := s.get(s.client)
|
||||
state.Put(constants.ArmCaptureTemplate, template)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepCaptureImage) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepCaptureImageShouldFailIfCaptureFails(t *testing.T) {
|
||||
var testSubject = &StepCaptureImage{
|
||||
captureVhd: func(context.Context, string, string, *compute.VirtualMachineCaptureParameters) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
generalizeVM: func(string, string) error {
|
||||
return nil
|
||||
},
|
||||
get: func(client *AzureClient) *CaptureTemplate {
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCaptureImage()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCaptureImageShouldPassIfCapturePasses(t *testing.T) {
|
||||
var testSubject = &StepCaptureImage{
|
||||
captureVhd: func(context.Context, string, string, *compute.VirtualMachineCaptureParameters) error { return nil },
|
||||
generalizeVM: func(string, string) error {
|
||||
return nil
|
||||
},
|
||||
get: func(client *AzureClient) *CaptureTemplate {
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCaptureImage()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCaptureImageShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
cancelCh := make(chan<- struct{})
|
||||
defer close(cancelCh)
|
||||
|
||||
var actualResourceGroupName string
|
||||
var actualComputeName string
|
||||
var actualVirtualMachineCaptureParameters *compute.VirtualMachineCaptureParameters
|
||||
actualCaptureTemplate := &CaptureTemplate{
|
||||
Schema: "!! Unit Test !!",
|
||||
}
|
||||
|
||||
var testSubject = &StepCaptureImage{
|
||||
captureVhd: func(ctx context.Context, resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualComputeName = computeName
|
||||
actualVirtualMachineCaptureParameters = parameters
|
||||
|
||||
return nil
|
||||
},
|
||||
generalizeVM: func(string, string) error {
|
||||
return nil
|
||||
},
|
||||
get: func(client *AzureClient) *CaptureTemplate {
|
||||
return actualCaptureTemplate
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCaptureImage()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
var expectedComputeName = stateBag.Get(constants.ArmComputeName).(string)
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
var expectedVirtualMachineCaptureParameters = stateBag.Get(constants.ArmVirtualMachineCaptureParameters).(*compute.VirtualMachineCaptureParameters)
|
||||
var expectedCaptureTemplate = stateBag.Get(constants.ArmCaptureTemplate).(*CaptureTemplate)
|
||||
|
||||
if actualComputeName != expectedComputeName {
|
||||
t.Fatal("Expected StepCaptureImage to source 'constants.ArmComputeName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected StepCaptureImage to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualVirtualMachineCaptureParameters != expectedVirtualMachineCaptureParameters {
|
||||
t.Fatal("Expected StepCaptureImage to source 'constants.ArmVirtualMachineCaptureParameters' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualCaptureTemplate != expectedCaptureTemplate {
|
||||
t.Fatal("Expected StepCaptureImage to source 'constants.ArmCaptureTemplate' from the state bag, but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepCaptureImage() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmLocation, "localhost")
|
||||
stateBag.Put(constants.ArmComputeName, "Unit Test: ComputeName")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmVirtualMachineCaptureParameters, &compute.VirtualMachineCaptureParameters{})
|
||||
|
||||
stateBag.Put(constants.ArmIsManagedImage, false)
|
||||
stateBag.Put(constants.ArmManagedImageResourceGroupName, "")
|
||||
stateBag.Put(constants.ArmManagedImageName, "")
|
||||
stateBag.Put(constants.ArmImageParameters, &compute.Image{})
|
||||
|
||||
return stateBag
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepCertificateInKeyVault struct {
|
||||
config *Config
|
||||
client common.AZVaultClientIface
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepCertificateInKeyVault(cli common.AZVaultClientIface, ui packersdk.Ui, config *Config) *StepCertificateInKeyVault {
|
||||
var step = &StepCertificateInKeyVault{
|
||||
client: cli,
|
||||
config: config,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCertificateInKeyVault) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Setting the certificate in the KeyVault...")
|
||||
var keyVaultName = state.Get(constants.ArmKeyVaultName).(string)
|
||||
|
||||
err := s.client.SetSecret(keyVaultName, DefaultSecretName, s.config.winrmCertificate)
|
||||
if err != nil {
|
||||
s.error(fmt.Errorf("Error setting winrm cert in custom keyvault: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepCertificateInKeyVault) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
azcommon "github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestNewStepCertificateInKeyVault(t *testing.T) {
|
||||
cli := azcommon.MockAZVaultClient{}
|
||||
ui := &packersdk.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put(constants.ArmKeyVaultName, "testKeyVaultName")
|
||||
|
||||
config := &Config{
|
||||
winrmCertificate: "testCertificateString",
|
||||
}
|
||||
|
||||
certKVStep := NewStepCertificateInKeyVault(&cli, ui, config)
|
||||
stepAction := certKVStep.Run(context.TODO(), state)
|
||||
|
||||
if stepAction == multistep.ActionHalt {
|
||||
t.Fatalf("step should have succeeded.")
|
||||
}
|
||||
if !cli.SetSecretCalled {
|
||||
t.Fatalf("Step should have called SetSecret on Azure client.")
|
||||
}
|
||||
if cli.SetSecretCert != "testCertificateString" {
|
||||
t.Fatalf("Step should have read cert from winRMCertificate field on config.")
|
||||
}
|
||||
if cli.SetSecretVaultName != "testKeyVaultName" {
|
||||
t.Fatalf("step should have read keyvault name from state.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStepCertificateInKeyVault_error(t *testing.T) {
|
||||
// Tell mock to return an error
|
||||
cli := azcommon.MockAZVaultClient{}
|
||||
cli.IsError = true
|
||||
|
||||
ui := &packersdk.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put(constants.ArmKeyVaultName, "testKeyVaultName")
|
||||
|
||||
config := &Config{
|
||||
winrmCertificate: "testCertificateString",
|
||||
}
|
||||
|
||||
certKVStep := NewStepCertificateInKeyVault(&cli, ui, config)
|
||||
stepAction := certKVStep.Run(context.TODO(), state)
|
||||
|
||||
if stepAction != multistep.ActionHalt {
|
||||
t.Fatalf("step should have failed.")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepCreateResourceGroup struct {
|
||||
client *AzureClient
|
||||
create func(ctx context.Context, resourceGroupName string, location string, tags map[string]*string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
exists func(ctx context.Context, resourceGroupName string) (bool, error)
|
||||
}
|
||||
|
||||
func NewStepCreateResourceGroup(client *AzureClient, ui packersdk.Ui) *StepCreateResourceGroup {
|
||||
var step = &StepCreateResourceGroup{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
step.create = step.createResourceGroup
|
||||
step.exists = step.doesResourceGroupExist
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCreateResourceGroup) createResourceGroup(ctx context.Context, resourceGroupName string, location string, tags map[string]*string) error {
|
||||
_, err := s.client.GroupsClient.CreateOrUpdate(ctx, resourceGroupName, resources.Group{
|
||||
Location: &location,
|
||||
Tags: tags,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepCreateResourceGroup) doesResourceGroupExist(ctx context.Context, resourceGroupName string) (bool, error) {
|
||||
exists, err := s.client.GroupsClient.CheckExistence(ctx, resourceGroupName)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
|
||||
return exists.Response.StatusCode != 404, err
|
||||
}
|
||||
|
||||
func (s *StepCreateResourceGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
var doubleResource, ok = state.GetOk(constants.ArmDoubleResourceGroupNameSet)
|
||||
if ok && doubleResource.(bool) {
|
||||
err := errors.New("You have filled in both temp_resource_group_name and build_resource_group_name. Please choose one.")
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var location = state.Get(constants.ArmLocation).(string)
|
||||
tags, ok := state.Get(constants.ArmTags).(map[string]*string)
|
||||
if !ok {
|
||||
err := fmt.Errorf("failed to extract tags from state bag")
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
exists, err := s.exists(ctx, resourceGroupName)
|
||||
if err != nil {
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
configThinksExists := state.Get(constants.ArmIsExistingResourceGroup).(bool)
|
||||
if exists != configThinksExists {
|
||||
if configThinksExists {
|
||||
err = errors.New("The resource group you want to use does not exist yet. Please use temp_resource_group_name to create a temporary resource group.")
|
||||
} else {
|
||||
err = errors.New("A resource group with that name already exists. Please use build_resource_group_name to use an existing resource group.")
|
||||
}
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
|
||||
// If the resource group exists, we may not have permissions to update it so we don't.
|
||||
if !exists {
|
||||
s.say("Creating resource group ...")
|
||||
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> Location : '%s'", location))
|
||||
s.say(fmt.Sprintf(" -> Tags :"))
|
||||
for k, v := range tags {
|
||||
s.say(fmt.Sprintf(" ->> %s : %s", k, *v))
|
||||
}
|
||||
err = s.create(ctx, resourceGroupName, location, tags)
|
||||
if err == nil {
|
||||
state.Put(constants.ArmIsResourceGroupCreated, true)
|
||||
}
|
||||
} else {
|
||||
s.say("Using existing resource group ...")
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> Location : '%s'", location))
|
||||
state.Put(constants.ArmIsResourceGroupCreated, true)
|
||||
}
|
||||
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
|
||||
func (s *StepCreateResourceGroup) Cleanup(state multistep.StateBag) {
|
||||
isCreated, ok := state.GetOk(constants.ArmIsResourceGroupCreated)
|
||||
if !ok || !isCreated.(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
if state.Get(constants.ArmIsExistingResourceGroup).(bool) {
|
||||
ui.Say("\nThe resource group was not created by Packer, not deleting ...")
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
||||
if exists, err := s.exists(ctx, resourceGroupName); !exists || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ui.Say("\nCleanup requested, deleting resource group ...")
|
||||
f, err := s.client.GroupsClient.Delete(ctx, resourceGroupName)
|
||||
if err == nil {
|
||||
if state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
|
||||
s.say(fmt.Sprintf("\n Not waiting for Resource Group delete as requested by user. Resource Group Name is %s", resourceGroupName))
|
||||
} else {
|
||||
err = f.WaitForCompletionRef(ctx, s.client.GroupsClient.Client)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting resource group. Please delete it manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", resourceGroupName, err))
|
||||
return
|
||||
}
|
||||
if !state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
|
||||
ui.Say("Resource group has been deleted.")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepCreateResourceGroupShouldFailIfBothGroupNames(t *testing.T) {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmDoubleResourceGroupNameSet, true)
|
||||
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]*string{
|
||||
"tag01": &value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
}
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateResourceGroup()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateResourceGroupShouldFailIfExistsFails(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, errors.New("FAIL") },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateResourceGroup()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateResourceGroup()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualLocation string
|
||||
var actualTags map[string]*string
|
||||
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(ctx context.Context, resourceGroupName string, location string, tags map[string]*string) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualLocation = location
|
||||
actualTags = tags
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateResourceGroup()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
var expectedLocation = stateBag.Get(constants.ArmLocation).(string)
|
||||
var expectedTags = stateBag.Get(constants.ArmTags).(map[string]*string)
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualLocation != expectedLocation {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if len(expectedTags) != len(actualTags) && expectedTags["tag01"] != actualTags["tag01"] {
|
||||
t.Fatal("Expected the step to source 'constants.ArmTags' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
_, ok := stateBag.GetOk(constants.ArmIsResourceGroupCreated)
|
||||
if !ok {
|
||||
t.Fatal("Expected the step to add item to stateBag['constants.ArmIsResourceGroupCreated'], but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateResourceGroupMarkShouldFailIfTryingExistingButDoesntExist(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
}
|
||||
|
||||
stateBag := createTestExistingStateBagStepCreateResourceGroup()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateResourceGroupMarkShouldFailIfTryingTempButExist(t *testing.T) {
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return true, nil },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateResourceGroup()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepCreateResourceGroup() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
|
||||
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]*string{
|
||||
"tag01": &value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
return stateBag
|
||||
}
|
||||
|
||||
func createTestExistingStateBagStepCreateResourceGroup() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
|
||||
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]*string{
|
||||
"tag01": &value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
return stateBag
|
||||
}
|
||||
|
||||
func TestStepCreateResourceGroupShouldFailIfTagsFailCast(t *testing.T) {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
|
||||
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]string{
|
||||
"tag01": value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
var testSubject = &StepCreateResourceGroup{
|
||||
create: func(context.Context, string, string, map[string]*string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
exists: func(context.Context, string) (bool, error) { return false, nil },
|
||||
}
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepDeleteAdditionalDisk struct {
|
||||
client *AzureClient
|
||||
delete func(string, string) error
|
||||
deleteManaged func(context.Context, string, string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepDeleteAdditionalDisks(client *AzureClient, ui packersdk.Ui) *StepDeleteAdditionalDisk {
|
||||
var step = &StepDeleteAdditionalDisk{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
step.delete = step.deleteBlob
|
||||
step.deleteManaged = step.deleteManagedDisk
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepDeleteAdditionalDisk) deleteBlob(storageContainerName string, blobName string) error {
|
||||
blob := s.client.BlobStorageClient.GetContainerReference(storageContainerName).GetBlobReference(blobName)
|
||||
_, err := blob.BreakLease(nil)
|
||||
if err != nil && !strings.Contains(err.Error(), "LeaseNotPresentWithLeaseOperation") {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = blob.Delete(nil)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeleteAdditionalDisk) deleteManagedDisk(ctx context.Context, resourceGroupName string, diskName string) error {
|
||||
xs := strings.Split(diskName, "/")
|
||||
diskName = xs[len(xs)-1]
|
||||
f, err := s.client.DisksClient.Delete(ctx, resourceGroupName, diskName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, s.client.DisksClient.Client)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeleteAdditionalDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Deleting the temporary Additional disk ...")
|
||||
|
||||
var dataDisks []string
|
||||
|
||||
if disks := state.Get(constants.ArmAdditionalDiskVhds); disks != nil {
|
||||
dataDisks = disks.([]string)
|
||||
}
|
||||
var isManagedDisk = state.Get(constants.ArmIsManagedImage).(bool)
|
||||
var isExistingResourceGroup = state.Get(constants.ArmIsExistingResourceGroup).(bool)
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
if dataDisks == nil {
|
||||
s.say(fmt.Sprintf(" -> No Additional Disks specified"))
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if isManagedDisk && !isExistingResourceGroup {
|
||||
s.say(fmt.Sprintf(" -> Additional Disk : skipping, managed disk was used..."))
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
for i, additionaldisk := range dataDisks {
|
||||
s.say(fmt.Sprintf(" -> Additional Disk %d: '%s'", i+1, additionaldisk))
|
||||
var err error
|
||||
if isManagedDisk {
|
||||
err = s.deleteManaged(ctx, resourceGroupName, additionaldisk)
|
||||
if err != nil {
|
||||
s.say("Failed to delete the managed Additional Disk!")
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
u, err := url.Parse(additionaldisk)
|
||||
if err != nil {
|
||||
s.say("Failed to parse the Additional Disk's VHD URI!")
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
|
||||
xs := strings.Split(u.Path, "/")
|
||||
if len(xs) < 3 {
|
||||
err = errors.New("Failed to parse Additional Disk's VHD URI!")
|
||||
} else {
|
||||
var storageAccountName = xs[1]
|
||||
var blobName = strings.Join(xs[2:], "/")
|
||||
|
||||
err = s.delete(storageAccountName, blobName)
|
||||
}
|
||||
if err != nil {
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepDeleteAdditionalDisk) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepDeleteAdditionalDiskShouldFailIfGetFails(t *testing.T) {
|
||||
var testSubject = &StepDeleteAdditionalDisk{
|
||||
delete: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := DeleteTestStateBagStepDeleteAdditionalDisk([]string{"http://storage.blob.core.windows.net/images/pkrvm_os.vhd"})
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeleteAdditionalDiskShouldPassIfGetPasses(t *testing.T) {
|
||||
var testSubject = &StepDeleteAdditionalDisk{
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := DeleteTestStateBagStepDeleteAdditionalDisk([]string{"http://storage.blob.core.windows.net/images/pkrvm_os.vhd"})
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeleteAdditionalDiskShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualStorageContainerName string
|
||||
var actualBlobName string
|
||||
|
||||
var testSubject = &StepDeleteAdditionalDisk{
|
||||
delete: func(storageContainerName string, blobName string) error {
|
||||
actualStorageContainerName = storageContainerName
|
||||
actualBlobName = blobName
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := DeleteTestStateBagStepDeleteAdditionalDisk([]string{"http://storage.blob.core.windows.net/images/pkrvm_os.vhd"})
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if actualStorageContainerName != "images" {
|
||||
t.Fatalf("Expected the storage container name to be 'images', but found '%s'.", actualStorageContainerName)
|
||||
}
|
||||
|
||||
if actualBlobName != "pkrvm_os.vhd" {
|
||||
t.Fatalf("Expected the blob name to be 'pkrvm_os.vhd', but found '%s'.", actualBlobName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeleteAdditionalDiskShouldHandleComplexStorageContainerNames(t *testing.T) {
|
||||
var actualStorageContainerName string
|
||||
var actualBlobName string
|
||||
|
||||
var testSubject = &StepDeleteAdditionalDisk{
|
||||
delete: func(storageContainerName string, blobName string) error {
|
||||
actualStorageContainerName = storageContainerName
|
||||
actualBlobName = blobName
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := DeleteTestStateBagStepDeleteAdditionalDisk([]string{"http://storage.blob.core.windows.net/abc/def/pkrvm_os.vhd"})
|
||||
testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if actualStorageContainerName != "abc" {
|
||||
t.Fatalf("Expected the storage container name to be 'abc/def', but found '%s'.", actualStorageContainerName)
|
||||
}
|
||||
|
||||
if actualBlobName != "def/pkrvm_os.vhd" {
|
||||
t.Fatalf("Expected the blob name to be 'pkrvm_os.vhd', but found '%s'.", actualBlobName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeleteAdditionalDiskShouldFailIfVHDNameCannotBeURLParsed(t *testing.T) {
|
||||
var testSubject = &StepDeleteAdditionalDisk{
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
}
|
||||
|
||||
// Invalid URL per https://golang.org/src/net/url/url_test.go
|
||||
stateBag := DeleteTestStateBagStepDeleteAdditionalDisk([]string{"http://[fe80::1%en0]/"})
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%v'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to not stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
func TestStepDeleteAdditionalDiskShouldFailIfVHDNameIsTooShort(t *testing.T) {
|
||||
var testSubject = &StepDeleteAdditionalDisk{
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
}
|
||||
|
||||
stateBag := DeleteTestStateBagStepDeleteAdditionalDisk([]string{"storage.blob.core.windows.net/abc"})
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to not stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeleteAdditionalDiskShouldPassIfManagedDiskInTempResourceGroup(t *testing.T) {
|
||||
var testSubject = &StepDeleteAdditionalDisk{
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
stateBag.Put(constants.ArmAdditionalDiskVhds, []string{"subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk"})
|
||||
stateBag.Put(constants.ArmIsManagedImage, true)
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
|
||||
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeleteAdditionalDiskShouldFailIfManagedDiskInExistingResourceGroupFailsToDelete(t *testing.T) {
|
||||
var testSubject = &StepDeleteAdditionalDisk{
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return errors.New("UNIT TEST FAIL!") },
|
||||
}
|
||||
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
stateBag.Put(constants.ArmAdditionalDiskVhds, []string{"subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk"})
|
||||
stateBag.Put(constants.ArmIsManagedImage, true)
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
|
||||
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to not stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeleteAdditionalDiskShouldFailIfManagedDiskInExistingResourceGroupIsDeleted(t *testing.T) {
|
||||
var testSubject = &StepDeleteAdditionalDisk{
|
||||
delete: func(string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||
}
|
||||
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
stateBag.Put(constants.ArmAdditionalDiskVhds, []string{"subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk"})
|
||||
stateBag.Put(constants.ArmIsManagedImage, true)
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
|
||||
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteTestStateBagStepDeleteAdditionalDisk(osDiskVhds []string) multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
stateBag.Put(constants.ArmAdditionalDiskVhds, osDiskVhds)
|
||||
stateBag.Put(constants.ArmIsManagedImage, false)
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
|
||||
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
|
||||
|
||||
return stateBag
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepDeployTemplate struct {
|
||||
client *AzureClient
|
||||
deploy func(ctx context.Context, resourceGroupName string, deploymentName string) error
|
||||
delete func(ctx context.Context, deploymentName, resourceGroupName string) error
|
||||
disk func(ctx context.Context, resourceGroupName string, computeName string) (string, string, error)
|
||||
deleteDisk func(ctx context.Context, imageType string, imageName string, resourceGroupName string) error
|
||||
deleteDeployment func(ctx context.Context, state multistep.StateBag) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
config *Config
|
||||
factory templateFactoryFunc
|
||||
name string
|
||||
}
|
||||
|
||||
func NewStepDeployTemplate(client *AzureClient, ui packersdk.Ui, config *Config, deploymentName string, factory templateFactoryFunc) *StepDeployTemplate {
|
||||
var step = &StepDeployTemplate{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
config: config,
|
||||
factory: factory,
|
||||
name: deploymentName,
|
||||
}
|
||||
|
||||
step.deploy = step.deployTemplate
|
||||
step.delete = step.deleteDeploymentResources
|
||||
step.disk = step.getImageDetails
|
||||
step.deleteDisk = step.deleteImage
|
||||
step.deleteDeployment = step.deleteDeploymentObject
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Deploying deployment template ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", s.name))
|
||||
|
||||
return processStepResult(
|
||||
s.deploy(ctx, resourceGroupName, s.name),
|
||||
s.error, state)
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
||||
defer func() {
|
||||
err := s.deleteDeployment(context.Background(), state)
|
||||
if err != nil {
|
||||
s.say(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
ui.Say("\nDeleting individual resources ...")
|
||||
|
||||
deploymentName := s.name
|
||||
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
||||
// Get image disk details before deleting the image; otherwise we won't be able to
|
||||
// delete the disk as the image request will return a 404
|
||||
computeName := state.Get(constants.ArmComputeName).(string)
|
||||
imageType, imageName, err := s.disk(context.TODO(), resourceGroupName, computeName)
|
||||
|
||||
if err != nil && !strings.Contains(err.Error(), "ResourceNotFound") {
|
||||
ui.Error(fmt.Sprintf("Could not retrieve OS Image details: %s", err))
|
||||
}
|
||||
err = s.delete(context.TODO(), deploymentName, resourceGroupName)
|
||||
if err != nil {
|
||||
s.reportIfError(err, resourceGroupName)
|
||||
}
|
||||
|
||||
// The disk was not found on the VM, this is an error.
|
||||
if imageType == "" && imageName == "" {
|
||||
ui.Error(fmt.Sprintf("Failed to find temporary OS disk on VM. Please delete manually.\n\n"+
|
||||
"VM Name: %s\n"+
|
||||
"Error: %s", computeName, err))
|
||||
return
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf(" Deleting -> %s : '%s'", imageType, imageName))
|
||||
err = s.deleteDisk(context.TODO(), imageType, imageName, resourceGroupName)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", imageName, err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
||||
deployment, err := s.factory(s.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := s.client.DeploymentsClient.CreateOrUpdate(ctx, resourceGroupName, deploymentName, *deployment)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
||||
if err == nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deleteDeploymentObject(ctx context.Context, state multistep.StateBag) error {
|
||||
deploymentName := s.name
|
||||
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
|
||||
ui.Say(fmt.Sprintf("Removing the created Deployment object: '%s'", deploymentName))
|
||||
f, err := s.client.DeploymentsClient.Delete(ctx, resourceGroupName, deploymentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupName string, computeName string) (string, string, error) {
|
||||
//We can't depend on constants.ArmOSDiskVhd being set
|
||||
var imageName, imageType string
|
||||
vm, err := s.client.VirtualMachinesClient.Get(ctx, resourceGroupName, computeName, "")
|
||||
if err != nil {
|
||||
return imageName, imageType, err
|
||||
}
|
||||
|
||||
if vm.StorageProfile.OsDisk.Vhd != nil {
|
||||
imageType = "image"
|
||||
imageName = *vm.StorageProfile.OsDisk.Vhd.URI
|
||||
return imageType, imageName, nil
|
||||
}
|
||||
|
||||
imageType = "Microsoft.Compute/disks"
|
||||
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
|
||||
|
||||
return imageType, imageName, nil
|
||||
}
|
||||
|
||||
//TODO(paulmey): move to helpers file
|
||||
func deleteResource(ctx context.Context, client *AzureClient, resourceType string, resourceName string, resourceGroupName string) error {
|
||||
switch resourceType {
|
||||
case "Microsoft.Compute/virtualMachines":
|
||||
f, err := client.VirtualMachinesClient.Delete(ctx, resourceGroupName, resourceName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, client.VirtualMachinesClient.Client)
|
||||
}
|
||||
return err
|
||||
case "Microsoft.KeyVault/vaults":
|
||||
_, err := client.VaultClientDelete.Delete(ctx, resourceGroupName, resourceName)
|
||||
return err
|
||||
case "Microsoft.Network/networkInterfaces":
|
||||
f, err := client.InterfacesClient.Delete(ctx, resourceGroupName, resourceName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, client.InterfacesClient.Client)
|
||||
}
|
||||
return err
|
||||
case "Microsoft.Network/virtualNetworks":
|
||||
f, err := client.VirtualNetworksClient.Delete(ctx, resourceGroupName, resourceName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, client.VirtualNetworksClient.Client)
|
||||
}
|
||||
return err
|
||||
case "Microsoft.Network/networkSecurityGroups":
|
||||
f, err := client.SecurityGroupsClient.Delete(ctx, resourceGroupName, resourceName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, client.SecurityGroupsClient.Client)
|
||||
}
|
||||
return err
|
||||
case "Microsoft.Network/publicIPAddresses":
|
||||
f, err := client.PublicIPAddressesClient.Delete(ctx, resourceGroupName, resourceName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, client.PublicIPAddressesClient.Client)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deleteImage(ctx context.Context, imageType string, imageName string, resourceGroupName string) error {
|
||||
// Managed disk
|
||||
if imageType == "Microsoft.Compute/disks" {
|
||||
xs := strings.Split(imageName, "/")
|
||||
diskName := xs[len(xs)-1]
|
||||
f, err := s.client.DisksClient.Delete(ctx, resourceGroupName, diskName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, s.client.DisksClient.Client)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// VHD image
|
||||
u, err := url.Parse(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
xs := strings.Split(u.Path, "/")
|
||||
if len(xs) < 3 {
|
||||
return errors.New("Unable to parse path of image " + imageName)
|
||||
}
|
||||
var storageAccountName = xs[1]
|
||||
var blobName = strings.Join(xs[2:], "/")
|
||||
|
||||
blob := s.client.BlobStorageClient.GetContainerReference(storageAccountName).GetBlobReference(blobName)
|
||||
_, err = blob.BreakLease(nil)
|
||||
if err != nil && !strings.Contains(err.Error(), "LeaseNotPresentWithLeaseOperation") {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return blob.Delete(nil)
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deleteDeploymentResources(ctx context.Context, deploymentName, resourceGroupName string) error {
|
||||
var maxResources int32 = 50
|
||||
deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(ctx, resourceGroupName, deploymentName, &maxResources)
|
||||
if err != nil {
|
||||
s.reportIfError(err, resourceGroupName)
|
||||
return err
|
||||
}
|
||||
|
||||
resources := map[string]string{}
|
||||
|
||||
for deploymentOperations.NotDone() {
|
||||
deploymentOperation := deploymentOperations.Value()
|
||||
// Sometimes an empty operation is added to the list by Azure
|
||||
if deploymentOperation.Properties.TargetResource == nil {
|
||||
_ = deploymentOperations.Next()
|
||||
continue
|
||||
}
|
||||
|
||||
resourceName := *deploymentOperation.Properties.TargetResource.ResourceName
|
||||
resourceType := *deploymentOperation.Properties.TargetResource.ResourceType
|
||||
|
||||
s.say(fmt.Sprintf("Adding to deletion queue -> %s : '%s'", resourceType, resourceName))
|
||||
resources[resourceType] = resourceName
|
||||
|
||||
if err = deploymentOperations.Next(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(resources))
|
||||
|
||||
for resourceType, resourceName := range resources {
|
||||
go func(resourceType, resourceName string) {
|
||||
defer wg.Done()
|
||||
retryConfig := retry.Config{
|
||||
Tries: 10,
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 600 * time.Second, Multiplier: 2}).Linear,
|
||||
}
|
||||
|
||||
err = retryConfig.Run(ctx, func(ctx context.Context) error {
|
||||
s.say(fmt.Sprintf("Attempting deletion -> %s : '%s'", resourceType, resourceName))
|
||||
err := deleteResource(ctx, s.client,
|
||||
resourceType,
|
||||
resourceName,
|
||||
resourceGroupName)
|
||||
if err != nil {
|
||||
s.say(fmt.Sprintf("Error deleting resource. Will retry.\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s\n", resourceName, err.Error()))
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
s.reportIfError(err, resourceName)
|
||||
}
|
||||
}(resourceType, resourceName)
|
||||
}
|
||||
|
||||
s.say("Waiting for deletion of all resources...")
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) reportIfError(err error, resourceName string) {
|
||||
if err != nil {
|
||||
s.say(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
||||
"Name: %s\n"+
|
||||
"Error: %s", resourceName, err.Error()))
|
||||
s.error(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepDeployTemplateShouldFailIfDeployFails(t *testing.T) {
|
||||
var testSubject = &StepDeployTemplate{
|
||||
deploy: func(context.Context, string, string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepDeployTemplate()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeployTemplateShouldPassIfDeployPasses(t *testing.T) {
|
||||
var testSubject = &StepDeployTemplate{
|
||||
deploy: func(context.Context, string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepDeployTemplate()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeployTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualDeploymentName string
|
||||
|
||||
var testSubject = &StepDeployTemplate{
|
||||
deploy: func(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualDeploymentName = deploymentName
|
||||
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
name: "--deployment-name--",
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepValidateTemplate()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
if actualDeploymentName != "--deployment-name--" {
|
||||
t.Fatal("Expected StepValidateTemplate to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeployTemplateDeleteImageShouldFailWhenImageUrlCannotBeParsed(t *testing.T) {
|
||||
var testSubject = &StepDeployTemplate{
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
name: "--deployment-name--",
|
||||
}
|
||||
// Invalid URL per https://golang.org/src/net/url/url_test.go
|
||||
err := testSubject.deleteImage(context.TODO(), "image", "http://[fe80::1%en0]/", "Unit Test: ResourceGroupName")
|
||||
if err == nil {
|
||||
t.Fatal("Expected a failure because of the failed image name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeployTemplateDeleteImageShouldFailWithInvalidImage(t *testing.T) {
|
||||
var testSubject = &StepDeployTemplate{
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
name: "--deployment-name--",
|
||||
}
|
||||
err := testSubject.deleteImage(context.TODO(), "image", "storage.blob.core.windows.net/abc", "Unit Test: ResourceGroupName")
|
||||
if err == nil {
|
||||
t.Fatal("Expected a failure because of the failed image name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeployTemplateCleanupShouldDeleteManagedOSImageInExistingResourceGroup(t *testing.T) {
|
||||
var deleteDiskCounter = 0
|
||||
var testSubject = createTestStepDeployTemplateDeleteOSImage(&deleteDiskCounter)
|
||||
|
||||
stateBag := createTestStateBagStepDeployTemplate()
|
||||
stateBag.Put(constants.ArmIsManagedImage, true)
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
|
||||
stateBag.Put(constants.ArmIsResourceGroupCreated, true)
|
||||
stateBag.Put("ui", packersdk.TestUi(t))
|
||||
|
||||
testSubject.Cleanup(stateBag)
|
||||
if deleteDiskCounter != 1 {
|
||||
t.Fatalf("Expected DeployTemplate Cleanup to invoke deleteDisk 1 time, but invoked %d times", deleteDiskCounter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeployTemplateCleanupShouldDeleteManagedOSImageInTemporaryResourceGroup(t *testing.T) {
|
||||
var deleteDiskCounter = 0
|
||||
var testSubject = createTestStepDeployTemplateDeleteOSImage(&deleteDiskCounter)
|
||||
|
||||
stateBag := createTestStateBagStepDeployTemplate()
|
||||
stateBag.Put(constants.ArmIsManagedImage, true)
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
|
||||
stateBag.Put(constants.ArmIsResourceGroupCreated, true)
|
||||
stateBag.Put("ui", packersdk.TestUi(t))
|
||||
|
||||
testSubject.Cleanup(stateBag)
|
||||
if deleteDiskCounter != 1 {
|
||||
t.Fatalf("Expected DeployTemplate Cleanup to invoke deleteDisk 1 times, but invoked %d times", deleteDiskCounter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeployTemplateCleanupShouldDeleteVHDOSImageInExistingResourceGroup(t *testing.T) {
|
||||
var deleteDiskCounter = 0
|
||||
var testSubject = createTestStepDeployTemplateDeleteOSImage(&deleteDiskCounter)
|
||||
|
||||
stateBag := createTestStateBagStepDeployTemplate()
|
||||
stateBag.Put(constants.ArmIsManagedImage, false)
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
|
||||
stateBag.Put(constants.ArmIsResourceGroupCreated, true)
|
||||
stateBag.Put("ui", packersdk.TestUi(t))
|
||||
|
||||
testSubject.Cleanup(stateBag)
|
||||
if deleteDiskCounter != 1 {
|
||||
t.Fatalf("Expected DeployTemplate Cleanup to invoke deleteDisk 1 time, but invoked %d times", deleteDiskCounter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeployTemplateCleanupShouldVHDOSImageInTemporaryResourceGroup(t *testing.T) {
|
||||
var deleteDiskCounter = 0
|
||||
var testSubject = createTestStepDeployTemplateDeleteOSImage(&deleteDiskCounter)
|
||||
|
||||
stateBag := createTestStateBagStepDeployTemplate()
|
||||
stateBag.Put(constants.ArmIsManagedImage, false)
|
||||
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
|
||||
stateBag.Put(constants.ArmIsResourceGroupCreated, true)
|
||||
stateBag.Put("ui", packersdk.TestUi(t))
|
||||
|
||||
testSubject.Cleanup(stateBag)
|
||||
if deleteDiskCounter != 1 {
|
||||
t.Fatalf("Expected DeployTemplate Cleanup to invoke deleteDisk 1 times, but invoked %d times", deleteDiskCounter)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepDeployTemplate() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmDeploymentName, "Unit Test: DeploymentName")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmComputeName, "Unit Test: ComputeName")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
|
||||
func createTestStepDeployTemplateDeleteOSImage(deleteDiskCounter *int) *StepDeployTemplate {
|
||||
return &StepDeployTemplate{
|
||||
deploy: func(context.Context, string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
deleteDisk: func(ctx context.Context, imageType string, imageName string, resourceGroupName string) error {
|
||||
*deleteDiskCounter++
|
||||
return nil
|
||||
},
|
||||
disk: func(ctx context.Context, resourceGroupName, computeName string) (string, string, error) {
|
||||
return "Microsoft.Compute/disks", "", nil
|
||||
},
|
||||
delete: func(ctx context.Context, deploymentName, resourceGroupName string) error {
|
||||
return nil
|
||||
},
|
||||
deleteDeployment: func(ctx context.Context, state multistep.StateBag) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepGetDataDisk struct {
|
||||
client *AzureClient
|
||||
query func(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error)
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepGetAdditionalDisks(client *AzureClient, ui packersdk.Ui) *StepGetDataDisk {
|
||||
var step = &StepGetDataDisk{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
step.query = step.queryCompute
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepGetDataDisk) queryCompute(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
vm, err := s.client.VirtualMachinesClient.Get(ctx, resourceGroupName, computeName, "")
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return vm, err
|
||||
}
|
||||
|
||||
func (s *StepGetDataDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Querying the machine's additional disks properties ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var computeName = state.Get(constants.ArmComputeName).(string)
|
||||
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName))
|
||||
|
||||
vm, err := s.query(ctx, resourceGroupName, computeName)
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if vm.StorageProfile.DataDisks != nil {
|
||||
var vhdUri string
|
||||
additional_disks := make([]string, len(*vm.StorageProfile.DataDisks))
|
||||
for i, additionaldisk := range *vm.StorageProfile.DataDisks {
|
||||
if additionaldisk.Vhd != nil {
|
||||
vhdUri = *additionaldisk.Vhd.URI
|
||||
s.say(fmt.Sprintf(" -> Additional Disk %d : '%s'", i+1, vhdUri))
|
||||
} else {
|
||||
vhdUri = *additionaldisk.ManagedDisk.ID
|
||||
s.say(fmt.Sprintf(" -> Managed Additional Disk %d : '%s'", i+1, vhdUri))
|
||||
}
|
||||
additional_disks[i] = vhdUri
|
||||
}
|
||||
state.Put(constants.ArmAdditionalDiskVhds, additional_disks)
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepGetDataDisk) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepGetAdditionalDiskShouldFailIfGetFails(t *testing.T) {
|
||||
var testSubject = &StepGetDataDisk{
|
||||
query: func(context.Context, string, string) (compute.VirtualMachine, error) {
|
||||
return createVirtualMachineWithDataDisksFromUri("test.vhd"), fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetAdditionalDisks()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepGetAdditionalDiskShouldPassIfGetPasses(t *testing.T) {
|
||||
var testSubject = &StepGetDataDisk{
|
||||
query: func(context.Context, string, string) (compute.VirtualMachine, error) {
|
||||
return createVirtualMachineWithDataDisksFromUri("test.vhd"), nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetAdditionalDisks()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepGetAdditionalDiskShouldTakeValidateArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualComputeName string
|
||||
|
||||
var testSubject = &StepGetDataDisk{
|
||||
query: func(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualComputeName = computeName
|
||||
|
||||
return createVirtualMachineWithDataDisksFromUri("test.vhd"), nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetAdditionalDisks()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
var expectedComputeName = stateBag.Get(constants.ArmComputeName).(string)
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
if actualComputeName != expectedComputeName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
expectedAdditionalDiskVhds, ok := stateBag.GetOk(constants.ArmAdditionalDiskVhds)
|
||||
if !ok {
|
||||
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.ArmAdditionalDiskVhds)
|
||||
}
|
||||
|
||||
expectedAdditionalDiskVhd := expectedAdditionalDiskVhds.([]string)
|
||||
if expectedAdditionalDiskVhd[0] != "test.vhd" {
|
||||
t.Fatalf("Expected the value of stateBag[%s] to be 'test.vhd', but got '%s'.", constants.ArmAdditionalDiskVhds, expectedAdditionalDiskVhd[0])
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepGetAdditionalDisks() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmComputeName, "Unit Test: ComputeName")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
|
||||
func createVirtualMachineWithDataDisksFromUri(vhdUri string) compute.VirtualMachine {
|
||||
vm := compute.VirtualMachine{
|
||||
VirtualMachineProperties: &compute.VirtualMachineProperties{
|
||||
StorageProfile: &compute.StorageProfile{
|
||||
OsDisk: &compute.OSDisk{
|
||||
Vhd: &compute.VirtualHardDisk{
|
||||
URI: &vhdUri,
|
||||
},
|
||||
},
|
||||
DataDisks: &[]compute.DataDisk{
|
||||
{
|
||||
Vhd: &compute.VirtualHardDisk{
|
||||
URI: &vhdUri,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return vm
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepGetCertificate struct {
|
||||
client *AzureClient
|
||||
get func(keyVaultName string, secretName string) (string, error)
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
pause func()
|
||||
}
|
||||
|
||||
func NewStepGetCertificate(client *AzureClient, ui packersdk.Ui) *StepGetCertificate {
|
||||
var step = &StepGetCertificate{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
pause: func() { time.Sleep(30 * time.Second) },
|
||||
}
|
||||
|
||||
step.get = step.getCertificateUrl
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepGetCertificate) getCertificateUrl(keyVaultName string, secretName string) (string, error) {
|
||||
secret, err := s.client.GetSecret(keyVaultName, secretName)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
return *secret.ID, err
|
||||
}
|
||||
|
||||
func (s *StepGetCertificate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Getting the certificate's URL ...")
|
||||
|
||||
var keyVaultName = state.Get(constants.ArmKeyVaultName).(string)
|
||||
|
||||
s.say(fmt.Sprintf(" -> Key Vault Name : '%s'", keyVaultName))
|
||||
s.say(fmt.Sprintf(" -> Key Vault Secret Name : '%s'", DefaultSecretName))
|
||||
|
||||
var err error
|
||||
var url string
|
||||
for i := 0; i < 5; i++ {
|
||||
url, err = s.get(keyVaultName, DefaultSecretName)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
s.say(fmt.Sprintf(" ...failed to get certificate URL, retry(%d)", i))
|
||||
s.pause()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.say(fmt.Sprintf(" -> Certificate URL : '%s'", url))
|
||||
state.Put(constants.ArmCertificateUrl, url)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepGetCertificate) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepGetCertificateShouldFailIfGetFails(t *testing.T) {
|
||||
var testSubject = &StepGetCertificate{
|
||||
get: func(string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
pause: func() {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetCertificate()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepGetCertificateShouldPassIfGetPasses(t *testing.T) {
|
||||
var testSubject = &StepGetCertificate{
|
||||
get: func(string, string) (string, error) { return "", nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
pause: func() {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetCertificate()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepGetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualKeyVaultName string
|
||||
var actualSecretName string
|
||||
|
||||
var testSubject = &StepGetCertificate{
|
||||
get: func(keyVaultName string, secretName string) (string, error) {
|
||||
actualKeyVaultName = keyVaultName
|
||||
actualSecretName = secretName
|
||||
|
||||
return "http://key.vault/1", nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
pause: func() {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetCertificate()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
var expectedKeyVaultName = stateBag.Get(constants.ArmKeyVaultName).(string)
|
||||
|
||||
if actualKeyVaultName != expectedKeyVaultName {
|
||||
t.Fatal("Expected StepGetCertificate to source 'constants.ArmKeyVaultName' from the state bag, but it did not.")
|
||||
}
|
||||
if actualSecretName != DefaultSecretName {
|
||||
t.Fatal("Expected StepGetCertificate to use default value for secret, but it did not.")
|
||||
}
|
||||
|
||||
expectedCertificateUrl, ok := stateBag.GetOk(constants.ArmCertificateUrl)
|
||||
if !ok {
|
||||
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.ArmCertificateUrl)
|
||||
}
|
||||
|
||||
if expectedCertificateUrl != "http://key.vault/1" {
|
||||
t.Fatalf("Expected the value of stateBag[%s] to be 'http://key.vault/1', but got '%s'.", constants.ArmCertificateUrl, expectedCertificateUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepGetCertificate() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
stateBag.Put(constants.ArmKeyVaultName, "Unit Test: KeyVaultName")
|
||||
|
||||
return stateBag
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type EndpointType int
|
||||
|
||||
const (
|
||||
PublicEndpoint EndpointType = iota
|
||||
PrivateEndpoint
|
||||
PublicEndpointInPrivateNetwork
|
||||
)
|
||||
|
||||
var (
|
||||
EndpointCommunicationText = map[EndpointType]string{
|
||||
PublicEndpoint: "PublicEndpoint",
|
||||
PrivateEndpoint: "PrivateEndpoint",
|
||||
PublicEndpointInPrivateNetwork: "PublicEndpointInPrivateNetwork",
|
||||
}
|
||||
)
|
||||
|
||||
type StepGetIPAddress struct {
|
||||
client *AzureClient
|
||||
endpoint EndpointType
|
||||
get func(ctx context.Context, resourceGroupName string, ipAddressName string, interfaceName string) (string, error)
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepGetIPAddress(client *AzureClient, ui packersdk.Ui, endpoint EndpointType) *StepGetIPAddress {
|
||||
var step = &StepGetIPAddress{
|
||||
client: client,
|
||||
endpoint: endpoint,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
switch endpoint {
|
||||
case PrivateEndpoint:
|
||||
step.get = step.getPrivateIP
|
||||
case PublicEndpoint:
|
||||
step.get = step.getPublicIP
|
||||
case PublicEndpointInPrivateNetwork:
|
||||
step.get = step.getPublicIPInPrivateNetwork
|
||||
}
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) getPrivateIP(ctx context.Context, resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
|
||||
resp, err := s.client.InterfacesClient.Get(ctx, resourceGroupName, interfaceName, "")
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
return *(*resp.IPConfigurations)[0].PrivateIPAddress, nil
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) getPublicIP(ctx context.Context, resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
|
||||
resp, err := s.client.PublicIPAddressesClient.Get(ctx, resourceGroupName, ipAddressName, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return *resp.IPAddress, nil
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) getPublicIPInPrivateNetwork(ctx context.Context, resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
|
||||
s.getPrivateIP(ctx, resourceGroupName, ipAddressName, interfaceName)
|
||||
return s.getPublicIP(ctx, resourceGroupName, ipAddressName, interfaceName)
|
||||
}
|
||||
|
||||
func (s *StepGetIPAddress) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Getting the VM's IP address ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var ipAddressName = state.Get(constants.ArmPublicIPAddressName).(string)
|
||||
var nicName = state.Get(constants.ArmNicName).(string)
|
||||
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> PublicIPAddressName : '%s'", ipAddressName))
|
||||
s.say(fmt.Sprintf(" -> NicName : '%s'", nicName))
|
||||
s.say(fmt.Sprintf(" -> Network Connection : '%s'", EndpointCommunicationText[s.endpoint]))
|
||||
|
||||
address, err := s.get(ctx, resourceGroupName, ipAddressName, nicName)
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put(constants.SSHHost, address)
|
||||
s.say(fmt.Sprintf(" -> IP Address : '%s'", address))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepGetIPAddress) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
|
||||
endpoints := []EndpointType{PublicEndpoint, PublicEndpointInPrivateNetwork}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
var testSubject = &StepGetIPAddress{
|
||||
get: func(context.Context, string, string, string) (string, error) {
|
||||
return "", fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
endpoint: endpoint,
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetIPAddress()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
|
||||
endpoints := []EndpointType{PublicEndpoint, PublicEndpointInPrivateNetwork}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
var testSubject = &StepGetIPAddress{
|
||||
get: func(context.Context, string, string, string) (string, error) { return "", nil },
|
||||
endpoint: endpoint,
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetIPAddress()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualIPAddressName string
|
||||
var actualNicName string
|
||||
endpoints := []EndpointType{PublicEndpoint, PublicEndpointInPrivateNetwork}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
var testSubject = &StepGetIPAddress{
|
||||
get: func(ctx context.Context, resourceGroupName string, ipAddressName string, nicName string) (string, error) {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualIPAddressName = ipAddressName
|
||||
actualNicName = nicName
|
||||
|
||||
return "127.0.0.1", nil
|
||||
},
|
||||
endpoint: endpoint,
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetIPAddress()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
var expectedIPAddressName = stateBag.Get(constants.ArmPublicIPAddressName).(string)
|
||||
var expectedNicName = stateBag.Get(constants.ArmNicName).(string)
|
||||
|
||||
if actualIPAddressName != expectedIPAddressName {
|
||||
t.Fatal("Expected StepGetIPAddress to source 'constants.ArmIPAddressName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected StepGetIPAddress to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualNicName != expectedNicName {
|
||||
t.Fatalf("Expected StepGetIPAddress to source 'constants.ArmNetworkInterfaceName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
expectedIPAddress, ok := stateBag.GetOk(constants.SSHHost)
|
||||
if !ok {
|
||||
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.SSHHost)
|
||||
}
|
||||
|
||||
if expectedIPAddress != "127.0.0.1" {
|
||||
t.Fatalf("Expected the value of stateBag[%s] to be '127.0.0.1', but got '%s'.", constants.SSHHost, expectedIPAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepGetIPAddress() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmPublicIPAddressName, "Unit Test: PublicIPAddressName")
|
||||
stateBag.Put(constants.ArmNicName, "Unit Test: NicName")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
|
||||
return stateBag
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepGetOSDisk struct {
|
||||
client *AzureClient
|
||||
query func(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error)
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepGetOSDisk(client *AzureClient, ui packersdk.Ui) *StepGetOSDisk {
|
||||
var step = &StepGetOSDisk{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
step.query = step.queryCompute
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepGetOSDisk) queryCompute(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
vm, err := s.client.VirtualMachinesClient.Get(ctx, resourceGroupName, computeName, "")
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return vm, err
|
||||
}
|
||||
|
||||
func (s *StepGetOSDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Querying the machine's properties ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var computeName = state.Get(constants.ArmComputeName).(string)
|
||||
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName))
|
||||
|
||||
vm, err := s.query(ctx, resourceGroupName, computeName)
|
||||
if err != nil {
|
||||
state.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var vhdUri string
|
||||
if vm.StorageProfile.OsDisk.Vhd != nil {
|
||||
vhdUri = *vm.StorageProfile.OsDisk.Vhd.URI
|
||||
s.say(fmt.Sprintf(" -> OS Disk : '%s'", vhdUri))
|
||||
} else {
|
||||
vhdUri = *vm.StorageProfile.OsDisk.ManagedDisk.ID
|
||||
s.say(fmt.Sprintf(" -> Managed OS Disk : '%s'", vhdUri))
|
||||
}
|
||||
|
||||
state.Put(constants.ArmOSDiskVhd, vhdUri)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepGetOSDisk) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepGetOSDiskShouldFailIfGetFails(t *testing.T) {
|
||||
var testSubject = &StepGetOSDisk{
|
||||
query: func(context.Context, string, string) (compute.VirtualMachine, error) {
|
||||
return createVirtualMachineFromUri("test.vhd"), fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetOSDisk()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepGetOSDiskShouldPassIfGetPasses(t *testing.T) {
|
||||
var testSubject = &StepGetOSDisk{
|
||||
query: func(context.Context, string, string) (compute.VirtualMachine, error) {
|
||||
return createVirtualMachineFromUri("test.vhd"), nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetOSDisk()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepGetOSDiskShouldTakeValidateArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualComputeName string
|
||||
|
||||
var testSubject = &StepGetOSDisk{
|
||||
query: func(ctx context.Context, resourceGroupName string, computeName string) (compute.VirtualMachine, error) {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualComputeName = computeName
|
||||
|
||||
return createVirtualMachineFromUri("test.vhd"), nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepGetOSDisk()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
var expectedComputeName = stateBag.Get(constants.ArmComputeName).(string)
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
if actualComputeName != expectedComputeName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
expectedOSDiskVhd, ok := stateBag.GetOk(constants.ArmOSDiskVhd)
|
||||
if !ok {
|
||||
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.ArmOSDiskVhd)
|
||||
}
|
||||
|
||||
if expectedOSDiskVhd != "test.vhd" {
|
||||
t.Fatalf("Expected the value of stateBag[%s] to be 'test.vhd', but got '%s'.", constants.ArmOSDiskVhd, expectedOSDiskVhd)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepGetOSDisk() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmComputeName, "Unit Test: ComputeName")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
|
||||
func createVirtualMachineFromUri(vhdUri string) compute.VirtualMachine {
|
||||
vm := compute.VirtualMachine{
|
||||
VirtualMachineProperties: &compute.VirtualMachineProperties{
|
||||
StorageProfile: &compute.StorageProfile{
|
||||
OsDisk: &compute.OSDisk{
|
||||
Vhd: &compute.VirtualHardDisk{
|
||||
URI: &vhdUri,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return vm
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepPowerOffCompute struct {
|
||||
client *AzureClient
|
||||
powerOff func(ctx context.Context, resourceGroupName string, computeName string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepPowerOffCompute(client *AzureClient, ui packersdk.Ui) *StepPowerOffCompute {
|
||||
var step = &StepPowerOffCompute{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
step.powerOff = step.powerOffCompute
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepPowerOffCompute) powerOffCompute(ctx context.Context, resourceGroupName string, computeName string) error {
|
||||
f, err := s.client.VirtualMachinesClient.Deallocate(ctx, resourceGroupName, computeName)
|
||||
if err == nil {
|
||||
err = f.WaitForCompletionRef(ctx, s.client.VirtualMachinesClient.Client)
|
||||
}
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepPowerOffCompute) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Powering off machine ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var computeName = state.Get(constants.ArmComputeName).(string)
|
||||
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName))
|
||||
|
||||
err := s.powerOff(ctx, resourceGroupName, computeName)
|
||||
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
|
||||
func (*StepPowerOffCompute) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepPowerOffComputeShouldFailIfPowerOffFails(t *testing.T) {
|
||||
var testSubject = &StepPowerOffCompute{
|
||||
powerOff: func(context.Context, string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepPowerOffCompute()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepPowerOffComputeShouldPassIfPowerOffPasses(t *testing.T) {
|
||||
var testSubject = &StepPowerOffCompute{
|
||||
powerOff: func(context.Context, string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepPowerOffCompute()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepPowerOffComputeShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualComputeName string
|
||||
|
||||
var testSubject = &StepPowerOffCompute{
|
||||
powerOff: func(ctx context.Context, resourceGroupName string, computeName string) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualComputeName = computeName
|
||||
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepPowerOffCompute()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
var expectedComputeName = stateBag.Get(constants.ArmComputeName).(string)
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
if actualComputeName != expectedComputeName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepPowerOffCompute() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmComputeName, "Unit Test: ComputeName")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
|
||||
return stateBag
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute"
|
||||
"github.com/Azure/go-autorest/autorest/date"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepPublishToSharedImageGallery struct {
|
||||
client *AzureClient
|
||||
publish func(ctx context.Context, mdiID, miSigPubRg, miSIGalleryName, miSGImageName, miSGImageVersion string, miSigReplicationRegions []string, miSGImageVersionEndOfLifeDate string, miSGImageVersionExcludeFromLatest bool, miSigReplicaCount int32, location string, tags map[string]*string) (string, error)
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
toSIG func() bool
|
||||
}
|
||||
|
||||
func NewStepPublishToSharedImageGallery(client *AzureClient, ui packersdk.Ui, config *Config) *StepPublishToSharedImageGallery {
|
||||
var step = &StepPublishToSharedImageGallery{
|
||||
client: client,
|
||||
say: func(message string) {
|
||||
ui.Say(message)
|
||||
},
|
||||
error: func(e error) {
|
||||
ui.Error(e.Error())
|
||||
},
|
||||
toSIG: func() bool {
|
||||
return config.isManagedImage() && config.SharedGalleryDestination.SigDestinationGalleryName != ""
|
||||
},
|
||||
}
|
||||
|
||||
step.publish = step.publishToSig
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepPublishToSharedImageGallery) publishToSig(ctx context.Context, mdiID string, miSigPubRg string, miSIGalleryName string, miSGImageName string, miSGImageVersion string, miSigReplicationRegions []string, miSGImageVersionEndOfLifeDate string, miSGImageVersionExcludeFromLatest bool, miSigReplicaCount int32, location string, tags map[string]*string) (string, error) {
|
||||
|
||||
replicationRegions := make([]compute.TargetRegion, len(miSigReplicationRegions))
|
||||
for i, v := range miSigReplicationRegions {
|
||||
regionName := v
|
||||
replicationRegions[i] = compute.TargetRegion{Name: ®ionName}
|
||||
}
|
||||
|
||||
var endOfLifeDate *date.Time
|
||||
if miSGImageVersionEndOfLifeDate != "" {
|
||||
parseDate, err := date.ParseTime("2006-01-02T15:04:05.99Z", miSGImageVersionEndOfLifeDate)
|
||||
if err != nil {
|
||||
s.say(fmt.Sprintf("Error parsing date from shared_gallery_image_version_end_of_life_date: %s", err))
|
||||
return "", err
|
||||
}
|
||||
endOfLifeDate = &date.Time{Time: parseDate}
|
||||
} else {
|
||||
endOfLifeDate = (*date.Time)(nil)
|
||||
}
|
||||
galleryImageVersion := compute.GalleryImageVersion{
|
||||
Location: &location,
|
||||
Tags: tags,
|
||||
GalleryImageVersionProperties: &compute.GalleryImageVersionProperties{
|
||||
PublishingProfile: &compute.GalleryImageVersionPublishingProfile{
|
||||
Source: &compute.GalleryArtifactSource{
|
||||
ManagedImage: &compute.ManagedArtifact{
|
||||
ID: &mdiID,
|
||||
},
|
||||
},
|
||||
TargetRegions: &replicationRegions,
|
||||
EndOfLifeDate: endOfLifeDate,
|
||||
ExcludeFromLatest: &miSGImageVersionExcludeFromLatest,
|
||||
ReplicaCount: &miSigReplicaCount,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
f, err := s.client.GalleryImageVersionsClient.CreateOrUpdate(ctx, miSigPubRg, miSIGalleryName, miSGImageName, miSGImageVersion, galleryImageVersion)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = f.WaitForCompletionRef(ctx, s.client.GalleryImageVersionsClient.Client)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
createdSGImageVersion, err := f.Result(s.client.GalleryImageVersionsClient)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.say(fmt.Sprintf(" -> Shared Gallery Image Version ID : '%s'", *(createdSGImageVersion.ID)))
|
||||
return *(createdSGImageVersion.ID), nil
|
||||
}
|
||||
|
||||
func (s *StepPublishToSharedImageGallery) Run(ctx context.Context, stateBag multistep.StateBag) multistep.StepAction {
|
||||
if !s.toSIG() {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
s.say("Publishing to Shared Image Gallery ...")
|
||||
|
||||
location := stateBag.Get(constants.ArmLocation).(string)
|
||||
|
||||
miSigPubRg := stateBag.Get(constants.ArmManagedImageSigPublishResourceGroup).(string)
|
||||
miSIGalleryName := stateBag.Get(constants.ArmManagedImageSharedGalleryName).(string)
|
||||
miSGImageName := stateBag.Get(constants.ArmManagedImageSharedGalleryImageName).(string)
|
||||
miSGImageVersion := stateBag.Get(constants.ArmManagedImageSharedGalleryImageVersion).(string)
|
||||
miSigReplicationRegions := stateBag.Get(constants.ArmManagedImageSharedGalleryReplicationRegions).([]string)
|
||||
|
||||
tags := stateBag.Get(constants.ArmTags).(map[string]*string)
|
||||
targetManagedImageResourceGroupName := stateBag.Get(constants.ArmManagedImageResourceGroupName).(string)
|
||||
targetManagedImageName := stateBag.Get(constants.ArmManagedImageName).(string)
|
||||
|
||||
managedImageSubscription := stateBag.Get(constants.ArmManagedImageSubscription).(string)
|
||||
mdiID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/images/%s", managedImageSubscription, targetManagedImageResourceGroupName, targetManagedImageName)
|
||||
|
||||
miSGImageVersionEndOfLifeDate, _ := stateBag.Get(constants.ArmManagedImageSharedGalleryImageVersionEndOfLifeDate).(string)
|
||||
miSGImageVersionExcludeFromLatest, _ := stateBag.Get(constants.ArmManagedImageSharedGalleryImageVersionExcludeFromLatest).(bool)
|
||||
miSigReplicaCount, _ := stateBag.Get(constants.ArmManagedImageSharedGalleryImageVersionReplicaCount).(int32)
|
||||
// Replica count must be between 1 and 10 inclusive.
|
||||
if miSigReplicaCount <= 0 {
|
||||
miSigReplicaCount = constants.SharedImageGalleryImageVersionDefaultMinReplicaCount
|
||||
} else if miSigReplicaCount > 10 {
|
||||
miSigReplicaCount = constants.SharedImageGalleryImageVersionDefaultMaxReplicaCount
|
||||
}
|
||||
|
||||
s.say(fmt.Sprintf(" -> MDI ID used for SIG publish : '%s'", mdiID))
|
||||
s.say(fmt.Sprintf(" -> SIG publish resource group : '%s'", miSigPubRg))
|
||||
s.say(fmt.Sprintf(" -> SIG gallery name : '%s'", miSIGalleryName))
|
||||
s.say(fmt.Sprintf(" -> SIG image name : '%s'", miSGImageName))
|
||||
s.say(fmt.Sprintf(" -> SIG image version : '%s'", miSGImageVersion))
|
||||
s.say(fmt.Sprintf(" -> SIG replication regions : '%v'", miSigReplicationRegions))
|
||||
s.say(fmt.Sprintf(" -> SIG image version endoflife date : '%s'", miSGImageVersionEndOfLifeDate))
|
||||
s.say(fmt.Sprintf(" -> SIG image version exclude from latest : '%t'", miSGImageVersionExcludeFromLatest))
|
||||
s.say(fmt.Sprintf(" -> SIG replica count [1, 10] : '%d'", miSigReplicaCount))
|
||||
|
||||
createdGalleryImageVersionID, err := s.publish(ctx, mdiID, miSigPubRg, miSIGalleryName, miSGImageName, miSGImageVersion, miSigReplicationRegions, miSGImageVersionEndOfLifeDate, miSGImageVersionExcludeFromLatest, miSigReplicaCount, location, tags)
|
||||
|
||||
if err != nil {
|
||||
stateBag.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryId, createdGalleryImageVersionID)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepPublishToSharedImageGallery) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepPublishToSharedImageGalleryShouldNotPublishForVhd(t *testing.T) {
|
||||
var testSubject = &StepPublishToSharedImageGallery{
|
||||
publish: func(context.Context, string, string, string, string, string, []string, string, bool, int32, string, map[string]*string) (string, error) {
|
||||
return "test", nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
toSIG: func() bool { return false },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepPublishToSharedImageGalleryForVhd()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepPublishToSharedImageGalleryShouldPublishForManagedImageWithSig(t *testing.T) {
|
||||
var testSubject = &StepPublishToSharedImageGallery{
|
||||
publish: func(context.Context, string, string, string, string, string, []string, string, bool, int32, string, map[string]*string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
toSIG: func() bool { return true },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepPublishToSharedImageGallery()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepPublishToSharedImageGallery() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmManagedImageSigPublishResourceGroup, "Unit Test: ManagedImageSigPublishResourceGroup")
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryName, "Unit Test: ManagedImageSharedGalleryName")
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryImageName, "Unit Test: ManagedImageSharedGalleryImageName")
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersion, "Unit Test: ManagedImageSharedGalleryImageVersion")
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]*string{
|
||||
"tag01": &value,
|
||||
}
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
stateBag.Put(constants.ArmManagedImageSharedGalleryReplicationRegions, []string{"ManagedImageSharedGalleryReplicationRegionA", "ManagedImageSharedGalleryReplicationRegionB"})
|
||||
stateBag.Put(constants.ArmManagedImageResourceGroupName, "Unit Test: ManagedImageResourceGroupName")
|
||||
stateBag.Put(constants.ArmManagedImageName, "Unit Test: ManagedImageName")
|
||||
stateBag.Put(constants.ArmManagedImageSubscription, "Unit Test: ManagedImageSubscription")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
|
||||
func createTestStateBagStepPublishToSharedImageGalleryForVhd() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]*string{
|
||||
"tag01": &value,
|
||||
}
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
|
||||
return stateBag
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepSetCertificate struct {
|
||||
config *Config
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
}
|
||||
|
||||
func NewStepSetCertificate(config *Config, ui packersdk.Ui) *StepSetCertificate {
|
||||
var step = &StepSetCertificate{
|
||||
config: config,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepSetCertificate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Setting the certificate's URL ...")
|
||||
|
||||
var winRMCertificateUrl = state.Get(constants.ArmCertificateUrl).(string)
|
||||
s.config.tmpWinRMCertificateUrl = winRMCertificateUrl
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepSetCertificate) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepSetCertificateShouldPassIfGetPasses(t *testing.T) {
|
||||
var testSubject = &StepSetCertificate{
|
||||
config: new(Config),
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepSetCertificate()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepSetCertificateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
config := new(Config)
|
||||
var testSubject = &StepSetCertificate{
|
||||
config: config,
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepSetCertificate()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if config.tmpWinRMCertificateUrl != stateBag.Get(constants.ArmCertificateUrl) {
|
||||
t.Fatalf("Expected config.tmpWinRMCertificateUrl to be %s, but got %s'", stateBag.Get(constants.ArmCertificateUrl), config.tmpWinRMCertificateUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepSetCertificate() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
stateBag.Put(constants.ArmCertificateUrl, "Unit Test: Certificate URL")
|
||||
return stateBag
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepSnapshotDataDisks struct {
|
||||
client *AzureClient
|
||||
create func(ctx context.Context, resourceGroupName string, srcUriVhd string, location string, tags map[string]*string, dstSnapshotName string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
enable func() bool
|
||||
}
|
||||
|
||||
func NewStepSnapshotDataDisks(client *AzureClient, ui packersdk.Ui, config *Config) *StepSnapshotDataDisks {
|
||||
var step = &StepSnapshotDataDisks{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
enable: func() bool { return config.isManagedImage() && config.ManagedImageDataDiskSnapshotPrefix != "" },
|
||||
}
|
||||
|
||||
step.create = step.createDataDiskSnapshot
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepSnapshotDataDisks) createDataDiskSnapshot(ctx context.Context, resourceGroupName string, srcUriVhd string, location string, tags map[string]*string, dstSnapshotName string) error {
|
||||
|
||||
srcVhdToSnapshot := compute.Snapshot{
|
||||
DiskProperties: &compute.DiskProperties{
|
||||
CreationData: &compute.CreationData{
|
||||
CreateOption: compute.Copy,
|
||||
SourceResourceID: to.StringPtr(srcUriVhd),
|
||||
},
|
||||
},
|
||||
Location: to.StringPtr(location),
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
f, err := s.client.SnapshotsClient.CreateOrUpdate(ctx, resourceGroupName, dstSnapshotName, srcVhdToSnapshot)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.WaitForCompletionRef(ctx, s.client.SnapshotsClient.Client)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
createdSnapshot, err := f.Result(s.client.SnapshotsClient)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
s.say(fmt.Sprintf(" -> Snapshot ID : '%s'", *(createdSnapshot.ID)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StepSnapshotDataDisks) Run(ctx context.Context, stateBag multistep.StateBag) multistep.StepAction {
|
||||
if !s.enable() {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
var resourceGroupName = stateBag.Get(constants.ArmManagedImageResourceGroupName).(string)
|
||||
var location = stateBag.Get(constants.ArmLocation).(string)
|
||||
var tags = stateBag.Get(constants.ArmTags).(map[string]*string)
|
||||
var additionalDisks = stateBag.Get(constants.ArmAdditionalDiskVhds).([]string)
|
||||
var dstSnapshotPrefix = stateBag.Get(constants.ArmManagedImageDataDiskSnapshotPrefix).(string)
|
||||
|
||||
if len(additionalDisks) == 1 {
|
||||
s.say(fmt.Sprintf("Snapshotting data disk ..."))
|
||||
} else {
|
||||
s.say(fmt.Sprintf("Snapshotting data disks ..."))
|
||||
}
|
||||
|
||||
for i, disk := range additionalDisks {
|
||||
s.say(fmt.Sprintf(" -> Data Disk : '%s'", disk))
|
||||
|
||||
dstSnapshotName := dstSnapshotPrefix + strconv.Itoa(i)
|
||||
err := s.create(ctx, resourceGroupName, disk, location, tags, dstSnapshotName)
|
||||
|
||||
if err != nil {
|
||||
stateBag.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepSnapshotDataDisks) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepSnapshotDataDisksShouldFailIfSnapshotFails(t *testing.T) {
|
||||
var testSubject = &StepSnapshotDataDisks{
|
||||
create: func(context.Context, string, string, string, map[string]*string, string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
enable: func() bool { return true },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepSnapshotDataDisks()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepSnapshotDataDisksShouldNotExecute(t *testing.T) {
|
||||
var testSubject = &StepSnapshotDataDisks{
|
||||
create: func(context.Context, string, string, string, map[string]*string, string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
enable: func() bool { return false },
|
||||
}
|
||||
|
||||
var result = testSubject.Run(context.Background(), nil)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepSnapshotDataDisksShouldPassIfSnapshotPasses(t *testing.T) {
|
||||
var testSubject = &StepSnapshotDataDisks{
|
||||
create: func(context.Context, string, string, string, map[string]*string, string) error {
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
enable: func() bool { return true },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepSnapshotDataDisks()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepSnapshotDataDisks() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmManagedImageResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]*string{
|
||||
"tag01": &value,
|
||||
}
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
|
||||
stateBag.Put(constants.ArmAdditionalDiskVhds, []string{"subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk"})
|
||||
stateBag.Put(constants.ArmManagedImageDataDiskSnapshotPrefix, "Unit Test: ManagedImageDataDiskSnapshotPrefix")
|
||||
|
||||
return stateBag
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepSnapshotOSDisk struct {
|
||||
client *AzureClient
|
||||
create func(ctx context.Context, resourceGroupName string, srcUriVhd string, location string, tags map[string]*string, dstSnapshotName string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
enable func() bool
|
||||
}
|
||||
|
||||
func NewStepSnapshotOSDisk(client *AzureClient, ui packersdk.Ui, config *Config) *StepSnapshotOSDisk {
|
||||
var step = &StepSnapshotOSDisk{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
enable: func() bool { return config.isManagedImage() && config.ManagedImageOSDiskSnapshotName != "" },
|
||||
}
|
||||
|
||||
step.create = step.createSnapshot
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepSnapshotOSDisk) createSnapshot(ctx context.Context, resourceGroupName string, srcUriVhd string, location string, tags map[string]*string, dstSnapshotName string) error {
|
||||
|
||||
srcVhdToSnapshot := compute.Snapshot{
|
||||
DiskProperties: &compute.DiskProperties{
|
||||
CreationData: &compute.CreationData{
|
||||
CreateOption: compute.Copy,
|
||||
SourceResourceID: to.StringPtr(srcUriVhd),
|
||||
},
|
||||
},
|
||||
Location: to.StringPtr(location),
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
f, err := s.client.SnapshotsClient.CreateOrUpdate(ctx, resourceGroupName, dstSnapshotName, srcVhdToSnapshot)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.WaitForCompletionRef(ctx, s.client.SnapshotsClient.Client)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
createdSnapshot, err := f.Result(s.client.SnapshotsClient)
|
||||
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
s.say(fmt.Sprintf(" -> Snapshot ID : '%s'", *(createdSnapshot.ID)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StepSnapshotOSDisk) Run(ctx context.Context, stateBag multistep.StateBag) multistep.StepAction {
|
||||
if !s.enable() {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
s.say("Snapshotting OS disk ...")
|
||||
|
||||
var resourceGroupName = stateBag.Get(constants.ArmManagedImageResourceGroupName).(string)
|
||||
var location = stateBag.Get(constants.ArmLocation).(string)
|
||||
var tags = stateBag.Get(constants.ArmTags).(map[string]*string)
|
||||
var srcUriVhd = stateBag.Get(constants.ArmOSDiskVhd).(string)
|
||||
var dstSnapshotName = stateBag.Get(constants.ArmManagedImageOSDiskSnapshotName).(string)
|
||||
|
||||
s.say(fmt.Sprintf(" -> OS Disk : '%s'", srcUriVhd))
|
||||
err := s.create(ctx, resourceGroupName, srcUriVhd, location, tags, dstSnapshotName)
|
||||
|
||||
if err != nil {
|
||||
stateBag.Put(constants.Error, err)
|
||||
s.error(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepSnapshotOSDisk) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepSnapshotOSDiskShouldFailIfSnapshotFails(t *testing.T) {
|
||||
var testSubject = &StepSnapshotOSDisk{
|
||||
create: func(context.Context, string, string, string, map[string]*string, string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
enable: func() bool { return true },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepSnapshotOSDisk()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepSnapshotOSDiskShouldNotExecute(t *testing.T) {
|
||||
var testSubject = &StepSnapshotOSDisk{
|
||||
create: func(context.Context, string, string, string, map[string]*string, string) error {
|
||||
return fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
enable: func() bool { return false },
|
||||
}
|
||||
|
||||
var result = testSubject.Run(context.Background(), nil)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepSnapshotOSDiskShouldPassIfSnapshotPasses(t *testing.T) {
|
||||
var testSubject = &StepSnapshotOSDisk{
|
||||
create: func(context.Context, string, string, string, map[string]*string, string) error {
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
enable: func() bool { return true },
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepSnapshotOSDisk()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepSnapshotOSDisk() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmManagedImageResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
|
||||
|
||||
value := "Unit Test: Tags"
|
||||
tags := map[string]*string{
|
||||
"tag01": &value,
|
||||
}
|
||||
|
||||
stateBag.Put(constants.ArmTags, tags)
|
||||
|
||||
stateBag.Put(constants.ArmOSDiskVhd, "subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk")
|
||||
stateBag.Put(constants.ArmManagedImageOSDiskSnapshotName, "Unit Test: ManagedImageOSDiskSnapshotName")
|
||||
|
||||
return stateBag
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestProcessStepResultShouldContinueForNonErrors(t *testing.T) {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
code := processStepResult(nil, func(error) { t.Fatal("Should not be called!") }, stateBag)
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok {
|
||||
t.Errorf("Error was nil, but was still in the state bag.")
|
||||
}
|
||||
|
||||
if code != multistep.ActionContinue {
|
||||
t.Errorf("Expected ActionContinue(%d), but got=%d", multistep.ActionContinue, code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessStepResultShouldHaltOnError(t *testing.T) {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
isSaidError := false
|
||||
|
||||
code := processStepResult(fmt.Errorf("boom"), func(error) { isSaidError = true }, stateBag)
|
||||
if _, ok := stateBag.GetOk(constants.Error); !ok {
|
||||
t.Errorf("Error was non nil, but was not in the state bag.")
|
||||
}
|
||||
|
||||
if !isSaidError {
|
||||
t.Errorf("Expected error to be said, but it was not.")
|
||||
}
|
||||
|
||||
if code != multistep.ActionHalt {
|
||||
t.Errorf("Expected ActionHalt(%d), but got=%d", multistep.ActionHalt, code)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
type StepValidateTemplate struct {
|
||||
client *AzureClient
|
||||
validate func(ctx context.Context, resourceGroupName string, deploymentName string) error
|
||||
say func(message string)
|
||||
error func(e error)
|
||||
config *Config
|
||||
factory templateFactoryFunc
|
||||
}
|
||||
|
||||
func NewStepValidateTemplate(client *AzureClient, ui packersdk.Ui, config *Config, factory templateFactoryFunc) *StepValidateTemplate {
|
||||
var step = &StepValidateTemplate{
|
||||
client: client,
|
||||
say: func(message string) { ui.Say(message) },
|
||||
error: func(e error) { ui.Error(e.Error()) },
|
||||
config: config,
|
||||
factory: factory,
|
||||
}
|
||||
|
||||
step.validate = step.validateTemplate
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepValidateTemplate) validateTemplate(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
||||
deployment, err := s.factory(s.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.client.DeploymentsClient.Validate(ctx, resourceGroupName, deploymentName, *deployment)
|
||||
if err != nil {
|
||||
s.say(s.client.LastError.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepValidateTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.say("Validating deployment template ...")
|
||||
|
||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
|
||||
|
||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", deploymentName))
|
||||
|
||||
err := s.validate(ctx, resourceGroupName, deploymentName)
|
||||
return processStepResult(err, s.error, state)
|
||||
}
|
||||
|
||||
func (*StepValidateTemplate) Cleanup(multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
)
|
||||
|
||||
func TestStepValidateTemplateShouldFailIfValidateFails(t *testing.T) {
|
||||
var testSubject = &StepValidateTemplate{
|
||||
validate: func(context.Context, string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepValidateTemplate()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepValidateTemplateShouldPassIfValidatePasses(t *testing.T) {
|
||||
var testSubject = &StepValidateTemplate{
|
||||
validate: func(context.Context, string, string) error { return nil },
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepValidateTemplate()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepValidateTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||
var actualResourceGroupName string
|
||||
var actualDeploymentName string
|
||||
|
||||
var testSubject = &StepValidateTemplate{
|
||||
validate: func(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
||||
actualResourceGroupName = resourceGroupName
|
||||
actualDeploymentName = deploymentName
|
||||
|
||||
return nil
|
||||
},
|
||||
say: func(message string) {},
|
||||
error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepValidateTemplate()
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
var expectedDeploymentName = stateBag.Get(constants.ArmDeploymentName).(string)
|
||||
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
|
||||
|
||||
if actualDeploymentName != expectedDeploymentName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
|
||||
}
|
||||
|
||||
if actualResourceGroupName != expectedResourceGroupName {
|
||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepValidateTemplate() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put(constants.ArmDeploymentName, "Unit Test: DeploymentName")
|
||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||
|
||||
return stateBag
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/builder/azure/common/template"
|
||||
)
|
||||
|
||||
type templateFactoryFunc func(*Config) (*resources.Deployment, error)
|
||||
|
||||
func GetKeyVaultDeployment(config *Config) (*resources.Deployment, error) {
|
||||
params := &template.TemplateParameters{
|
||||
KeyVaultName: &template.TemplateParameter{Value: config.tmpKeyVaultName},
|
||||
KeyVaultSKU: &template.TemplateParameter{Value: config.BuildKeyVaultSKU},
|
||||
KeyVaultSecretValue: &template.TemplateParameter{Value: config.winrmCertificate},
|
||||
ObjectId: &template.TemplateParameter{Value: config.ClientConfig.ObjectID},
|
||||
TenantId: &template.TemplateParameter{Value: config.ClientConfig.TenantID},
|
||||
}
|
||||
|
||||
builder, _ := template.NewTemplateBuilder(template.KeyVault)
|
||||
builder.SetTags(&config.AzureTags)
|
||||
|
||||
doc, _ := builder.ToJSON()
|
||||
return createDeploymentParameters(*doc, params)
|
||||
}
|
||||
|
||||
func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error) {
|
||||
params := &template.TemplateParameters{
|
||||
AdminUsername: &template.TemplateParameter{Value: config.UserName},
|
||||
AdminPassword: &template.TemplateParameter{Value: config.Password},
|
||||
DnsNameForPublicIP: &template.TemplateParameter{Value: config.tmpComputeName},
|
||||
NicName: &template.TemplateParameter{Value: config.tmpNicName},
|
||||
OSDiskName: &template.TemplateParameter{Value: config.tmpOSDiskName},
|
||||
DataDiskName: &template.TemplateParameter{Value: config.tmpDataDiskName},
|
||||
PublicIPAddressName: &template.TemplateParameter{Value: config.tmpPublicIPAddressName},
|
||||
SubnetName: &template.TemplateParameter{Value: config.tmpSubnetName},
|
||||
StorageAccountBlobEndpoint: &template.TemplateParameter{Value: config.storageAccountBlobEndpoint},
|
||||
VirtualNetworkName: &template.TemplateParameter{Value: config.tmpVirtualNetworkName},
|
||||
NsgName: &template.TemplateParameter{Value: config.tmpNsgName},
|
||||
VMSize: &template.TemplateParameter{Value: config.VMSize},
|
||||
VMName: &template.TemplateParameter{Value: config.tmpComputeName},
|
||||
}
|
||||
|
||||
builder, err := template.NewTemplateBuilder(template.BasicTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
osType := compute.Linux
|
||||
|
||||
switch config.OSType {
|
||||
case constants.Target_Linux:
|
||||
err = builder.BuildLinux(config.sshAuthorizedKey, config.Comm.SSHPassword == "") // if ssh password is not explicitly specified, disable password auth
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case constants.Target_Windows:
|
||||
osType = compute.Windows
|
||||
err = builder.BuildWindows(config.tmpKeyVaultName, config.tmpWinRMCertificateUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(config.UserAssignedManagedIdentities) != 0 {
|
||||
if err := builder.SetIdentity(config.UserAssignedManagedIdentities); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.ImageUrl != "" {
|
||||
err = builder.SetImageUrl(config.ImageUrl, osType, config.diskCachingType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if config.CustomManagedImageName != "" {
|
||||
err = builder.SetManagedDiskUrl(config.customManagedImageID, config.managedImageStorageAccountType, config.diskCachingType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if config.ManagedImageName != "" && config.ImagePublisher != "" {
|
||||
imageID := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/ArtifactTypes/vmimage/offers/%s/skus/%s/versions/%s",
|
||||
config.ClientConfig.SubscriptionID,
|
||||
config.Location,
|
||||
config.ImagePublisher,
|
||||
config.ImageOffer,
|
||||
config.ImageSku,
|
||||
config.ImageVersion)
|
||||
|
||||
builder.SetManagedMarketplaceImage(config.Location, config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion, imageID, config.managedImageStorageAccountType, config.diskCachingType)
|
||||
} else if config.SharedGallery.Subscription != "" {
|
||||
imageID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/galleries/%s/images/%s",
|
||||
config.SharedGallery.Subscription,
|
||||
config.SharedGallery.ResourceGroup,
|
||||
config.SharedGallery.GalleryName,
|
||||
config.SharedGallery.ImageName)
|
||||
if config.SharedGallery.ImageVersion != "" {
|
||||
imageID += fmt.Sprintf("/versions/%s",
|
||||
config.SharedGallery.ImageVersion)
|
||||
}
|
||||
|
||||
err = builder.SetSharedGalleryImage(config.Location, imageID, config.diskCachingType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err = builder.SetMarketPlaceImage(config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion, config.diskCachingType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.OSDiskSizeGB > 0 {
|
||||
err = builder.SetOSDiskSizeGB(config.OSDiskSizeGB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(config.AdditionalDiskSize) > 0 {
|
||||
isManaged := config.CustomManagedImageName != "" || (config.ManagedImageName != "" && config.ImagePublisher != "") || config.SharedGallery.Subscription != ""
|
||||
err = builder.SetAdditionalDisks(config.AdditionalDiskSize, config.tmpDataDiskName, isManaged, config.diskCachingType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.customData != "" {
|
||||
err = builder.SetCustomData(config.customData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.PlanInfo.PlanName != "" {
|
||||
err = builder.SetPlanInfo(config.PlanInfo.PlanName, config.PlanInfo.PlanProduct, config.PlanInfo.PlanPublisher, config.PlanInfo.PlanPromotionCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.VirtualNetworkName != "" && DefaultPrivateVirtualNetworkWithPublicIp != config.PrivateVirtualNetworkWithPublicIp {
|
||||
err = builder.SetPrivateVirtualNetworkWithPublicIp(
|
||||
config.VirtualNetworkResourceGroupName,
|
||||
config.VirtualNetworkName,
|
||||
config.VirtualNetworkSubnetName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if config.VirtualNetworkName != "" {
|
||||
err = builder.SetVirtualNetwork(
|
||||
config.VirtualNetworkResourceGroupName,
|
||||
config.VirtualNetworkName,
|
||||
config.VirtualNetworkSubnetName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.AllowedInboundIpAddresses != nil && len(config.AllowedInboundIpAddresses) >= 1 && config.Comm.Port() != 0 {
|
||||
err = builder.SetNetworkSecurityGroup(config.AllowedInboundIpAddresses, config.Comm.Port())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.BootDiagSTGAccount != "" {
|
||||
err = builder.SetBootDiagnostics(config.BootDiagSTGAccount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = builder.SetTags(&config.AzureTags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
doc, _ := builder.ToJSON()
|
||||
return createDeploymentParameters(*doc, params)
|
||||
}
|
||||
|
||||
func createDeploymentParameters(doc string, parameters *template.TemplateParameters) (*resources.Deployment, error) {
|
||||
var template map[string]interface{}
|
||||
err := json.Unmarshal(([]byte)(doc), &template)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(*parameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var templateParameters map[string]interface{}
|
||||
err = json.Unmarshal(bs, &templateParameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resources.Deployment{
|
||||
Properties: &resources.DeploymentProperties{
|
||||
Mode: resources.Incremental,
|
||||
Template: &template,
|
||||
Parameters: &templateParameters,
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"keyVaultName": {
|
||||
"type": "string"
|
||||
},
|
||||
"keyVaultSKU": {
|
||||
"type": "string"
|
||||
},
|
||||
"keyVaultSecretValue": {
|
||||
"type": "securestring"
|
||||
},
|
||||
"objectId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tenantId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('keyVaultName')]",
|
||||
"properties": {
|
||||
"accessPolicies": [
|
||||
{
|
||||
"objectId": "[parameters('objectId')]",
|
||||
"permissions": {
|
||||
"keys": [
|
||||
"all"
|
||||
],
|
||||
"secrets": [
|
||||
"all"
|
||||
]
|
||||
},
|
||||
"tenantId": "[parameters('tenantId')]"
|
||||
}
|
||||
],
|
||||
"enableSoftDelete": "true",
|
||||
"enabledForDeployment": "true",
|
||||
"enabledForTemplateDeployment": "true",
|
||||
"sku": {
|
||||
"family": "A",
|
||||
"name": "[parameters('keyVaultSKU')]"
|
||||
},
|
||||
"tenantId": "[parameters('tenantId')]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]"
|
||||
],
|
||||
"name": "[variables('keyVaultSecretName')]",
|
||||
"properties": {
|
||||
"value": "[parameters('keyVaultSecretValue')]"
|
||||
},
|
||||
"type": "secrets"
|
||||
}
|
||||
],
|
||||
"tags": {
|
||||
"tag01": "value01",
|
||||
"tag02": "value02",
|
||||
"tag03": "value03"
|
||||
},
|
||||
"type": "Microsoft.KeyVault/vaults"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"apiVersion": "2015-06-01",
|
||||
"keyVaultSecretName": "packerKeyVaultSecret",
|
||||
"location": "[resourceGroup().location]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"tags": {
|
||||
"PlanInfo": "planName00",
|
||||
"PlanProduct": "planProduct00",
|
||||
"PlanPromotionCode": "",
|
||||
"PlanPublisher": "planPublisher00"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tags": {
|
||||
"PlanInfo": "planName00",
|
||||
"PlanProduct": "planProduct00",
|
||||
"PlanPromotionCode": "",
|
||||
"PlanPublisher": "planPublisher00"
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tags": {
|
||||
"PlanInfo": "planName00",
|
||||
"PlanProduct": "planProduct00",
|
||||
"PlanPromotionCode": "",
|
||||
"PlanPublisher": "planPublisher00"
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"plan": {
|
||||
"name": "planName00",
|
||||
"product": "planProduct00",
|
||||
"publisher": "planPublisher00"
|
||||
},
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"disablePasswordAuthentication": true,
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"offer": "ignored00",
|
||||
"publisher": "ignored00",
|
||||
"sku": "ignored00",
|
||||
"version": "latest"
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"PlanInfo": "planName00",
|
||||
"PlanProduct": "planProduct00",
|
||||
"PlanPromotionCode": "",
|
||||
"PlanPublisher": "planPublisher00"
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"tags": {
|
||||
"PlanInfo": "planName00",
|
||||
"PlanProduct": "planProduct00",
|
||||
"PlanPromotionCode": "planPromotionCode00",
|
||||
"PlanPublisher": "planPublisher00",
|
||||
"dept": "engineering"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tags": {
|
||||
"PlanInfo": "planName00",
|
||||
"PlanProduct": "planProduct00",
|
||||
"PlanPromotionCode": "planPromotionCode00",
|
||||
"PlanPublisher": "planPublisher00",
|
||||
"dept": "engineering"
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tags": {
|
||||
"PlanInfo": "planName00",
|
||||
"PlanProduct": "planProduct00",
|
||||
"PlanPromotionCode": "planPromotionCode00",
|
||||
"PlanPublisher": "planPublisher00",
|
||||
"dept": "engineering"
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"plan": {
|
||||
"name": "planName00",
|
||||
"product": "planProduct00",
|
||||
"promotionCode": "planPromotionCode00",
|
||||
"publisher": "planPublisher00"
|
||||
},
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"disablePasswordAuthentication": true,
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"offer": "ignored00",
|
||||
"publisher": "ignored00",
|
||||
"sku": "ignored00",
|
||||
"version": "latest"
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"PlanInfo": "planName00",
|
||||
"PlanProduct": "planProduct00",
|
||||
"PlanPromotionCode": "planPromotionCode00",
|
||||
"PlanPublisher": "planPublisher00",
|
||||
"dept": "engineering"
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminPassword": "[parameters('adminPassword')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"offer": "ImageOffer",
|
||||
"publisher": "ImagePublisher",
|
||||
"sku": "ImageSku",
|
||||
"version": "ImageVersion"
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"disablePasswordAuthentication": true,
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminPassword": "[parameters('adminPassword')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "virtualNetworkSubnetName",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "virtualNetworkName",
|
||||
"virtualNetworkResourceGroup": "virtualNetworkResourceGroupName",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"tags": {
|
||||
"tag01": "value01",
|
||||
"tag02": "value02",
|
||||
"tag03": "value03"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tags": {
|
||||
"tag01": "value01",
|
||||
"tag02": "value02",
|
||||
"tag03": "value03"
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tags": {
|
||||
"tag01": "value01",
|
||||
"tag02": "value02",
|
||||
"tag03": "value03"
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"disablePasswordAuthentication": true,
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"tag01": "value01",
|
||||
"tag02": "value02",
|
||||
"tag03": "value03"
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminPassword": "[parameters('adminPassword')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"customData": "I2Nsb3VkLWNvbmZpZwpncm93cGFydDoKICBtb2RlOiBvZmYK",
|
||||
"linuxConfiguration": {
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"image": {
|
||||
"uri": "https://localhost/custom.vhd"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"disablePasswordAuthentication": true,
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"id": ""
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Standard_LRS"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminPassword": "[parameters('adminPassword')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"offer": "--image-offer--",
|
||||
"publisher": "--image-publisher--",
|
||||
"sku": "--image-sku--",
|
||||
"version": "--version--"
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Standard_LRS"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"disablePasswordAuthentication": true,
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"offer": "--image-offer--",
|
||||
"publisher": "--image-publisher--",
|
||||
"sku": "--image-sku--",
|
||||
"version": "--version--"
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Standard_LRS"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "--virtual_network_subnet_name--",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "--virtual_network_name--",
|
||||
"virtualNetworkResourceGroup": "--virtual_network_resource_group_name--",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminPassword": "[parameters('adminPassword')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"dataDisks": [
|
||||
{
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "Empty",
|
||||
"diskSizeGB": 32,
|
||||
"lun": 0,
|
||||
"name": "[concat(parameters('dataDiskName'),'-1')]",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/',parameters('dataDiskName'),'-1','.vhd')]"
|
||||
}
|
||||
}
|
||||
],
|
||||
"imageReference": {
|
||||
"offer": "--image-offer--",
|
||||
"publisher": "--image-publisher--",
|
||||
"sku": "--image-sku--",
|
||||
"version": "--version--"
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"vhd": {
|
||||
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"disablePasswordAuthentication": true,
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"dataDisks": [
|
||||
{
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "Empty",
|
||||
"diskSizeGB": 32,
|
||||
"lun": 0,
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Standard_LRS"
|
||||
},
|
||||
"name": "[concat(parameters('dataDiskName'),'-1')]"
|
||||
}
|
||||
],
|
||||
"imageReference": {
|
||||
"offer": "--image-offer--",
|
||||
"publisher": "--image-publisher--",
|
||||
"sku": "--image-sku--",
|
||||
"version": "--version--"
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Standard_LRS"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminPassword": "[parameters('adminPassword')]",
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"secrets": [
|
||||
{
|
||||
"sourceVault": {
|
||||
"id": "[resourceId(resourceGroup().name, 'Microsoft.KeyVault/vaults', '--keyvault-name--')]"
|
||||
},
|
||||
"vaultCertificates": [
|
||||
{
|
||||
"certificateStore": "My",
|
||||
"certificateUrl": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"windowsConfiguration": {
|
||||
"provisionVMAgent": true,
|
||||
"winRM": {
|
||||
"listeners": [
|
||||
{
|
||||
"certificateUrl": "",
|
||||
"protocol": "https"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"offer": "--image-offer--",
|
||||
"publisher": "--image-publisher--",
|
||||
"sku": "--image-sku--",
|
||||
"version": "--version--"
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Standard_LRS"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Windows"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkSecurityGroupsApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nsgName')]",
|
||||
"properties": {
|
||||
"securityRules": [
|
||||
{
|
||||
"name": "AllowIPsToSshWinRMInbound",
|
||||
"properties": {
|
||||
"access": "Allow",
|
||||
"description": "Allow inbound traffic from specified IP addresses",
|
||||
"destinationAddressPrefix": "VirtualNetwork",
|
||||
"destinationPortRange": "5985",
|
||||
"direction": "Inbound",
|
||||
"priority": 100,
|
||||
"protocol": "Tcp",
|
||||
"sourceAddressPrefixes": [
|
||||
"127.0.0.1",
|
||||
"192.168.100.0/24"
|
||||
],
|
||||
"sourcePortRange": "*"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkSecurityGroups"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkSecurityGroups/', parameters('nsgName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]",
|
||||
"networkSecurityGroup": {
|
||||
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('nsgName'))]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dnsNameForPublicIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicIPAddressName": {
|
||||
"type": "string"
|
||||
},
|
||||
"storageAccountBlobEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "[variables('publicIPAddressApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('publicIPAddressName')]",
|
||||
"properties": {
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
|
||||
},
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
|
||||
},
|
||||
"type": "Microsoft.Network/publicIPAddresses"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('virtualNetworksApiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"name": "[variables('virtualNetworkName')]",
|
||||
"properties": {
|
||||
"addressSpace": {
|
||||
"addressPrefixes": [
|
||||
"[variables('addressPrefix')]"
|
||||
]
|
||||
},
|
||||
"subnets": [
|
||||
{
|
||||
"name": "[variables('subnetName')]",
|
||||
"properties": {
|
||||
"addressPrefix": "[variables('subnetAddressPrefix')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/virtualNetworks"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('networkInterfacesApiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
|
||||
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('nicName')]",
|
||||
"properties": {
|
||||
"ipConfigurations": [
|
||||
{
|
||||
"name": "ipconfig",
|
||||
"properties": {
|
||||
"privateIPAllocationMethod": "Dynamic",
|
||||
"publicIPAddress": {
|
||||
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
|
||||
},
|
||||
"subnet": {
|
||||
"id": "[variables('subnetRef')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "Microsoft.Network/networkInterfaces"
|
||||
},
|
||||
{
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
|
||||
],
|
||||
"location": "[variables('location')]",
|
||||
"name": "[parameters('vmName')]",
|
||||
"properties": {
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": true,
|
||||
"storageUri": "https://diagstgaccnt.blob.core.windows.net"
|
||||
}
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"vmSize": "[parameters('vmSize')]"
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"adminUsername": "[parameters('adminUsername')]",
|
||||
"computerName": "[parameters('vmName')]",
|
||||
"linuxConfiguration": {
|
||||
"disablePasswordAuthentication": true,
|
||||
"ssh": {
|
||||
"publicKeys": [
|
||||
{
|
||||
"keyData": "",
|
||||
"path": "[variables('sshKeyPath')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"id": ""
|
||||
},
|
||||
"osDisk": {
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage",
|
||||
"managedDisk": {
|
||||
"storageAccountType": "Standard_LRS"
|
||||
},
|
||||
"name": "[parameters('osDiskName')]",
|
||||
"osType": "Linux"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
],
|
||||
"variables": {
|
||||
"addressPrefix": "10.0.0.0/16",
|
||||
"apiVersion": "2017-03-30",
|
||||
"location": "[resourceGroup().location]",
|
||||
"managedDiskApiVersion": "2017-03-30",
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
"subnetAddressPrefix": "10.0.0.0/24",
|
||||
"subnetName": "[parameters('subnetName')]",
|
||||
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
|
||||
"virtualNetworkName": "[parameters('virtualNetworkName')]",
|
||||
"virtualNetworkResourceGroup": "[resourceGroup().name]",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"vmStorageAccountContainerName": "images",
|
||||
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,659 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources"
|
||||
approvaltests "github.com/approvals/go-approval-tests"
|
||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||
"github.com/hashicorp/packer/builder/azure/common/template"
|
||||
)
|
||||
|
||||
// Ensure the link values are not set, and the concrete values are set.
|
||||
func TestVirtualMachineDeployment00(t *testing.T) {
|
||||
var c Config
|
||||
c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if deployment.Properties.Mode != resources.Incremental {
|
||||
t.Errorf("Expected deployment.Properties.Mode to be %s, but got %s", resources.Incremental, deployment.Properties.Mode)
|
||||
}
|
||||
|
||||
if deployment.Properties.ParametersLink != nil {
|
||||
t.Error("Expected the ParametersLink to be nil!")
|
||||
}
|
||||
|
||||
if deployment.Properties.TemplateLink != nil {
|
||||
t.Error("Expected the TemplateLink to be nil!")
|
||||
}
|
||||
|
||||
if deployment.Properties.Parameters == nil {
|
||||
t.Error("Expected the Parameters to not be nil!")
|
||||
}
|
||||
|
||||
if deployment.Properties.Template == nil {
|
||||
t.Error("Expected the Template to not be nil!")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the Virtual Machine template is a valid JSON document.
|
||||
func TestVirtualMachineDeployment01(t *testing.T) {
|
||||
var c Config
|
||||
c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = json.Marshal(deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the Virtual Machine template parameters are correct.
|
||||
func TestVirtualMachineDeployment02(t *testing.T) {
|
||||
var c Config
|
||||
c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(deployment.Properties.Parameters)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var params template.TemplateParameters
|
||||
err = json.Unmarshal(bs, ¶ms)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if params.AdminUsername.Value != c.UserName {
|
||||
t.Errorf("Expected template parameter 'AdminUsername' to be %s, but got %s.", params.AdminUsername.Value, c.UserName)
|
||||
}
|
||||
if params.AdminPassword.Value != c.tmpAdminPassword {
|
||||
t.Errorf("Expected template parameter 'AdminPassword' to be %s, but got %s.", params.AdminPassword.Value, c.tmpAdminPassword)
|
||||
}
|
||||
if params.DnsNameForPublicIP.Value != c.tmpComputeName {
|
||||
t.Errorf("Expected template parameter 'DnsNameForPublicIP' to be %s, but got %s.", params.DnsNameForPublicIP.Value, c.tmpComputeName)
|
||||
}
|
||||
if params.OSDiskName.Value != c.tmpOSDiskName {
|
||||
t.Errorf("Expected template parameter 'OSDiskName' to be %s, but got %s.", params.OSDiskName.Value, c.tmpOSDiskName)
|
||||
}
|
||||
if params.StorageAccountBlobEndpoint.Value != c.storageAccountBlobEndpoint {
|
||||
t.Errorf("Expected template parameter 'StorageAccountBlobEndpoint' to be %s, but got %s.", params.StorageAccountBlobEndpoint.Value, c.storageAccountBlobEndpoint)
|
||||
}
|
||||
if params.VMSize.Value != c.VMSize {
|
||||
t.Errorf("Expected template parameter 'VMSize' to be %s, but got %s.", params.VMSize.Value, c.VMSize)
|
||||
}
|
||||
if params.VMName.Value != c.tmpComputeName {
|
||||
t.Errorf("Expected template parameter 'VMName' to be %s, but got %s.", params.VMName.Value, c.tmpComputeName)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the VM template is correct when using a market place image.
|
||||
func TestVirtualMachineDeployment03(t *testing.T) {
|
||||
m := getArmBuilderConfiguration()
|
||||
m["image_publisher"] = "ImagePublisher"
|
||||
m["image_offer"] = "ImageOffer"
|
||||
m["image_sku"] = "ImageSku"
|
||||
m["image_version"] = "ImageVersion"
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(m, getPackerConfiguration(), getPackerSSHPasswordCommunicatorConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the VM template is correct when using a custom image.
|
||||
func TestVirtualMachineDeployment04(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "https://localhost/custom.vhd",
|
||||
"resource_group_name": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVirtualMachineDeployment05(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "https://localhost/custom.vhd",
|
||||
"resource_group_name": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"virtual_network_name": "virtualNetworkName",
|
||||
"virtual_network_resource_group_name": "virtualNetworkResourceGroupName",
|
||||
"virtual_network_subnet_name": "virtualNetworkSubnetName",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration(), getPackerSSHPasswordCommunicatorConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that tags are properly applied to every resource
|
||||
func TestVirtualMachineDeployment06(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "https://localhost/custom.vhd",
|
||||
"resource_group_name": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"azure_tags": map[string]string{
|
||||
"tag01": "value01",
|
||||
"tag02": "value02",
|
||||
"tag03": "value03",
|
||||
},
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that custom data are properly inserted
|
||||
func TestVirtualMachineDeployment07(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"capture_name_prefix": "ignore",
|
||||
"capture_container_name": "ignore",
|
||||
"location": "ignore",
|
||||
"image_url": "https://localhost/custom.vhd",
|
||||
"resource_group_name": "ignore",
|
||||
"storage_account": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration(), getPackerSSHPasswordCommunicatorConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// The user specifies a configuration value for the setting custom_data_file.
|
||||
// The config type will read that file, and base64 encode it. The encoded
|
||||
// contents are then assigned to Config's customData property, which are directly
|
||||
// injected into the template.
|
||||
//
|
||||
// I am not aware of an easy to mimic this situation in a test without having
|
||||
// a file on disk, which I am loathe to do. The alternative is to inject base64
|
||||
// encoded data myself, which is what I am doing here.
|
||||
customData := `#cloud-config
|
||||
growpart:
|
||||
mode: off
|
||||
`
|
||||
base64CustomData := base64.StdEncoding.EncodeToString([]byte(customData))
|
||||
c.customData = base64CustomData
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the VM template is correct when building from a custom managed image.
|
||||
func TestVirtualMachineDeployment08(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"custom_managed_image_resource_group_name": "CustomManagedImageResourceGroupName",
|
||||
"custom_managed_image_name": "CustomManagedImageName",
|
||||
"managed_image_name": "ManagedImageName",
|
||||
"managed_image_resource_group_name": "ManagedImageResourceGroupName",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the VM template is correct when building from a platform managed image.
|
||||
func TestVirtualMachineDeployment09(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"image_publisher": "--image-publisher--",
|
||||
"image_offer": "--image-offer--",
|
||||
"image_sku": "--image-sku--",
|
||||
"image_version": "--version--",
|
||||
"managed_image_name": "ManagedImageName",
|
||||
"managed_image_resource_group_name": "ManagedImageResourceGroupName",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration(), getPackerSSHPasswordCommunicatorConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the VM template is correct when building with PublicIp and connect to Private Network
|
||||
func TestVirtualMachineDeployment10(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"image_publisher": "--image-publisher--",
|
||||
"image_offer": "--image-offer--",
|
||||
"image_sku": "--image-sku--",
|
||||
"image_version": "--version--",
|
||||
|
||||
"virtual_network_resource_group_name": "--virtual_network_resource_group_name--",
|
||||
"virtual_network_name": "--virtual_network_name--",
|
||||
"virtual_network_subnet_name": "--virtual_network_subnet_name--",
|
||||
"private_virtual_network_with_public_ip": true,
|
||||
|
||||
"managed_image_name": "ManagedImageName",
|
||||
"managed_image_resource_group_name": "ManagedImageResourceGroupName",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the VM template is correct when building with additional unmanaged disks
|
||||
func TestVirtualMachineDeployment11(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"image_publisher": "--image-publisher--",
|
||||
"image_offer": "--image-offer--",
|
||||
"image_sku": "--image-sku--",
|
||||
"image_version": "--version--",
|
||||
|
||||
"disk_additional_size": []uint{32},
|
||||
|
||||
"resource_group_name": "packergroup",
|
||||
"storage_account": "packerartifacts",
|
||||
"capture_name_prefix": "packer",
|
||||
"capture_container_name": "packerimages",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration(), getPackerSSHPasswordCommunicatorConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the VM template is correct when building with additional managed disks
|
||||
func TestVirtualMachineDeployment12(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"image_publisher": "--image-publisher--",
|
||||
"image_offer": "--image-offer--",
|
||||
"image_sku": "--image-sku--",
|
||||
"image_version": "--version--",
|
||||
|
||||
"disk_additional_size": []uint{32},
|
||||
|
||||
"managed_image_name": "ManagedImageName",
|
||||
"managed_image_resource_group_name": "ManagedImageResourceGroupName",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the VM template is correct when building with list of allowed IP addresses
|
||||
func TestVirtualMachineDeployment13(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Windows,
|
||||
"communicator": "winrm",
|
||||
"winrm_username": "ignore",
|
||||
"image_publisher": "--image-publisher--",
|
||||
"image_offer": "--image-offer--",
|
||||
"image_sku": "--image-sku--",
|
||||
"image_version": "--version--",
|
||||
"managed_image_name": "ManagedImageName",
|
||||
"managed_image_resource_group_name": "ManagedImageResourceGroupName",
|
||||
"allowed_inbound_ip_addresses": []string{"127.0.0.1", "192.168.100.0/24"},
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.tmpKeyVaultName = "--keyvault-name--"
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the VM template is correct when building with bootdiagnostics
|
||||
func TestVirtualMachineDeployment14(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"location": "ignore",
|
||||
"subscription_id": "ignore",
|
||||
"os_type": constants.Target_Linux,
|
||||
"communicator": "none",
|
||||
"custom_managed_image_resource_group_name": "CustomManagedImageResourceGroupName",
|
||||
"custom_managed_image_name": "CustomManagedImageName",
|
||||
"managed_image_name": "ManagedImageName",
|
||||
"managed_image_resource_group_name": "ManagedImageResourceGroupName",
|
||||
"boot_diag_storage_account": "diagstgaccnt",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the link values are not set, and the concrete values are set.
|
||||
func TestKeyVaultDeployment00(t *testing.T) {
|
||||
var c Config
|
||||
c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
deployment, err := GetKeyVaultDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if deployment.Properties.Mode != resources.Incremental {
|
||||
t.Errorf("Expected deployment.Properties.Mode to be %s, but got %s", resources.Incremental, deployment.Properties.Mode)
|
||||
}
|
||||
|
||||
if deployment.Properties.ParametersLink != nil {
|
||||
t.Error("Expected the ParametersLink to be nil!")
|
||||
}
|
||||
|
||||
if deployment.Properties.TemplateLink != nil {
|
||||
t.Error("Expected the TemplateLink to be nil!")
|
||||
}
|
||||
|
||||
if deployment.Properties.Parameters == nil {
|
||||
t.Error("Expected the Parameters to not be nil!")
|
||||
}
|
||||
|
||||
if deployment.Properties.Template == nil {
|
||||
t.Error("Expected the Template to not be nil!")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the KeyVault template is a valid JSON document.
|
||||
func TestKeyVaultDeployment01(t *testing.T) {
|
||||
var c Config
|
||||
c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
deployment, err := GetKeyVaultDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = json.Marshal(deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the KeyVault template parameters are correct.
|
||||
func TestKeyVaultDeployment02(t *testing.T) {
|
||||
var c Config
|
||||
c.Prepare(getArmBuilderConfigurationWithWindows(), getPackerConfiguration())
|
||||
|
||||
deployment, err := GetKeyVaultDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(deployment.Properties.Parameters)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var params template.TemplateParameters
|
||||
err = json.Unmarshal(bs, ¶ms)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if params.ObjectId.Value != c.ClientConfig.ObjectID {
|
||||
t.Errorf("Expected template parameter 'ObjectId' to be %s, but got %s.", params.ObjectId.Value, c.ClientConfig.ObjectID)
|
||||
}
|
||||
if params.TenantId.Value != c.ClientConfig.TenantID {
|
||||
t.Errorf("Expected template parameter 'TenantId' to be %s, but got %s.", params.TenantId.Value, c.ClientConfig.TenantID)
|
||||
}
|
||||
if params.KeyVaultName.Value != c.tmpKeyVaultName {
|
||||
t.Errorf("Expected template parameter 'KeyVaultName' to be %s, but got %s.", params.KeyVaultName.Value, c.tmpKeyVaultName)
|
||||
}
|
||||
if params.KeyVaultSecretValue.Value != c.winrmCertificate {
|
||||
t.Errorf("Expected template parameter 'KeyVaultSecretValue' to be %s, but got %s.", params.KeyVaultSecretValue.Value, c.winrmCertificate)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the KeyVault template is correct when tags are supplied.
|
||||
func TestKeyVaultDeployment03(t *testing.T) {
|
||||
tags := map[string]interface{}{
|
||||
"azure_tags": map[string]string{
|
||||
"tag01": "value01",
|
||||
"tag02": "value02",
|
||||
"tag03": "value03",
|
||||
},
|
||||
}
|
||||
|
||||
var c Config
|
||||
c.Prepare(tags, getArmBuilderConfigurationWithWindows(), getPackerConfiguration())
|
||||
deployment, err := GetKeyVaultDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanInfo01(t *testing.T) {
|
||||
planInfo := map[string]interface{}{
|
||||
"plan_info": map[string]string{
|
||||
"plan_name": "planName00",
|
||||
"plan_product": "planProduct00",
|
||||
"plan_publisher": "planPublisher00",
|
||||
},
|
||||
}
|
||||
|
||||
var c Config
|
||||
c.Prepare(planInfo, getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanInfo02(t *testing.T) {
|
||||
planInfo := map[string]interface{}{
|
||||
"azure_tags": map[string]string{
|
||||
"dept": "engineering",
|
||||
},
|
||||
"plan_info": map[string]string{
|
||||
"plan_name": "planName00",
|
||||
"plan_product": "planProduct00",
|
||||
"plan_publisher": "planPublisher00",
|
||||
"plan_promotion_code": "planPromotionCode00",
|
||||
},
|
||||
}
|
||||
|
||||
var c Config
|
||||
c.Prepare(planInfo, getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
deployment, err := GetVirtualMachineDeployment(&c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/random"
|
||||
)
|
||||
|
||||
type TempName struct {
|
||||
AdminPassword string
|
||||
CertificatePassword string
|
||||
ComputeName string
|
||||
DeploymentName string
|
||||
KeyVaultName string
|
||||
ResourceGroupName string
|
||||
OSDiskName string
|
||||
DataDiskName string
|
||||
NicName string
|
||||
SubnetName string
|
||||
PublicIPAddressName string
|
||||
VirtualNetworkName string
|
||||
NsgName string
|
||||
}
|
||||
|
||||
func NewTempName(p string) *TempName {
|
||||
tempName := &TempName{}
|
||||
|
||||
suffix := random.AlphaNumLower(5)
|
||||
if p == "" {
|
||||
p = "pkr"
|
||||
suffix = random.AlphaNumLower(10)
|
||||
}
|
||||
|
||||
tempName.ComputeName = fmt.Sprintf("%svm%s", p, suffix)
|
||||
tempName.DeploymentName = fmt.Sprintf("%sdp%s", p, suffix)
|
||||
tempName.KeyVaultName = fmt.Sprintf("%skv%s", p, suffix)
|
||||
tempName.OSDiskName = fmt.Sprintf("%sos%s", p, suffix)
|
||||
tempName.DataDiskName = fmt.Sprintf("%sdd%s", p, suffix)
|
||||
tempName.NicName = fmt.Sprintf("%sni%s", p, suffix)
|
||||
tempName.PublicIPAddressName = fmt.Sprintf("%sip%s", p, suffix)
|
||||
tempName.SubnetName = fmt.Sprintf("%ssn%s", p, suffix)
|
||||
tempName.VirtualNetworkName = fmt.Sprintf("%svn%s", p, suffix)
|
||||
tempName.NsgName = fmt.Sprintf("%ssg%s", p, suffix)
|
||||
tempName.ResourceGroupName = fmt.Sprintf("%s-Resource-Group-%s", p, suffix)
|
||||
|
||||
tempName.AdminPassword = generatePassword()
|
||||
tempName.CertificatePassword = random.AlphaNum(32)
|
||||
|
||||
return tempName
|
||||
}
|
||||
|
||||
// generate a password that is acceptable to Azure
|
||||
// Three of the four items must be met.
|
||||
// 1. Contains an uppercase character
|
||||
// 2. Contains a lowercase character
|
||||
// 3. Contains a numeric digit
|
||||
// 4. Contains a special character
|
||||
func generatePassword() string {
|
||||
var s string
|
||||
for i := 0; i < 100; i++ {
|
||||
s := random.AlphaNum(32)
|
||||
if !strings.ContainsAny(s, random.PossibleNumbers) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.ContainsAny(s, random.PossibleLowerCase) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.ContainsAny(s, random.PossibleUpperCase) {
|
||||
continue
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// if an acceptable password cannot be generated in 100 tries, give up
|
||||
return s
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/random"
|
||||
)
|
||||
|
||||
func TestTempNameShouldCreatePrefixedRandomNames(t *testing.T) {
|
||||
tempName := NewTempName("")
|
||||
|
||||
if strings.Index(tempName.ComputeName, "pkrvm") != 0 {
|
||||
t.Errorf("Expected ComputeName to begin with 'pkrvm', but got '%s'!", tempName.ComputeName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.DeploymentName, "pkrdp") != 0 {
|
||||
t.Errorf("Expected ComputeName to begin with 'pkrdp', but got '%s'!", tempName.ComputeName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.OSDiskName, "pkros") != 0 {
|
||||
t.Errorf("Expected OSDiskName to begin with 'pkros', but got '%s'!", tempName.OSDiskName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.NicName, "pkrni") != 0 {
|
||||
t.Errorf("Expected NicName to begin with 'pkrni', but got '%s'!", tempName.NicName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.PublicIPAddressName, "pkrip") != 0 {
|
||||
t.Errorf("Expected PublicIPAddressName to begin with 'pkrip', but got '%s'!", tempName.PublicIPAddressName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.ResourceGroupName, "pkr-Resource-Group-") != 0 {
|
||||
t.Errorf("Expected ResourceGroupName to begin with 'pkr-Resource-Group-', but got '%s'!", tempName.ResourceGroupName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.SubnetName, "pkrsn") != 0 {
|
||||
t.Errorf("Expected SubnetName to begin with 'pkrip', but got '%s'!", tempName.SubnetName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.VirtualNetworkName, "pkrvn") != 0 {
|
||||
t.Errorf("Expected VirtualNetworkName to begin with 'pkrvn', but got '%s'!", tempName.VirtualNetworkName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.NsgName, "pkrsg") != 0 {
|
||||
t.Errorf("Expected NsgName to begin with 'pkrsg', but got '%s'!", tempName.NsgName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTempAdminPassword(t *testing.T) {
|
||||
tempName := NewTempName("")
|
||||
|
||||
if !strings.ContainsAny(tempName.AdminPassword, random.PossibleNumbers) {
|
||||
t.Errorf("Expected AdminPassword to contain at least one of '%s'!", random.PossibleNumbers)
|
||||
}
|
||||
if !strings.ContainsAny(tempName.AdminPassword, random.PossibleLowerCase) {
|
||||
t.Errorf("Expected AdminPassword to contain at least one of '%s'!", random.PossibleLowerCase)
|
||||
}
|
||||
if !strings.ContainsAny(tempName.AdminPassword, random.PossibleUpperCase) {
|
||||
t.Errorf("Expected AdminPassword to contain at least one of '%s'!", random.PossibleUpperCase)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTempNameShouldHaveSameSuffix(t *testing.T) {
|
||||
tempName := NewTempName("")
|
||||
suffix := tempName.ComputeName[5:]
|
||||
|
||||
if strings.HasSuffix(tempName.ComputeName, suffix) != true {
|
||||
t.Errorf("Expected ComputeName to end with '%s', but the value is '%s'!", suffix, tempName.ComputeName)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(tempName.DeploymentName, suffix) != true {
|
||||
t.Errorf("Expected DeploymentName to end with '%s', but the value is '%s'!", suffix, tempName.DeploymentName)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(tempName.OSDiskName, suffix) != true {
|
||||
t.Errorf("Expected OSDiskName to end with '%s', but the value is '%s'!", suffix, tempName.OSDiskName)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(tempName.NicName, suffix) != true {
|
||||
t.Errorf("Expected NicName to end with '%s', but the value is '%s'!", suffix, tempName.PublicIPAddressName)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(tempName.PublicIPAddressName, suffix) != true {
|
||||
t.Errorf("Expected PublicIPAddressName to end with '%s', but the value is '%s'!", suffix, tempName.PublicIPAddressName)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(tempName.ResourceGroupName, suffix) != true {
|
||||
t.Errorf("Expected ResourceGroupName to end with '%s', but the value is '%s'!", suffix, tempName.ResourceGroupName)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(tempName.SubnetName, suffix) != true {
|
||||
t.Errorf("Expected SubnetName to end with '%s', but the value is '%s'!", suffix, tempName.SubnetName)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(tempName.VirtualNetworkName, suffix) != true {
|
||||
t.Errorf("Expected VirtualNetworkName to end with '%s', but the value is '%s'!", suffix, tempName.VirtualNetworkName)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(tempName.NsgName, suffix) != true {
|
||||
t.Errorf("Expected NsgName to end with '%s', but the value is '%s'!", suffix, tempName.NsgName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTempNameShouldCreateCustomPrefix(t *testing.T) {
|
||||
tempName := NewTempName("CustPrefix")
|
||||
|
||||
if strings.Index(tempName.ComputeName, "CustPrefixvm") != 0 {
|
||||
t.Errorf("Expected ComputeName to begin with 'CustPrefixvm', but got '%s'!", tempName.ComputeName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.DeploymentName, "CustPrefixdp") != 0 {
|
||||
t.Errorf("Expected ComputeName to begin with 'CustPrefixdp', but got '%s'!", tempName.ComputeName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.OSDiskName, "CustPrefixos") != 0 {
|
||||
t.Errorf("Expected OSDiskName to begin with 'CustPrefixos', but got '%s'!", tempName.OSDiskName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.NicName, "CustPrefixni") != 0 {
|
||||
t.Errorf("Expected NicName to begin with 'CustPrefixni', but got '%s'!", tempName.NicName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.PublicIPAddressName, "CustPrefixip") != 0 {
|
||||
t.Errorf("Expected PublicIPAddressName to begin with 'CustPrefixip', but got '%s'!", tempName.PublicIPAddressName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.ResourceGroupName, "CustPrefix-Resource-Group-") != 0 {
|
||||
t.Errorf("Expected ResourceGroupName to begin with 'packer-Resource-Group-', but got '%s'!", tempName.ResourceGroupName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.SubnetName, "CustPrefixsn") != 0 {
|
||||
t.Errorf("Expected SubnetName to begin with 'pkrip', but got '%s'!", tempName.SubnetName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.VirtualNetworkName, "CustPrefixvn") != 0 {
|
||||
t.Errorf("Expected VirtualNetworkName to begin with 'CustPrefixvn', but got '%s'!", tempName.VirtualNetworkName)
|
||||
}
|
||||
|
||||
if strings.Index(tempName.NsgName, "CustPrefixsg") != 0 {
|
||||
t.Errorf("Expected NsgName to begin with 'CustPrefixsg', but got '%s'!", tempName.NsgName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,622 @@
|
|||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
|
||||
// Package chroot is able to create an Azure managed image without requiring the
|
||||
// launch of a new virtual machine for every build. It does this by attaching and
|
||||
// mounting the root disk and chrooting into that directory.
|
||||
// It then creates a managed image from that attached disk.
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer-plugin-sdk/chroot"
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
azcommon "github.com/hashicorp/packer/builder/azure/common"
|
||||
"github.com/hashicorp/packer/builder/azure/common/client"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// BuilderID is the unique ID for this builder
|
||||
const BuilderID = "azure.chroot"
|
||||
|
||||
// Config is the configuration that is chained through the steps and settable
|
||||
// from the template.
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
ClientConfig client.Config `mapstructure:",squash"`
|
||||
|
||||
// When set to `true`, starts with an empty, unpartitioned disk. Defaults to `false`.
|
||||
FromScratch bool `mapstructure:"from_scratch"`
|
||||
// One of the following can be used as a source for an image:
|
||||
// - a shared image version resource ID
|
||||
// - a managed disk resource ID
|
||||
// - a publisher:offer:sku:version specifier for plaform image sources.
|
||||
Source string `mapstructure:"source" required:"true"`
|
||||
sourceType sourceType
|
||||
|
||||
// How to run shell commands. This may be useful to set environment variables or perhaps run
|
||||
// a command with sudo or so on. This is a configuration template where the `.Command` variable
|
||||
// is replaced with the command to be run. Defaults to `{{.Command}}`.
|
||||
CommandWrapper string `mapstructure:"command_wrapper"`
|
||||
// A series of commands to execute after attaching the root volume and before mounting the chroot.
|
||||
// This is not required unless using `from_scratch`. If so, this should include any partitioning
|
||||
// and filesystem creation commands. The path to the device is provided by `{{.Device}}`.
|
||||
PreMountCommands []string `mapstructure:"pre_mount_commands"`
|
||||
// Options to supply the `mount` command when mounting devices. Each option will be prefixed with
|
||||
// `-o` and supplied to the `mount` command ran by Packer. Because this command is ran in a shell,
|
||||
// user discretion is advised. See this manual page for the `mount` command for valid file system specific options.
|
||||
MountOptions []string `mapstructure:"mount_options"`
|
||||
// The partition number containing the / partition. By default this is the first partition of the volume.
|
||||
MountPartition string `mapstructure:"mount_partition"`
|
||||
// The path where the volume will be mounted. This is where the chroot environment will be. This defaults
|
||||
// to `/mnt/packer-amazon-chroot-volumes/{{.Device}}`. This is a configuration template where the `.Device`
|
||||
// variable is replaced with the name of the device where the volume is attached.
|
||||
MountPath string `mapstructure:"mount_path"`
|
||||
// As `pre_mount_commands`, but the commands are executed after mounting the root device and before the
|
||||
// extra mount and copy steps. The device and mount path are provided by `{{.Device}}` and `{{.MountPath}}`.
|
||||
PostMountCommands []string `mapstructure:"post_mount_commands"`
|
||||
// This is a list of devices to mount into the chroot environment. This configuration parameter requires
|
||||
// some additional documentation which is in the "Chroot Mounts" section below. Please read that section
|
||||
// for more information on how to use this.
|
||||
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
|
||||
// Paths to files on the running Azure instance that will be copied into the chroot environment prior to
|
||||
// provisioning. Defaults to `/etc/resolv.conf` so that DNS lookups work. Pass an empty list to skip copying
|
||||
// `/etc/resolv.conf`. You may need to do this if you're building an image that uses systemd.
|
||||
CopyFiles []string `mapstructure:"copy_files"`
|
||||
|
||||
// Try to resize the OS disk to this size on the first copy. Disks can only be englarged. If not specified,
|
||||
// the disk will keep its original size. Required when using `from_scratch`
|
||||
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb"`
|
||||
// The [storage SKU](https://docs.microsoft.com/en-us/rest/api/compute/disks/createorupdate#diskstorageaccounttypes)
|
||||
// to use for the OS Disk. Defaults to `Standard_LRS`.
|
||||
OSDiskStorageAccountType string `mapstructure:"os_disk_storage_account_type"`
|
||||
// The [cache type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#cachingtypes)
|
||||
// specified in the resulting image and for attaching it to the Packer VM. Defaults to `ReadOnly`
|
||||
OSDiskCacheType string `mapstructure:"os_disk_cache_type"`
|
||||
|
||||
// The [storage SKU](https://docs.microsoft.com/en-us/rest/api/compute/disks/createorupdate#diskstorageaccounttypes)
|
||||
// to use for datadisks. Defaults to `Standard_LRS`.
|
||||
DataDiskStorageAccountType string `mapstructure:"data_disk_storage_account_type"`
|
||||
// The [cache type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#cachingtypes)
|
||||
// specified in the resulting image and for attaching it to the Packer VM. Defaults to `ReadOnly`
|
||||
DataDiskCacheType string `mapstructure:"data_disk_cache_type"`
|
||||
|
||||
// The [Hyper-V generation type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#hypervgenerationtypes) for Managed Image output.
|
||||
// Defaults to `V1`.
|
||||
ImageHyperVGeneration string `mapstructure:"image_hyperv_generation"`
|
||||
|
||||
// The id of the temporary OS disk that will be created. Will be generated if not set.
|
||||
TemporaryOSDiskID string `mapstructure:"temporary_os_disk_id"`
|
||||
|
||||
// The id of the temporary OS disk snapshot that will be created. Will be generated if not set.
|
||||
TemporaryOSDiskSnapshotID string `mapstructure:"temporary_os_disk_snapshot_id"`
|
||||
|
||||
// The prefix for the resource ids of the temporary data disks that will be created. The disks will be suffixed with a number. Will be generated if not set.
|
||||
TemporaryDataDiskIDPrefix string `mapstructure:"temporary_data_disk_id_prefix"`
|
||||
|
||||
// The prefix for the resource ids of the temporary data disk snapshots that will be created. The snapshots will be suffixed with a number. Will be generated if not set.
|
||||
TemporaryDataDiskSnapshotIDPrefix string `mapstructure:"temporary_data_disk_snapshot_id"`
|
||||
|
||||
// If set to `true`, leaves the temporary disks and snapshots behind in the Packer VM resource group. Defaults to `false`
|
||||
SkipCleanup bool `mapstructure:"skip_cleanup"`
|
||||
|
||||
// The managed image to create using this build.
|
||||
ImageResourceID string `mapstructure:"image_resource_id"`
|
||||
|
||||
// The shared image to create using this build.
|
||||
SharedImageGalleryDestination SharedImageGalleryDestination `mapstructure:"shared_image_destination"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type sourceType string
|
||||
|
||||
const (
|
||||
sourcePlatformImage sourceType = "PlatformImage"
|
||||
sourceDisk sourceType = "Disk"
|
||||
sourceSharedImage sourceType = "SharedImage"
|
||||
)
|
||||
|
||||
// GetContext implements ContextProvider to allow steps to use the config context
|
||||
// for template interpolation
|
||||
func (c *Config) GetContext() interpolate.Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
// verify interface implementation
|
||||
var _ packersdk.Builder = &Builder{}
|
||||
|
||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
b.config.ctx.Funcs = azcommon.TemplateFuncs
|
||||
b.config.ctx.Funcs["vm"] = CreateVMMetadataTemplateFunc()
|
||||
md := &mapstructure.Metadata{}
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
PluginType: BuilderID,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
// these fields are interpolated in the steps,
|
||||
// when more information is available
|
||||
"command_wrapper",
|
||||
"post_mount_commands",
|
||||
"pre_mount_commands",
|
||||
"mount_path",
|
||||
},
|
||||
},
|
||||
Metadata: md,
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var errs *packersdk.MultiError
|
||||
var warns []string
|
||||
|
||||
// Defaults
|
||||
err = b.config.ClientConfig.SetDefaultValues()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if b.config.ChrootMounts == nil {
|
||||
b.config.ChrootMounts = make([][]string, 0)
|
||||
}
|
||||
|
||||
if len(b.config.ChrootMounts) == 0 {
|
||||
b.config.ChrootMounts = [][]string{
|
||||
{"proc", "proc", "/proc"},
|
||||
{"sysfs", "sysfs", "/sys"},
|
||||
{"bind", "/dev", "/dev"},
|
||||
{"devpts", "devpts", "/dev/pts"},
|
||||
{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
|
||||
}
|
||||
}
|
||||
|
||||
// set default copy file if we're not giving our own
|
||||
if b.config.CopyFiles == nil {
|
||||
if !b.config.FromScratch {
|
||||
b.config.CopyFiles = []string{"/etc/resolv.conf"}
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.CommandWrapper == "" {
|
||||
b.config.CommandWrapper = "{{.Command}}"
|
||||
}
|
||||
|
||||
if b.config.MountPath == "" {
|
||||
b.config.MountPath = "/mnt/packer-azure-chroot-disks/{{.Device}}"
|
||||
}
|
||||
|
||||
if b.config.MountPartition == "" {
|
||||
b.config.MountPartition = "1"
|
||||
}
|
||||
|
||||
if b.config.TemporaryOSDiskID == "" {
|
||||
if def, err := interpolate.Render(
|
||||
"/subscriptions/{{ vm `subscription_id` }}/resourceGroups/{{ vm `resource_group` }}/providers/Microsoft.Compute/disks/PackerTemp-osdisk-{{timestamp}}",
|
||||
&b.config.ctx); err == nil {
|
||||
b.config.TemporaryOSDiskID = def
|
||||
} else {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary disk id: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.TemporaryOSDiskSnapshotID == "" {
|
||||
if def, err := interpolate.Render(
|
||||
"/subscriptions/{{ vm `subscription_id` }}/resourceGroups/{{ vm `resource_group` }}/providers/Microsoft.Compute/snapshots/PackerTemp-osdisk-snapshot-{{timestamp}}",
|
||||
&b.config.ctx); err == nil {
|
||||
b.config.TemporaryOSDiskSnapshotID = def
|
||||
} else {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary snapshot id: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.TemporaryDataDiskIDPrefix == "" {
|
||||
if def, err := interpolate.Render(
|
||||
"/subscriptions/{{ vm `subscription_id` }}/resourceGroups/{{ vm `resource_group` }}/providers/Microsoft.Compute/disks/PackerTemp-datadisk-{{timestamp}}-",
|
||||
&b.config.ctx); err == nil {
|
||||
b.config.TemporaryDataDiskIDPrefix = def
|
||||
} else {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary data disk id prefix: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.TemporaryDataDiskSnapshotIDPrefix == "" {
|
||||
if def, err := interpolate.Render(
|
||||
"/subscriptions/{{ vm `subscription_id` }}/resourceGroups/{{ vm `resource_group` }}/providers/Microsoft.Compute/snapshots/PackerTemp-datadisk-snapshot-{{timestamp}}-",
|
||||
&b.config.ctx); err == nil {
|
||||
b.config.TemporaryDataDiskSnapshotIDPrefix = def
|
||||
} else {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary data disk snapshot id prefix: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.OSDiskStorageAccountType == "" {
|
||||
b.config.OSDiskStorageAccountType = string(compute.PremiumLRS)
|
||||
}
|
||||
|
||||
if b.config.OSDiskCacheType == "" {
|
||||
b.config.OSDiskCacheType = string(compute.CachingTypesReadOnly)
|
||||
}
|
||||
|
||||
if b.config.DataDiskStorageAccountType == "" {
|
||||
b.config.DataDiskStorageAccountType = string(compute.PremiumLRS)
|
||||
}
|
||||
|
||||
if b.config.DataDiskCacheType == "" {
|
||||
b.config.DataDiskCacheType = string(compute.CachingTypesReadOnly)
|
||||
}
|
||||
|
||||
if b.config.ImageHyperVGeneration == "" {
|
||||
b.config.ImageHyperVGeneration = string(compute.V1)
|
||||
}
|
||||
|
||||
// checks, accumulate any errors or warnings
|
||||
|
||||
if b.config.FromScratch {
|
||||
if b.config.Source != "" {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("source cannot be specified when building from_scratch"))
|
||||
}
|
||||
if b.config.OSDiskSizeGB == 0 {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("os_disk_size_gb is required with from_scratch"))
|
||||
}
|
||||
if len(b.config.PreMountCommands) == 0 {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("pre_mount_commands is required with from_scratch"))
|
||||
}
|
||||
} 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 := client.ParseResourceID(b.config.Source); err == nil &&
|
||||
strings.EqualFold(id.Provider, "Microsoft.Compute") &&
|
||||
strings.EqualFold(id.ResourceType.String(), "disks") {
|
||||
log.Println("Source is a disk resource ID:", b.config.Source)
|
||||
b.config.sourceType = sourceDisk
|
||||
} else if id, err := client.ParseResourceID(b.config.Source); err == nil &&
|
||||
strings.EqualFold(id.Provider, "Microsoft.Compute") &&
|
||||
strings.EqualFold(id.ResourceType.String(), "galleries/images/versions") {
|
||||
log.Println("Source is a shared image ID:", b.config.Source)
|
||||
b.config.sourceType = sourceSharedImage
|
||||
} else {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("source: %q is not a valid platform image specifier, nor is it a disk resource ID", b.config.Source))
|
||||
}
|
||||
}
|
||||
|
||||
if err := checkDiskCacheType(b.config.OSDiskCacheType); err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("os_disk_cache_type: %v", err))
|
||||
}
|
||||
|
||||
if err := checkStorageAccountType(b.config.OSDiskStorageAccountType); err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("os_disk_storage_account_type: %v", err))
|
||||
}
|
||||
|
||||
if err := checkDiskCacheType(b.config.DataDiskCacheType); err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("data_disk_cache_type: %v", err))
|
||||
}
|
||||
|
||||
if err := checkStorageAccountType(b.config.DataDiskStorageAccountType); err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("data_disk_storage_account_type: %v", err))
|
||||
}
|
||||
|
||||
if b.config.ImageResourceID != "" {
|
||||
r, err := azure.ParseResourceID(b.config.ImageResourceID)
|
||||
if err != nil ||
|
||||
!strings.EqualFold(r.Provider, "Microsoft.Compute") ||
|
||||
!strings.EqualFold(r.ResourceType, "images") {
|
||||
errs = packersdk.MultiErrorAppend(fmt.Errorf(
|
||||
"image_resource_id: %q is not a valid image resource id", b.config.ImageResourceID))
|
||||
}
|
||||
}
|
||||
|
||||
if azcommon.StringsContains(md.Keys, "shared_image_destination") {
|
||||
e, w := b.config.SharedImageGalleryDestination.Validate("shared_image_destination")
|
||||
if len(e) > 0 {
|
||||
errs = packersdk.MultiErrorAppend(errs, e...)
|
||||
}
|
||||
if len(w) > 0 {
|
||||
warns = append(warns, w...)
|
||||
}
|
||||
}
|
||||
|
||||
if !azcommon.StringsContains(md.Keys, "shared_image_destination") && b.config.ImageResourceID == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("image_resource_id or shared_image_destination is required"))
|
||||
}
|
||||
|
||||
if err := checkHyperVGeneration(b.config.ImageHyperVGeneration); err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("image_hyperv_generation: %v", err))
|
||||
}
|
||||
|
||||
if errs != nil {
|
||||
return nil, warns, errs
|
||||
}
|
||||
|
||||
packersdk.LogSecretFilter.Set(b.config.ClientConfig.ClientSecret, b.config.ClientConfig.ClientJWT)
|
||||
return nil, warns, nil
|
||||
}
|
||||
|
||||
func checkDiskCacheType(s string) interface{} {
|
||||
for _, v := range compute.PossibleCachingTypesValues() {
|
||||
if compute.CachingTypes(s) == v {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%q is not a valid value %v",
|
||||
s, compute.PossibleCachingTypesValues())
|
||||
}
|
||||
|
||||
func checkStorageAccountType(s string) interface{} {
|
||||
for _, v := range compute.PossibleDiskStorageAccountTypesValues() {
|
||||
if compute.DiskStorageAccountTypes(s) == v {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%q is not a valid value %v",
|
||||
s, compute.PossibleDiskStorageAccountTypesValues())
|
||||
}
|
||||
|
||||
func checkHyperVGeneration(s string) interface{} {
|
||||
for _, v := range compute.PossibleHyperVGenerationValues() {
|
||||
if compute.HyperVGeneration(s) == v {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%q is not a valid value %v",
|
||||
s, compute.PossibleHyperVGenerationValues())
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
||||
switch runtime.GOOS {
|
||||
case "linux", "freebsd":
|
||||
break
|
||||
default:
|
||||
return nil, errors.New("the azure-chroot builder only works on Linux and FreeBSD environments")
|
||||
}
|
||||
|
||||
err := b.config.ClientConfig.FillParameters()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting Azure client defaults: %v", err)
|
||||
}
|
||||
azcli, err := client.New(b.config.ClientConfig, ui.Say)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating Azure client: %v", err)
|
||||
}
|
||||
|
||||
wrappedCommand := func(command string) (string, error) {
|
||||
ictx := b.config.ctx
|
||||
ictx.Data = &struct{ Command string }{Command: command}
|
||||
return interpolate.Render(b.config.CommandWrapper, &ictx)
|
||||
}
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
state.Put("azureclient", azcli)
|
||||
state.Put("wrappedCommand", common.CommandWrapper(wrappedCommand))
|
||||
|
||||
info, err := azcli.MetadataClient().GetComputeInfo()
|
||||
if err != nil {
|
||||
log.Printf("MetadataClient().GetComputeInfo(): error: %+v", err)
|
||||
err := fmt.Errorf(
|
||||
"Error retrieving information ARM resource ID and location" +
|
||||
"of the VM that Packer is running on.\n" +
|
||||
"Please verify that Packer is running on a proper Azure VM.")
|
||||
ui.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
state.Put("instance", info)
|
||||
|
||||
// Build the step array from the config
|
||||
steps := buildsteps(b.config, info)
|
||||
|
||||
// Run!
|
||||
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(ctx, state)
|
||||
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// Build the artifact and return it
|
||||
artifact := &azcommon.Artifact{
|
||||
BuilderIdValue: BuilderID,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
AzureClientSet: azcli,
|
||||
}
|
||||
if b.config.ImageResourceID != "" {
|
||||
artifact.Resources = append(artifact.Resources, b.config.ImageResourceID)
|
||||
}
|
||||
if e, _ := b.config.SharedImageGalleryDestination.Validate(""); len(e) == 0 {
|
||||
artifact.Resources = append(artifact.Resources, b.config.SharedImageGalleryDestination.ResourceID(info.SubscriptionID))
|
||||
}
|
||||
if b.config.SkipCleanup {
|
||||
if d, ok := state.GetOk(stateBagKey_Diskset); ok {
|
||||
for _, disk := range d.(Diskset) {
|
||||
artifact.Resources = append(artifact.Resources, disk.String())
|
||||
}
|
||||
}
|
||||
if d, ok := state.GetOk(stateBagKey_Snapshotset); ok {
|
||||
for _, snapshot := range d.(Diskset) {
|
||||
artifact.Resources = append(artifact.Resources, snapshot.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func buildsteps(config Config, info *client.ComputeInfo) []multistep.Step {
|
||||
// Build the steps
|
||||
var steps []multistep.Step
|
||||
addSteps := func(s ...multistep.Step) { // convenience function
|
||||
steps = append(steps, s...)
|
||||
}
|
||||
|
||||
e, _ := config.SharedImageGalleryDestination.Validate("")
|
||||
hasValidSharedImage := len(e) == 0
|
||||
|
||||
if hasValidSharedImage {
|
||||
// validate destination early
|
||||
addSteps(
|
||||
&StepVerifySharedImageDestination{
|
||||
Image: config.SharedImageGalleryDestination,
|
||||
Location: info.Location,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if config.FromScratch {
|
||||
addSteps(&StepCreateNewDiskset{
|
||||
OSDiskID: config.TemporaryOSDiskID,
|
||||
OSDiskSizeGB: config.OSDiskSizeGB,
|
||||
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
|
||||
HyperVGeneration: config.ImageHyperVGeneration,
|
||||
Location: info.Location})
|
||||
} else {
|
||||
switch config.sourceType {
|
||||
case sourcePlatformImage:
|
||||
if pi, err := client.ParsePlatformImageURN(config.Source); err == nil {
|
||||
if strings.EqualFold(pi.Version, "latest") {
|
||||
addSteps(
|
||||
&StepResolvePlatformImageVersion{
|
||||
PlatformImage: pi,
|
||||
Location: info.Location,
|
||||
})
|
||||
}
|
||||
addSteps(
|
||||
&StepCreateNewDiskset{
|
||||
OSDiskID: config.TemporaryOSDiskID,
|
||||
OSDiskSizeGB: config.OSDiskSizeGB,
|
||||
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
|
||||
HyperVGeneration: config.ImageHyperVGeneration,
|
||||
Location: info.Location,
|
||||
SourcePlatformImage: pi,
|
||||
|
||||
SkipCleanup: config.SkipCleanup,
|
||||
})
|
||||
} else {
|
||||
panic("Couldn't parse platfrom image urn: " + config.Source + " err: " + err.Error())
|
||||
}
|
||||
|
||||
case sourceDisk:
|
||||
addSteps(
|
||||
&StepVerifySourceDisk{
|
||||
SourceDiskResourceID: config.Source,
|
||||
Location: info.Location,
|
||||
},
|
||||
&StepCreateNewDiskset{
|
||||
OSDiskID: config.TemporaryOSDiskID,
|
||||
OSDiskSizeGB: config.OSDiskSizeGB,
|
||||
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
|
||||
HyperVGeneration: config.ImageHyperVGeneration,
|
||||
SourceOSDiskResourceID: config.Source,
|
||||
Location: info.Location,
|
||||
|
||||
SkipCleanup: config.SkipCleanup,
|
||||
})
|
||||
|
||||
case sourceSharedImage:
|
||||
addSteps(
|
||||
&StepVerifySharedImageSource{
|
||||
SharedImageID: config.Source,
|
||||
SubscriptionID: info.SubscriptionID,
|
||||
Location: info.Location,
|
||||
},
|
||||
&StepCreateNewDiskset{
|
||||
OSDiskID: config.TemporaryOSDiskID,
|
||||
DataDiskIDPrefix: config.TemporaryDataDiskIDPrefix,
|
||||
OSDiskSizeGB: config.OSDiskSizeGB,
|
||||
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
|
||||
DataDiskStorageAccountType: config.DataDiskStorageAccountType,
|
||||
SourceImageResourceID: config.Source,
|
||||
Location: info.Location,
|
||||
|
||||
SkipCleanup: config.SkipCleanup,
|
||||
})
|
||||
|
||||
default:
|
||||
panic(fmt.Errorf("Unknown source type: %+q", config.sourceType))
|
||||
}
|
||||
}
|
||||
|
||||
addSteps(
|
||||
&StepAttachDisk{}, // uses os_disk_resource_id and sets 'device' in stateBag
|
||||
&chroot.StepPreMountCommands{
|
||||
Commands: config.PreMountCommands,
|
||||
},
|
||||
&StepMountDevice{
|
||||
MountOptions: config.MountOptions,
|
||||
MountPartition: config.MountPartition,
|
||||
MountPath: config.MountPath,
|
||||
},
|
||||
&chroot.StepPostMountCommands{
|
||||
Commands: config.PostMountCommands,
|
||||
},
|
||||
&chroot.StepMountExtra{
|
||||
ChrootMounts: config.ChrootMounts,
|
||||
},
|
||||
&chroot.StepCopyFiles{
|
||||
Files: config.CopyFiles,
|
||||
},
|
||||
&chroot.StepChrootProvision{},
|
||||
&chroot.StepEarlyCleanup{},
|
||||
)
|
||||
|
||||
if config.ImageResourceID != "" {
|
||||
addSteps(&StepCreateImage{
|
||||
ImageResourceID: config.ImageResourceID,
|
||||
ImageOSState: string(compute.Generalized),
|
||||
OSDiskCacheType: config.OSDiskCacheType,
|
||||
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
|
||||
Location: info.Location,
|
||||
})
|
||||
}
|
||||
if hasValidSharedImage {
|
||||
addSteps(
|
||||
&StepCreateSnapshotset{
|
||||
OSDiskSnapshotID: config.TemporaryOSDiskSnapshotID,
|
||||
DataDiskSnapshotIDPrefix: config.TemporaryDataDiskSnapshotIDPrefix,
|
||||
Location: info.Location,
|
||||
SkipCleanup: config.SkipCleanup,
|
||||
},
|
||||
&StepCreateSharedImageVersion{
|
||||
Destination: config.SharedImageGalleryDestination,
|
||||
OSDiskCacheType: config.OSDiskCacheType,
|
||||
Location: info.Location,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return steps
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
||||
CloudEnvironmentName *string `mapstructure:"cloud_environment_name" required:"false" cty:"cloud_environment_name" hcl:"cloud_environment_name"`
|
||||
ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"`
|
||||
ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"`
|
||||
ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"`
|
||||
ClientCertExpireTimeout *string `mapstructure:"client_cert_token_timeout" required:"false" cty:"client_cert_token_timeout" hcl:"client_cert_token_timeout"`
|
||||
ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"`
|
||||
ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"`
|
||||
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
|
||||
SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"`
|
||||
UseAzureCLIAuth *bool `mapstructure:"use_azure_cli_auth" required:"false" cty:"use_azure_cli_auth" hcl:"use_azure_cli_auth"`
|
||||
FromScratch *bool `mapstructure:"from_scratch" cty:"from_scratch" hcl:"from_scratch"`
|
||||
Source *string `mapstructure:"source" required:"true" cty:"source" hcl:"source"`
|
||||
CommandWrapper *string `mapstructure:"command_wrapper" cty:"command_wrapper" hcl:"command_wrapper"`
|
||||
PreMountCommands []string `mapstructure:"pre_mount_commands" cty:"pre_mount_commands" hcl:"pre_mount_commands"`
|
||||
MountOptions []string `mapstructure:"mount_options" cty:"mount_options" hcl:"mount_options"`
|
||||
MountPartition *string `mapstructure:"mount_partition" cty:"mount_partition" hcl:"mount_partition"`
|
||||
MountPath *string `mapstructure:"mount_path" cty:"mount_path" hcl:"mount_path"`
|
||||
PostMountCommands []string `mapstructure:"post_mount_commands" cty:"post_mount_commands" hcl:"post_mount_commands"`
|
||||
ChrootMounts [][]string `mapstructure:"chroot_mounts" cty:"chroot_mounts" hcl:"chroot_mounts"`
|
||||
CopyFiles []string `mapstructure:"copy_files" cty:"copy_files" hcl:"copy_files"`
|
||||
OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" cty:"os_disk_size_gb" hcl:"os_disk_size_gb"`
|
||||
OSDiskStorageAccountType *string `mapstructure:"os_disk_storage_account_type" cty:"os_disk_storage_account_type" hcl:"os_disk_storage_account_type"`
|
||||
OSDiskCacheType *string `mapstructure:"os_disk_cache_type" cty:"os_disk_cache_type" hcl:"os_disk_cache_type"`
|
||||
DataDiskStorageAccountType *string `mapstructure:"data_disk_storage_account_type" cty:"data_disk_storage_account_type" hcl:"data_disk_storage_account_type"`
|
||||
DataDiskCacheType *string `mapstructure:"data_disk_cache_type" cty:"data_disk_cache_type" hcl:"data_disk_cache_type"`
|
||||
ImageHyperVGeneration *string `mapstructure:"image_hyperv_generation" cty:"image_hyperv_generation" hcl:"image_hyperv_generation"`
|
||||
TemporaryOSDiskID *string `mapstructure:"temporary_os_disk_id" cty:"temporary_os_disk_id" hcl:"temporary_os_disk_id"`
|
||||
TemporaryOSDiskSnapshotID *string `mapstructure:"temporary_os_disk_snapshot_id" cty:"temporary_os_disk_snapshot_id" hcl:"temporary_os_disk_snapshot_id"`
|
||||
TemporaryDataDiskIDPrefix *string `mapstructure:"temporary_data_disk_id_prefix" cty:"temporary_data_disk_id_prefix" hcl:"temporary_data_disk_id_prefix"`
|
||||
TemporaryDataDiskSnapshotIDPrefix *string `mapstructure:"temporary_data_disk_snapshot_id" cty:"temporary_data_disk_snapshot_id" hcl:"temporary_data_disk_snapshot_id"`
|
||||
SkipCleanup *bool `mapstructure:"skip_cleanup" cty:"skip_cleanup" hcl:"skip_cleanup"`
|
||||
ImageResourceID *string `mapstructure:"image_resource_id" cty:"image_resource_id" hcl:"image_resource_id"`
|
||||
SharedImageGalleryDestination *FlatSharedImageGalleryDestination `mapstructure:"shared_image_destination" cty:"shared_image_destination" hcl:"shared_image_destination"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||
"cloud_environment_name": &hcldec.AttrSpec{Name: "cloud_environment_name", Type: cty.String, Required: false},
|
||||
"client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false},
|
||||
"client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false},
|
||||
"client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false},
|
||||
"client_cert_token_timeout": &hcldec.AttrSpec{Name: "client_cert_token_timeout", Type: cty.String, Required: false},
|
||||
"client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false},
|
||||
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
|
||||
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
|
||||
"subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false},
|
||||
"use_azure_cli_auth": &hcldec.AttrSpec{Name: "use_azure_cli_auth", Type: cty.Bool, Required: false},
|
||||
"from_scratch": &hcldec.AttrSpec{Name: "from_scratch", Type: cty.Bool, Required: false},
|
||||
"source": &hcldec.AttrSpec{Name: "source", Type: cty.String, Required: false},
|
||||
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
|
||||
"pre_mount_commands": &hcldec.AttrSpec{Name: "pre_mount_commands", Type: cty.List(cty.String), Required: false},
|
||||
"mount_options": &hcldec.AttrSpec{Name: "mount_options", Type: cty.List(cty.String), Required: false},
|
||||
"mount_partition": &hcldec.AttrSpec{Name: "mount_partition", Type: cty.String, Required: false},
|
||||
"mount_path": &hcldec.AttrSpec{Name: "mount_path", Type: cty.String, Required: false},
|
||||
"post_mount_commands": &hcldec.AttrSpec{Name: "post_mount_commands", Type: cty.List(cty.String), Required: false},
|
||||
"chroot_mounts": &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.List(cty.String)), Required: false},
|
||||
"copy_files": &hcldec.AttrSpec{Name: "copy_files", Type: cty.List(cty.String), Required: false},
|
||||
"os_disk_size_gb": &hcldec.AttrSpec{Name: "os_disk_size_gb", Type: cty.Number, Required: false},
|
||||
"os_disk_storage_account_type": &hcldec.AttrSpec{Name: "os_disk_storage_account_type", Type: cty.String, Required: false},
|
||||
"os_disk_cache_type": &hcldec.AttrSpec{Name: "os_disk_cache_type", Type: cty.String, Required: false},
|
||||
"data_disk_storage_account_type": &hcldec.AttrSpec{Name: "data_disk_storage_account_type", Type: cty.String, Required: false},
|
||||
"data_disk_cache_type": &hcldec.AttrSpec{Name: "data_disk_cache_type", Type: cty.String, Required: false},
|
||||
"image_hyperv_generation": &hcldec.AttrSpec{Name: "image_hyperv_generation", Type: cty.String, Required: false},
|
||||
"temporary_os_disk_id": &hcldec.AttrSpec{Name: "temporary_os_disk_id", Type: cty.String, Required: false},
|
||||
"temporary_os_disk_snapshot_id": &hcldec.AttrSpec{Name: "temporary_os_disk_snapshot_id", Type: cty.String, Required: false},
|
||||
"temporary_data_disk_id_prefix": &hcldec.AttrSpec{Name: "temporary_data_disk_id_prefix", Type: cty.String, Required: false},
|
||||
"temporary_data_disk_snapshot_id": &hcldec.AttrSpec{Name: "temporary_data_disk_snapshot_id", Type: cty.String, Required: false},
|
||||
"skip_cleanup": &hcldec.AttrSpec{Name: "skip_cleanup", Type: cty.Bool, Required: false},
|
||||
"image_resource_id": &hcldec.AttrSpec{Name: "image_resource_id", Type: cty.String, Required: false},
|
||||
"shared_image_destination": &hcldec.BlockSpec{TypeName: "shared_image_destination", Nested: hcldec.ObjectSpec((*FlatSharedImageGalleryDestination)(nil).HCL2Spec())},
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer/builder/azure/common/client"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
|
||||
)
|
||||
|
||||
func TestBuilder_Prepare(t *testing.T) {
|
||||
type config map[string]interface{}
|
||||
type regexMatchers map[string]string // map of regex : error message
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config config
|
||||
validate func(Config)
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "platform image to managed disk",
|
||||
config: config{
|
||||
"client_id": "123",
|
||||
"client_secret": "456",
|
||||
"subscription_id": "789",
|
||||
"source": "credativ:Debian:9:latest",
|
||||
"image_resource_id": "/subscriptions/789/resourceGroups/otherrgname/providers/Microsoft.Compute/images/MyDebianOSImage-{{timestamp}}",
|
||||
"shared_image_destination": config{
|
||||
"resource_group": "otherrgname",
|
||||
"gallery_name": "myGallery",
|
||||
"image_name": "imageName",
|
||||
"image_version": "1.0.2",
|
||||
},
|
||||
},
|
||||
validate: func(c Config) {
|
||||
if c.OSDiskSizeGB != 0 {
|
||||
t.Errorf("Expected OSDiskSizeGB to be 0, was %+v", c.OSDiskSizeGB)
|
||||
}
|
||||
if c.MountPartition != "1" {
|
||||
t.Errorf("Expected MountPartition to be %s, but found %s", "1", c.MountPartition)
|
||||
}
|
||||
if c.OSDiskStorageAccountType != string(compute.PremiumLRS) {
|
||||
t.Errorf("Expected OSDiskStorageAccountType to be %s, but found %s", string(compute.PremiumLRS), c.OSDiskStorageAccountType)
|
||||
}
|
||||
if c.OSDiskCacheType != string(compute.CachingTypesReadOnly) {
|
||||
t.Errorf("Expected OSDiskCacheType to be %s, but found %s", string(compute.CachingTypesReadOnly), c.OSDiskCacheType)
|
||||
}
|
||||
if c.ImageHyperVGeneration != string(compute.V1) {
|
||||
t.Errorf("Expected ImageHyperVGeneration to be %s, but found %s", string(compute.V1), c.ImageHyperVGeneration)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disk to managed image, validate temp disk id expansion",
|
||||
config: config{
|
||||
"source": "/subscriptions/789/resourceGroups/testrg/providers/Microsoft.Compute/disks/diskname",
|
||||
"image_resource_id": "/subscriptions/789/resourceGroups/otherrgname/providers/Microsoft.Compute/images/MyDebianOSImage-{{timestamp}}",
|
||||
},
|
||||
validate: func(c Config) {
|
||||
prefix := "/subscriptions/testSubscriptionID/resourceGroups/testResourceGroup/providers/Microsoft.Compute/disks/PackerTemp-osdisk-"
|
||||
if !strings.HasPrefix(c.TemporaryOSDiskID, prefix) {
|
||||
t.Errorf("Expected TemporaryOSDiskID to start with %q, but got %q", prefix, c.TemporaryOSDiskID)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disk to both managed image and shared image",
|
||||
config: config{
|
||||
"source": "/subscriptions/789/resourceGroups/testrg/providers/Microsoft.Compute/disks/diskname",
|
||||
"image_resource_id": "/subscriptions/789/resourceGroups/otherrgname/providers/Microsoft.Compute/images/MyDebianOSImage-{{timestamp}}",
|
||||
"shared_image_destination": config{
|
||||
"resource_group": "rg",
|
||||
"gallery_name": "galleryName",
|
||||
"image_name": "imageName",
|
||||
"image_version": "0.1.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disk to both managed image and shared image with missing property",
|
||||
config: config{
|
||||
"source": "/subscriptions/789/resourceGroups/testrg/providers/Microsoft.Compute/disks/diskname",
|
||||
"image_resource_id": "/subscriptions/789/resourceGroups/otherrgname/providers/Microsoft.Compute/images/MyDebianOSImage-{{timestamp}}",
|
||||
"shared_image_destination": config{
|
||||
"resource_group": "rg",
|
||||
"gallery_name": "galleryName",
|
||||
"image_version": "0.1.0",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "from shared image",
|
||||
config: config{
|
||||
"shared_image_destination": config{
|
||||
"resource_group": "otherrgname",
|
||||
"gallery_name": "myGallery",
|
||||
"image_name": "imageName",
|
||||
"image_version": "1.0.2",
|
||||
},
|
||||
"source": "/subscriptions/789/resourceGroups/testrg/providers/Microsoft.Compute/disks/diskname",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "err: no output",
|
||||
config: config{
|
||||
"source": "/subscriptions/789/resourceGroups/testrg/providers/Microsoft.Compute/disks/diskname",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
withMetadataStub(func() {
|
||||
b := &Builder{}
|
||||
|
||||
_, _, err := b.Prepare(tt.config)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Builder.Prepare() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if tt.validate != nil {
|
||||
tt.validate(b.config)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildsteps(t *testing.T) {
|
||||
info := &client.ComputeInfo{
|
||||
Location: "northpole",
|
||||
Name: "unittestVM",
|
||||
ResourceGroupName: "unittestResourceGroup",
|
||||
SubscriptionID: "96854241-60c7-426d-9a27-3fdeec8957f4",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config Config
|
||||
verify func([]multistep.Step, *testing.T)
|
||||
}{
|
||||
{
|
||||
name: "Source FromScrath creates empty disk",
|
||||
config: Config{FromScratch: true},
|
||||
verify: func(steps []multistep.Step, _ *testing.T) {
|
||||
for _, s := range steps {
|
||||
if s, ok := s.(*StepCreateNewDiskset); ok {
|
||||
if s.SourceOSDiskResourceID == "" &&
|
||||
s.SourcePlatformImage == nil {
|
||||
return
|
||||
}
|
||||
t.Errorf("found misconfigured StepCreateNewDisk: %+v", s)
|
||||
}
|
||||
}
|
||||
t.Error("did not find a StepCreateNewDisk")
|
||||
}},
|
||||
{
|
||||
name: "Source Platform image disk creation",
|
||||
config: Config{Source: "publisher:offer:sku:version", sourceType: sourcePlatformImage},
|
||||
verify: func(steps []multistep.Step, _ *testing.T) {
|
||||
for _, s := range steps {
|
||||
if s, ok := s.(*StepCreateNewDiskset); ok {
|
||||
if s.SourceOSDiskResourceID == "" &&
|
||||
s.SourcePlatformImage != nil &&
|
||||
s.SourcePlatformImage.Publisher == "publisher" {
|
||||
return
|
||||
}
|
||||
t.Errorf("found misconfigured StepCreateNewDisk: %+v", s)
|
||||
}
|
||||
}
|
||||
t.Error("did not find a StepCreateNewDisk")
|
||||
}},
|
||||
{
|
||||
name: "Source Platform image with version latest adds StepResolvePlatformImageVersion",
|
||||
config: Config{Source: "publisher:offer:sku:latest", sourceType: sourcePlatformImage},
|
||||
verify: func(steps []multistep.Step, _ *testing.T) {
|
||||
for _, s := range steps {
|
||||
if s, ok := s.(*StepResolvePlatformImageVersion); ok {
|
||||
if s.PlatformImage != nil &&
|
||||
s.Location == info.Location {
|
||||
return
|
||||
}
|
||||
t.Errorf("found misconfigured StepResolvePlatformImageVersion: %+v", s)
|
||||
}
|
||||
}
|
||||
t.Error("did not find a StepResolvePlatformImageVersion")
|
||||
}},
|
||||
{
|
||||
name: "Source Disk adds correct disk creation",
|
||||
config: Config{Source: "diskresourceid", sourceType: sourceDisk},
|
||||
verify: func(steps []multistep.Step, _ *testing.T) {
|
||||
for _, s := range steps {
|
||||
if s, ok := s.(*StepCreateNewDiskset); ok {
|
||||
if s.SourceOSDiskResourceID == "diskresourceid" &&
|
||||
s.SourcePlatformImage == nil {
|
||||
return
|
||||
}
|
||||
t.Errorf("found misconfigured StepCreateNewDisk: %+v", s)
|
||||
}
|
||||
}
|
||||
t.Error("did not find a StepCreateNewDisk")
|
||||
}},
|
||||
{
|
||||
name: "Source disk adds StepVerifySourceDisk",
|
||||
config: Config{Source: "diskresourceid", sourceType: sourceDisk},
|
||||
verify: func(steps []multistep.Step, _ *testing.T) {
|
||||
for _, s := range steps {
|
||||
if s, ok := s.(*StepVerifySourceDisk); ok {
|
||||
if s.SourceDiskResourceID == "diskresourceid" &&
|
||||
s.Location == info.Location {
|
||||
return
|
||||
}
|
||||
t.Errorf("found misconfigured StepVerifySourceDisk: %+v", s)
|
||||
}
|
||||
}
|
||||
t.Error("did not find a StepVerifySourceDisk")
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
withMetadataStub(func() { // ensure that values are taken from info, instead of retrieved again
|
||||
got := buildsteps(tt.config, info)
|
||||
tt.verify(got, t)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package chroot
|
||||
|
||||
const (
|
||||
stateBagKey_Diskset = "diskset"
|
||||
stateBagKey_Snapshotset = "snapshotset"
|
||||
)
|
|
@ -0,0 +1,190 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/hashicorp/packer/builder/azure/common/client"
|
||||
)
|
||||
|
||||
type DiskAttacher interface {
|
||||
AttachDisk(ctx context.Context, disk string) (lun int32, err error)
|
||||
WaitForDevice(ctx context.Context, i int32) (device string, err error)
|
||||
DetachDisk(ctx context.Context, disk string) (err error)
|
||||
WaitForDetach(ctx context.Context, diskID string) error
|
||||
}
|
||||
|
||||
var NewDiskAttacher = func(azureClient client.AzureClientSet) DiskAttacher {
|
||||
return &diskAttacher{
|
||||
azcli: azureClient,
|
||||
}
|
||||
}
|
||||
|
||||
type diskAttacher struct {
|
||||
azcli client.AzureClientSet
|
||||
|
||||
vm *client.ComputeInfo // store info about this VM so that we don't have to ask metadata service on every call
|
||||
}
|
||||
|
||||
var DiskNotFoundError = errors.New("Disk not found")
|
||||
|
||||
func (da *diskAttacher) DetachDisk(ctx context.Context, diskID string) error {
|
||||
log.Println("Fetching list of disks currently attached to VM")
|
||||
currentDisks, err := da.getDisks(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Removing %q from list of disks currently attached to VM", diskID)
|
||||
newDisks := []compute.DataDisk{}
|
||||
for _, disk := range currentDisks {
|
||||
if disk.ManagedDisk != nil &&
|
||||
!strings.EqualFold(to.String(disk.ManagedDisk.ID), diskID) {
|
||||
newDisks = append(newDisks, disk)
|
||||
}
|
||||
}
|
||||
if len(currentDisks) == len(newDisks) {
|
||||
return DiskNotFoundError
|
||||
}
|
||||
|
||||
log.Println("Updating new list of disks attached to VM")
|
||||
err = da.setDisks(ctx, newDisks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (da *diskAttacher) WaitForDetach(ctx context.Context, diskID string) error {
|
||||
for { // loop until disk is not attached, timeout or error
|
||||
list, err := da.getDisks(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if findDiskInList(list, diskID) == nil {
|
||||
log.Println("Disk is no longer in VM model, assuming detached")
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(time.Second): //continue
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (da *diskAttacher) AttachDisk(ctx context.Context, diskID string) (int32, error) {
|
||||
dataDisks, err := da.getDisks(ctx)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// check to see if disk is already attached, remember lun if found
|
||||
if disk := findDiskInList(dataDisks, diskID); disk != nil {
|
||||
// disk is already attached, just take this lun
|
||||
if disk.Lun == nil {
|
||||
return -1, errors.New("disk is attached, but lun was not set in VM model (possibly an error in the Azure APIs)")
|
||||
}
|
||||
return to.Int32(disk.Lun), nil
|
||||
}
|
||||
|
||||
// disk was not found on VM, go and actually attach it
|
||||
|
||||
var lun int32 = -1
|
||||
findFreeLun:
|
||||
for lun = 0; lun < 64; lun++ {
|
||||
for _, v := range dataDisks {
|
||||
if to.Int32(v.Lun) == lun {
|
||||
continue findFreeLun
|
||||
}
|
||||
}
|
||||
// no datadisk is using this lun
|
||||
break
|
||||
}
|
||||
|
||||
// append new data disk to collection
|
||||
dataDisks = append(dataDisks, compute.DataDisk{
|
||||
CreateOption: compute.DiskCreateOptionTypesAttach,
|
||||
ManagedDisk: &compute.ManagedDiskParameters{
|
||||
ID: to.StringPtr(diskID),
|
||||
},
|
||||
Lun: to.Int32Ptr(lun),
|
||||
})
|
||||
|
||||
// prepare resource object for update operation
|
||||
err = da.setDisks(ctx, dataDisks)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return lun, nil
|
||||
}
|
||||
|
||||
func (da *diskAttacher) getThisVM(ctx context.Context) (compute.VirtualMachine, error) {
|
||||
// getting resource info for this VM
|
||||
if da.vm == nil {
|
||||
vm, err := da.azcli.MetadataClient().GetComputeInfo()
|
||||
if err != nil {
|
||||
return compute.VirtualMachine{}, err
|
||||
}
|
||||
da.vm = vm
|
||||
}
|
||||
|
||||
// retrieve actual VM
|
||||
vmResource, err := da.azcli.VirtualMachinesClient().Get(ctx, da.vm.ResourceGroupName, da.vm.Name, "")
|
||||
if err != nil {
|
||||
return compute.VirtualMachine{}, err
|
||||
}
|
||||
if vmResource.StorageProfile == nil {
|
||||
return compute.VirtualMachine{}, errors.New("properties.storageProfile is not set on VM, this is unexpected")
|
||||
}
|
||||
|
||||
return vmResource, nil
|
||||
}
|
||||
|
||||
func (da diskAttacher) getDisks(ctx context.Context) ([]compute.DataDisk, error) {
|
||||
vmResource, err := da.getThisVM(ctx)
|
||||
if err != nil {
|
||||
return []compute.DataDisk{}, err
|
||||
}
|
||||
|
||||
return *vmResource.StorageProfile.DataDisks, nil
|
||||
}
|
||||
|
||||
func (da diskAttacher) setDisks(ctx context.Context, disks []compute.DataDisk) error {
|
||||
vmResource, err := da.getThisVM(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := azure.ParseResourceID(to.String(vmResource.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vmResource.StorageProfile.DataDisks = &disks
|
||||
vmResource.Resources = nil
|
||||
|
||||
// update the VM resource, attach disk
|
||||
_, err = da.azcli.VirtualMachinesClient().CreateOrUpdate(ctx, id.ResourceGroup, id.ResourceName, vmResource)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func findDiskInList(list []compute.DataDisk, diskID string) *compute.DataDisk {
|
||||
for _, disk := range list {
|
||||
if disk.ManagedDisk != nil &&
|
||||
strings.EqualFold(to.String(disk.ManagedDisk.ID), diskID) {
|
||||
return &disk
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
|
||||
// This builder will always be running in Azure, where data disks show up
|
||||
// on scbus5 target 0. The camcontrol command always outputs LUNs in
|
||||
// unpadded hexadecimal format.
|
||||
regexStr := fmt.Sprintf(`at scbus5 target 0 lun %x \(.*?da([\d]+)`, lun)
|
||||
devRegex, err := regexp.Compile(regexStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for {
|
||||
cmd := exec.Command("camcontrol", "devlist")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
outString := out.String()
|
||||
scanner := bufio.NewScanner(strings.NewReader(outString))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// Check if this is the correct bus, target, and LUN.
|
||||
if matches := devRegex.FindStringSubmatch(line); matches != nil {
|
||||
// If this function immediately returns, devfs won't have
|
||||
// created the device yet.
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
return fmt.Sprintf("/dev/da%s", matches[1]), nil
|
||||
}
|
||||
}
|
||||
if err = scanner.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
// continue
|
||||
case <-ctx.Done():
|
||||
return "", ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func diskPathForLun(lun int32) string {
|
||||
return fmt.Sprintf("/dev/disk/azure/scsi1/lun%d", lun)
|
||||
}
|
||||
|
||||
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
|
||||
path := diskPathForLun(lun)
|
||||
|
||||
for {
|
||||
link, err := os.Readlink(path)
|
||||
if err == nil {
|
||||
return filepath.Abs("/dev/disk/azure/scsi1/" + link)
|
||||
} else if err != os.ErrNotExist {
|
||||
if pe, ok := err.(*os.PathError); ok && pe.Err != syscall.ENOENT {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
// continue
|
||||
case <-ctx.Done():
|
||||
return "", ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// +build !linux,!freebsd
|
||||
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
|
||||
panic("The azure-chroot builder does not work on this platform.")
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
|
||||
"github.com/hashicorp/packer/builder/azure/common/client"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testvm = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/Microsoft.Compute/virtualMachines/testVM"
|
||||
testdisk = "/subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/testGroup2/Microsoft.Compute/disks/testDisk"
|
||||
)
|
||||
|
||||
// Tests assume current machine is capable of running chroot builder (i.e. an Azure VM)
|
||||
|
||||
func Test_DiskAttacherAttachesDiskToVM(t *testing.T) {
|
||||
azcli, err := client.GetTestClientSet(t) // integration test
|
||||
require.Nil(t, err)
|
||||
da := NewDiskAttacher(azcli)
|
||||
testDiskName := t.Name()
|
||||
|
||||
vm, err := azcli.MetadataClient().GetComputeInfo()
|
||||
require.Nil(t, err, "Test needs to run on an Azure VM, unable to retrieve VM information")
|
||||
t.Log("Creating new disk '", testDiskName, "' in ", vm.ResourceGroupName)
|
||||
|
||||
disk, err := azcli.DisksClient().Get(context.TODO(), vm.ResourceGroupName, testDiskName)
|
||||
if err == nil {
|
||||
t.Log("Disk already exists")
|
||||
if disk.DiskState == compute.Attached {
|
||||
t.Log("Disk is attached, assuming to this machine, trying to detach")
|
||||
err = da.DetachDisk(context.TODO(), to.String(disk.ID))
|
||||
require.Nil(t, err)
|
||||
}
|
||||
t.Log("Deleting disk")
|
||||
result, err := azcli.DisksClient().Delete(context.TODO(), vm.ResourceGroupName, testDiskName)
|
||||
require.Nil(t, err)
|
||||
err = result.WaitForCompletionRef(context.TODO(), azcli.PollClient())
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
t.Log("Creating disk")
|
||||
r, err := azcli.DisksClient().CreateOrUpdate(context.TODO(), vm.ResourceGroupName, testDiskName, compute.Disk{
|
||||
Location: to.StringPtr(vm.Location),
|
||||
Sku: &compute.DiskSku{
|
||||
Name: compute.StandardLRS,
|
||||
},
|
||||
DiskProperties: &compute.DiskProperties{
|
||||
DiskSizeGB: to.Int32Ptr(30),
|
||||
CreationData: &compute.CreationData{CreateOption: compute.Empty},
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
err = r.WaitForCompletionRef(context.TODO(), azcli.PollClient())
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Log("Retrieving disk properties")
|
||||
d, err := azcli.DisksClient().Get(context.TODO(), vm.ResourceGroupName, testDiskName)
|
||||
require.Nil(t, err)
|
||||
assert.NotNil(t, d)
|
||||
|
||||
t.Log("Attaching disk")
|
||||
lun, err := da.AttachDisk(context.TODO(), to.String(d.ID))
|
||||
assert.Nil(t, err)
|
||||
|
||||
t.Log("Waiting for device")
|
||||
dev, err := da.WaitForDevice(context.TODO(), lun)
|
||||
assert.Nil(t, err)
|
||||
|
||||
t.Log("Device path:", dev)
|
||||
|
||||
t.Log("Detaching disk")
|
||||
err = da.DetachDisk(context.TODO(), to.String(d.ID))
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Log("Deleting disk")
|
||||
result, err := azcli.DisksClient().Delete(context.TODO(), vm.ResourceGroupName, testDiskName)
|
||||
if err == nil {
|
||||
err = result.WaitForCompletionRef(context.TODO(), azcli.PollClient())
|
||||
}
|
||||
require.Nil(t, err)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package chroot
|
||||
|
||||
import "github.com/hashicorp/packer/builder/azure/common/client"
|
||||
|
||||
// Diskset represents all of the disks or snapshots associated with an image.
|
||||
// It maps lun to resource ids. The OS disk is stored with lun=-1.
|
||||
type Diskset map[int32]client.Resource
|
||||
|
||||
// OS return the OS disk resource ID or nil if it is not assigned
|
||||
func (ds Diskset) OS() *client.Resource {
|
||||
if r, ok := ds[-1]; ok {
|
||||
return &r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Data return the data disk resource ID or nil if it is not assigned
|
||||
func (ds Diskset) Data(lun int32) *client.Resource {
|
||||
if r, ok := ds[lun]; ok {
|
||||
return &r
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package chroot
|
||||
|
||||
import "github.com/hashicorp/packer/builder/azure/common/client"
|
||||
|
||||
// diskset easily creates a diskset for testing
|
||||
func diskset(ids ...string) Diskset {
|
||||
diskset := make(Diskset)
|
||||
for i, id := range ids {
|
||||
r, err := client.ParseResourceID(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
diskset[int32(i-1)] = r
|
||||
}
|
||||
return diskset
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package chroot
|
||||
|
||||
import "github.com/hashicorp/packer/builder/azure/common/client"
|
||||
|
||||
func withMetadataStub(f func()) {
|
||||
mdc := client.DefaultMetadataClient
|
||||
defer func() { client.DefaultMetadataClient = mdc }()
|
||||
client.DefaultMetadataClient = client.MetadataClientStub{
|
||||
ComputeInfo: client.ComputeInfo{
|
||||
SubscriptionID: "testSubscriptionID",
|
||||
ResourceGroupName: "testResourceGroup",
|
||||
Name: "testVM",
|
||||
Location: "testLocation",
|
||||
},
|
||||
}
|
||||
|
||||
f()
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
// testUI returns a test ui plus a function to retrieve the errors written to the ui
|
||||
func testUI() (packersdk.Ui, func() string) {
|
||||
errorBuffer := &strings.Builder{}
|
||||
ui := &packersdk.BasicUi{
|
||||
Reader: strings.NewReader(""),
|
||||
Writer: ioutil.Discard,
|
||||
ErrorWriter: errorBuffer,
|
||||
}
|
||||
return ui, errorBuffer.String
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type SharedImageGalleryDestination,TargetRegion
|
||||
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// SharedImageGalleryDestination models an image version in a Shared
|
||||
// Image Gallery that can be used as a destination.
|
||||
type SharedImageGalleryDestination struct {
|
||||
ResourceGroup string `mapstructure:"resource_group" required:"true"`
|
||||
GalleryName string `mapstructure:"gallery_name" required:"true"`
|
||||
ImageName string `mapstructure:"image_name" required:"true"`
|
||||
ImageVersion string `mapstructure:"image_version" required:"true"`
|
||||
|
||||
TargetRegions []TargetRegion `mapstructure:"target_regions"`
|
||||
ExcludeFromLatest bool `mapstructure:"exclude_from_latest"`
|
||||
ExcludeFromLatestTypo bool `mapstructure:"exlude_from_latest" undocumented:"true"`
|
||||
}
|
||||
|
||||
// TargetRegion describes a region where the shared image should be replicated
|
||||
type TargetRegion struct {
|
||||
// Name of the Azure region
|
||||
Name string `mapstructure:"name" required:"true"`
|
||||
// Number of replicas in this region. Default: 1
|
||||
ReplicaCount int32 `mapstructure:"replicas"`
|
||||
// Storage account type: Standard_LRS or Standard_ZRS. Default: Standard_ZRS
|
||||
StorageAccountType string `mapstructure:"storage_account_type"`
|
||||
}
|
||||
|
||||
// ResourceID returns the resource ID string
|
||||
func (sigd SharedImageGalleryDestination) ResourceID(subscriptionID string) string {
|
||||
return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/galleries/%s/images/%s/versions/%s",
|
||||
subscriptionID,
|
||||
sigd.ResourceGroup,
|
||||
sigd.GalleryName,
|
||||
sigd.ImageName,
|
||||
sigd.ImageVersion)
|
||||
}
|
||||
|
||||
// Validate validates that the values in the shared image are valid (without checking them on the network)
|
||||
func (sigd *SharedImageGalleryDestination) Validate(prefix string) (errs []error, warns []string) {
|
||||
if sigd.ResourceGroup == "" {
|
||||
errs = append(errs, fmt.Errorf("%s.resource_group is required", prefix))
|
||||
}
|
||||
if sigd.GalleryName == "" {
|
||||
errs = append(errs, fmt.Errorf("%s.gallery_name is required", prefix))
|
||||
}
|
||||
if sigd.ImageName == "" {
|
||||
errs = append(errs, fmt.Errorf("%s.image_name is required", prefix))
|
||||
}
|
||||
if match, err := regexp.MatchString("^[0-9]+\\.[0-9]+\\.[0-9]+$", sigd.ImageVersion); !match {
|
||||
if err != nil {
|
||||
warns = append(warns, fmt.Sprintf("Error matching pattern for %s.image_version: %s (this is probably a bug)", prefix, err))
|
||||
}
|
||||
errs = append(errs, fmt.Errorf("%s.image_version should match '^[0-9]+\\.[0-9]+\\.[0-9]+$'", prefix))
|
||||
}
|
||||
if len(sigd.TargetRegions) == 0 {
|
||||
warns = append(warns,
|
||||
fmt.Sprintf("%s.target_regions is empty; image will only be available in the region of the gallery", prefix))
|
||||
}
|
||||
if sigd.ExcludeFromLatestTypo == true && sigd.ExcludeFromLatest == false {
|
||||
warns = append(warns,
|
||||
fmt.Sprintf("%s.exlude_from_latest is being deprecated, please use exclude_from_latest", prefix))
|
||||
sigd.ExcludeFromLatest = sigd.ExcludeFromLatestTypo
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatSharedImageGalleryDestination struct {
|
||||
ResourceGroup *string `mapstructure:"resource_group" required:"true" cty:"resource_group" hcl:"resource_group"`
|
||||
GalleryName *string `mapstructure:"gallery_name" required:"true" cty:"gallery_name" hcl:"gallery_name"`
|
||||
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
|
||||
ImageVersion *string `mapstructure:"image_version" required:"true" cty:"image_version" hcl:"image_version"`
|
||||
TargetRegions []FlatTargetRegion `mapstructure:"target_regions" cty:"target_regions" hcl:"target_regions"`
|
||||
ExcludeFromLatest *bool `mapstructure:"exclude_from_latest" cty:"exclude_from_latest" hcl:"exclude_from_latest"`
|
||||
ExcludeFromLatestTypo *bool `mapstructure:"exlude_from_latest" undocumented:"true" cty:"exlude_from_latest" hcl:"exlude_from_latest"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatSharedImageGalleryDestination.
|
||||
// FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*SharedImageGalleryDestination) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatSharedImageGalleryDestination)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a SharedImageGalleryDestination.
|
||||
// This spec is used by HCL to read the fields of SharedImageGalleryDestination.
|
||||
// The decoded values from this spec will then be applied to a FlatSharedImageGalleryDestination.
|
||||
func (*FlatSharedImageGalleryDestination) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
|
||||
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
|
||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
|
||||
"target_regions": &hcldec.BlockListSpec{TypeName: "target_regions", Nested: hcldec.ObjectSpec((*FlatTargetRegion)(nil).HCL2Spec())},
|
||||
"exclude_from_latest": &hcldec.AttrSpec{Name: "exclude_from_latest", Type: cty.Bool, Required: false},
|
||||
"exlude_from_latest": &hcldec.AttrSpec{Name: "exlude_from_latest", Type: cty.Bool, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatTargetRegion is an auto-generated flat version of TargetRegion.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatTargetRegion struct {
|
||||
Name *string `mapstructure:"name" required:"true" cty:"name" hcl:"name"`
|
||||
ReplicaCount *int32 `mapstructure:"replicas" cty:"replicas" hcl:"replicas"`
|
||||
StorageAccountType *string `mapstructure:"storage_account_type" cty:"storage_account_type" hcl:"storage_account_type"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatTargetRegion.
|
||||
// FlatTargetRegion is an auto-generated flat version of TargetRegion.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*TargetRegion) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatTargetRegion)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a TargetRegion.
|
||||
// This spec is used by HCL to read the fields of TargetRegion.
|
||||
// The decoded values from this spec will then be applied to a FlatTargetRegion.
|
||||
func (*FlatTargetRegion) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false},
|
||||
"replicas": &hcldec.AttrSpec{Name: "replicas", Type: cty.Number, Required: false},
|
||||
"storage_account_type": &hcldec.AttrSpec{Name: "storage_account_type", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSharedImageGalleryDestination_ResourceID(t *testing.T) {
|
||||
sigd := SharedImageGalleryDestination{
|
||||
ResourceGroup: "ResourceGroup",
|
||||
GalleryName: "GalleryName",
|
||||
ImageName: "ImageName",
|
||||
ImageVersion: "ImageVersion",
|
||||
}
|
||||
want := "/subscriptions/SubscriptionID/resourceGroups/ResourceGroup/providers/Microsoft.Compute/galleries/GalleryName/images/ImageName/versions/ImageVersion"
|
||||
if got := sigd.ResourceID("SubscriptionID"); !strings.EqualFold(got, want) {
|
||||
t.Errorf("SharedImageGalleryDestination.ResourceID() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharedImageGalleryDestination_Validate(t *testing.T) {
|
||||
type fields struct {
|
||||
ResourceGroup string
|
||||
GalleryName string
|
||||
ImageName string
|
||||
ImageVersion string
|
||||
TargetRegions []TargetRegion
|
||||
ExcludeFromLatest bool
|
||||
ExcludeFromLatestTypo bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantErrs []string
|
||||
wantWarns []string
|
||||
}{
|
||||
{
|
||||
name: "complete",
|
||||
fields: fields{
|
||||
ResourceGroup: "ResourceGroup",
|
||||
GalleryName: "GalleryName",
|
||||
ImageName: "ImageName",
|
||||
ImageVersion: "0.1.2",
|
||||
TargetRegions: []TargetRegion{
|
||||
TargetRegion{
|
||||
Name: "region1",
|
||||
ReplicaCount: 5,
|
||||
StorageAccountType: "Standard_ZRS",
|
||||
},
|
||||
TargetRegion{
|
||||
Name: "region2",
|
||||
ReplicaCount: 3,
|
||||
StorageAccountType: "Standard_LRS",
|
||||
},
|
||||
},
|
||||
ExcludeFromLatest: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "warn if target regions not specified",
|
||||
fields: fields{
|
||||
ResourceGroup: "ResourceGroup",
|
||||
GalleryName: "GalleryName",
|
||||
ImageName: "ImageName",
|
||||
ImageVersion: "0.1.2",
|
||||
},
|
||||
wantWarns: []string{"sigdest.target_regions is empty; image will only be available in the region of the gallery"},
|
||||
},
|
||||
{
|
||||
name: "warn if using exlude_from_latest",
|
||||
fields: fields{
|
||||
ResourceGroup: "ResourceGroup",
|
||||
GalleryName: "GalleryName",
|
||||
ImageName: "ImageName",
|
||||
ImageVersion: "0.1.2",
|
||||
TargetRegions: []TargetRegion{
|
||||
TargetRegion{
|
||||
Name: "region1",
|
||||
ReplicaCount: 5,
|
||||
StorageAccountType: "Standard_ZRS",
|
||||
},
|
||||
TargetRegion{
|
||||
Name: "region2",
|
||||
ReplicaCount: 3,
|
||||
StorageAccountType: "Standard_LRS",
|
||||
},
|
||||
},
|
||||
ExcludeFromLatestTypo: true,
|
||||
},
|
||||
wantWarns: []string{"sigdest.exlude_from_latest is being deprecated, please use exclude_from_latest"},
|
||||
},
|
||||
{
|
||||
name: "version format",
|
||||
wantErrs: []string{
|
||||
"sigdest.image_version should match '^[0-9]+\\.[0-9]+\\.[0-9]+$'",
|
||||
},
|
||||
fields: fields{
|
||||
ResourceGroup: "ResourceGroup",
|
||||
GalleryName: "GalleryName",
|
||||
ImageName: "ImageName",
|
||||
ImageVersion: "0.1.2alpha",
|
||||
TargetRegions: []TargetRegion{
|
||||
TargetRegion{
|
||||
Name: "region1",
|
||||
ReplicaCount: 5,
|
||||
StorageAccountType: "Standard_ZRS",
|
||||
},
|
||||
TargetRegion{
|
||||
Name: "region2",
|
||||
ReplicaCount: 3,
|
||||
StorageAccountType: "Standard_LRS",
|
||||
},
|
||||
},
|
||||
ExcludeFromLatest: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "required fields",
|
||||
wantErrs: []string{
|
||||
"sigdest.resource_group is required",
|
||||
"sigdest.gallery_name is required",
|
||||
"sigdest.image_name is required",
|
||||
"sigdest.image_version should match '^[0-9]+\\.[0-9]+\\.[0-9]+$'",
|
||||
},
|
||||
wantWarns: []string{"sigdest.target_regions is empty; image will only be available in the region of the gallery"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sigd := &SharedImageGalleryDestination{
|
||||
ResourceGroup: tt.fields.ResourceGroup,
|
||||
GalleryName: tt.fields.GalleryName,
|
||||
ImageName: tt.fields.ImageName,
|
||||
ImageVersion: tt.fields.ImageVersion,
|
||||
TargetRegions: tt.fields.TargetRegions,
|
||||
ExcludeFromLatest: tt.fields.ExcludeFromLatest,
|
||||
ExcludeFromLatestTypo: tt.fields.ExcludeFromLatestTypo,
|
||||
}
|
||||
gotErrs, gotWarns := sigd.Validate("sigdest")
|
||||
|
||||
var gotStrErrs []string
|
||||
if gotErrs != nil {
|
||||
gotStrErrs = make([]string, len(gotErrs))
|
||||
for i, e := range gotErrs {
|
||||
gotStrErrs[i] = e.Error()
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(gotStrErrs, tt.wantErrs) {
|
||||
t.Errorf("SharedImageGalleryDestination.Validate() gotErrs = %q, want %q", gotStrErrs, tt.wantErrs)
|
||||
}
|
||||
if !reflect.DeepEqual(gotWarns, tt.wantWarns) {
|
||||
t.Errorf("SharedImageGalleryDestination.Validate() gotWarns = %q, want %q", gotWarns, tt.wantWarns)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/client"
|
||||
)
|
||||
|
||||
var _ multistep.Step = &StepAttachDisk{}
|
||||
|
||||
type StepAttachDisk struct {
|
||||
attached bool
|
||||
}
|
||||
|
||||
func (s *StepAttachDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
azcli := state.Get("azureclient").(client.AzureClientSet)
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
diskset := state.Get(stateBagKey_Diskset).(Diskset)
|
||||
diskResourceID := diskset.OS().String()
|
||||
|
||||
ui.Say(fmt.Sprintf("Attaching disk '%s'", diskResourceID))
|
||||
|
||||
da := NewDiskAttacher(azcli)
|
||||
lun, err := da.AttachDisk(ctx, diskResourceID)
|
||||
if err != nil {
|
||||
log.Printf("StepAttachDisk.Run: error: %+v", err)
|
||||
err := fmt.Errorf(
|
||||
"error attaching disk '%s': %v", diskResourceID, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Disk attached, waiting for device to show up")
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Minute*3) // in case is not configured correctly
|
||||
defer cancel()
|
||||
device, err := da.WaitForDevice(ctx, lun)
|
||||
if err != nil {
|
||||
log.Printf("StepAttachDisk.Run: error: %+v", err)
|
||||
err := fmt.Errorf(
|
||||
"error attaching disk '%s': %v", diskResourceID, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Disk available at %q", device))
|
||||
s.attached = true
|
||||
state.Put("device", device)
|
||||
state.Put("attach_cleanup", s)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepAttachDisk) Cleanup(state multistep.StateBag) {
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
if err := s.CleanupFunc(state); err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepAttachDisk) CleanupFunc(state multistep.StateBag) error {
|
||||
|
||||
if s.attached {
|
||||
azcli := state.Get("azureclient").(client.AzureClientSet)
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
diskset := state.Get(stateBagKey_Diskset).(Diskset)
|
||||
diskResourceID := diskset.OS().String()
|
||||
|
||||
ui.Say(fmt.Sprintf("Detaching disk '%s'", diskResourceID))
|
||||
|
||||
da := NewDiskAttacher(azcli)
|
||||
err := da.DetachDisk(context.Background(), diskResourceID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error detaching %q: %v", diskResourceID, err)
|
||||
}
|
||||
s.attached = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"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-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/client"
|
||||
)
|
||||
|
||||
func TestStepAttachDisk_Run(t *testing.T) {
|
||||
type fields struct {
|
||||
GetDiskResponseCode int
|
||||
GetDiskResponseBody string
|
||||
|
||||
attachError error
|
||||
waitForDeviceError error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want multistep.StepAction
|
||||
}{
|
||||
{
|
||||
name: "HappyPath",
|
||||
want: multistep.ActionContinue,
|
||||
},
|
||||
{
|
||||
name: "AttachError",
|
||||
fields: fields{
|
||||
attachError: errors.New("unit test"),
|
||||
},
|
||||
want: multistep.ActionHalt,
|
||||
},
|
||||
{
|
||||
name: "WaitForDeviceError",
|
||||
fields: fields{
|
||||
waitForDeviceError: errors.New("unit test"),
|
||||
},
|
||||
want: multistep.ActionHalt,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &StepAttachDisk{}
|
||||
|
||||
NewDiskAttacher = func(azcli client.AzureClientSet) DiskAttacher {
|
||||
return &fakeDiskAttacher{
|
||||
attachError: tt.fields.attachError,
|
||||
waitForDeviceError: tt.fields.waitForDeviceError,
|
||||
}
|
||||
}
|
||||
|
||||
dm := compute.NewDisksClient("subscriptionId")
|
||||
dm.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
Request: r,
|
||||
Body: ioutil.NopCloser(strings.NewReader(tt.fields.GetDiskResponseBody)),
|
||||
StatusCode: tt.fields.GetDiskResponseCode,
|
||||
}, nil
|
||||
})
|
||||
|
||||
errorBuffer := &strings.Builder{}
|
||||
ui := &packersdk.BasicUi{
|
||||
Reader: strings.NewReader(""),
|
||||
Writer: ioutil.Discard,
|
||||
ErrorWriter: errorBuffer,
|
||||
}
|
||||
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("azureclient", &client.AzureClientSetMock{})
|
||||
state.Put("ui", ui)
|
||||
state.Put(stateBagKey_Diskset, diskset("/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1"))
|
||||
|
||||
got := s.Run(context.TODO(), state)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("StepAttachDisk.Run() = %v, want %v", got, tt.want)
|
||||
}
|
||||
|
||||
if got == multistep.ActionHalt {
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("Expected 'error' to be set in statebag after failure")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fakeDiskAttacher struct {
|
||||
attachError error
|
||||
waitForDeviceError error
|
||||
}
|
||||
|
||||
var _ DiskAttacher = &fakeDiskAttacher{}
|
||||
|
||||
func (da *fakeDiskAttacher) AttachDisk(ctx context.Context, disk string) (lun int32, err error) {
|
||||
if da.attachError != nil {
|
||||
return 0, da.attachError
|
||||
}
|
||||
return 3, nil
|
||||
}
|
||||
|
||||
func (da *fakeDiskAttacher) DiskPathForLun(lun int32) string {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (da *fakeDiskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
|
||||
if da.waitForDeviceError != nil {
|
||||
return "", da.waitForDeviceError
|
||||
}
|
||||
if lun == 3 {
|
||||
return "/dev/sdq", nil
|
||||
}
|
||||
panic("expected lun==3")
|
||||
}
|
||||
|
||||
func (da *fakeDiskAttacher) DetachDisk(ctx context.Context, disk string) (err error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (da *fakeDiskAttacher) WaitForDetach(ctx context.Context, diskID string) error {
|
||||
panic("not implemented")
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
|
||||
"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"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/azure/common/client"
|
||||
)
|
||||
|
||||
var _ multistep.Step = &StepCreateImage{}
|
||||
|
||||
type StepCreateImage struct {
|
||||
ImageResourceID string
|
||||
ImageOSState string
|
||||
OSDiskStorageAccountType string
|
||||
OSDiskCacheType string
|
||||
DataDiskStorageAccountType string
|
||||
DataDiskCacheType string
|
||||
Location string
|
||||
}
|
||||
|
||||
func (s *StepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
azcli := state.Get("azureclient").(client.AzureClientSet)
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
diskset := state.Get(stateBagKey_Diskset).(Diskset)
|
||||
diskResourceID := diskset.OS().String()
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating image %s\n using %s for os disk.",
|
||||
s.ImageResourceID,
|
||||
diskResourceID))
|
||||
|
||||
imageResource, err := azure.ParseResourceID(s.ImageResourceID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("StepCreateImage.Run: error: %+v", err)
|
||||
err := fmt.Errorf(
|
||||
"error parsing image resource id '%s': %v", s.ImageResourceID, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
image := compute.Image{
|
||||
Location: to.StringPtr(s.Location),
|
||||
ImageProperties: &compute.ImageProperties{
|
||||
StorageProfile: &compute.ImageStorageProfile{
|
||||
OsDisk: &compute.ImageOSDisk{
|
||||
OsState: compute.OperatingSystemStateTypes(s.ImageOSState),
|
||||
OsType: compute.Linux,
|
||||
ManagedDisk: &compute.SubResource{
|
||||
ID: &diskResourceID,
|
||||
},
|
||||
StorageAccountType: compute.StorageAccountTypes(s.OSDiskStorageAccountType),
|
||||
Caching: compute.CachingTypes(s.OSDiskCacheType),
|
||||
},
|
||||
// DataDisks: nil,
|
||||
// ZoneResilient: nil,
|
||||
},
|
||||
},
|
||||
// Tags: nil,
|
||||
}
|
||||
|
||||
var datadisks []compute.ImageDataDisk
|
||||
for lun, resource := range diskset {
|
||||
if lun != -1 {
|
||||
ui.Say(fmt.Sprintf(" using %q for data disk (lun %d).", resource, lun))
|
||||
|
||||
datadisks = append(datadisks, compute.ImageDataDisk{
|
||||
Lun: to.Int32Ptr(lun),
|
||||
ManagedDisk: &compute.SubResource{ID: to.StringPtr(resource.String())},
|
||||
StorageAccountType: compute.StorageAccountTypes(s.DataDiskStorageAccountType),
|
||||
Caching: compute.CachingTypes(s.DataDiskCacheType),
|
||||
})
|
||||
}
|
||||
}
|
||||
if datadisks != nil {
|
||||
sort.Slice(datadisks, func(i, j int) bool {
|
||||
return *datadisks[i].Lun < *datadisks[j].Lun
|
||||
})
|
||||
image.ImageProperties.StorageProfile.DataDisks = &datadisks
|
||||
}
|
||||
|
||||
f, err := azcli.ImagesClient().CreateOrUpdate(
|
||||
ctx,
|
||||
imageResource.ResourceGroup,
|
||||
imageResource.ResourceName,
|
||||
image)
|
||||
if err == nil {
|
||||
log.Println("Image creation in process...")
|
||||
err = f.WaitForCompletionRef(ctx, azcli.PollClient())
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("StepCreateImage.Run: error: %+v", err)
|
||||
err := fmt.Errorf(
|
||||
"error creating image '%s': %v", s.ImageResourceID, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
log.Printf("Image creation complete: %s", f.Status())
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepCreateImage) Cleanup(bag multistep.StateBag) {} // this is the final artifact, don't delete
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue