Zim's Notes

Just work related notes.

Setting Azure CLI Development Environment Using Visual Studio Code Remote Development With Containers

I have tested new Visual Studio Code Remote Development feature with Azure CLI. Now you can start developing Azure CLI in a few simple steps:

  • Install Visual Studio Code Insiders Edition
  • Clone Azure CLI Repository
  • Launch Visual Studio Code and wait for a few minutes
  • Set a breakpoint and press F5

To replicate this you need do download Visual Studio Code from here:

https://code.visualstudio.com/insiders/

Clone my fork of Azure CLI repository and switch to adding-devcontainer-setup branch.

1
2
3
git clone https://github.com/zikalino/azure-cli.git
cd azure-cli
git checkout adding-devcontainer-setup

Then run Visual Code from cloned repository:

1
code-insiders .

Visual Studio Code should detect that .devcontainer folder is present, and will ask whether you want to use container.

Reopen in Container

When you choose Reopen in Container, VSC will build the image from docker file, that may take a few minutes, but it will be done only once (for current folder).

Build Container

When container is ready, just set a breakpoint, for instance in azure-cli/src/azure-cli/main.py and press F5.

Run Container

More Details on the Container

My Dockerfile is pretty simple. As a base I used Ubuntu 16.04, and:

  • Installed Python 3.6
  • Cloned Azure CLI repo (just to install dependencies)
  • Created virtual environment located at /env (outside Azure CLI repo)
  • Installed azdev using pip
  • Installed Azure CLI dependencies using azdev setup
  • Made sure that /env is activated every time bash is started by Visual Studio Code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# start from Ubuntu 16.04
FROM ubuntu:xenial

# install python 3.6, pip, venv and git
RUN apt-get update
RUN apt-get -y install software-properties-common
RUN add-apt-repository ppa:deadsnakes/ppa
RUN apt-get update
RUN apt-get -y install python3.6 python3-pip python3-venv git

# clone azure cli into /workspaces/azure-cli
RUN mkdir /workspaces; cd /workspaces; git clone https://github.com/Azure/azure-cli.git

# create environment in /env and set up 
RUN python3 -m venv env
RUN /bin/bash -c "source /env/bin/activate; pip3 install azdev; azdev setup --cli /workspaces/azure-cli"

# remove azure-cli as it will be used 
RUN rm -rf ./workspaces/azure-cli

# add environment activation to .bashrc
RUN echo "source /env/bin/activate" >> ~/.bashrc

And Configuration File

Configuration file devcontainer.json is pretty simple. It specifies Dockerfile and a path to Python interpreter inside /env.

Please note that instead of specifying dockerFile it’s possible to specify existing image.

1
2
3
4
5
6
7
8
9
10
11
12
{
  "name": "Azure CLI Dev",
  "dockerFile": "Dockerfile",
  "context": ".",
  "extensions": [
      "ms-python.python"
  ],
  "settings": {
      "python.pythonPath": "/env/bin/python3"
  },
  "postCreateCommand": "bash"
}

Other Considerations

(1) It’s possible to specify existing image instead of dockerfile. If we have existing development image that would make setup even faster.

(2) Perhaps it would be a good idea to clone all azure-cli, azure-cli-dev-tools, azure-cli-extensions under workspaces. Then it should be possible to map all three of them when starting the container.

(3) Perhaps leaving azure-cli (I currently delete it) would be a good idea, this way the container could be used as a standalone dev environment with source code cloned inside.

(4) Instead of cloning azure-cli it’s possible to map external repository. I am not doing it right now, as there’s .dockerignore file that would need to be modified in order to make this solution to work, and that could affect

Documents

Development environment setup instructions:

https://github.com/Azure/azure-cli-dev-tools

About VSC Remote Development

https://code.visualstudio.com/docs/remote/remote-overview

Idea of Using Azure CLI Extension to Test, Validate, Fix and Create Azure Swagger Examples

Overview

This idea occured to me for several reasons:

  • there’s no single tool used to create, test and validate Azure Swagger examples
  • process of validating and updating Azure REST API examples is quite awkward and time consuming. Updating even single input value reauires a lot of steps, for instance responses should be updated as well
  • there are no single naming conventions
  • Azure CLI is the most commonly used tool to manage Azure resources
  • it would be very easy to create an extension based on az resource which already allows raw REST API calls meaning it provides entire framework

Advantages

  • easy, fast and consistent process of creating new and updating existing examples
  • validation and dependency checking
  • enforcing consistent naming conventions, that would lead to more consistent documentation and compatibility between examples (for instance all the examples would use single resource group myResourceGroup)
  • simple way to check examples without using any additional tool

Running / Validating Existing Examples

Overview

The tool should allow to:

  • run existing examples directly from Azure REST API specs (local or GitHub repo)
  • run examples in their raw form
  • automatically fix problems / adjust naming conventions to match common naming convention
  • allow applying custom parameter fixes
  • generate final adjusted / updated example source code
  • optionally copy example to original source tree or create a pull request in original GitHub repo (fork of Azure REST API specification)

Command

To run existing example:

az swagger run –example {location} [–raw] [–override {field}={value}] [–dependencies] [–update]

where:

–example

{location} - either URL of example in GitHub or local path to example in cloned GitHub repo

–raw

If specified all field values from the example files will be used as is.

By default extension would override existing values using default resource naming conventions.

–override

{field} - field name or path to a field in

–dependencies

The tool will attempt to create all the dependencies if they don’t exist, e.g. resource group or parent resource (using appropriate examples from swagger).

If this option is note specified, the tool will display appropriate warnings.

–update

If this option is specified extension will attempt to update existing sample in the local filesystem. In the future it could create a branch and pull requests directly to the GitHub Repo.

The attempt will be made if:

  • example was run successfully
  • there are differences between existing example input parameters and overrided input parameters
  • there is difference in return value specified and value actually returned

Sample input could look like that:

1
az swagger run --example https://github.com/Azure/azure-rest-api-specs/blob/master/specification/botservice/resource-manager/Microsoft.BotService/preview/2018-07-12/examples/CreateBot.json

Sample output could look like this:

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
-------- WARNINGS
- resourceGroupName "OneResourceGroup" was replaced by "myResourceGroup"
- resourceName "sampleBot" was replaced by "myBot"
-------- ORIGINAL EXAMPLE
{
  "parameters": {
    "subscriptionId": "subscription-id",
    "resourceGroupName": "OneResourceGroupName",
    "api-version": "2017-01-01",
    "resourceName": "samplebotname",
    "parameters": {
      "location": "West US",
      "sku": {
        "name": "S1"
      },
      "etag": "etag1",
      "tags": {
        "tag1": "value1",
        "tag2": "value2"
      },
      "name": "samplename",
      "type": "sampletype",
      "id": "someid",
      "kind": "sdk",
      "properties": {
        "description": "The description of the bot",
        "developerAppInsightKey": "appinsightskey",
        "developerAppInsightsApiKey": "appinsightsapikey",
        "developerAppInsightsApplicationId": "appinsightsappid",
        "displayName": "The Name of the bot",
        "endpoint": "http://mybot.coffee",
        "iconUrl": "http://myicon",
        "luisAppIds": [
          "luisappid1",
          "luisappid2"
        ],
        "luisKey": "luiskey",
        "msaAppId": "exampleappid"
      }
    }
  },
  "responses": {
    "200": {
      "body": {
        "location": "West US",
        "tags": {
          "tag1": "value1",
          "tag2": "value2"
        },
        "name": "samplename",
        "type": "sampletype",
        "id": "someid",
        "kind": "sdk",
        "etag": "etag1",
        "properties": {
          "description": "The description of the bot",
          "developerAppInsightKey": "appinsightskey",
          "developerAppInsightsApplicationId": "appinsightsappid",
          "displayName": "The Name of the bot",
          "endpoint": "http://mybot.coffee",
          "endpointVersion": "version",
          "iconUrl": "http://myicon",
          "luisAppIds": [
            "luisappid1",
            "luisappid2"
          ],
          "msaAppId": "msaappid",
          "configuredChannels": [
            "facebook",
            "groupme"
          ],
          "enabledChannels": [
            "facebook"
          ]
        }
      }
    },
    "201": {
      "body": {
        "location": "West US",
        "tags": {
          "tag1": "value1",
          "tag2": "value2"
        },
        "name": "samplename",
        "type": "sampletype",
        "id": "someid",
        "kind": "sdk",
        "properties": {
          "description": "The description of the bot",
          "developerAppInsightsApplicationId": "appinsightsappid",
          "displayName": "The Name of the bot",
          "endpoint": "http://mybot.coffee",
          "endpointVersion": "version",
          "iconUrl": "http://myicon",
          "luisAppIds": [
            "luisappid1",
            "luisappid2"
          ],
          "msaAppId": "msaappid",
          "configuredChannels": [
            "facebook",
            "groupme"
          ],
          "enabledChannels": [
            "facebook"
          ]
        }
      }
    }
  }
}
-------- UPDATED EXAMPLE (createbot.json)
{
  "parameters": {
    "subscriptionId": "subscription-id",
    "resourceGroupName": "myResourceGroup",
    "api-version": "2017-01-01",
    "resourceName": "myBot",
    "parameters": {
      "location": "West US",
      "sku": {
        "name": "S1"
      },
      "etag": "etag1",
      "tags": {
        "tag1": "value1",
        "tag2": "value2"
      },
      "name": "samplename",
      "type": "sampletype",
      "id": "someid",
      "kind": "sdk",
      "properties": {
        "description": "The description of the bot",
        "developerAppInsightKey": "appinsightskey",
        "developerAppInsightsApiKey": "appinsightsapikey",
        "developerAppInsightsApplicationId": "appinsightsappid",
        "displayName": "The Name of the bot",
        "endpoint": "http://mybot.coffee",
        "iconUrl": "http://myicon",
        "luisAppIds": [
          "luisappid1",
          "luisappid2"
        ],
        "luisKey": "luiskey",
        "msaAppId": "exampleappid"
      }
    }
  },
  "responses": {
    "200": {
      "body": {
        "location": "West US",
        "tags": {
          "tag1": "value1",
          "tag2": "value2"
        },
        "name": "samplename",
        "type": "sampletype",
        "id": "someid",
        "kind": "sdk",
        "etag": "etag1",
        "properties": {
          "description": "The description of the bot",
          "developerAppInsightKey": "appinsightskey",
          "developerAppInsightsApplicationId": "appinsightsappid",
          "displayName": "The Name of the bot",
          "endpoint": "http://mybot.coffee",
          "endpointVersion": "version",
          "iconUrl": "http://myicon",
          "luisAppIds": [
            "luisappid1",
            "luisappid2"
          ],
          "msaAppId": "msaappid",
          "configuredChannels": [
            "facebook",
            "groupme"
          ],
          "enabledChannels": [
            "facebook"
          ]
        }
      }
    },
    "201": {
      "body": {
        "location": "West US",
        "tags": {
          "tag1": "value1",
          "tag2": "value2"
        },
        "name": "samplename",
        "type": "sampletype",
        "id": "someid",
        "kind": "sdk",
        "properties": {
          "description": "The description of the bot",
          "developerAppInsightsApplicationId": "appinsightsappid",
          "displayName": "The Name of the bot",
          "endpoint": "http://mybot.coffee",
          "endpointVersion": "version",
          "iconUrl": "http://myicon",
          "luisAppIds": [
            "luisappid1",
            "luisappid2"
          ],
          "msaAppId": "msaappid",
          "configuredChannels": [
            "facebook",
            "groupme"
          ],
          "enabledChannels": [
            "facebook"
          ]
        }
      }
    }
  }
}
-------- RESULT
UPDATED: YES
RUN: SUCCESS

Scaffolding Examples

Overview

If example doesn’t exist, the tool will allow creating a template based on REST API specs. In the next step user should update the template and rerun the tool to validate example until it runs correctly.

Command

az swagger scaffold –swagger {location} –uri {resource-uri} –method {method} [–update]

–example

{location} - either URL of specific swagger json file or path in local

–uri

Resource URI as appears in swagger specification / documentation.

–method

Method for which scaffolding should be done, e.g. put, get, etc…

–update

If this option is specified extension will attempt to update existing sample in the local filesystem. In the future it could create a branch and pull requests directly to the GitHub Repo.

Ansible and Azure CLI - Closer Than I Thought

What if…

… Ansible support for Azure was based on Azure CLI?

Similarities and Differences

Item Azure CLI Ansible
Implementation Python/Python SDK Python/Python SDK
UX Azure CLI Options are flattened, simplified and well designed Ansible options are flattened and we strive to simplify the structure. The level of simplification of Azure CLI is our ultimate goal.
Error Handling Excellent. Azure CLI has a lot of checks and gracefully handles critical situations. User can easily create any new resource by reading documentation and reading error responses when something goes wrong. Poor, if user specifies wrong parameters in most cases module propagates error received from Azure, which in most cases is meaningless
Idempotence Not a major goal of Azure CLI, however the idea is that Azure CLI commands should be idempotent Idempotence is a major feature of Ansible, however we have several implementation problems, for instance not handling non-updatable fiels correctly, not detecting changes in parameters corectly. A lot of inconsistencies in the implementation
Documentation Excellent Good, but sometimes not complete or directly copied from REST API Specification
Check Mode NO YES (see notes on idempotence)
Coverage Around 60% of Azure API Around 12% of Azure API

How it Could Be Done?

Implementation could be extremely simple

We could have entire implementation in a single base az.py Ansible module to encapsulate Azure CLI.

Instead of implementing hundreds of modules for every single resource, we could just have symbolic links to the base module:

  • az_vm.py
  • az_acr.py
  • az_vmss.py

Base module implementation knows via which link it was loaded, so based on that it can dynamically create:

  • appropriate documentation
  • appropriate argument spec
  • handle arguments correctly and call correct commands

On the top of that module can: - recognise which options are updatable by comparing create and update parameters (using distilled knowledge from Azure CLI)

How module would work?

(1) call az xxx get to check whether resource exists, get current state (2) call az xxx create or az xxx update if resource exists (3) compare output of initial get with output of update, to check whether anything has changed

What’s missing?

Check Mode

Check mode is probably the only missing feature that Ansible has (but we are struggling to implement it correctly) and Azure CLI doesn’t.

As far as I know Azure CLI is striving to implement idempotency. I need to collect more details on that.

Anyway, a new option called –check-mode would be nice to have. It could work as follows:

User calls:

az xxxx create ….. params …..

Azure CLI calls get method on the resource to be created. Azure CLI prepares the structure to call Python SDK API. Acure CLI compares structure returned by get and structure prepared for create call. If the structures match, return no change required, if structures don’t match return list of changes detected.

This method would probably work for around 90% of Azure resources, it would fail with

This module would just handle all the interaction with unerlying Azure CLI, including: - handling currently selected subscription if necessary - handling authentication

On the top of that we could just create symbolic links to that module for every Azure resource, for instance:

az module knows through which link it was called, so it could generate documentation and argument specification dynamically.

Benefits

  • Azure CLI has extremely well maintained UX, which just appears ideal for tools like Ansible
  • Examples are curated for every resource and well tested
  • Documentation could be just reused without any changes

What’s missing?

  • idempotence
  • idempotence
  • idempotence

Processing Azure REST Examples

Some Issues with Azure REST API Examples

  • Examples are currently not tested
  • No naming conventions
  • Missing Examples (for instance Web App, Function App, I have created missing Resource Group example myself)
  • No dependencies

Solutions

Example naming conventions

I am using resource URIs to create sample names. For instance:

1
2
3
network_virtualnetworks_subnets_put.yml
network_virtualnetworks_put.yml
resourcegroups_put.yml

This approach makes it easy to find dependencies between examples, which is shown in the demo below.

Resource naming conventions within samples

I am overriding names of the resources with names derived from URI pattern.

For instance resource group will be always called myresourcegroup, and this makes combining examples trivial.

Extend Example Validation

More checks can be introduced to validate examples against actual REST API definition to detect mistakes. I will include more examples here.

Encourage Reusing REST API Examples Downstream

Currently examples from REST API are not used directly in any other project. Encouraging usage, testing and contributing fixes upstream would significantly improve REST API specs for everyone.

What we do in Ansible?

We are currently developing Autorest / Magic Modules solution to generate Ansible and Terraform modules.

Ansible modules should come with appropriate examples, and also having integration tests are requirement.

Before:

  • examples were usually written manually
  • examples were sometimes broken or getting outdated
  • integration tests were written manually

Where we are going to do now:

  • we want to have requirement that examples included in the module are all included/kept in sync with integration test
  • examples and integration tests are not written manually

Benefits:

  • Azure REST API Examples are tested
  • Examples are fixed/added at the source which is beneficial to everyone
  • Ensuring that Azure REST API documentation has actually complete/correct examples
  • Ansible/Terraform documentation has complete and correct examples that are inline with generic Azure documentation

What could be done next?

  • integrate autorest.devops autorest extension with Azure REST API Specs CI
  • enforce new rules / possibly generate warnings when necessary?
  • calculate API score for every resource including example coverage, following naming conventions, etc.

Sample Processed Output

Subnet Example

Below you can see network_virtualnetworks_subnet_put.yml example.

On the top you can see that it automatically includes network_virtualnetworks_put.yml example that creates virtual network.

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
- import_playbook: network_virtualnetworks_put.yml
- hosts: localhost
  roles:
    - ../modules
  vars_files:
    - _vars.yml
  vars:
    resource_group: myresourcegroup
    virtual_network_name: myvirtualnetwork
    subnet_name: mysubnet
  tasks:
    - name: Create subnet
      azure_rm_resource:
        idempotency: yes
        api_version: '2018-02-01'
        polling_timeout: 600
        polling_interval: 30
        # url: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{virtualNetworkName}/subnets/{subnetName}
        resource_group: '{{ resource_group }}'
        provider: Network
        resource_type: virtualNetworks
        resource_name: "{{ virtual_network_name }}"
        subresource:
          - type: subnets
            name: "{{ subnet_name }}"
        body:
          properties:
            addressPrefix: "10.0.0.0/16"

Virtual Network Example

Below you can see network_virtualnetwork_put.yml example that was referred in previous playbook.

On the top you can see that it automatically includes resourcegroups_put.yml example that creates required .

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
- import_playbook: resourcegroups_put.yml
- hosts: localhost
  roles:
    - ../modules
  vars_files:
    - _vars.yml
  vars:
    resource_group: myresourcegroup
    virtual_network_name: myvirtualnetwork
  tasks:
    - name: Create virtual network
      azure_rm_resource:
        idempotency: yes
        api_version: '2018-02-01'
        polling_timeout: 600
        polling_interval: 30
        # url: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{virtualNetworkName}
        resource_group: '{{ resource_group }}'
        provider: Network
        resource_type: virtualNetworks
        resource_name: "{{ virtual_network_name }}"
        body:
          properties:
            addressSpace:
              addressPrefixes:
                - "10.0.0.0/16"
          location: "eastus"

Resource Group Example

And finally you can check resource_groups_put.yml example that is required by previous playbooks.

On the top you can see that it automatically includes resourcegroups_put.yml example that creates required.

You can find a PR here that I have merged to REST API specs:

https://github.com/Azure/azure-rest-api-specs/pull/4971

You can find the same example was propagated here to Azure REST API docs:

https://docs.microsoft.com/en-us/rest/api/resources/resourcegroups/createorupdate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- hosts: localhost
  roles:
    - ../modules
  vars_files:
    - _vars.yml
  vars:
    resource_group: myresourcegroup
  tasks:
    - name: Create or update a resource group.
      azure_rm_resource:
        idempotency: yes
        api_version: '2018-05-01'
        # url: /subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}
        resource_group: '{{ resource_group }}'
        body:
          location: eastus

Demo

Prerequisites

Ansible 2.8

1
pip install ansible[azure]

Clone sample repository:

1
2
git clone https://github.com/zikalino/ansible-azure-rest-examples.git
cd examples

Running Examples Demo

From examples folder run:

1
ansible-playbook network_virtual_networks_subnet_put.yml

You should see that all dependencies and subnet are correctly created:

Running Examples Demo

Creating Azure Function App Using REST API

Finally I got it working…

I used appservice plan as created in my previous post:

1
az appservice plan create --name myplan -g zimsfromclixx --is-linux --location westeurope --sku S1 --number-of-workers 1

and updated my task to refer that Linux-enabled plan (serverFarmId):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- name: Create Function App using REST API
  azure_rm_resource:
    api_version: '2018-02-01'
    resource_group: ""
    provider: web
    resource_type: sites
    resource_name: ""
    body:
      kind: functionapp,linux,container
      properties:
        siteConfig:
          appSettings:
            - name: FUNCTIONS_EXTENSION_VERSION
              value': '~2'
            - name: DOCKER_REGISTRY_SERVER_URL
              value: 'https://index.docker.io'
          linuxFxVersion: 'DOCKER|httpd'
        serverFarmId: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsfromclixx/providers/Microsoft.Web/serverFarms/myplan
      location: eastus

Task is pretty simple, and I guess it may be simplified even further. I plan to create full sample, and add apropriate updates to azure_rm_functionapp module. Plus I will create a new sample for Azure REST API specification. That was at least two days of work to get here….

Creating Container Based Function App Using Azure CLI

It’s just a few lines of code, but took me some time to do it by trial and error.

Just a few steps:

(1) create resource group

(2) create storage account

(3) create appservice plan with is_linux option

(4) create function app and specify container image httpd from DockerHub

1
2
3
4
5
6
7
az group create --name zimsfromclixx --location westeurope

az storage account create --name zimsstoragefromclixx --location westeurope --resource-group zimsfromclixx --sku Standard_LRS

az appservice plan create --name myplan -g zimsfromclixx --is-linux --location westeurope --sku S1 --number-of-workers 1

az functionapp create --resource-group zimsfromclixx --os-type Linux --plan myplan --name zimsfromclixx --deployment-container-image-name httpd --storage-account zimsstoragefromclixx

Just run the commands using Azure CLI, go to the browser and enter https://zimsfromclixx.azurewebsites.net/ (you may use your own functionapp name here), and you should see standard Apache’s **It Works!!“ welcome page.

Sounds easy, however when creating functionapp, –plan argument is not obligatory, and if you omit step (3) and –plan argument, default plan will be created, and with default plan you can’t use containers. It took me some time to figure out….

Also some people run into similar problem, for instance here:

https://github.com/Azure/azure-cli/issues/7426

In fact only this PR helped me to understand how Azure CLI works:

https://github.com/Azure/azure-cli/pull/7435/files

When you try to specify –deployment-container-image-name httpd without passing –plan that is is_linux, Azure CLI resoponds with following message, which is quite misleading:

usage error: –runtime RUNTIME required for linux functions apps without custom image.

I submitted issue to Azure CLI regarding this: https://github.com/Azure/azure-cli/issues/8889

Finally, here’s a link to some documentation I found later:

https://docs.microsoft.com/en-us/azure/app-service/containers/app-service-linux-cli

Coming Rename of _facts Modules to _info Modules

It requires some statistics on what needs to be done:

Module Name Status Action
azure_rm_aks_facts 2.6 ** needs upgrade
azure_rm_applicationsecuritygroup_facts 2.8 rename
azure_rm_appserviceplan_facts 2.7 rename/obsolete
azure_rm_autoscale_facts 2.7 rename/obsolete
azure_rm_availabilityset_facts OLD ** needs upgrade
azure_rm_cdnendpoint_facts 2.8 rename
azure_rm_cdnprofile_facts 2.8 rename
azure_rm_containerinstance_facts 2.8 rename
azure_rm_containerregistry_facts 2.7 rename/obsolete
azure_rm_cosmosdbaccount_facts 2.8 rename
azure_rm_deployment_facts 2.8 rename
azure_rm_devtestlabarmtemplate_facts 2.8 rename
azure_rm_devtestlabartifactsource_facts 2.8 rename
azure_rm_devtestlabartifact_facts 2.8 rename
azure_rm_devtestlabcustomimage_facts 2.8 rename
azure_rm_devtestlabenvironment_facts 2.8 rename
azure_rm_devtestlabartifact_facts 2.8 rename
azure_rm_devtestlabpolicy_facts 2.8 rename
azure_rm_devtestlabvirtualmachine_facts 2.8 rename
azure_rm_devtestlabvirtualnetwork_facts 2.8 rename
azure_rm_devtestlab_facts 2.8 rename
azure_rm_dnsrecordset_facts OLD/SPLIT split/obsolete
azure_rm_dnszone_facts OLD/SPLIT split/obsolete
azure_rm_functionapp_facts OLD ** needs upgrade
azure_rm_hdinsight_facts 2.8 rename
azure_rm_image_facts 2.8 rename
azure_rm_loadbalancer_facts OLD ** needs upgrade
azure_rm_managed_disk_facts OLD ** needs upgrade
azure_rm_mariadbconfiguration_facts 2.8 rename
azure_rm_mariadbdatabase_facts 2.8 rename
azure_rm_mariadbfirewallrule_facts 2.8 rename
azure_rm_mariadbserver_facts 2.8 rename
azure_rm_mysqlconfiguration_facts 2.8 rename
azure_rm_mysqldatabase_facts rename/obsolete
azure_rm_mysqlfirewallrule_facts 2.8 rename
azure_rm_mysqlserver_facts rename/obsolete
azure_rm_networkinterface_facts OLD/SPLIT split/obsolete
azure_rm_postgresqlconfiguration_facts 2.8 rename
azure_rm_postgresqldatabase_facts rename/obsolete
azure_rm_postgresqlfirewallrule_facts 2.8 rename
azure_rm_postgresqlserver_facts rename/obsolete
azure_rm_publicipaddress_facts OLD/SPLIT split/obsolete
azure_rm_rediscache_facts 2.8 rename
azure_rm_resourcegroup_facts OLD ** needs upgrade
azure_rm_resource_facts 2.6 rename/obsolete
azure_rm_roleassignment_facts 2.8 rename
azure_rm_roledefinition_facts 2.8 rename
azure_rm_routetable_facts 2.7 rename/obsolete
azure_rm_securitygroup_facts OLD ** needs upgrade
azure_rm_sqldatabase_facts 2.8 rename
azure_rm_sqlfirewallrule_facts 2.8 rename
azure_rm_sqlserver_facts ** needs upgrade
azure_rm_subnet_facts 2.8 rename
azure_rm_storageaccount_facts OLD/SPLIT? split/obsolete
azure_rm_trafficmanagerendpoint_facts rename/obsolete
azure_rm_trafficmanagerprofile_facts rename/obsolete
azure_rm_virtualmachineextension_facts 2.8 rename
azure_rm_virtualmachineimage_facts OLD ** needs upgrade
azure_rm_virtualmachinescalesetextension_facts 2.8 rename
azure_rm_virtualmachinescalesetinstance_facts 2.8 rename
azure_rm_virtualmachine_facts 2.7 rename/obsolete
azure_rm_virtualmachine_scaleset_facts OLD/SPLIT split/obsolete
azure_rm_virtualnetwork_facts OLD/SPLIT split/obsolete
azure_rm_webapp_facts 2.7 rename/obsolete

From above:

needs upgrade - these modules need new curated version (9 modules)

split/obsolete - these modules alerady have curated format, need to be split (7 modules)

rename/obsolete - these modules were introduced before 2.8 and we need to rename them and made old version available via symbolic link and obsolete (14 modules)

rename - these modules we introduced in Ansible 2.8, meaning they are not release them, meaning we can release them before freeze (around 35 modules)

Impact on Sample Playbooks

  • we need to rename facts to info in all playbooks referring to new 2.8 facts modules
  • we have more then 1 year timeframe for older facts modules as they will be marked as obsolete

Impact on users

  • for all the new 2.8 modules they will see only new info modules, so there’s no impact
  • for older facts modules they can use either facts or info, if they use facts they will be encouraged to switch to new info modules, also impact is minimal
  • they will have a year to change

Impact on test

  • same tests executed on new info modules as old *facts modules
  • in most cases facts modules will be symbolic links to info modules, so content will be the same
  • for oldest modules, we can disable tests for old fact or we can test both facts and info

Impact on azure_preview_modules

  • no impact, we will just copy all the updated from devel, as usual

Dumping All the Resources in Resource Group Using Ansible

Now, this is pretty cool. With a very small Ansible playbook you can dump entire content of a Resource Group.

Please note it requires Ansible 2.8.

Running the Example

The example is now available in official Azure-Samples:

https://github.com/Azure-Samples/ansible-playbooks/blob/master/rest/resourcegroup_dump_resources.yml

The Playbook

The playbook uses azure_rm_resource_facts module and does following steps:

  • retrieve list of all of the resources in the resource group
  • dump list of resources
  • create list of resource ids
  • dump list of resource ids
  • iterate through resource ids and retrieve details of each resource
  • convert output to single list of all resources
  • dump detailed list of resources
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
- name: List all the resources under a resource group
  azure_rm_resource_facts:
    resource_group: zimsdtl
    resource_type: resources
  register: output

- name: Dump raw list of resouces in the resource group
  debug:
    var: output

- name: Create a list of resource IDs
  set_fact:
    resource_ids: "{{ output.response | map(attribute='id') | list }}"

- name: Dump list of resource IDs
  debug:
    var: resource_ids

- name: Iterate through the resources and query details on each one
  azure_rm_resource_facts:
    url: "{{ item }}"
  register: output
  with_items: "{{ resource_ids }}"

- name: Create single list of resources
  set_fact:
    resources: "{{ output.results | map(attribute='response') | sum(start=[]) }}"

- name: Dump final list of resources
  debug:
    var: resources

Final Output

And final output looks like this:

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
PLAY [List all resources in resource group] *********************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************************
ok: [localhost]

TASK [List all the resources under a resource group] ************************************************************************************************************************************
 [WARNING]: Azure API profile latest does not define an entry for GenericRestClient

ok: [localhost]

TASK [Dump raw list of resouces in the resource group] **********************************************************************************************************************************
ok: [localhost] => {
    "output": {
        "changed": false,
        "failed": false,
        "response": [
            {
                "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.DevTestLab/labs/labxxxx",
                "location": "eastus",
                "name": "labxxxx",
                "type": "Microsoft.DevTestLab/labs"
            },
            {
                "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.DevTestLab/labs/labxxxx/virtualMachines/vmxxxx",
                "location": "eastus",
                "name": "labxxxx/vmxxxx",
                "type": "Microsoft.DevTestLab/labs/virtualMachines"
            },
            {
                "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.KeyVault/vaults/labxxxx9607",
                "location": "eastus",
                "name": "labxxxx9607",
                "tags": {
                    "CreatedBy": "DevTestLabs",
                    "hidden-DevTestLabs-LabUId": "1f33aff8-4e19-43e2-b2fc-181ce85afd2a"
                },
                "type": "Microsoft.KeyVault/vaults"
            },
            {
                "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Network/virtualNetworks/vnxxxx",
                "location": "eastus",
                "name": "vnxxxx",
                "tags": {
                    "hidden-DevTestLabs-LabUId": "1f33aff8-4e19-43e2-b2fc-181ce85afd2a"
                },
                "type": "Microsoft.Network/virtualNetworks"
            },
            {
                "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Storage/storageAccounts/alabxxxx2409",
                "kind": "StorageV2",
                "location": "eastus",
                "name": "alabxxxx2409",
                "sku": {
                    "name": "Standard_LRS",
                    "tier": "Standard"
                },
                "tags": {
                    "hidden-DevTestLabs-LabUId": "1f33aff8-4e19-43e2-b2fc-181ce85afd2a"
                },
                "type": "Microsoft.Storage/storageAccounts"
            }
        ],
        "url": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/resources",
        "warnings": [
            "Azure API profile latest does not define an entry for GenericRestClient"
        ]
    }
}

TASK [Create a list of resource IDs] ****************************************************************************************************************************************************
ok: [localhost]

TASK [Dump list of resource IDs] ********************************************************************************************************************************************************
ok: [localhost] => {
    "resource_ids": [
        "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.DevTestLab/labs/labxxxx",
        "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.DevTestLab/labs/labxxxx/virtualMachines/vmxxxx",
        "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.KeyVault/vaults/labxxxx9607",
        "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Network/virtualNetworks/vnxxxx",
        "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Storage/storageAccounts/alabxxxx2409"
    ]
}

TASK [Iterate through the resources and query details on each one] **********************************************************************************************************************
ok: [localhost] => (item=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.DevTestLab/labs/labxxxx)
ok: [localhost] => (item=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.DevTestLab/labs/labxxxx/virtualMachines/vmxxxx)
ok: [localhost] => (item=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.KeyVault/vaults/labxxxx9607)
ok: [localhost] => (item=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Network/virtualNetworks/vnxxxx)
ok: [localhost] => (item=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Storage/storageAccounts/alabxxxx2409)

TASK [Create single list of resources] **************************************************************************************************************************************************
ok: [localhost]

TASK [Dump final list of resources] *****************************************************************************************************************************************************
ok: [localhost] => {
    "resources": [
        {
            "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourcegroups/zimsdtl/providers/microsoft.devtestlab/labs/labxxxx",
            "location": "eastus",
            "name": "labxxxx",
            "properties": {
                "announcement": {
                    "enabled": "Disabled",
                    "expired": false,
                    "markdown": "",
                    "title": ""
                },
                "artifactsStorageAccount": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Storage/storageAccounts/alabxxxx2409",
                "createdDate": "2019-03-01T08:42:35.4625319+00:00",
                "defaultPremiumStorageAccount": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Storage/storageAccounts/alabxxxx2409",
                "defaultStorageAccount": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Storage/storageAccounts/alabxxxx2409",
                "environmentPermission": "Reader",
                "labStorageType": "Standard",
                "mandatoryArtifactsResourceIdsLinux": [],
                "mandatoryArtifactsResourceIdsWindows": [],
                "premiumDataDiskStorageAccount": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Storage/storageAccounts/alabxxxx2409",
                "premiumDataDisks": "Disabled",
                "provisioningState": "Succeeded",
                "support": {
                    "enabled": "Disabled",
                    "markdown": ""
                },
                "uniqueIdentifier": "1f33aff8-4e19-43e2-b2fc-181ce85afd2a",
                "vaultName": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.KeyVault/vaults/labxxxx9607"
            },
            "type": "Microsoft.DevTestLab/labs"
        },
        {
            "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourcegroups/zimsdtl/providers/microsoft.devtestlab/labs/labxxxx/virtualmachines/vmxxxx",
            "location": "eastus",
            "name": "vmxxxx",
            "properties": {
                "allowClaim": false,
                "artifactDeploymentStatus": {
                    "artifactsApplied": 0,
                    "totalArtifacts": 0
                },
                "computeId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/labxxxx-vmxxxx-561112/providers/Microsoft.Compute/virtualMachines/vmxxxx",
                "createdByUser": "",
                "createdByUserId": "",
                "createdDate": "2019-03-01T08:44:56.9375632+00:00",
                "dataDiskParameters": [],
                "disallowPublicIpAddress": false,
                "expirationDate": "2029-02-22T01:49:12.117974+00:00",
                "fqdn": "vmxxxx.eastus.cloudapp.azure.com",
                "galleryImageReference": {
                    "offer": "UbuntuServer",
                    "osType": "Linux",
                    "publisher": "Canonical",
                    "sku": "16.04-LTS",
                    "version": "latest"
                },
                "labSubnetName": "vnxxxxSubnet",
                "labVirtualNetworkId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.DevTestLab/labs/labxxxx/virtualnetworks/vnxxxx",
                "lastKnownPowerState": "Stopped",
                "networkInterface": {},
                "notes": "Virtual machine notes, just something....",
                "osType": "Linux",
                "ownerObjectId": "20d81029-94cd-4923-a766-994415ff73bd",
                "ownerUserPrincipalName": "",
                "provisioningState": "Succeeded",
                "size": "Standard_A2_v2",
                "storageType": "Standard",
                "uniqueIdentifier": "39fc5a87-2b95-48ff-a827-468902b78149",
                "userName": "dtladmin",
                "virtualMachineCreationSource": "FromGalleryImage"
            },
            "type": "Microsoft.DevTestLab/labs/virtualMachines"
        },
        {
            "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.KeyVault/vaults/labxxxx9607",
            "location": "eastus",
            "name": "labxxxx9607",
            "properties": {
                "accessPolicies": [
                    {
                        "objectId": "9d706dfc-a44a-4341-9cac-e995cc8a1530",
                        "permissions": {
                            "secrets": [
                                "all"
                            ]
                        },
                        "tenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47"
                    }
                ],
                "enabledForDeployment": true,
                "provisioningState": "Succeeded",
                "sku": {
                    "family": "A",
                    "name": "standard"
                },
                "tenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
                "vaultUri": "https://labxxxx9607.vault.azure.net/"
            },
            "tags": {
                "CreatedBy": "DevTestLabs",
                "hidden-DevTestLabs-LabUId": "1f33aff8-4e19-43e2-b2fc-181ce85afd2a"
            },
            "type": "Microsoft.KeyVault/vaults"
        },
        {
            "etag": "W/\"806f86f0-0cdd-4424-9fbc-17b9ab331ad7\"",
            "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Network/virtualNetworks/vnxxxx",
            "location": "eastus",
            "name": "vnxxxx",
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "10.0.0.0/20"
                    ]
                },
                "dhcpOptions": {
                    "dnsServers": []
                },
                "enableDdosProtection": false,
                "enableVmProtection": false,
                "provisioningState": "Succeeded",
                "resourceGuid": "9f622d47-478c-44a1-9796-3ba51eb44259",
                "subnets": [
                    {
                        "etag": "W/\"806f86f0-0cdd-4424-9fbc-17b9ab331ad7\"",
                        "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Network/virtualNetworks/vnxxxx/subnets/vnxxxxSubnet",
                        "name": "vnxxxxSubnet",
                        "properties": {
                            "addressPrefix": "10.0.0.0/20",
                            "delegations": [],
                            "provisioningState": "Succeeded"
                        },
                        "type": "Microsoft.Network/virtualNetworks/subnets"
                    }
                ],
                "virtualNetworkPeerings": []
            },
            "tags": {
                "hidden-DevTestLabs-LabUId": "1f33aff8-4e19-43e2-b2fc-181ce85afd2a"
            },
            "type": "Microsoft.Network/virtualNetworks"
        },
        {
            "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/zimsdtl/providers/Microsoft.Storage/storageAccounts/alabxxxx2409",
            "kind": "StorageV2",
            "location": "eastus",
            "name": "alabxxxx2409",
            "properties": {
                "accessTier": "Hot",
                "creationTime": "2019-03-01T08:43:18.0722775Z",
                "encryption": {
                    "keySource": "Microsoft.Storage",
                    "services": {
                        "blob": {
                            "enabled": true,
                            "lastEnabledTime": "2019-03-01T08:43:18.1972664Z"
                        },
                        "file": {
                            "enabled": true,
                            "lastEnabledTime": "2019-03-01T08:43:18.1972664Z"
                        }
                    }
                },
                "networkAcls": {
                    "bypass": "AzureServices",
                    "defaultAction": "Allow",
                    "ipRules": [],
                    "virtualNetworkRules": []
                },
                "primaryEndpoints": {
                    "blob": "https://alabxxxx2409.blob.core.windows.net/",
                    "dfs": "https://alabxxxx2409.dfs.core.windows.net/",
                    "file": "https://alabxxxx2409.file.core.windows.net/",
                    "queue": "https://alabxxxx2409.queue.core.windows.net/",
                    "table": "https://alabxxxx2409.table.core.windows.net/",
                    "web": "https://alabxxxx2409.z13.web.core.windows.net/"
                },
                "primaryLocation": "eastus",
                "provisioningState": "Succeeded",
                "statusOfPrimary": "available",
                "supportsHttpsTrafficOnly": true
            },
            "sku": {
                "name": "Standard_LRS",
                "tier": "Standard"
            },
            "tags": {
                "hidden-DevTestLabs-LabUId": "1f33aff8-4e19-43e2-b2fc-181ce85afd2a"
            },
            "type": "Microsoft.Storage/storageAccounts"
        }
    ]
}

PLAY RECAP ******************************************************************************************************************************************************************************
localhost                  : ok=8    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

New Ansible Modules to Manage Azure DevTest Lab

The new sample containing tasks to create and manage Azure DevTest Lab is available here:

https://github.com/Azure-Samples/ansible-playbooks/blob/master/devtestlab-create.yml

This sample will:

  • create resource group
  • create DevTest Lab
  • create sample DevTest Lab policy
  • create sample DevTest Lab schedule
  • create sample DevTest Lab Virtual Network
  • create sample DevTest Lab artifact source
  • create instance of DTL Virtual Machine
  • list all artifact sources
  • get artifacts source facts
  • list ARM Template facts
  • get ARM Template facts
  • create instance of DevTest Lab Environment
  • create instance of DevTest Lab Image
  • delete instance of Lab

Running the sample

Variables

1
2
3
4
5
6
7
8
  vars:
    resource_group: "{{ resource_group_name }}"
    lab_name: myLab
    vn_name: myLabVirtualNetwork
    vm_name: myLabVm
    artifacts_name: myArtifacts
    github_token: "{{ lookup('env','GITHUB_ACCESS_TOKEN') }}"
    location: eastus

Following variables can be set in vars section of the playbook:

Variable Name Description Notes
resource_group resource group where resources will be created by default it’s using resource_group_name parameter passed when running the playbook
location location where resources should be created
lab_name name of the lab instance
vn_name lab virtual network name
vm_name name of the vm instance
artifacts_name lab artifacts name
github_token GitHub token to access artifacts sources by default playbook will attempt to get it from GITHUB_ACCESS_TOKEN environment variable

Create Resource Group

This simple task creates a resource group if doesn’t exist yet.

1
2
3
4
- name: Create a resource group
  azure_rm_resourcegroup:
    name: "{{ resource_group }}"
    location: "{{ location }}"

Creating DevTest Lab

This task creates an instance of DevTest Lab.

1
2
3
4
5
6
7
8
- name: Create instance of Lab
  azure_rm_devtestlab:
    resource_group: "{{ resource_group }}"
    name: "{{ lab_name }}"
    location: "{{ location }}"
    storage_type: standard
    premium_data_disks: no
  register: output_lab

DevTest Lab Policies

This task shows how to set up DevTest Lab policy settings.

Following values can be set:

Value Description
user_owned_lab_vm_count count of VMs that can be owned by an user
user_owned_lab_premium_vm_count count of premium VMs that can be owned by an user
lab_vm_count maximum lab VM count
lab_premium_vm_count maximum lab premium VM count
lab_vm_size allowed lab VMs size(s)
gallery_image allowed gallery image(s)
user_owned_lab_vm_count_in_subnet maximum number of user’s VMs in a subnet
lab_target_cost target cost of the lab
1
2
3
4
5
6
7
8
- name: Create instance of DevTest Lab Policy
  azure_rm_devtestlabpolicy:
    resource_group: "{{ resource_group }}"
    lab_name: "{{ lab_name }}"
    policy_set_name: myDtlPolicySet
    name: myDtlPolicy
    fact_name: user_owned_lab_vm_count
    threshold: 5

DevTest Lab Schedules

Sample task setting DevTest Lab VM schedule.

Currently allowed are: |Name|Description| |—-|———–| |lab_vms_startup|Lab VMs startup time| |lab_vms_shutdown|Lab VMs shutdown time|

Time and time zone id must be specified.

1
2
3
4
5
6
7
8
    - name: Create instance of DevTest Lab Schedule
      azure_rm_devtestlabschedule:
        resource_group: "{{ resource_group }}"
        lab_name: "{{ lab_name }}"
        name: lab_vms_shutdown
        time: "1030"
        time_zone_id: "UTC+12"
      register: output

DevTest Lab Virtual Network

This task creates default DevTest Lab virtual network.

1
2
3
4
5
6
7
8
    - name: Create instance of DevTest Labs virtual network
      azure_rm_devtestlabvirtualnetwork:
        resource_group: "{{ resource_group }}"
        lab_name: "{{ lab_name }}"
        name: "{{ vn_name }}"
        location: "{{ location }}"
        description: My DevTest Lab Virtual Network
      register: output

DevTest Lab Artifact Source

This task shows how to create DevTest Lab artifacts source. DevTest Lab artifacts source is properly structured GitHub repository that contains artifact definition and ARM templates. Please note that every lab comes with predefined public artifacts source.

1
2
3
4
5
6
7
8
9
    - name: Create instance of DevTest Labs artifacts source
      azure_rm_devtestlabartifactsource:
        resource_group: "{{ resource_group }}"
        lab_name: "{{ lab_name }}"
        name: "{{ artifacts_name }}"
        uri: https://github.com/Azure/azure_preview_modules.git
        source_type: github
        folder_path: /tasks
        security_token: "{{ github_token }}"

DevTest Lab Virtual Machine

This task shows how to create DevTest Labs virtual machine.

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
- name: Create instance of DTL Virtual Machine
  azure_rm_devtestlabvirtualmachine:
    resource_group: "{{ resource_group }}"
    lab_name: "{{ lab_name }}"
    name: "{{ vm_name }}"
    notes: Virtual machine notes, just something....
    os_type: linux
    vm_size: Standard_A2_v2
    user_name: dtladmin
    password: ZSasfovobocu$$21!
    lab_subnet:
      virtual_network_name: "{{ vn_name }}"
      name: "{{ vn_name }}Subnet"
    disallow_public_ip_address: no
    image:
      offer: UbuntuServer
      publisher: Canonical
      sku: 16.04-LTS
      os_type: Linux
      version: latest
    artifacts:
      - source_name: "{{ artifacts_name }}"
        source_path: "/Artifacts/linux-install-mongodb"
    allow_claim: no
    expiration_date: "2029-02-22T01:49:12.117974Z"

Listing All Artifact Sources and Artifacts

This task lists all artifacts sources in the lab. Please use it to see default and custom artifacts sources.

1
2
3
4
5
6
7
    - name: List all artifact sources
      azure_rm_devtestlabartifactsource_facts:
        resource_group: "{{ resource_group }}"
        lab_name: "{{ lab_name }}"
      register: output
    - debug:
        var: output

Following task lists all the artifact in public repo which is predefined artifact source.

1
2
3
4
5
6
7
8
    - name: Get artifacts source facts
      azure_rm_devtestlabartifact_facts:
        resource_group: "{{ resource_group }}"
        lab_name: "{{ lab_name }}"
        artifact_source_name: public repo
      register: output
    - debug:
        var: output

Getting Information on ARM Templates in Artifact Source

This task lists all the ARM templates in public environment repo that is predefined repo with templates.

1
2
3
4
5
6
7
8
    - name: List ARM Template facts
      azure_rm_devtestlabarmtemplate_facts:
        resource_group: "{{ resource_group }}"
        lab_name: "{{ lab_name }}"
        artifact_source_name: "public environment repo"
      register: output
    - debug:
        var: output

And following task retrieves details of a specific ARM template from the repository:

1
2
3
4
5
6
7
8
9
    - name: Get ARM Template facts
      azure_rm_devtestlabarmtemplate_facts:
        resource_group: "{{ resource_group }}"
        lab_name: "{{ lab_name }}"
        artifact_source_name: "public environment repo"
        name: ServiceFabric-LabCluster
      register: output
    - debug:
        var: output

Creating DevTest Lab Environment

Finally following task creates DevTest Lab environment. As you see it refers one of the templates from public environment repo.

1
2
3
4
5
6
7
8
9
    - name: Create instance of DevTest Lab Environment
      azure_rm_devtestlabenvironment:
        resource_group: "{{ resource_group }}"
        lab_name: "{{ lab_name }}"
        user_name: "@me"
        name: myEnvironment
        location: eastus
        deployment_template: "{{ output_lab.id }}/artifactSources/public environment repo/armTemplates/WebApp"
      register: output

Creating DevTest Lab Image

This is also a very useful task. It creates DevTest Lab image from existing DevTest Lab Virtual Machine. It can be later used to created new DevTest Lab virtual machines.

1
2
3
4
5
6
7
    - name: Create instance of DevTest Lab Image
      azure_rm_devtestlabcustomimage:
        resource_group: "{{ resource_group }}"
        lab_name: "{{ lab_name }}"
        name: myImage
        source_vm: "{{ output_vm.virtualmachines[0]['name'] }}"
        linux_os_state: non_deprovisioned

Deleting the Lab

Final task deletes entire lab.

1
2
3
4
5
6
7
8
9
10
- name: Delete instance of Lab
  azure_rm_devtestlab:
    resource_group: "{{ resource_group }}"
    name: "{{ lab_name }}"
    state: absent
  register: output
- name: Assert the change was correctly reported
  assert:
    that:
      - output.changed