kitchen-azurerm 0.15.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +92 -40
- data/lib/kitchen/driver/azure_credentials.rb +136 -0
- data/lib/kitchen/driver/azurerm.rb +162 -27
- data/templates/internal.erb +13 -6
- data/templates/public.erb +13 -6
- metadata +79 -17
- data/lib/kitchen/driver/credentials.rb +0 -90
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c53c5b50e83af16c56722ecbc2baa30b5a28621cc17f4edde82a59b22420812f
|
4
|
+
data.tar.gz: cd39338fdf0d29d39780e93b6a9fc8c27cd955daf59b6fc79f3be56801c7d891
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d62cae11a2fa2a679657fc9c81e6b03547802cd7773f6b801b644388beb09b6fe44de2f0d5f2e9a1601cf3eee92b7a2d86e1f29e7dd86b09e3e6445fdceaebd5
|
7
|
+
data.tar.gz: 8bbccc8d27fef365420257f9a11d89cd8b326cf6e60c97e737c1fe304b14f28ee543793c314e9e638096b606c6ccc64986e7d4db94345d53463981b84eadc931
|
data/LICENSE
CHANGED
@@ -186,7 +186,7 @@
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
187
187
|
identification within third-party archives.
|
188
188
|
|
189
|
-
Copyright
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
190
190
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
192
192
|
you may not use this file except in compliance with the License.
|
data/README.md
CHANGED
@@ -1,24 +1,43 @@
|
|
1
1
|
# kitchen-azurerm
|
2
2
|
|
3
|
-
[](http://badge.fury.io/rb/kitchen-azurerm)
|
3
|
+
[](http://badge.fury.io/rb/kitchen-azurerm) 
|
4
4
|
|
5
|
-
**kitchen-azurerm** is a driver for the popular test harness [Test Kitchen](http://kitchen.ci) that allows Microsoft Azure resources to be provisioned
|
5
|
+
**kitchen-azurerm** is a driver for the popular test harness [Test Kitchen](http://kitchen.ci) that allows Microsoft Azure resources to be provisioned before testing. This driver uses the new Microsoft Azure Resource Management REST API via the [azure-sdk-for-ruby](https://github.com/azure/azure-sdk-for-ruby).
|
6
6
|
|
7
|
-
This version has been tested on Windows,
|
7
|
+
This version has been tested on Windows, macOS, and Ubuntu. If you encounter a problem on your platform, please raise an issue.
|
8
8
|
|
9
9
|
## Quick-start
|
10
10
|
|
11
11
|
### Installation
|
12
12
|
|
13
|
-
This plugin is
|
13
|
+
This plugin ships in Chef Workstation out of the box so there is no need to install it when using Chef Workstation[https://downloads.chef.io/products/workstation].
|
14
14
|
|
15
|
-
|
15
|
+
If you're not using Chef Workstation and need to install the plugin as a gem run:
|
16
16
|
|
17
|
-
|
17
|
+
```$ gem install kitchen-azurerm```
|
18
18
|
|
19
19
|
### Configuration
|
20
20
|
|
21
|
-
For the driver to interact with the Microsoft Azure Resource management REST API, a Service Principal needs to be configured with Contributor rights against the specific subscription being targeted.
|
21
|
+
For the driver to interact with the Microsoft Azure Resource management REST API, a Service Principal needs to be configured with Contributor rights against the specific subscription being targeted. Using an Organizational (AAD) account and related password is no longer supported. To create a Service Principal and apply the correct permissions, you will need to [create an Azure service principal with the Azure CLI](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest#create-a-service-principal) using the [Azure CLI](https://azure.microsoft.com/en-us/documentation/articles/xplat-cli-install/). Make sure you stay within the section titled 'Authenticate service principal with password - Azure CLI'.
|
22
|
+
|
23
|
+
If the above is TLDR then try this after `az login` using your target subscription ID and the desired SP name:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
# Create a Service Principal using the desired subscription id from the command above
|
27
|
+
az ad sp create-for-rbac --name="kitchen-azurerm" --role="Contributor" --scopes="/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
28
|
+
|
29
|
+
#Output
|
30
|
+
#
|
31
|
+
#{
|
32
|
+
# "appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", <- Also known as the Client ID
|
33
|
+
# "displayName": "azure-cli-2018-12-12-14-15-39",
|
34
|
+
# "name": "http://azure-cli-2018-12-12-14-15-39",
|
35
|
+
# "password": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
36
|
+
# "tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
37
|
+
#}
|
38
|
+
```
|
39
|
+
|
40
|
+
NOTE: Don't forget to save the values from the output -- most importantly the `password`.
|
22
41
|
|
23
42
|
You will also need to ensure you have an active Azure subscription (you can get started [for free](https://azure.microsoft.com/en-us/free/) or use your [MSDN Subscription](https://azure.microsoft.com/en-us/pricing/member-offers/msdn-benefits/)).
|
24
43
|
|
@@ -29,25 +48,34 @@ You are now ready to configure kitchen-azurerm to use the credentials from the s
|
|
29
48
|
3. **Client Secret/Password**: this will be the password you supplied in the command in step 2.
|
30
49
|
4. **Tenant ID**: use the command detailed in "Manually provide credentials through Azure CLI" step 1 to get the TenantId.
|
31
50
|
|
32
|
-
Using a text editor, open or create the file ```~/.azure/credentials``` and add the following section, noting there is one section per Subscription ID.
|
51
|
+
Using a text editor, open or create the file ```~/.azure/credentials``` and add the following section, noting there is one section per Subscription ID. **Make sure you save the file with UTF-8 encoding**
|
33
52
|
|
34
53
|
```ruby
|
35
|
-
[
|
36
|
-
client_id = "
|
54
|
+
[ADD-YOUR-AZURE-SUBSCRIPTION-ID-HERE-IN-SQUARE-BRACKET]
|
55
|
+
client_id = "your-azure-client-id-here"
|
37
56
|
client_secret = "your-client-secret-here"
|
38
|
-
tenant_id = "
|
57
|
+
tenant_id = "your-azure-tenant-id-here"
|
39
58
|
```
|
40
59
|
|
41
60
|
If preferred, you may also set the following environment variables, however this would be incompatible with supporting multiple Azure subscriptions.
|
42
61
|
|
43
62
|
```ruby
|
44
|
-
AZURE_CLIENT_ID="
|
63
|
+
AZURE_CLIENT_ID="your-azure-client-id-here"
|
45
64
|
AZURE_CLIENT_SECRET="your-client-secret-here"
|
46
|
-
AZURE_TENANT_ID="
|
65
|
+
AZURE_TENANT_ID="your-azure-tenant-id-here"
|
47
66
|
```
|
48
67
|
|
49
68
|
Note that the environment variables, if set, take preference over the values in a configuration file.
|
50
69
|
|
70
|
+
After adjusting your ```~/.azure/credentials``` file you will need to adjust your ```kitchen.yml``` file to leverage the azurerm driver. Use the following examples to achieve this, then check your configuration with standard kitchen commands. For example,
|
71
|
+
|
72
|
+
```bash
|
73
|
+
% kitchen list
|
74
|
+
Instance Driver Provisioner Verifier Transport Last Action Last Error
|
75
|
+
wsus-windows-2019 Azurerm ChefZero Inspec Winrm <Not Created> <None>
|
76
|
+
wsus-windows-2016 Azurerm ChefZero Inspec Winrm <Not Created> <None>
|
77
|
+
```
|
78
|
+
|
51
79
|
### .kitchen.yml example 1 - Linux/Ubuntu
|
52
80
|
|
53
81
|
Here's an example ```.kitchen.yml``` file that provisions an Ubuntu Server, using Chef Zero as the provisioner and SSH as the transport. Note that if the key does not exist at the specified location, it will be created. Also note that if ```ssh_key``` is supplied, Test Kitchen will use this in preference to any default/configured passwords that are supplied.
|
@@ -56,7 +84,7 @@ Here's an example ```.kitchen.yml``` file that provisions an Ubuntu Server, usin
|
|
56
84
|
---
|
57
85
|
driver:
|
58
86
|
name: azurerm
|
59
|
-
subscription_id: '
|
87
|
+
subscription_id: 'your-azure-subscription-id-here'
|
60
88
|
location: 'West Europe'
|
61
89
|
machine_size: 'Standard_D1'
|
62
90
|
|
@@ -71,9 +99,6 @@ platforms:
|
|
71
99
|
driver:
|
72
100
|
image_urn: Canonical:UbuntuServer:14.04.4-LTS:latest
|
73
101
|
vm_name: trusty-vm
|
74
|
-
vm_tags:
|
75
|
-
ostype: linux
|
76
|
-
distro: ubuntu
|
77
102
|
|
78
103
|
suites:
|
79
104
|
- name: default
|
@@ -84,7 +109,7 @@ suites:
|
|
84
109
|
|
85
110
|
### Concurrent execution
|
86
111
|
|
87
|
-
Concurrent execution of create/converge/destroy is supported via the --concurrency parameter. Each machine is created in
|
112
|
+
Concurrent execution of create/converge/destroy is supported via the --concurrency parameter. Each machine is created in its own Azure Resource Group so it has no shared lifecycle with the other machines in the test run. To take advantage of parallel execution use the following command:
|
88
113
|
|
89
114
|
```kitchen test --concurrency <n>```
|
90
115
|
|
@@ -98,7 +123,7 @@ Here's a further example ```.kitchen.yml``` file that will provision a Windows S
|
|
98
123
|
---
|
99
124
|
driver:
|
100
125
|
name: azurerm
|
101
|
-
subscription_id: '
|
126
|
+
subscription_id: 'your-subscription-id-here'
|
102
127
|
location: 'West Europe'
|
103
128
|
machine_size: 'Standard_DS2_v2'
|
104
129
|
|
@@ -113,6 +138,9 @@ platforms:
|
|
113
138
|
resource_group_tags:
|
114
139
|
project: 'My Cool Project'
|
115
140
|
contact: 'me@somewhere.com'
|
141
|
+
vm_tags:
|
142
|
+
my_tag: its value
|
143
|
+
another_tag: its awesome value
|
116
144
|
transport:
|
117
145
|
name: winrm
|
118
146
|
suites:
|
@@ -125,16 +153,16 @@ suites:
|
|
125
153
|
### .kitchen.yml example 3 - "pre-deployment" ARM template
|
126
154
|
|
127
155
|
The following example introduces the ```pre_deployment_template``` and ```pre_deployment_parameters``` properties in the configuration file.
|
128
|
-
You can use this capability to execute an ARM template containing Azure resources to provision before the system under test is created.
|
156
|
+
You can use this capability to execute an ARM template containing Azure resources to provision before the system under test is created.
|
129
157
|
|
130
|
-
In the example the ARM template in the file ```predeploy.json``` would be executed with the parameters that are specified under ```pre_deployment_parameters```.
|
158
|
+
In the example the ARM template in the file ```predeploy.json``` would be executed with the parameters that are specified under ```pre_deployment_parameters```.
|
131
159
|
These resources will be created in the same Azure Resource Group as the VM under test, and therefore will be destroyed when you type ```kitchen destroy```.
|
132
160
|
|
133
161
|
```yaml
|
134
162
|
---
|
135
163
|
driver:
|
136
164
|
name: azurerm
|
137
|
-
subscription_id: '
|
165
|
+
subscription_id: 'your-azure-subscription-id-here'
|
138
166
|
location: 'West Europe'
|
139
167
|
machine_size: 'Standard_D1'
|
140
168
|
pre_deployment_template: predeploy.json
|
@@ -198,7 +226,7 @@ Example predeploy.json:
|
|
198
226
|
|
199
227
|
### .kitchen.yml example 4 - deploy VM to existing virtual network/subnet (use for ExpressRoute/VPN scenarios)
|
200
228
|
|
201
|
-
The following example introduces the ```vnet_id``` and ```subnet_id``` properties under "driver" in the configuration file.
|
229
|
+
The following example introduces the ```vnet_id``` and ```subnet_id``` properties under "driver" in the configuration file. This can be applied at the top level, or per platform.
|
202
230
|
You can use this capability to create the VM on an existing virtual network and subnet created in a different resource group.
|
203
231
|
|
204
232
|
In this case, the public IP address is not used unless ```public_ip``` is set to ```true```
|
@@ -207,7 +235,7 @@ In this case, the public IP address is not used unless ```public_ip``` is set to
|
|
207
235
|
---
|
208
236
|
driver:
|
209
237
|
name: azurerm
|
210
|
-
subscription_id: '
|
238
|
+
subscription_id: 'your-azure-subscription-id-here'
|
211
239
|
location: 'West Europe'
|
212
240
|
machine_size: 'Standard_D1'
|
213
241
|
|
@@ -241,7 +269,7 @@ Note: The image must be available first. On deletion the disk and everything is
|
|
241
269
|
---
|
242
270
|
driver:
|
243
271
|
name: azurerm
|
244
|
-
subscription_id: '
|
272
|
+
subscription_id: 'your-azure-subscription-id-here'
|
245
273
|
location: 'West Europe'
|
246
274
|
machine_size: 'Standard_D1'
|
247
275
|
|
@@ -270,7 +298,7 @@ suites:
|
|
270
298
|
|
271
299
|
This example a classic Custom VM Image (aka a VHD file) is used. As the Image VHD must be in the same storage account then the disk of the instance, the os disk is created in an existing image account.
|
272
300
|
|
273
|
-
Note: When the resource group ís deleted, the os disk is left in the
|
301
|
+
Note: When the resource group ís deleted, the os disk is left in the existing storage account blob. You must clean up manually.
|
274
302
|
|
275
303
|
This example will:
|
276
304
|
|
@@ -282,7 +310,7 @@ This example will:
|
|
282
310
|
---
|
283
311
|
driver:
|
284
312
|
name: azurerm
|
285
|
-
subscription_id: '
|
313
|
+
subscription_id: 'your-azure-subscription-id-here'
|
286
314
|
location: 'West Europe'
|
287
315
|
machine_size: 'Standard_D1'
|
288
316
|
|
@@ -319,7 +347,7 @@ Note: Custom data can be custom data or a file to custom data. Please also note
|
|
319
347
|
---
|
320
348
|
driver:
|
321
349
|
name: azurerm
|
322
|
-
subscription_id: '
|
350
|
+
subscription_id: 'your-azure-subscription-id-here'
|
323
351
|
location: 'West Europe'
|
324
352
|
machine_size: 'Standard_D1'
|
325
353
|
|
@@ -358,13 +386,13 @@ suites:
|
|
358
386
|
|
359
387
|
This example demonstrates how to add 3 additional Managed data disks to a Windows Server 2016 VM. Not supported with legacy (pre-managed disk) storage accounts.
|
360
388
|
|
361
|
-
Note the availability of a `format_data_disks` option (default: `false`).
|
389
|
+
Note the availability of a `format_data_disks` option (default: `false`). When set to true, a PowerShell script will execute at first boot to initialize and format the disks with an NTFS filesystem. This option does not affect Linux machines.
|
362
390
|
|
363
391
|
```yaml
|
364
392
|
---
|
365
393
|
driver:
|
366
394
|
name: azurerm
|
367
|
-
subscription_id: '
|
395
|
+
subscription_id: 'your-azure-subscription-id-here'
|
368
396
|
location: 'West Europe'
|
369
397
|
machine_size: 'Standard_F2s'
|
370
398
|
|
@@ -394,16 +422,16 @@ suites:
|
|
394
422
|
### .kitchen.yml example 9 - "post-deployment" ARM template with MSI authentication
|
395
423
|
|
396
424
|
The following example introduces the ```post_deployment_template``` and ```post_deployment_parameters``` properties in the configuration file.
|
397
|
-
You can use this capability to execute an ARM template containing Azure resources to provision after the system under test is created.
|
425
|
+
You can use this capability to execute an ARM template containing Azure resources to provision after the system under test is created.
|
398
426
|
|
399
|
-
In the example the ARM template in the file ```postdeploy.json``` would be executed with the parameters that are specified under ```post_deployment_parameters```.
|
427
|
+
In the example the ARM template in the file ```postdeploy.json``` would be executed with the parameters that are specified under ```post_deployment_parameters```.
|
400
428
|
These resources will be created in the same Azure Resource Group as the VM under test, and therefore will be destroyed when you type ```kitchen destroy```.
|
401
429
|
|
402
430
|
```yaml
|
403
431
|
---
|
404
432
|
driver:
|
405
433
|
name: azurerm
|
406
|
-
subscription_id: '
|
434
|
+
subscription_id: 'your-azure-subscription-id-here'
|
407
435
|
location: 'West Europe'
|
408
436
|
machine_size: 'Standard_D1'
|
409
437
|
post_deployment_template: postdeploy.json
|
@@ -488,7 +516,7 @@ See the [Managed identities for Azure resources](https://docs.microsoft.com/en-u
|
|
488
516
|
---
|
489
517
|
driver:
|
490
518
|
name: azurerm
|
491
|
-
subscription_id: '
|
519
|
+
subscription_id: 'your-azure-subscription-id-here'
|
492
520
|
location: 'West Europe'
|
493
521
|
machine_size: 'Standard_D1'
|
494
522
|
|
@@ -521,7 +549,7 @@ This following example introduces ```secret_url```, ```vault_name```, and ```vau
|
|
521
549
|
---
|
522
550
|
driver:
|
523
551
|
name: azurerm
|
524
|
-
subscription_id: '
|
552
|
+
subscription_id: 'your-azure-subscription-id-here'
|
525
553
|
location: 'CentralUS'
|
526
554
|
machine_size: 'Standard_D2s_v3'
|
527
555
|
secret_url: 'https://YOUR-SECRET-PATH'
|
@@ -546,7 +574,7 @@ suites:
|
|
546
574
|
|
547
575
|
## Support for Government and Sovereign Clouds (China and Germany)
|
548
576
|
|
549
|
-
Starting with v0.9.0 this driver has support for Azure Government and Sovereign Clouds via the use of the ```azure_environment``` setting.
|
577
|
+
Starting with v0.9.0 this driver has support for Azure Government and Sovereign Clouds via the use of the ```azure_environment``` setting. Valid Azure environments are ```Azure```, ```AzureUSGovernment```, ```AzureChina``` and ```AzureGermanCloud```
|
550
578
|
|
551
579
|
Note that the ```use_managed_disks``` option should be set to false until supported by AzureUSGovernment.
|
552
580
|
|
@@ -556,7 +584,7 @@ Note that the ```use_managed_disks``` option should be set to false until suppor
|
|
556
584
|
---
|
557
585
|
driver:
|
558
586
|
name: azurerm
|
559
|
-
subscription_id: '
|
587
|
+
subscription_id: 'your-azure-subscription-id-here'
|
560
588
|
azure_environment: 'AzureUSGovernment'
|
561
589
|
location: 'US Gov Iowa'
|
562
590
|
machine_size: 'Standard_D2_v2_Promo'
|
@@ -616,9 +644,9 @@ data: Canonical UbuntuServer 15.10-DAILY 15.10.201509220 westeurope
|
|
616
644
|
info: vm image list command OK
|
617
645
|
```
|
618
646
|
|
619
|
-
### Additional parameters that can be specified
|
647
|
+
### Additional parameters that can be specified in your `kitchen.yml` or added to your personal `kitchen.local.yml`
|
620
648
|
|
621
|
-
* Note that the ```driver``` section also
|
649
|
+
* Note that the ```driver``` section can also take explicit values for ```username``` and ```password```. Otherwise, the default username is "azure" and the password is a randomly generated 24 character password that can be found in your local kitchen state file (typically `.kitchen/<instance-name>.yml`) if you require it for any reason.
|
622
650
|
|
623
651
|
* The ```storage_account_type``` parameter defaults to 'Standard_LRS' and allows you to switch to premium storage (e.g. 'Premium_LRS')
|
624
652
|
|
@@ -626,6 +654,8 @@ info: vm image list command OK
|
|
626
654
|
|
627
655
|
* The optional ```vm_tags``` parameter allows you to define key:value pairs to tag VMs with on creation.
|
628
656
|
|
657
|
+
* The optional ```plan``` parameter allows you to define plan information when creating VMs 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.
|
658
|
+
|
629
659
|
* Managed disks are now enabled by default, to use the Storage account set ```use_managed_disks``` (default: true).
|
630
660
|
|
631
661
|
* The ```image_url``` (unmanaged disks only) parameter can be used to specify a custom vhd (This VHD must be in the same storage account as the disks of the VM, therefore ```existing_storage_account_blob_url``` must also be set and ```use_managed_disks``` must be set to false)
|
@@ -640,7 +670,7 @@ info: vm image list command OK
|
|
640
670
|
|
641
671
|
* The ```azure_resource_group_prefix``` and ```azure_resource_group_suffix``` can be used to further disambiguate Azure resource group names created by the driver.
|
642
672
|
|
643
|
-
* The ```explicit_resource_group_name``` and ```destroy_explicit_resource_group``` (default: "true") parameters can be used in scenarios where you are provided a pre-created Resource Group.
|
673
|
+
* The ```explicit_resource_group_name``` and ```destroy_explicit_resource_group``` (default: "true") parameters can be used in scenarios where you are provided a pre-created Resource Group. Example usage: ```explicit_resource_group_name: kitchen-<%= ENV["USERNAME"] %>```
|
644
674
|
|
645
675
|
* The ```destroy_resource_group_contents``` (default: "false") parameter can be used when you want to destroy the resources within a resource group without destroying the resource group itself. For example, the following configuration options used in combination would use an existing resource group (or create one if it doesn't exist) and will destroy the contents of the resource group in the ```kitchen destroy``` phase.
|
646
676
|
|
@@ -683,3 +713,25 @@ Contributions to the project are welcome via submitting Pull Requests.
|
|
683
713
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
684
714
|
4. Push to the branch (`git push origin my-new-feature`)
|
685
715
|
5. Create a new Pull Request
|
716
|
+
|
717
|
+
## Author
|
718
|
+
|
719
|
+
Stuart Preston
|
720
|
+
|
721
|
+
## License and Copyright
|
722
|
+
|
723
|
+
Copyright 2015-2020, Chef Software, Inc.
|
724
|
+
|
725
|
+
```
|
726
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
727
|
+
you may not use this file except in compliance with the License.
|
728
|
+
You may obtain a copy of the License at
|
729
|
+
|
730
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
731
|
+
|
732
|
+
Unless required by applicable law or agreed to in writing, software
|
733
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
734
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
735
|
+
See the License for the specific language governing permissions and
|
736
|
+
limitations under the License.
|
737
|
+
```
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require "inifile"
|
2
|
+
require "kitchen/logging"
|
3
|
+
|
4
|
+
module Kitchen
|
5
|
+
module Driver
|
6
|
+
#
|
7
|
+
# AzureCredentials
|
8
|
+
#
|
9
|
+
class AzureCredentials
|
10
|
+
include Kitchen::Logging
|
11
|
+
|
12
|
+
CONFIG_PATH = "#{ENV["HOME"]}/.azure/credentials".freeze
|
13
|
+
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
#
|
17
|
+
attr_reader :subscription_id
|
18
|
+
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
#
|
22
|
+
attr_reader :environment
|
23
|
+
|
24
|
+
#
|
25
|
+
# Creates and initializes a new instance of the Credentials class.
|
26
|
+
#
|
27
|
+
def initialize(subscription_id:, environment: "Azure")
|
28
|
+
@subscription_id = subscription_id
|
29
|
+
@environment = environment
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Retrieves an object containing options and credentials
|
34
|
+
#
|
35
|
+
# @return [Object] Object that can be supplied along with all Azure client requests.
|
36
|
+
#
|
37
|
+
def azure_options
|
38
|
+
options = { tenant_id: tenant_id!,
|
39
|
+
subscription_id: subscription_id,
|
40
|
+
credentials: ::MsRest::TokenCredentials.new(token_provider),
|
41
|
+
active_directory_settings: ad_settings,
|
42
|
+
base_url: endpoint_settings.resource_manager_endpoint_url }
|
43
|
+
options[:client_id] = client_id if client_id
|
44
|
+
options[:client_secret] = client_secret if client_secret
|
45
|
+
options
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def logger
|
51
|
+
Kitchen.logger
|
52
|
+
end
|
53
|
+
|
54
|
+
def config_path
|
55
|
+
@config_path ||= File.expand_path(ENV["AZURE_CONFIG_FILE"] || CONFIG_PATH)
|
56
|
+
end
|
57
|
+
|
58
|
+
def credentials
|
59
|
+
@credentials ||= begin
|
60
|
+
if File.file?(config_path)
|
61
|
+
IniFile.load(config_path)
|
62
|
+
else
|
63
|
+
warn "#{config_path} was not found or not accessible. Will attempt to use Managed Identity."
|
64
|
+
{}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def credentials_property(property)
|
70
|
+
credentials[subscription_id]&.[](property)
|
71
|
+
end
|
72
|
+
|
73
|
+
def tenant_id!
|
74
|
+
tenant_id || raise("Must provide tenant id. Use AZURE_TENANT_ID environment variable or set it in credentials file (#{config_path})")
|
75
|
+
end
|
76
|
+
|
77
|
+
def tenant_id
|
78
|
+
ENV["AZURE_TENANT_ID"] || credentials_property("tenant_id")
|
79
|
+
end
|
80
|
+
|
81
|
+
def client_id
|
82
|
+
ENV["AZURE_CLIENT_ID"] || credentials_property("client_id")
|
83
|
+
end
|
84
|
+
|
85
|
+
def client_secret
|
86
|
+
ENV["AZURE_CLIENT_SECRET"] || credentials_property("client_secret")
|
87
|
+
end
|
88
|
+
|
89
|
+
def token_provider
|
90
|
+
if client_id && client_secret
|
91
|
+
::MsRestAzure::ApplicationTokenProvider.new(tenant_id, client_id, client_secret, ad_settings)
|
92
|
+
elsif client_id
|
93
|
+
::MsRestAzure::MSITokenProvider.new(50342, ad_settings, { client_id: client_id })
|
94
|
+
else
|
95
|
+
::MsRestAzure::MSITokenProvider.new(50342, ad_settings)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Retrieves a [MsRestAzure::ActiveDirectoryServiceSettings] object representing the AD settings for the given cloud.
|
101
|
+
#
|
102
|
+
# @return [MsRestAzure::ActiveDirectoryServiceSettings] Settings to be used for subsequent requests
|
103
|
+
#
|
104
|
+
def ad_settings
|
105
|
+
case environment.downcase
|
106
|
+
when "azureusgovernment"
|
107
|
+
::MsRestAzure::ActiveDirectoryServiceSettings.get_azure_us_government_settings
|
108
|
+
when "azurechina"
|
109
|
+
::MsRestAzure::ActiveDirectoryServiceSettings.get_azure_china_settings
|
110
|
+
when "azuregermancloud"
|
111
|
+
::MsRestAzure::ActiveDirectoryServiceSettings.get_azure_german_settings
|
112
|
+
when "azure"
|
113
|
+
::MsRestAzure::ActiveDirectoryServiceSettings.get_azure_settings
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# Retrieves a [MsRestAzure::AzureEnvironment] object representing endpoint settings for the given cloud.
|
119
|
+
#
|
120
|
+
# @return [MsRestAzure::AzureEnvironment] Settings to be used for subsequent requests
|
121
|
+
#
|
122
|
+
def endpoint_settings
|
123
|
+
case environment.downcase
|
124
|
+
when "azureusgovernment"
|
125
|
+
::MsRestAzure::AzureEnvironments::AzureUSGovernment
|
126
|
+
when "azurechina"
|
127
|
+
::MsRestAzure::AzureEnvironments::AzureChinaCloud
|
128
|
+
when "azuregermancloud"
|
129
|
+
::MsRestAzure::AzureEnvironments::AzureGermanCloud
|
130
|
+
when "azure"
|
131
|
+
::MsRestAzure::AzureEnvironments::AzureCloud
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require "kitchen"
|
2
|
-
require_relative "
|
2
|
+
require_relative "azure_credentials"
|
3
3
|
require "securerandom"
|
4
4
|
require "azure_mgmt_resources"
|
5
5
|
require "azure_mgmt_network"
|
@@ -9,6 +9,7 @@ require "fileutils"
|
|
9
9
|
require "erb"
|
10
10
|
require "ostruct"
|
11
11
|
require "json"
|
12
|
+
require "faraday"
|
12
13
|
|
13
14
|
module Kitchen
|
14
15
|
module Driver
|
@@ -17,6 +18,9 @@ module Kitchen
|
|
17
18
|
#
|
18
19
|
class Azurerm < Kitchen::Driver::Base
|
19
20
|
attr_accessor :resource_management_client
|
21
|
+
attr_accessor :network_management_client
|
22
|
+
|
23
|
+
kitchen_driver_api_version 2
|
20
24
|
|
21
25
|
default_config(:azure_resource_group_prefix) do |_config|
|
22
26
|
"kitchen-"
|
@@ -71,7 +75,7 @@ module Kitchen
|
|
71
75
|
end
|
72
76
|
|
73
77
|
default_config(:password) do |_config|
|
74
|
-
|
78
|
+
SecureRandom.base64(25)
|
75
79
|
end
|
76
80
|
|
77
81
|
default_config(:vm_name) do |_config|
|
@@ -130,6 +134,10 @@ module Kitchen
|
|
130
134
|
{}
|
131
135
|
end
|
132
136
|
|
137
|
+
default_config(:plan) do |_config|
|
138
|
+
{}
|
139
|
+
end
|
140
|
+
|
133
141
|
default_config(:vm_tags) do |_config|
|
134
142
|
{}
|
135
143
|
end
|
@@ -190,6 +198,10 @@ module Kitchen
|
|
190
198
|
ENV["AZURE_SUBSCRIPTION_ID"]
|
191
199
|
end
|
192
200
|
|
201
|
+
default_config(:azure_api_retries) do |_config|
|
202
|
+
5
|
203
|
+
end
|
204
|
+
|
193
205
|
def create(state)
|
194
206
|
state = validate_state(state)
|
195
207
|
deployment_parameters = {
|
@@ -199,18 +211,21 @@ module Kitchen
|
|
199
211
|
bootDiagnosticsEnabled: config[:boot_diagnostics_enabled],
|
200
212
|
newStorageAccountName: "storage#{state[:uuid]}",
|
201
213
|
adminUsername: state[:username],
|
202
|
-
adminPassword: state[:password] || "P2ssw0rd",
|
203
214
|
dnsNameForPublicIP: "kitchen-#{state[:uuid]}",
|
204
215
|
vmName: state[:vm_name],
|
205
216
|
systemAssignedIdentity: config[:system_assigned_identity],
|
206
|
-
userAssignedIdentities: config[:user_assigned_identities],
|
217
|
+
userAssignedIdentities: config[:user_assigned_identities].map { |identity| [identity, {}] }.to_h,
|
207
218
|
secretUrl: config[:secret_url],
|
208
219
|
vaultName: config[:vault_name],
|
209
220
|
vaultResourceGroup: config[:vault_resource_group],
|
210
221
|
}
|
211
222
|
|
223
|
+
if instance.transport[:ssh_key].nil?
|
224
|
+
deployment_parameters["adminPassword"] = state[:password]
|
225
|
+
end
|
226
|
+
|
212
227
|
if config[:subscription_id].to_s == ""
|
213
|
-
raise "A subscription_id config value was not detected and kitchen-azurerm cannot continue. Please check your
|
228
|
+
raise "A subscription_id config value was not detected and kitchen-azurerm cannot continue. Please check your kitchen.yml configuration. Exiting."
|
214
229
|
end
|
215
230
|
|
216
231
|
if config[:nic_name].to_s == ""
|
@@ -255,7 +270,8 @@ module Kitchen
|
|
255
270
|
deployment_parameters["imageVersion"] = image_version
|
256
271
|
end
|
257
272
|
|
258
|
-
options = Kitchen::Driver::
|
273
|
+
options = Kitchen::Driver::AzureCredentials.new(subscription_id: config[:subscription_id],
|
274
|
+
environment: config[:azure_environment]).azure_options
|
259
275
|
|
260
276
|
debug "Azure environment: #{config[:azure_environment]}"
|
261
277
|
@resource_management_client = ::Azure::Resources::Profiles::Latest::Mgmt::Client.new(options)
|
@@ -266,7 +282,7 @@ module Kitchen
|
|
266
282
|
resource_group.tags = config[:resource_group_tags]
|
267
283
|
begin
|
268
284
|
info "Creating Resource Group: #{state[:azure_resource_group_name]}"
|
269
|
-
|
285
|
+
create_resource_group(state[:azure_resource_group_name], resource_group)
|
270
286
|
rescue ::MsRestAzure::AzureOperationError => operation_error
|
271
287
|
error operation_error.body
|
272
288
|
raise operation_error
|
@@ -277,17 +293,17 @@ module Kitchen
|
|
277
293
|
if File.file?(config[:pre_deployment_template])
|
278
294
|
pre_deployment_name = "pre-deploy-#{state[:uuid]}"
|
279
295
|
info "Creating deployment: #{pre_deployment_name}"
|
280
|
-
|
296
|
+
create_deployment_async(state[:azure_resource_group_name], pre_deployment_name, pre_deployment(config[:pre_deployment_template], config[:pre_deployment_parameters])).value!
|
281
297
|
follow_deployment_until_end_state(state[:azure_resource_group_name], pre_deployment_name)
|
282
298
|
end
|
283
299
|
deployment_name = "deploy-#{state[:uuid]}"
|
284
300
|
info "Creating deployment: #{deployment_name}"
|
285
|
-
|
301
|
+
create_deployment_async(state[:azure_resource_group_name], deployment_name, deployment(deployment_parameters)).value!
|
286
302
|
follow_deployment_until_end_state(state[:azure_resource_group_name], deployment_name)
|
287
303
|
if File.file?(config[:post_deployment_template])
|
288
304
|
post_deployment_name = "post-deploy-#{state[:uuid]}"
|
289
305
|
info "Creating deployment: #{post_deployment_name}"
|
290
|
-
|
306
|
+
create_deployment_async(state[:azure_resource_group_name], post_deployment_name, post_deployment(config[:post_deployment_template], config[:post_deployment_parameters])).value!
|
291
307
|
follow_deployment_until_end_state(state[:azure_resource_group_name], post_deployment_name)
|
292
308
|
end
|
293
309
|
rescue ::MsRestAzure::AzureOperationError => operation_error
|
@@ -302,17 +318,16 @@ module Kitchen
|
|
302
318
|
end
|
303
319
|
end
|
304
320
|
|
305
|
-
network_management_client = ::Azure::Network::Profiles::Latest::Mgmt::Client.new(options)
|
321
|
+
@network_management_client = ::Azure::Network::Profiles::Latest::Mgmt::Client.new(options)
|
306
322
|
|
307
323
|
if config[:vnet_id] == "" || config[:public_ip]
|
308
324
|
# Retrieve the public IP from the resource group:
|
309
|
-
result =
|
325
|
+
result = get_public_ip(state[:azure_resource_group_name], "publicip")
|
310
326
|
info "IP Address is: #{result.ip_address} [#{result.dns_settings.fqdn}]"
|
311
327
|
state[:hostname] = result.ip_address
|
312
328
|
else
|
313
329
|
# Retrieve the internal IP from the resource group:
|
314
|
-
|
315
|
-
result = network_interfaces.get(state[:azure_resource_group_name], vmnic.to_s)
|
330
|
+
result = get_network_interface(state[:azure_resource_group_name], vmnic.to_s)
|
316
331
|
info "IP Address is: #{result.ip_configurations[0].private_ipaddress}"
|
317
332
|
state[:hostname] = result.ip_configurations[0].private_ipaddress
|
318
333
|
end
|
@@ -475,7 +490,7 @@ module Kitchen
|
|
475
490
|
until end_provisioning_state_reached
|
476
491
|
list_outstanding_deployment_operations(resource_group, deployment_name)
|
477
492
|
sleep config[:deployment_sleep]
|
478
|
-
deployment_provisioning_state =
|
493
|
+
deployment_provisioning_state = get_deployment_state(resource_group, deployment_name)
|
479
494
|
end_provisioning_state_reached = end_provisioning_states.split(",").include?(deployment_provisioning_state)
|
480
495
|
end
|
481
496
|
info "Resource Template deployment reached end state of '#{deployment_provisioning_state}'."
|
@@ -483,7 +498,7 @@ module Kitchen
|
|
483
498
|
end
|
484
499
|
|
485
500
|
def show_failed_operations(resource_group, deployment_name)
|
486
|
-
failed_operations =
|
501
|
+
failed_operations = list_deployment_operations(resource_group, deployment_name)
|
487
502
|
failed_operations.each do |val|
|
488
503
|
resource_code = val.properties.status_code
|
489
504
|
raise val.properties.status_message.inspect.to_s if resource_code != "OK"
|
@@ -492,7 +507,7 @@ module Kitchen
|
|
492
507
|
|
493
508
|
def list_outstanding_deployment_operations(resource_group, deployment_name)
|
494
509
|
end_operation_states = "Failed,Succeeded"
|
495
|
-
deployment_operations =
|
510
|
+
deployment_operations = list_deployment_operations(resource_group, deployment_name)
|
496
511
|
deployment_operations.each do |val|
|
497
512
|
resource_provisioning_state = val.properties.provisioning_state
|
498
513
|
unless val.properties.target_resource.nil?
|
@@ -506,22 +521,18 @@ module Kitchen
|
|
506
521
|
end
|
507
522
|
end
|
508
523
|
|
509
|
-
def deployment_state(resource_group, deployment_name)
|
510
|
-
deployments = resource_management_client.deployments.get(resource_group, deployment_name)
|
511
|
-
deployments.properties.provisioning_state
|
512
|
-
end
|
513
|
-
|
514
524
|
def destroy(state)
|
515
525
|
return if state[:server_id].nil?
|
516
526
|
|
517
|
-
options = Kitchen::Driver::
|
527
|
+
options = Kitchen::Driver::AzureCredentials.new(subscription_id: state[:subscription_id],
|
528
|
+
environment: state[:azure_environment]).azure_options
|
518
529
|
@resource_management_client = ::Azure::Resources::Profiles::Latest::Mgmt::Client.new(options)
|
519
530
|
if config[:destroy_resource_group_contents] == true
|
520
531
|
info "Destroying individual resources within the Resource Group."
|
521
532
|
empty_deployment_name = "empty-deploy-#{state[:uuid]}"
|
522
533
|
begin
|
523
534
|
info "Creating deployment: #{empty_deployment_name}"
|
524
|
-
|
535
|
+
create_deployment_async(state[:azure_resource_group_name], empty_deployment_name, empty_deployment).value!
|
525
536
|
follow_deployment_until_end_state(state[:azure_resource_group_name], empty_deployment_name)
|
526
537
|
rescue ::MsRestAzure::AzureOperationError => operation_error
|
527
538
|
error operation_error.body
|
@@ -536,7 +547,7 @@ module Kitchen
|
|
536
547
|
info "Azure environment: #{state[:azure_environment]}"
|
537
548
|
begin
|
538
549
|
info "Destroying Resource Group: #{state[:azure_resource_group_name]}"
|
539
|
-
|
550
|
+
delete_resource_group_async(state[:azure_resource_group_name])
|
540
551
|
info "Destroy operation accepted and will continue in the background."
|
541
552
|
rescue ::MsRestAzure::AzureOperationError => operation_error
|
542
553
|
error operation_error.body
|
@@ -638,13 +649,25 @@ module Kitchen
|
|
638
649
|
|
639
650
|
def virtual_machine_deployment_template
|
640
651
|
if config[:vnet_id] == ""
|
641
|
-
virtual_machine_deployment_template_file("public.erb", vm_tags: vm_tag_string(config[:vm_tags]), use_managed_disks: config[:use_managed_disks], image_url: config[:image_url], existing_storage_account_blob_url: config[:existing_storage_account_blob_url], image_id: config[:image_id], existing_storage_account_container: config[:existing_storage_account_container], custom_data: config[:custom_data], os_disk_size_gb: config[:os_disk_size_gb], data_disks_for_vm_json: data_disks_for_vm_json, use_ephemeral_osdisk: config[:use_ephemeral_osdisk])
|
652
|
+
virtual_machine_deployment_template_file("public.erb", vm_tags: vm_tag_string(config[:vm_tags]), use_managed_disks: config[:use_managed_disks], image_url: config[:image_url], existing_storage_account_blob_url: config[:existing_storage_account_blob_url], image_id: config[:image_id], existing_storage_account_container: config[:existing_storage_account_container], custom_data: config[:custom_data], os_disk_size_gb: config[:os_disk_size_gb], data_disks_for_vm_json: data_disks_for_vm_json, use_ephemeral_osdisk: config[:use_ephemeral_osdisk], ssh_key: instance.transport[:ssh_key], plan_json: plan_json)
|
642
653
|
else
|
643
654
|
info "Using custom vnet: #{config[:vnet_id]}"
|
644
|
-
virtual_machine_deployment_template_file("internal.erb", vnet_id: config[:vnet_id], subnet_id: config[:subnet_id], public_ip: config[:public_ip], vm_tags: vm_tag_string(config[:vm_tags]), use_managed_disks: config[:use_managed_disks], image_url: config[:image_url], existing_storage_account_blob_url: config[:existing_storage_account_blob_url], image_id: config[:image_id], existing_storage_account_container: config[:existing_storage_account_container], custom_data: config[:custom_data], os_disk_size_gb: config[:os_disk_size_gb], data_disks_for_vm_json: data_disks_for_vm_json, use_ephemeral_osdisk: config[:use_ephemeral_osdisk])
|
655
|
+
virtual_machine_deployment_template_file("internal.erb", vnet_id: config[:vnet_id], subnet_id: config[:subnet_id], public_ip: config[:public_ip], vm_tags: vm_tag_string(config[:vm_tags]), use_managed_disks: config[:use_managed_disks], image_url: config[:image_url], existing_storage_account_blob_url: config[:existing_storage_account_blob_url], image_id: config[:image_id], existing_storage_account_container: config[:existing_storage_account_container], custom_data: config[:custom_data], os_disk_size_gb: config[:os_disk_size_gb], data_disks_for_vm_json: data_disks_for_vm_json, use_ephemeral_osdisk: config[:use_ephemeral_osdisk], ssh_key: instance.transport[:ssh_key], plan_json: plan_json)
|
645
656
|
end
|
646
657
|
end
|
647
658
|
|
659
|
+
def plan_json
|
660
|
+
return nil if config[:plan].empty?
|
661
|
+
|
662
|
+
plan = {}
|
663
|
+
plan["name"] = config[:plan][:name] if config[:plan][:name]
|
664
|
+
plan["product"] = config[:plan][:product] if config[:plan][:product]
|
665
|
+
plan["promotionCode"] = config[:plan][:promotion_code] if config[:plan][:promotion_code]
|
666
|
+
plan["publisher"] = config[:plan][:publisher] if config[:plan][:publisher]
|
667
|
+
|
668
|
+
plan.to_json
|
669
|
+
end
|
670
|
+
|
648
671
|
def virtual_machine_deployment_template_file(template_file, data = {})
|
649
672
|
template = File.read(File.expand_path(File.join(__dir__, "../../../templates", template_file)))
|
650
673
|
render_binding = OpenStruct.new(data)
|
@@ -676,6 +699,118 @@ module Kitchen
|
|
676
699
|
end
|
677
700
|
end
|
678
701
|
end
|
702
|
+
|
703
|
+
private
|
704
|
+
|
705
|
+
#
|
706
|
+
# Wrapper methods for the Azure API calls to retry the calls when getting timeouts.
|
707
|
+
#
|
708
|
+
|
709
|
+
def create_resource_group(resource_group_name, resource_group)
|
710
|
+
retries = config[:azure_api_retries]
|
711
|
+
begin
|
712
|
+
resource_management_client.resource_groups.create_or_update(resource_group_name, resource_group)
|
713
|
+
rescue Faraday::TimeoutError, Faraday::ClientError => exception
|
714
|
+
send_exception_message(exception, "while creating resource group '#{resource_group_name}'. #{retries} retries left.")
|
715
|
+
raise if retries == 0
|
716
|
+
|
717
|
+
retries -= 1
|
718
|
+
retry
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
def create_deployment_async(resource_group, deployment_name, deployment)
|
723
|
+
retries = config[:azure_api_retries]
|
724
|
+
begin
|
725
|
+
resource_management_client.deployments.begin_create_or_update_async(resource_group, deployment_name, deployment)
|
726
|
+
rescue Faraday::TimeoutError, Faraday::ClientError => exception
|
727
|
+
send_exception_message(exception, "while sending deployment creation request for deployment '#{deployment_name}'. #{retries} retries left.")
|
728
|
+
raise if retries == 0
|
729
|
+
|
730
|
+
retries -= 1
|
731
|
+
retry
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
def get_public_ip(resource_group_name, public_ip_name)
|
736
|
+
retries = config[:azure_api_retries]
|
737
|
+
begin
|
738
|
+
network_management_client.public_ipaddresses.get(resource_group_name, public_ip_name)
|
739
|
+
rescue Faraday::TimeoutError, Faraday::ClientError => exception
|
740
|
+
send_exception_message(exception, "while fetching public ip '#{public_ip_name}' for resource group '#{resource_group_name}'. #{retries} retries left.")
|
741
|
+
raise if retries == 0
|
742
|
+
|
743
|
+
retries -= 1
|
744
|
+
retry
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
def get_network_interface(resource_group_name, network_interface_name)
|
749
|
+
retries = config[:azure_api_retries]
|
750
|
+
begin
|
751
|
+
network_interfaces = ::Azure::Network::Profiles::Latest::Mgmt::NetworkInterfaces.new(network_management_client)
|
752
|
+
network_interfaces.get(resource_group_name, network_interface_name)
|
753
|
+
rescue Faraday::TimeoutError, Faraday::ClientError => exception
|
754
|
+
send_exception_message(exception, "while fetching network interface '#{network_interface_name}' for resource group '#{resource_group_name}'. #{retries} retries left.")
|
755
|
+
raise if retries == 0
|
756
|
+
|
757
|
+
retries -= 1
|
758
|
+
retry
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
762
|
+
def list_deployment_operations(resource_group, deployment_name)
|
763
|
+
retries = config[:azure_api_retries]
|
764
|
+
begin
|
765
|
+
resource_management_client.deployment_operations.list(resource_group, deployment_name)
|
766
|
+
rescue Faraday::TimeoutError, Faraday::ClientError => exception
|
767
|
+
send_exception_message(exception, "while listing deployment operations for deployment '#{deployment_name}'. #{retries} retries left.")
|
768
|
+
raise if retries == 0
|
769
|
+
|
770
|
+
retries -= 1
|
771
|
+
retry
|
772
|
+
end
|
773
|
+
end
|
774
|
+
|
775
|
+
def get_deployment_state(resource_group, deployment_name)
|
776
|
+
retries = config[:azure_api_retries]
|
777
|
+
begin
|
778
|
+
deployments = resource_management_client.deployments.get(resource_group, deployment_name)
|
779
|
+
deployments.properties.provisioning_state
|
780
|
+
rescue Faraday::TimeoutError, Faraday::ClientError => exception
|
781
|
+
send_exception_message(exception, "while retrieving state for deployment '#{deployment_name}'. #{retries} retries left.")
|
782
|
+
raise if retries == 0
|
783
|
+
|
784
|
+
retries -= 1
|
785
|
+
retry
|
786
|
+
end
|
787
|
+
end
|
788
|
+
|
789
|
+
def delete_resource_group_async(resource_group_name)
|
790
|
+
retries = config[:azure_api_retries]
|
791
|
+
begin
|
792
|
+
resource_management_client.resource_groups.begin_delete(resource_group_name)
|
793
|
+
rescue Faraday::TimeoutError, Faraday::ClientError => exception
|
794
|
+
send_exception_message(exception, "while sending resource group deletion request for '#{resource_group_name}'. #{retries} retries left.")
|
795
|
+
raise if retries == 0
|
796
|
+
|
797
|
+
retries -= 1
|
798
|
+
retry
|
799
|
+
end
|
800
|
+
end
|
801
|
+
|
802
|
+
def send_exception_message(exception, message)
|
803
|
+
if exception.is_a?(Faraday::TimeoutError)
|
804
|
+
header = "Timed out"
|
805
|
+
elsif exception.is_a?(Faraday::ClientError)
|
806
|
+
header = "Connection reset by peer"
|
807
|
+
else
|
808
|
+
# Unhandled exception, return early
|
809
|
+
info "Unrecognized exception type."
|
810
|
+
return
|
811
|
+
end
|
812
|
+
info "#{header} #{message}"
|
813
|
+
end
|
679
814
|
end
|
680
815
|
end
|
681
816
|
end
|
data/templates/internal.erb
CHANGED
@@ -26,12 +26,14 @@
|
|
26
26
|
"description": "User name for the Virtual Machine."
|
27
27
|
}
|
28
28
|
},
|
29
|
+
<%- if ssh_key.nil? -%>
|
29
30
|
"adminPassword": {
|
30
31
|
"type": "securestring",
|
31
32
|
"metadata": {
|
32
33
|
"description": "Password for the Virtual Machine."
|
33
34
|
}
|
34
35
|
},
|
36
|
+
<%- end -%>
|
35
37
|
"dnsNameForPublicIP": {
|
36
38
|
"type": "string",
|
37
39
|
"metadata": {
|
@@ -176,10 +178,10 @@
|
|
176
178
|
}
|
177
179
|
},
|
178
180
|
"userAssignedIdentities": {
|
179
|
-
"type": "
|
180
|
-
"defaultValue":
|
181
|
+
"type": "object",
|
182
|
+
"defaultValue": {},
|
181
183
|
"metadata": {
|
182
|
-
"description": "
|
184
|
+
"description": "An object whose keys are resource IDs for user identities to associate with the Virtual Machine and whose values are empty objects, or empty to disable user assigned identities."
|
183
185
|
}
|
184
186
|
},
|
185
187
|
"bootDiagnosticsEnabled": {
|
@@ -322,8 +324,10 @@
|
|
322
324
|
]
|
323
325
|
],
|
324
326
|
<%- end -%>
|
325
|
-
|
326
|
-
"adminPassword": "[parameters('adminPassword')]"
|
327
|
+
<%- if ssh_key.nil? -%>
|
328
|
+
"adminPassword": "[parameters('adminPassword')]",
|
329
|
+
<%- end -%>
|
330
|
+
"adminUsername": "[parameters('adminUsername')]"
|
327
331
|
},
|
328
332
|
"storageProfile": {
|
329
333
|
<%- if image_url.empty? and image_id.empty? -%>
|
@@ -406,9 +410,12 @@
|
|
406
410
|
<%- end -%>
|
407
411
|
}
|
408
412
|
},
|
413
|
+
<%- unless plan_json.nil? -%>
|
414
|
+
"plan": <%= plan_json %>,
|
415
|
+
<%- end -%>
|
409
416
|
"identity": {
|
410
417
|
"type": "[variables('vmIdentityType')]",
|
411
|
-
"
|
418
|
+
"userAssignedIdentities": "[if(empty(parameters('userAssignedIdentities')), json('null'), parameters('userAssignedIdentities'))]"
|
412
419
|
},
|
413
420
|
"tags": {
|
414
421
|
<%= vm_tags unless vm_tags.empty? %>
|
data/templates/public.erb
CHANGED
@@ -26,12 +26,14 @@
|
|
26
26
|
"description": "User name for the Virtual Machine."
|
27
27
|
}
|
28
28
|
},
|
29
|
+
<%- if ssh_key.nil? -%>
|
29
30
|
"adminPassword": {
|
30
31
|
"type": "securestring",
|
31
32
|
"metadata": {
|
32
33
|
"description": "Password for the Virtual Machine."
|
33
34
|
}
|
34
35
|
},
|
36
|
+
<%- end -%>
|
35
37
|
"dnsNameForPublicIP": {
|
36
38
|
"type": "string",
|
37
39
|
"metadata": {
|
@@ -176,10 +178,10 @@
|
|
176
178
|
}
|
177
179
|
},
|
178
180
|
"userAssignedIdentities": {
|
179
|
-
"type": "
|
180
|
-
"defaultValue":
|
181
|
+
"type": "object",
|
182
|
+
"defaultValue": {},
|
181
183
|
"metadata": {
|
182
|
-
"description": "
|
184
|
+
"description": "An object whose keys are resource IDs for user identities to associate with the Virtual Machine and whose values are empty objects, or empty to disable user assigned identities."
|
183
185
|
}
|
184
186
|
},
|
185
187
|
"bootDiagnosticsEnabled": {
|
@@ -341,8 +343,10 @@
|
|
341
343
|
]
|
342
344
|
],
|
343
345
|
<%- end -%>
|
344
|
-
|
345
|
-
"adminPassword": "[parameters('adminPassword')]"
|
346
|
+
<%- if ssh_key.nil? -%>
|
347
|
+
"adminPassword": "[parameters('adminPassword')]",
|
348
|
+
<%- end -%>
|
349
|
+
"adminUsername": "[parameters('adminUsername')]"
|
346
350
|
},
|
347
351
|
"storageProfile": {
|
348
352
|
<%- if image_url.empty? and image_id.empty? -%>
|
@@ -425,9 +429,12 @@
|
|
425
429
|
<%- end -%>
|
426
430
|
}
|
427
431
|
},
|
432
|
+
<%- unless plan_json.nil? -%>
|
433
|
+
"plan": <%= plan_json %>,
|
434
|
+
<%- end -%>
|
428
435
|
"identity": {
|
429
436
|
"type": "[variables('vmIdentityType')]",
|
430
|
-
"
|
437
|
+
"userAssignedIdentities": "[if(empty(parameters('userAssignedIdentities')), json('null'), parameters('userAssignedIdentities'))]"
|
431
438
|
},
|
432
439
|
"tags": {
|
433
440
|
<%= vm_tags unless vm_tags.empty? %>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kitchen-azurerm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stuart Preston
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: azure_mgmt_network
|
@@ -54,22 +54,22 @@ dependencies:
|
|
54
54
|
name: inifile
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
|
-
- - ">="
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: 3.0.0
|
60
57
|
- - "~>"
|
61
58
|
- !ruby/object:Gem::Version
|
62
59
|
version: '3.0'
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 3.0.0
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 3.0.0
|
70
67
|
- - "~>"
|
71
68
|
- !ruby/object:Gem::Version
|
72
69
|
version: '3.0'
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 3.0.0
|
73
73
|
- !ruby/object:Gem::Dependency
|
74
74
|
name: sshkey
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|
@@ -91,19 +91,25 @@ dependencies:
|
|
91
91
|
- !ruby/object:Gem::Version
|
92
92
|
version: '3'
|
93
93
|
- !ruby/object:Gem::Dependency
|
94
|
-
name:
|
94
|
+
name: test-kitchen
|
95
95
|
requirement: !ruby/object:Gem::Requirement
|
96
96
|
requirements:
|
97
97
|
- - ">="
|
98
98
|
- !ruby/object:Gem::Version
|
99
|
-
version: '
|
100
|
-
|
99
|
+
version: '1.20'
|
100
|
+
- - "<"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.0'
|
103
|
+
type: :runtime
|
101
104
|
prerelease: false
|
102
105
|
version_requirements: !ruby/object:Gem::Requirement
|
103
106
|
requirements:
|
104
107
|
- - ">="
|
105
108
|
- !ruby/object:Gem::Version
|
106
|
-
version: '
|
109
|
+
version: '1.20'
|
110
|
+
- - "<"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '3.0'
|
107
113
|
- !ruby/object:Gem::Dependency
|
108
114
|
name: rake
|
109
115
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,6 +138,62 @@ dependencies:
|
|
132
138
|
- - ">="
|
133
139
|
- !ruby/object:Gem::Version
|
134
140
|
version: '0'
|
141
|
+
- !ruby/object:Gem::Dependency
|
142
|
+
name: rspec
|
143
|
+
requirement: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - "~>"
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '3.5'
|
148
|
+
type: :development
|
149
|
+
prerelease: false
|
150
|
+
version_requirements: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - "~>"
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '3.5'
|
155
|
+
- !ruby/object:Gem::Dependency
|
156
|
+
name: rspec-mocks
|
157
|
+
requirement: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - "~>"
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '3.5'
|
162
|
+
type: :development
|
163
|
+
prerelease: false
|
164
|
+
version_requirements: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - "~>"
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '3.5'
|
169
|
+
- !ruby/object:Gem::Dependency
|
170
|
+
name: rspec-expectations
|
171
|
+
requirement: !ruby/object:Gem::Requirement
|
172
|
+
requirements:
|
173
|
+
- - "~>"
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '3.5'
|
176
|
+
type: :development
|
177
|
+
prerelease: false
|
178
|
+
version_requirements: !ruby/object:Gem::Requirement
|
179
|
+
requirements:
|
180
|
+
- - "~>"
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: '3.5'
|
183
|
+
- !ruby/object:Gem::Dependency
|
184
|
+
name: rspec-its
|
185
|
+
requirement: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - "~>"
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: 1.3.0
|
190
|
+
type: :development
|
191
|
+
prerelease: false
|
192
|
+
version_requirements: !ruby/object:Gem::Requirement
|
193
|
+
requirements:
|
194
|
+
- - "~>"
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: 1.3.0
|
135
197
|
description: Test Kitchen driver for the Microsoft Azure Resource Manager (ARM) API
|
136
198
|
email:
|
137
199
|
- stuart@chef.io
|
@@ -141,8 +203,8 @@ extra_rdoc_files: []
|
|
141
203
|
files:
|
142
204
|
- LICENSE
|
143
205
|
- README.md
|
206
|
+
- lib/kitchen/driver/azure_credentials.rb
|
144
207
|
- lib/kitchen/driver/azurerm.rb
|
145
|
-
- lib/kitchen/driver/credentials.rb
|
146
208
|
- templates/empty.erb
|
147
209
|
- templates/internal.erb
|
148
210
|
- templates/public.erb
|
@@ -150,7 +212,7 @@ homepage: https://github.com/test-kitchen/kitchen-azurerm
|
|
150
212
|
licenses:
|
151
213
|
- Apache-2.0
|
152
214
|
metadata: {}
|
153
|
-
post_install_message:
|
215
|
+
post_install_message:
|
154
216
|
rdoc_options: []
|
155
217
|
require_paths:
|
156
218
|
- lib
|
@@ -165,8 +227,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
227
|
- !ruby/object:Gem::Version
|
166
228
|
version: '0'
|
167
229
|
requirements: []
|
168
|
-
rubygems_version: 3.
|
169
|
-
signing_key:
|
230
|
+
rubygems_version: 3.1.2
|
231
|
+
signing_key:
|
170
232
|
specification_version: 4
|
171
233
|
summary: Test Kitchen driver for Azure Resource Manager.
|
172
234
|
test_files: []
|
@@ -1,90 +0,0 @@
|
|
1
|
-
require "inifile"
|
2
|
-
|
3
|
-
module Kitchen
|
4
|
-
module Driver
|
5
|
-
#
|
6
|
-
# Credentials
|
7
|
-
#
|
8
|
-
class Credentials
|
9
|
-
CONFIG_PATH = "#{ENV["HOME"]}/.azure/credentials".freeze
|
10
|
-
|
11
|
-
#
|
12
|
-
# Creates and initializes a new instance of the Credentials class.
|
13
|
-
#
|
14
|
-
def initialize
|
15
|
-
config_file = ENV["AZURE_CONFIG_FILE"] || File.expand_path(CONFIG_PATH)
|
16
|
-
if File.file?(config_file)
|
17
|
-
@credentials = IniFile.load(File.expand_path(config_file))
|
18
|
-
else
|
19
|
-
warn "#{CONFIG_PATH} was not found or not accessible."
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
#
|
24
|
-
# Retrieves an object containing options and credentials for the given
|
25
|
-
# subscription_id and azure_environment.
|
26
|
-
#
|
27
|
-
# @param subscription_id [String] The subscription_id to retrieve a token for
|
28
|
-
# @param azure_environment [String] The azure_environment to use
|
29
|
-
#
|
30
|
-
# @return [Object] Object that can be supplied along with all Azure client requests.
|
31
|
-
#
|
32
|
-
def azure_options_for_subscription(subscription_id, azure_environment = "Azure")
|
33
|
-
tenant_id = ENV["AZURE_TENANT_ID"] || @credentials[subscription_id]["tenant_id"]
|
34
|
-
client_id = ENV["AZURE_CLIENT_ID"] || @credentials[subscription_id]["client_id"]
|
35
|
-
client_secret = ENV["AZURE_CLIENT_SECRET"] || @credentials[subscription_id]["client_secret"]
|
36
|
-
token_provider = ::MsRestAzure::ApplicationTokenProvider.new(tenant_id, client_id, client_secret, ad_settings_for_azure_environment(azure_environment))
|
37
|
-
options = { tenant_id: tenant_id,
|
38
|
-
client_id: client_id,
|
39
|
-
client_secret: client_secret,
|
40
|
-
subscription_id: subscription_id,
|
41
|
-
credentials: ::MsRest::TokenCredentials.new(token_provider),
|
42
|
-
active_directory_settings: ad_settings_for_azure_environment(azure_environment),
|
43
|
-
base_url: endpoint_settings_for_azure_environment(azure_environment).resource_manager_endpoint_url }
|
44
|
-
options
|
45
|
-
end
|
46
|
-
|
47
|
-
#
|
48
|
-
# Retrieves a [MsRestAzure::ActiveDirectoryServiceSettings] object representing the AD settings for the given cloud.
|
49
|
-
# @param azure_environment [String] The Azure environment to retrieve settings for.
|
50
|
-
#
|
51
|
-
# @return [MsRestAzure::ActiveDirectoryServiceSettings] Settings to be used for subsequent requests
|
52
|
-
#
|
53
|
-
def ad_settings_for_azure_environment(azure_environment)
|
54
|
-
case azure_environment.downcase
|
55
|
-
when "azureusgovernment"
|
56
|
-
::MsRestAzure::ActiveDirectoryServiceSettings.get_azure_us_government_settings
|
57
|
-
when "azurechina"
|
58
|
-
::MsRestAzure::ActiveDirectoryServiceSettings.get_azure_china_settings
|
59
|
-
when "azuregermancloud"
|
60
|
-
::MsRestAzure::ActiveDirectoryServiceSettings.get_azure_german_settings
|
61
|
-
when "azure"
|
62
|
-
::MsRestAzure::ActiveDirectoryServiceSettings.get_azure_settings
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
#
|
67
|
-
# Retrieves a [MsRestAzure::AzureEnvironment] object representing endpoint settings for the given cloud.
|
68
|
-
# @param azure_environment [String] The Azure environment to retrieve settings for.
|
69
|
-
#
|
70
|
-
# @return [MsRestAzure::AzureEnvironment] Settings to be used for subsequent requests
|
71
|
-
#
|
72
|
-
def endpoint_settings_for_azure_environment(azure_environment)
|
73
|
-
case azure_environment.downcase
|
74
|
-
when "azureusgovernment"
|
75
|
-
::MsRestAzure::AzureEnvironments::AzureUSGovernment
|
76
|
-
when "azurechina"
|
77
|
-
::MsRestAzure::AzureEnvironments::AzureChinaCloud
|
78
|
-
when "azuregermancloud"
|
79
|
-
::MsRestAzure::AzureEnvironments::AzureGermanCloud
|
80
|
-
when "azure"
|
81
|
-
::MsRestAzure::AzureEnvironments::AzureCloud
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def self.singleton
|
86
|
-
@credentials ||= Credentials.new
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|