Implémenter un transformateur et un collecteur de propriétés dans un modèle Azure Resource Manager

Dans l’article Utiliser des objets en tant que paramètres dans une boucle de copie dans un modèle Azure Resource Manager, vous pouvez voir comment stocker des valeurs de propriété de ressource dans un objet et comment les appliquer à une ressource pendant le déploiement. Il s'agit d’une méthode très utile pour gérer vos paramètres, mais elle exige que vous fassiez correspondre les propriétés de l’objet aux propriétés des ressources chaque fois que vous utilisez l’objet dans votre modèle.

Pour contourner ce problème, vous pouvez implémenter un modèle de transformateur et de collecteur de propriétés qui itère votre tableau d’objets et le transforme en schéma JSON pour la ressource.

Important

Pour utiliser cette approche, vous devez disposer d’une connaissance approfondie des modèles et fonctions Resource Manager.

Examinons un exemple qui implémente un collecteur de propriétés et un transformateur pour déployer un groupe de sécurité réseau. Le diagramme ci-dessous montre comment nos modèles sont liés aux ressources de ces modèles :

Architecture du collecteur et du transformateur de propriétés

Notre modèle d’appel inclut deux ressources :

  • Un lien de modèle qui appelle notre modèle de collecteur
  • Ressource de groupe de sécurité réseau à déployer

Notre modèle de collecteur inclut deux ressources :

  • Une ressource ancre
  • Un lien de modèle qui appelle le modèle de transformation dans une boucle de copie

Notre modèle de transformateur ne comporte qu’une seule ressource : un modèle vide avec une variable qui transforme notre code JSON source en schéma JSON attendu par notre ressource de groupe de sécurité réseau dans le modèle principal.

Objet de paramètre

Nous utilisons notre objet de paramètre securityRulesà partir de Utiliser des objets en tant que paramètres dans une boucle de copie dans un modèle Azure Resource Manager. Notre modèle de transformateur transformera chaque objet du tableau securityRules en schéma JSON attendu par la ressource de groupe de sécurité réseau dans notre modèle d’appel.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "networkSecurityGroupsSettings": {
            "value": {
                "securityRules": [
                    {
                        "name": "RDPAllow",
                        "description": "allow RDP connections",
                        "direction": "Inbound",
                        "priority": 100,
                        "sourceAddressPrefix": "*",
                        "destinationAddressPrefix": "10.0.0.0/24",
                        "sourcePortRange": "*",
                        "destinationPortRange": "3389",
                        "access": "Allow",
                        "protocol": "Tcp"
                    },
                    {
                        "name": "HTTPAllow",
                        "description": "allow HTTP connections",
                        "direction": "Inbound",
                        "priority": 200,
                        "sourceAddressPrefix": "*",
                        "destinationAddressPrefix": "10.0.1.0/24",
                        "sourcePortRange": "*",
                        "destinationPortRange": "80",
                        "access": "Allow",
                        "protocol": "Tcp"
                    }
                ]
            }
        }
    }
}

Commençons par examiner notre modèle de transformateur.

Modèle de transformateur

Notre modèle de transformateur comporte deux paramètres qui sont transmis par le modèle de collecteur :

  • source est un objet qui reçoit l’un des objets de valeur de propriété du tableau de propriétés. Dans notre exemple, les différents objets du tableau securityRules sont transmis un par un.
  • state est un tableau qui reçoit les résultats concaténés de toutes les transformations précédentes. Il s’agit de la collection de code JSON transformé.

Nos paramètres ressemblent à ceci :

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "source": {
            "type": "object"
        },
        "state": {
            "type": "array",
            "defaultValue": []
        }
    },

Notre modèle définit également une variable nommée instance qui transforme notre source objet en schéma JSON requis :

"variables": {
    "instance": [
        {
            "name": "[parameters('source').name]",
            "properties": {
                "description": "[parameters('source').description]",
                "protocol": "[parameters('source').protocol]",
                "sourcePortRange": "[parameters('source').sourcePortRange]",
                "destinationPortRange": "[parameters('source').destinationPortRange]",
                "sourceAddressPrefix": "[parameters('source').sourceAddressPrefix]",
                "destinationAddressPrefix": "[parameters('source').destinationAddressPrefix]",
                "access": "[parameters('source').access]",
                "priority": "[parameters('source').priority]",
                "direction": "[parameters('source').direction]"
            }
        }
    ]
}

Enfin, l’élément output de notre modèle concatène les transformations collectées de notre paramètre state avec la transformation actuelle effectuée par notre variable instance :

"resources": [],
"outputs": {
    "collection": {
        "type": "array",
        "value": "[concat(parameters('state'), variables('instance'))]"
    }
}

À présent, examinons notre modèle de collecteur pour découvrir la façon dont il transmet nos valeurs de paramètre.

Modèle de collecteur

Notre modèle de collecteur comporte trois paramètres :

  • source est notre tableau d’objets de paramètre complet. Il est transmis par le modèle d’appel. Il a le même nom que le paramètre source dans notre modèle de transformation, mais il existe une différence clé : bien qu’il s’agisse du tableau complet, nous ne transmettons qu’un seul élément de tableau à la fois au modèle de transformation.
  • transformTemplateUri est l’URI de notre modèle de transformateur. Nous le définissons ici sous la forme d’un paramètre pour permettre la réutilisation du modèle.
  • state est un tableau initialement vide que nous transmettons à notre modèle de transformateur. Il stocke la collection d’objets de paramètre transformés lorsque la boucle de copie est terminée.

Nos paramètres ressemblent à ceci :

"parameters": {
    "source": {
        "type": "array"
    },
    "transformTemplateUri": {
        "type": "string"
    },
    "state": {
        "type": "array",
        "defaultValue": []
    }
}

Ensuite, nous définissons une variable nommée count. Sa valeur correspond à la longueur du tableau d’objets de paramètre source :

"variables": {
    "count": "[length(parameters('source'))]"
}

Nous l’utilisons pour le nombre d’itérations dans notre boucle de copie.

À présent, examinons nos ressources. Nous définissons deux ressources :

  • loop-0 est la ressource de base zéro pour notre boucle de copie.
  • loop- est concaténé avec le résultat de la fonction copyIndex(1) afin de générer pour notre ressource un nom unique basé sur l’itération, en commençant par 1.

Nos ressources ressemblent à ceci :

"resources": [
    {
        "type": "Microsoft.Resources/deployments",
        "apiVersion": "2015-01-01",
        "name": "loop-0",
        "properties": {
            "mode": "Incremental",
            "parameters": { },
            "template": {
                "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
                "contentVersion": "1.0.0.0",
                "parameters": { },
                "variables": { },
                "resources": [ ],
                "outputs": {
                    "collection": {
                        "type": "array",
                        "value": "[parameters('state')]"
                    }
                }
            }
        }
    },
    {
        "type": "Microsoft.Resources/deployments",
        "apiVersion": "2015-01-01",
        "name": "[concat('loop-', copyindex(1))]",
        "copy": {
            "name": "iterator",
            "count": "[variables('count')]",
            "mode": "serial"
        },
        "dependsOn": [
            "loop-0"
        ],
        "properties": {
            "mode": "Incremental",
            "templateLink": { "uri": "[parameters('transformTemplateUri')]" },
            "parameters": {
                "source": { "value": "[parameters('source')[copyindex()]]" },
                "state": { "value": "[reference(concat('loop-', copyindex())).outputs.collection.value]" }
            }
        }
    }
]

Examinons plus attentivement les paramètres que nous transmettons à notre modèle de transformateur dans le modèle imbriqué. Comme indiqué plus haut, notre paramètre source transmet l’objet actuel au tableau d’objets de paramètre source. Le paramètre state est l’endroit où la collecte se produit, car il prend la sortie de l’itération précédente de notre boucle de copie et la passe à l’itération actuelle. Notez que la fonction reference()utilise la fonction copyIndex() sans paramètre pour référencer le name de notre objet de modèle lié précédent.

Enfin, l’élément output de notre modèle renvoie l’élément output de la dernière itération de notre modèle de transformateur :

"outputs": {
    "result": {
        "type": "array",
        "value": "[reference(concat('loop-', variables('count'))).outputs.collection.value]"
    }
}

Il peut sembler contre-intuitif de renvoyer le output de la dernière itération de notre modèle de transformation à notre modèle d'appel, car il semble que nous l'ayons stockée dans notre paramètresource. Toutefois, il s’agit de la dernière itération de notre modèle de transformateur qui stocke le tableau complet d’objets de propriété transformés, lequel correspond précisément à l’élément que nous souhaitons renvoyer.

Enfin, examinons la façon dont nous pouvons appeler le modèle de collecteur à partir de notre modèle d’appel.

Modèle d’appel

Notre modèle d’appel définit un paramètre unique nommé networkSecurityGroupsSettings :

...
"parameters": {
    "networkSecurityGroupsSettings": {
        "type": "object"
    }
}

Ensuite, notre modèle définit une seule variable nommée collectorTemplateUri :

"variables": {
    "collectorTemplateUri": "[uri(deployment().properties.templateLink.uri, 'collector.template.json')]"
}

Il s’agit de l’URI du modèle de collecteur qui sera utilisé par notre ressource de modèle lié :

{
    "apiVersion": "2020-06-01",
    "name": "collector",
    "type": "Microsoft.Resources/deployments",
    "properties": {
        "mode": "Incremental",
        "templateLink": {
            "uri": "[variables('collectorTemplateUri')]",
            "contentVersion": "1.0.0.0"
        },
        "parameters": {
            "source": {
                "value": "[parameters('networkSecurityGroupsSettings').securityRules]"
            },
            "transformTemplateUri": {
                "value": "[uri(deployment().properties.templateLink.uri, 'transform.json')]"
            }
        }
    }
}

Nous transmettons deux paramètres au modèle de collecteur :

  • source est notre tableau d’objets de propriété. Dans notre exemple, il s’agit de notre paramètre networkSecurityGroupsSettings.
  • transformTemplateUri est la variable que nous venons de définir avec l’URI de notre modèle de collecteur.

Enfin, notre ressource Microsoft.Network/networkSecurityGroups attribue directement l’élément output de la ressource de modèle lié collector à sa propriété securityRules :

"resources": [
    {
        "apiVersion": "2020-05-01",
        "type": "Microsoft.Network/networkSecurityGroups",
        "name": "networkSecurityGroup1",
        "location": "[resourceGroup().location]",
        "properties": {
            "securityRules": "[reference('collector').outputs.result.value]"
        }
    }
],
"outputs": {
    "instance": {
        "type": "array",
        "value": "[reference('collector').outputs.result.value]"
    }
}

Essayer le modèle

Un exemple de modèle est disponible sur GitHub. Pour déployer le modèle, clonez le référentiel et exécutez les commandes Azure CLI suivantes :

git clone https://github.com/mspnp/template-examples.git
cd template-examples/example4-collector
az group create --location <location> --name <resource-group-name>
az deployment group create -g <resource-group-name> \
    --template-uri https://raw.githubusercontent.com/mspnp/template-examples/master/example4-collector/deploy.json \
    --parameters deploy.parameters.json

Étapes suivantes