generated from hashicorp/packer-plugin-scaffolding
/
config.go
1519 lines (1353 loc) · 64.2 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation,Spot
package arm
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"math/big"
"net"
"regexp"
"strings"
"time"
"github.com/hashicorp/packer-plugin-sdk/random"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute"
"github.com/Azure/go-autorest/autorest/to"
"github.com/masterzen/winrm"
azcommon "github.com/hashicorp/packer-plugin-azure/builder/azure/common"
"github.com/hashicorp/packer-plugin-azure/builder/azure/common/client"
"github.com/hashicorp/packer-plugin-azure/builder/azure/common/constants"
"github.com/hashicorp/packer-plugin-azure/builder/azure/pkcs12"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/communicator"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"golang.org/x/crypto/ssh"
)
const (
DefaultImageVersion = "latest"
DefaultUserName = "packer"
DefaultPrivateVirtualNetworkWithPublicIp = false
DefaultVMSize = "Standard_A1"
DefaultKeyVaultSKU = "standard"
)
const (
// https://docs.microsoft.com/en-us/azure/architecture/best-practices/naming-conventions#naming-rules-and-restrictions
// Regular expressions in Go are not expressive enough, such that the regular expression returned by Azure
// can be used (no backtracking).
//
// -> ^[^_\W][\w-._]{0,79}(?<![-.])$
//
// This is not an exhaustive match, but it should be extremely close.
validResourceGroupNameRe = "^[^_\\W][\\w-._\\(\\)]{0,89}$"
validManagedDiskName = "^[^_\\W][\\w-._)]{0,79}$"
)
var (
reCaptureContainerName = regexp.MustCompile(`^[a-z0-9][a-z0-9\-]{2,62}$`)
reCaptureNamePrefix = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_\-\.]{0,23}$`)
reManagedDiskName = regexp.MustCompile(validManagedDiskName)
reResourceGroupName = regexp.MustCompile(validResourceGroupNameRe)
reSnapshotName = regexp.MustCompile(`^[A-Za-z0-9_]{1,79}$`)
reSnapshotPrefix = regexp.MustCompile(`^[A-Za-z0-9_]{1,59}$`)
reResourceNamePrefix = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9-]{0,9}$`)
)
type PlanInformation struct {
PlanName string `mapstructure:"plan_name"`
PlanProduct string `mapstructure:"plan_product"`
PlanPublisher string `mapstructure:"plan_publisher"`
PlanPromotionCode string `mapstructure:"plan_promotion_code"`
}
type SharedImageGallery struct {
Subscription string `mapstructure:"subscription"`
ResourceGroup string `mapstructure:"resource_group"`
GalleryName string `mapstructure:"gallery_name"` //Gallery resource name
ImageName string `mapstructure:"image_name"`
// Specify a specific version of an OS to boot from.
// Defaults to latest. There may be a difference in versions available
// across regions due to image synchronization latency. To ensure a consistent
// version across regions set this value to one that is available in all
// regions where you are deploying.
ImageVersion string `mapstructure:"image_version" required:"false"`
// Id of the community gallery image : /CommunityGalleries/{galleryUniqueName}/Images/{img}[/Versions/{}] (Versions part is optional)
CommunityGalleryImageId string `mapstructure:"community_gallery_image_id" required:"false"`
// Id of the direct shared gallery image : /sharedGalleries/{galleryUniqueName}/Images/{img}[/Versions/{}] (Versions part is optional)
DirectSharedGalleryImageID string `mapstructure:"direct_shared_gallery_image_id" required:"false"`
}
type SharedImageGalleryDestination struct {
SigDestinationSubscription string `mapstructure:"subscription"`
SigDestinationResourceGroup string `mapstructure:"resource_group"`
SigDestinationGalleryName string `mapstructure:"gallery_name"`
SigDestinationImageName string `mapstructure:"image_name"`
SigDestinationImageVersion string `mapstructure:"image_version"`
SigDestinationReplicationRegions []string `mapstructure:"replication_regions"`
// Specify a storage account type for the Shared Image Gallery Image Version.
// Defaults to `Standard_LRS`. Accepted values are `Standard_LRS`, `Standard_ZRS` and `Premium_LRS`
SigDestinationStorageAccountType string `mapstructure:"storage_account_type"`
// Set to true if publishing to a Specialized Gallery, this skips a call to set the build VM's OS state as Generalized
SigDestinationSpecialized bool `mapstructure:"specialized"`
}
type Spot struct {
// Specify eviction policy for spot instance: "Deallocate" or "Delete". If this is set, a spot instance will be used.
EvictionPolicy compute.VirtualMachineEvictionPolicyTypes `mapstructure:"eviction_policy"`
// How much should the VM cost maximally per hour. Specify -1 (or do not specify) to not evict based on price.
MaxPrice float32 `mapstructure:"max_price"`
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
azcommon.Config `mapstructure:",squash"`
// Authentication via OAUTH
ClientConfig client.Config `mapstructure:",squash"`
// A list of one or more fully-qualified resource IDs of user assigned
// managed identities to be configured on the VM.
// See [documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token)
// for how to acquire tokens within the VM.
// To assign a user assigned managed identity to a VM, the provided account or service principal must have [Managed Identity Operator](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#managed-identity-operator)
// and [Virtual Machine Contributor](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#virtual-machine-contributor) role assignments.
UserAssignedManagedIdentities []string `mapstructure:"user_assigned_managed_identities" required:"false"`
// VHD prefix.
CaptureNamePrefix string `mapstructure:"capture_name_prefix"`
// Destination container name.
CaptureContainerName string `mapstructure:"capture_container_name"`
// Use a [Shared Gallery
// image](https://azure.microsoft.com/en-us/blog/announcing-the-public-preview-of-shared-image-gallery/)
// as the source for this build. *VHD targets are incompatible with this
// build type* - the target must be a *Managed Image*. When using shared_image_gallery as a source, image_publisher,
// image_offer, image_sku, image_version, and custom_managed_image_name should not be set.
//
// In JSON
// ```json
// "shared_image_gallery": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0",
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
// ```
// In HCL2
// ```hcl
// shared_image_gallery {
// subscription = "00000000-0000-0000-0000-00000000000"
// resource_group = "ResourceGroup"
// gallery_name = "GalleryName"
// image_name = "ImageName"
// image_version = "1.0.0"
// }
// managed_image_name = "TargetImageName"
// managed_image_resource_group_name = "TargetResourceGroup"
// ```
SharedGallery SharedImageGallery `mapstructure:"shared_image_gallery" required:"false"`
// The name of the Shared Image Gallery under which the managed image will be published as Shared Gallery Image version.
//
// Following is an example.
//
// In JSON
// ```json
// "shared_image_gallery_destination": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0",
// "replication_regions": ["regionA", "regionB", "regionC"],
// "storage_account_type": "Standard_LRS"
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
// ```
// In HCL2
// ```hcl
// shared_image_gallery_destination {
// subscription = "00000000-0000-0000-0000-00000000000"
// resource_group = "ResourceGroup"
// gallery_name = "GalleryName"
// image_name = "ImageName"
// image_version = "1.0.0"
// replication_regions = ["regionA", "regionB", "regionC"]
// storage_account_type = "Standard_LRS"
// }
// managed_image_name = "TargetImageName"
// managed_image_resource_group_name = "TargetResourceGroup"
// ```
SharedGalleryDestination SharedImageGalleryDestination `mapstructure:"shared_image_gallery_destination"`
// How long to wait for an image to be published to the shared image
// gallery before timing out. If your Packer build is failing on the
// Publishing to Shared Image Gallery step with the error `Original Error:
// context deadline exceeded`, but the image is present when you check your
// Azure dashboard, then you probably need to increase this timeout from
// its default of "60m" (valid time units include `s` for seconds, `m` for
// minutes, and `h` for hours.)
SharedGalleryTimeout time.Duration `mapstructure:"shared_image_gallery_timeout"`
// The end of life date (2006-01-02T15:04:05.99Z) of the gallery Image Version. This property
// can be used for decommissioning purposes.
SharedGalleryImageVersionEndOfLifeDate string `mapstructure:"shared_gallery_image_version_end_of_life_date" required:"false"`
// The number of replicas of the Image Version to be created per region. This
// property would take effect for a region when regionalReplicaCount is not specified.
// Replica count must be between 1 and 100, but 50 replicas should be sufficient for most use cases.
SharedGalleryImageVersionReplicaCount int32 `mapstructure:"shared_image_gallery_replica_count" required:"false"`
// If set to true, Virtual Machines deployed from the latest version of the
// Image Definition won't use this Image Version.
SharedGalleryImageVersionExcludeFromLatest bool `mapstructure:"shared_gallery_image_version_exclude_from_latest" required:"false"`
// Name of the publisher to use for your base image (Azure Marketplace Images only). See
// [documentation](https://docs.microsoft.com/en-us/cli/azure/vm/image)
// for details.
//
// CLI example `az vm image list-publishers --location westus`
ImagePublisher string `mapstructure:"image_publisher" required:"true"`
// Name of the publisher's offer to use for your base image (Azure Marketplace Images only). See
// [documentation](https://docs.microsoft.com/en-us/cli/azure/vm/image)
// for details.
//
// CLI example
// `az vm image list-offers --location westus --publisher Canonical`
ImageOffer string `mapstructure:"image_offer" required:"true"`
// SKU of the image offer to use for your base image (Azure Marketplace Images only). See
// [documentation](https://docs.microsoft.com/en-us/cli/azure/vm/image)
// for details.
//
// CLI example
// `az vm image list-skus --location westus --publisher Canonical --offer UbuntuServer`
ImageSku string `mapstructure:"image_sku" required:"true"`
// Specify a specific version of an OS to boot from.
// Defaults to `latest`. There may be a difference in versions available
// across regions due to image synchronization latency. To ensure a consistent
// version across regions set this value to one that is available in all
// regions where you are deploying.
//
// CLI example
// `az vm image list --location westus --publisher Canonical --offer UbuntuServer --sku 16.04.0-LTS --all`
ImageVersion string `mapstructure:"image_version" required:"false"`
// URL to a custom VHD to use for your base image. If this value is set,
// image_publisher, image_offer, image_sku, or image_version should not be set.
ImageUrl string `mapstructure:"image_url" required:"true"`
// Name of a custom managed image to use for your base image. If this value is set, do
// not set image_publisher, image_offer, image_sku, or image_version.
// If this value is set, the option
// `custom_managed_image_resource_group_name` must also be set. See
// [documentation](https://docs.microsoft.com/en-us/azure/storage/storage-managed-disks-overview#images)
// to learn more about managed images.
CustomManagedImageName string `mapstructure:"custom_managed_image_name" required:"true"`
// Name of a custom managed image's resource group to use for your base image. If this
// value is set, image_publisher, image_offer, image_sku, or image_version should not be set.
// If this value is set, the option
// `custom_managed_image_name` must also be set. See
// [documentation](https://docs.microsoft.com/en-us/azure/storage/storage-managed-disks-overview#images)
// to learn more about managed images.
CustomManagedImageResourceGroupName string `mapstructure:"custom_managed_image_resource_group_name" required:"true"`
customManagedImageID string
// Azure datacenter in which your VM will build.
Location string `mapstructure:"location"`
// Size of the VM used for building. This can be changed when you deploy a
// VM from your VHD. See
// [pricing](https://azure.microsoft.com/en-us/pricing/details/virtual-machines/)
// information. Defaults to `Standard_A1`.
//
// CLI example `az vm list-sizes --location westus`
VMSize string `mapstructure:"vm_size" required:"false"`
// If set use a spot instance during build; spot configuration settings only apply to the virtual machine launched by Packer and will not be persisted on the resulting image artifact.
//
// Following is an example.
//
// In JSON
//
// ```json
// "spot": {
// "eviction_policy": "Delete",
// "max_price": "0.4",
// }
// ```
//
// In HCL2
//
// ```hcl
// spot {
// eviction_policy = "Delete"
// max_price = "0.4"
// }
// ```
Spot Spot `mapstructure:"spot" required:"false"`
// Specify the managed image resource group name where the result of the
// Packer build will be saved. The resource group must already exist. If
// this value is set, the value managed_image_name must also be set. See
// documentation to learn more about managed images.
ManagedImageResourceGroupName string `mapstructure:"managed_image_resource_group_name"`
// Specify the managed image name where the result of the Packer build will
// be saved. The image name must not exist ahead of time, and will not be
// overwritten. If this value is set, the value
// managed_image_resource_group_name must also be set. See documentation to
// learn more about managed images.
ManagedImageName string `mapstructure:"managed_image_name"`
// Specify the storage account
// type for a managed image. Valid values are Standard_LRS and Premium_LRS.
// The default is Standard_LRS.
ManagedImageStorageAccountType string `mapstructure:"managed_image_storage_account_type" required:"false"`
managedImageStorageAccountType compute.StorageAccountTypes
// If
// managed_image_os_disk_snapshot_name is set, a snapshot of the OS disk
// is created with the same name as this value before the VM is captured.
ManagedImageOSDiskSnapshotName string `mapstructure:"managed_image_os_disk_snapshot_name" required:"false"`
// If
// managed_image_data_disk_snapshot_prefix is set, snapshot of the data
// disk(s) is created with the same prefix as this value before the VM is
// captured.
ManagedImageDataDiskSnapshotPrefix string `mapstructure:"managed_image_data_disk_snapshot_prefix" required:"false"`
// If
// keep_os_disk is set, the OS disk is not deleted.
// The default is false.
KeepOSDisk bool `mapstructure:"keep_os_disk" required:"false"`
// Store the image in zone-resilient storage. You need to create it in a
// region that supports [availability
// zones](https://docs.microsoft.com/en-us/azure/availability-zones/az-overview).
ManagedImageZoneResilient bool `mapstructure:"managed_image_zone_resilient" required:"false"`
// Name/value pair tags to apply to every resource deployed i.e. Resource
// Group, VM, NIC, VNET, Public IP, KeyVault, etc. The user can define up
// to 15 tags. Tag names cannot exceed 512 characters, and tag values
// cannot exceed 256 characters.
AzureTags map[string]string `mapstructure:"azure_tags" required:"false"`
// Same as [`azure_tags`](#azure_tags) but defined as a singular repeatable block
// containing a `name` and a `value` field. In HCL2 mode the
// [`dynamic_block`](/packer/docs/templates/hcl_templates/expressions#dynamic-blocks)
// will allow you to create those programatically.
AzureTag config.NameValues `mapstructure:"azure_tag" required:"false"`
// Resource group under which the final artifact will be stored.
ResourceGroupName string `mapstructure:"resource_group_name"`
// Storage account under which the final artifact will be stored.
StorageAccount string `mapstructure:"storage_account"`
// temporary name assigned to the VM. If this
// value is not set, a random value will be assigned. Knowing the resource
// group and VM name allows one to execute commands to update the VM during a
// Packer build, e.g. attach a resource disk to the VM.
TempComputeName string `mapstructure:"temp_compute_name" required:"false"`
// temporary name assigned to the Nic. If this
// value is not set, a random value will be assigned. Being able to assign a custom
// nicname could ease deployment if naming conventions are used.
TempNicName string `mapstructure:"temp_nic_name" required:"false"`
// name assigned to the temporary resource group created during the build.
// If this value is not set, a random value will be assigned. This resource
// group is deleted at the end of the build.
TempResourceGroupName string `mapstructure:"temp_resource_group_name"`
// Specify an existing resource group to run the build in.
BuildResourceGroupName string `mapstructure:"build_resource_group_name"`
// Specify an existing key vault to use for uploading certificates to the
// instance to connect.
BuildKeyVaultName string `mapstructure:"build_key_vault_name"`
// Specify the KeyVault SKU to create during the build. Valid values are
// standard or premium. The default value is standard.
BuildKeyVaultSKU string `mapstructure:"build_key_vault_sku"`
// Specify the Disk Encryption Set ID to use to encrypt the OS and data disks created with the VM during the build
// Only supported when publishing to Shared Image Galleries, without a managed image
// The disk encryption set ID can be found in the properties tab of a disk encryption set on the Azure Portal, and is labeled as its resource ID
// https://learn.microsoft.com/en-us/azure/virtual-machines/image-version-encryption
DiskEncryptionSetId string `mapstructure:"disk_encryption_set_id"`
storageAccountBlobEndpoint string
// This value allows you to
// set a virtual_network_name and obtain a public IP. If this value is not
// set and virtual_network_name is defined Packer is only allowed to be
// executed from a host on the same subnet / virtual network.
PrivateVirtualNetworkWithPublicIp bool `mapstructure:"private_virtual_network_with_public_ip" required:"false"`
// Use a pre-existing virtual network for the
// VM. This option enables private communication with the VM, no public IP
// address is used or provisioned (unless you set
// private_virtual_network_with_public_ip).
VirtualNetworkName string `mapstructure:"virtual_network_name" required:"false"`
// If virtual_network_name is set,
// this value may also be set. If virtual_network_name is set, and this
// value is not set the builder attempts to determine the subnet to use with
// the virtual network. If the subnet cannot be found, or it cannot be
// disambiguated, this value should be set.
VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name" required:"false"`
// If virtual_network_name is
// set, this value may also be set. If virtual_network_name is set, and
// this value is not set the builder attempts to determine the resource group
// containing the virtual network. If the resource group cannot be found, or
// it cannot be disambiguated, this value should be set.
VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name" required:"false"`
// Specify a file containing custom data to inject into the cloud-init
// process. The contents of the file are read and injected into the ARM
// template. The custom data will be passed to cloud-init for processing at
// the time of provisioning. See
// [documentation](http://cloudinit.readthedocs.io/en/latest/topics/examples.html)
// to learn more about custom data, and how it can be used to influence the
// provisioning process.
CustomDataFile string `mapstructure:"custom_data_file" required:"false"`
customData string
// Specify a Base64-encode custom data to apply when launching the instance.
// Note that you need to be careful about escaping characters due to the templates being JSON.
// The custom data will be passed to cloud-init for processing at
// the time of provisioning. See
// [documentation](http://cloudinit.readthedocs.io/en/latest/topics/examples.html)
// to learn more about custom data, and how it can be used to influence the
// provisioning process.
CustomData string `mapstructure:"custom_data" required:"false"`
// Specify a file containing user data to inject into the cloud-init
// process. The contents of the file are read and injected into the ARM
// template. The user data will be available from the provision until the vm is
// deleted. Any application on the virtual machine can access the user data
// from the Azure Instance Metadata Service (IMDS) after provision.
// See [documentation](https://docs.microsoft.com/en-us/azure/virtual-machines/user-data)
// to learn more about user data.
UserDataFile string `mapstructure:"user_data_file" required:"false"`
userData string
// Specify a Base64-encode user data to apply
// Note that you need to be careful about escaping characters due to the templates being JSON.
// The user data will be available from the provision until the vm is
// deleted. Any application on the virtual machine can access the user data
// from the Azure Instance Metadata Service (IMDS) after provision.
// See [documentation](https://docs.microsoft.com/en-us/azure/virtual-machines/user-data)
// to learn more about user data.
UserData string `mapstructure:"user_data" required:"false"`
// Used for running a script on VM provision during the image build
// The following example executes the contents of the file specified by `user_data_file`:
// ```hcl2
// custom_script = "powershell -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -Command \"$userData = (Invoke-RestMethod -Headers @{Metadata=$true} -Method GET -Uri http://169.254.169.254/metadata/instance/compute/userData?api-version=2021-01-01$([char]38)format=text); $contents = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($userData)); set-content -path c:\\Windows\\Temp\\userdata.ps1 -value $contents; . c:\\Windows\\Temp\\userdata.ps1;\""
// user_data_file = "./scripts/userdata.ps1"
// ```
// Specify a command to inject into the CustomScriptExtension, to run on startup
// on Windows builds, before the communicator attempts to connect
// See [documentation](https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/custom-script-windows)
// to learn more.
CustomScript string `mapstructure:"custom_script" required:"false"`
// Used for creating images from Marketplace images. Please refer to
// [Deploy an image with Marketplace
// terms](https://aka.ms/azuremarketplaceapideployment) for more details.
// Not all Marketplace images support programmatic deployment, and support
// is controlled by the image publisher.
//
// An example plan\_info object is defined below.
//
// ```json
// {
// "plan_info": {
// "plan_name": "rabbitmq",
// "plan_product": "rabbitmq",
// "plan_publisher": "bitnami"
// }
// }
// ```
//
// `plan_name` (string) - The plan name, required. `plan_product` (string) -
// The plan product, required. `plan_publisher` (string) - The plan publisher,
// required. `plan_promotion_code` (string) - Some images accept a promotion
// code, optional.
//
// Images created from the Marketplace with `plan_info` **must** specify
// `plan_info` whenever the image is deployed. The builder automatically adds
// tags to the image to ensure this information is not lost. The following
// tags are added.
//
// ```text
// 1. PlanName
// 2. PlanProduct
// 3. PlanPublisher
// 4. PlanPromotionCode
// ```
//
PlanInfo PlanInformation `mapstructure:"plan_info" required:"false"`
// The default PollingDuration for azure is 15mins, this property will override
// that value. See [Azure DefaultPollingDuration](https://godoc.org/github.com/Azure/go-autorest/autorest#pkg-constants)
// If your Packer build is failing on the
// ARM deployment step with the error `Original Error:
// context deadline exceeded`, then you probably need to increase this timeout from
// its default of "15m" (valid time units include `s` for seconds, `m` for
// minutes, and `h` for hours.)
PollingDurationTimeout time.Duration `mapstructure:"polling_duration_timeout" required:"false"`
// If either Linux or Windows is specified Packer will
// automatically configure authentication credentials for the provisioned
// machine. For Linux this configures an SSH authorized key. For Windows
// this configures a WinRM certificate.
OSType string `mapstructure:"os_type" required:"false"`
// temporary name assigned to the OSDisk. If this
// value is not set, a random value will be assigned. Being able to assign a custom
// osDiskName could ease deployment if naming conventions are used.
TempOSDiskName string `mapstructure:"temp_os_disk_name" required:"false"`
// Specify the size of the OS disk in GB
// (gigabytes). Values of zero or less than zero are ignored.
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb" required:"false"`
// The size(s) of any additional hard disks for the VM in gigabytes. If
// this is not specified then the VM will only contain an OS disk. The
// number of additional disks and maximum size of a disk depends on the
// configuration of your VM. See
// [Windows](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/about-disks-and-vhds)
// or
// [Linux](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/about-disks-and-vhds)
// for more information.
//
// For VHD builds the final artifacts will be named
// `PREFIX-dataDisk-<n>.UUID.vhd` and stored in the specified capture
// container along side the OS disk. The additional disks are included in
// the deployment template `PREFIX-vmTemplate.UUID`.
//
// For Managed build the final artifacts are included in the managed image.
// The additional disk will have the same storage account type as the OS
// disk, as specified with the `managed_image_storage_account_type`
// setting.
AdditionalDiskSize []int32 `mapstructure:"disk_additional_size" required:"false"`
// Specify the disk caching type. Valid values
// are None, ReadOnly, and ReadWrite. The default value is ReadWrite.
DiskCachingType string `mapstructure:"disk_caching_type" required:"false"`
diskCachingType compute.CachingTypes
// Specify the list of IP addresses and CIDR blocks that should be
// allowed access to the VM. If provided, an Azure Network Security
// Group will be created with corresponding rules and be bound to
// the subnet of the VM.
// Providing `allowed_inbound_ip_addresses` in combination with
// `virtual_network_name` is not allowed.
AllowedInboundIpAddresses []string `mapstructure:"allowed_inbound_ip_addresses"`
// Specify storage to store Boot Diagnostics -- Enabling this option
// will create 2 Files in the specified storage account. (serial console log & screehshot file)
// once the build is completed, it has to be removed manually.
// see [here](https://docs.microsoft.com/en-us/azure/virtual-machines/troubleshooting/boot-diagnostics) for more info
BootDiagSTGAccount string `mapstructure:"boot_diag_storage_account" required:"false"`
// specify custom azure resource names during build limited to max 10 characters
// this will set the prefix for the resources. The actuall resource names will be
// `custom_resource_build_prefix` + resourcetype + 5 character random alphanumeric string
CustomResourcePrefix string `mapstructure:"custom_resource_build_prefix" required:"false"`
// Specify a license type for the build VM to enable Azure Hybrid Benefit. If not set, Pay-As-You-Go license
// model (default) will be used. Valid values are:
//
// For Windows:
// - `Windows_Client`
// - `Windows_Server`
//
// For Linux:
// - `RHEL_BYOS`
// - `SLES_BYOS`
//
// Refer to the following documentation for more information about Hybrid Benefit:
// [Windows](https://learn.microsoft.com/en-us/azure/virtual-machines/windows/hybrid-use-benefit-licensing)
// or
// [Linux](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/azure-hybrid-benefit-linux)
LicenseType string `mapstructure:"license_type" required:"false"`
// Specifies if Secure Boot and Trusted Launch is enabled for the Virtual Machine.
SecureBootEnabled bool `mapstructure:"secure_boot_enabled" required:"false"`
// Specifies if Encryption at host is enabled for the Virtual Machine.
// Requires enabling encryption at host in the Subscription read more [here](https://learn.microsoft.com/en-us/azure/virtual-machines/disks-enable-host-based-encryption-portal?tabs=azure-powershell)
EncryptionAtHost bool `mapstructure:"encryption_at_host" required:"false"`
// Specifies if vTPM (virtual Trusted Platform Module) and Trusted Launch is enabled for the Virtual Machine.
VTpmEnabled bool `mapstructure:"vtpm_enabled" required:"false"`
// Runtime Values
UserName string `mapstructure-to-hcl2:",skip"`
Password string `mapstructure-to-hcl2:",skip"`
tmpAdminPassword string
tmpCertificatePassword string
tmpResourceGroupName string
tmpComputeName string
tmpNicName string
tmpPublicIPAddressName string
tmpDeploymentName string
tmpKeyVaultName string
tmpOSDiskName string
tmpDataDiskName string
tmpSubnetName string
tmpVirtualNetworkName string
tmpNsgName string
tmpWinRMCertificateUrl string
// Authentication with the VM via SSH
sshAuthorizedKey string
// Authentication with the VM via WinRM
winrmCertificate string
Comm communicator.Config `mapstructure:",squash"`
ctx interpolate.Context
// If you want packer to delete the
// temporary resource group asynchronously set this value. It's a boolean
// value and defaults to false. Important Setting this true means that
// your builds are faster, however any failed deletes are not reported.
AsyncResourceGroupDelete bool `mapstructure:"async_resourcegroup_delete" required:"false"`
}
type keyVaultCertificate struct {
Data string `json:"data"`
DataType string `json:"dataType"`
Password string `json:"password,omitempty"`
}
func (c *Config) getSourceSharedImageGalleryID() string {
id := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/galleries/%s/images/%s",
c.SharedGallery.Subscription,
c.SharedGallery.ResourceGroup,
c.SharedGallery.GalleryName,
c.SharedGallery.ImageName)
if c.SharedGallery.ImageVersion != "" {
id += fmt.Sprintf("/versions/%s",
c.SharedGallery.ImageVersion)
}
return id
}
func (c *Config) toVMID() string {
var resourceGroupName string
if c.tmpResourceGroupName != "" {
resourceGroupName = c.tmpResourceGroupName
} else {
resourceGroupName = c.BuildResourceGroupName
}
return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", c.ClientConfig.SubscriptionID, resourceGroupName, c.tmpComputeName)
}
func (c *Config) isManagedImage() bool {
return c.ManagedImageName != ""
}
func (c *Config) isPublishToSIG() bool {
return c.SharedGalleryDestination.SigDestinationGalleryName != ""
}
func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCaptureParameters {
return &compute.VirtualMachineCaptureParameters{
DestinationContainerName: &c.CaptureContainerName,
VhdPrefix: &c.CaptureNamePrefix,
OverwriteVhds: to.BoolPtr(false),
}
}
func (c *Config) toImageParameters() *compute.Image {
return &compute.Image{
ImageProperties: &compute.ImageProperties{
SourceVirtualMachine: &compute.SubResource{
ID: to.StringPtr(c.toVMID()),
},
StorageProfile: &compute.ImageStorageProfile{
ZoneResilient: to.BoolPtr(c.ManagedImageZoneResilient),
},
},
Location: to.StringPtr(c.Location),
Tags: azcommon.MapToAzureTags(c.AzureTags),
}
}
func (c *Config) createCertificate() (string, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
err = fmt.Errorf("Failed to Generate Private Key: %s", err)
return "", err
}
return c.formatCertificateForKeyVault(privateKey)
}
func (c *Config) formatCertificateForKeyVault(privateKey *rsa.PrivateKey) (string, error) {
host := fmt.Sprintf("%s.cloudapp.net", c.tmpComputeName)
notBefore := time.Now()
notAfter := notBefore.Add(24 * time.Hour)
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
err = fmt.Errorf("Failed to Generate Serial Number: %v", err)
return "", err
}
template := x509.Certificate{
SerialNumber: serialNumber,
Issuer: pkix.Name{
CommonName: host,
},
Subject: pkix.Name{
CommonName: host,
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
err = fmt.Errorf("Failed to Create Certificate: %s", err)
return "", err
}
pfxBytes, err := pkcs12.Encode(derBytes, privateKey, c.tmpCertificatePassword)
if err != nil {
err = fmt.Errorf("Failed to encode certificate as PFX: %s", err)
return "", err
}
keyVaultDescription := keyVaultCertificate{
Data: base64.StdEncoding.EncodeToString(pfxBytes),
DataType: "pfx",
Password: c.tmpCertificatePassword,
}
bytes, err := json.Marshal(keyVaultDescription)
if err != nil {
err = fmt.Errorf("Failed to marshal key vault description: %s", err)
return "", err
}
return base64.StdEncoding.EncodeToString(bytes), nil
}
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
c.ctx.Funcs = azcommon.TemplateFuncs
err := config.Decode(c, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &c.ctx,
}, raws...)
if err != nil {
return nil, err
}
provideDefaultValues(c)
setRuntimeValues(c)
err = setUserNamePassword(c)
if err != nil {
return nil, err
}
// copy singular blocks
c.AzureTag.CopyOn(&c.AzureTags)
err = c.ClientConfig.SetDefaultValues()
if err != nil {
return nil, err
}
err = setCustomData(c)
if err != nil {
return nil, err
}
err = setCustomDataFile(c)
if err != nil {
return nil, err
}
err = setUserData(c)
if err != nil {
return nil, err
}
err = setUserDataFile(c)
if err != nil {
return nil, err
}
// NOTE: if the user did not specify a communicator, then default to both
// SSH and WinRM. This is for backwards compatibility because the code did
// not specifically force the user to set a communicator.
if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "ssh") {
err = setSshValues(c)
if err != nil {
return nil, err
}
}
if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "winrm") {
err = setWinRMCertificate(c)
if err != nil {
return nil, err
}
}
var errs *packersdk.MultiError
errs = packersdk.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
assertRequiredParametersSet(c, errs)
assertTagProperties(c, errs)
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
return nil, nil
}
func setSshValues(c *Config) error {
if c.Comm.SSHTimeout == 0 {
c.Comm.SSHTimeout = 20 * time.Minute
}
if c.Comm.SSHPrivateKeyFile != "" {
privateKeyBytes, err := c.Comm.ReadSSHPrivateKeyFile()
if err != nil {
return err
}
signer, err := ssh.ParsePrivateKey(privateKeyBytes)
if err != nil {
return err
}
publicKey := signer.PublicKey()
c.sshAuthorizedKey = fmt.Sprintf("%s %s packer Azure Deployment%s",
publicKey.Type(),
base64.StdEncoding.EncodeToString(publicKey.Marshal()),
time.Now().Format(time.RFC3339))
c.Comm.SSHPrivateKey = privateKeyBytes
} else {
sshKeyPair, err := NewOpenSshKeyPair()
if err != nil {
return err
}
c.sshAuthorizedKey = sshKeyPair.AuthorizedKey()
c.Comm.SSHPrivateKey = sshKeyPair.PrivateKey()
}
return nil
}
func setWinRMCertificate(c *Config) error {
c.Comm.WinRMTransportDecorator =
func() winrm.Transporter {
return &winrm.ClientNTLM{}
}
cert, err := c.createCertificate()
c.winrmCertificate = cert
return err
}
func setRuntimeValues(c *Config) {
var tempName = NewTempName(c.CustomResourcePrefix)
c.tmpAdminPassword = tempName.AdminPassword
// store so that we can access this later during provisioning
packersdk.LogSecretFilter.Set(c.tmpAdminPassword)
c.tmpCertificatePassword = tempName.CertificatePassword
if c.TempComputeName == "" {
c.tmpComputeName = tempName.ComputeName
} else {
c.tmpComputeName = c.TempComputeName
}
c.tmpDeploymentName = tempName.DeploymentName
// Only set tmpResourceGroupName if no name has been specified
if c.TempResourceGroupName == "" && c.BuildResourceGroupName == "" {
c.tmpResourceGroupName = tempName.ResourceGroupName
} else if c.TempResourceGroupName != "" && c.BuildResourceGroupName == "" {
c.tmpResourceGroupName = c.TempResourceGroupName
}
if c.TempNicName == "" {
c.tmpNicName = tempName.NicName
} else {
c.tmpNicName = c.TempNicName
}
c.tmpPublicIPAddressName = tempName.PublicIPAddressName
if c.TempOSDiskName == "" {
c.tmpOSDiskName = tempName.OSDiskName
} else {
c.tmpOSDiskName = c.TempOSDiskName
}
c.tmpDataDiskName = tempName.DataDiskName
c.tmpSubnetName = tempName.SubnetName
c.tmpVirtualNetworkName = tempName.VirtualNetworkName
c.tmpNsgName = tempName.NsgName
c.tmpKeyVaultName = tempName.KeyVaultName
}
func setUserNamePassword(c *Config) error {
// Set default credentials generated by the builder
c.UserName = DefaultUserName
c.Password = c.tmpAdminPassword
// Set communicator specific credentials and update defaults if different.
// Communicator specific credentials need to be updated as the standard Packer
// SSHConfigFunc and WinRMConfigFunc use communicator specific credentials, unless overwritten.
// SSH comm
if c.Comm.SSHUsername == "" {
c.Comm.SSHUsername = c.UserName
}
c.UserName = c.Comm.SSHUsername
// if user has an explicit wish to use an SSH password, we'll set it
if c.Comm.SSHPassword != "" {
c.Password = c.Comm.SSHPassword
}
if c.Comm.Type == "ssh" {
if c.Comm.SSHPassword == "" {
c.Comm.SSHPassword = c.Password
}
return nil
}
// WinRM comm
if c.Comm.WinRMUser == "" {
c.Comm.WinRMUser = c.UserName
}
c.UserName = c.Comm.WinRMUser
if c.Comm.WinRMPassword == "" {
// Configure password settings using Azure generated credentials
c.Comm.WinRMPassword = c.Password
}
if !isValidPassword(c.Comm.WinRMPassword) {
return fmt.Errorf("The supplied \"winrm_password\" must be between 8-123 characters long and must satisfy at least 3 from the following: \n1) Contains an uppercase character \n2) Contains a lowercase character\n3) Contains a numeric digit\n4) Contains a special character\n5) Control characters are not allowed")
}
c.Password = c.Comm.WinRMPassword
return nil
}
func setCustomData(c *Config) error {
if c.CustomData == "" {
return nil
}
c.customData = c.CustomData
return nil
}
func setCustomDataFile(c *Config) error {
if c.CustomDataFile == "" {
return nil
}
b, err := ioutil.ReadFile(c.CustomDataFile)
if err != nil {
return err
}
c.customData = base64.StdEncoding.EncodeToString(b)
return nil
}
func setUserData(c *Config) error {
if c.UserData == "" {
return nil
}
c.userData = c.UserData
return nil
}
func setUserDataFile(c *Config) error {
if c.UserDataFile == "" {
return nil
}
b, err := ioutil.ReadFile(c.UserDataFile)
if err != nil {
return err
}
c.userData = base64.StdEncoding.EncodeToString(b)
return nil
}
func provideDefaultValues(c *Config) {
if c.VMSize == "" {
c.VMSize = DefaultVMSize
}
if c.ManagedImageStorageAccountType == "" {
c.managedImageStorageAccountType = compute.StorageAccountTypesStandardLRS
}
if c.DiskCachingType == "" {
c.diskCachingType = compute.CachingTypesReadWrite
}
if c.ImagePublisher != "" && c.ImageVersion == "" {
c.ImageVersion = DefaultImageVersion
}
if c.BuildKeyVaultSKU == "" {
c.BuildKeyVaultSKU = DefaultKeyVaultSKU
}