Implementación de un recopilador y transformador de propiedades en una plantilla de Azure Resource Manager

En el artículo Uso de objetos como parámetros en un bucle de copia en una plantilla de Azure Resource Manager, puede ver cómo almacenar valores de propiedad de recursos en un objeto y cómo aplicarlos a un recurso durante la implementación. Aunque esta es una forma muy útil de administrar los parámetros, sigue siendo necesario que asigne las propiedades del objeto a las propiedades de los recursos cada vez que lo use en la plantilla.

Para evitar esto, puede implementar una plantilla de recopilador y transformador de propiedades que recorre la matriz de objetos en iteración y los transforma en el esquema JSON para el recurso.

Importante

Esta técnica requiere que conozca a fondo las plantillas y las funciones de Resource Manager.

Veamos un ejemplo que implementa un recopilador de propiedades y un transformador para implementar un grupo de seguridad de red. En el diagrama siguiente se muestra cómo están relacionadas nuestras plantillas con los recursos de esas plantillas:

arquitectura del recopilador y transformador de propiedades

Nuestra plantilla de llamada incluye dos recursos:

  • Un vínculo de plantilla que invoca a nuestra plantilla de recopilador
  • Recurso del grupo de seguridad de red que se va a implementar

Nuestra plantilla de recopilador incluye dos recursos:

  • Recurso de delimitador
  • Vínculo de plantilla que invoca la plantilla de transformador en un bucle de copia

Nuestra plantilla de transformación incluye un único recurso: una plantilla vacía con una variable que transforma nuestro código JSON source en el esquema JSON que esperaba el recurso del grupo de seguridad de red de la plantilla principal.

Objeto de parámetro

Usamos nuestro objeto de parámetro securityRules de Uso de objetos como parámetros en un bucle de copia en una plantilla de Azure Resource Manager. Nuestra plantilla de transformación transformará cada objeto de la matriz securityRules en el esquema JSON que espera el recurso del grupo de seguridad de red de nuestra plantilla de llamadas.

{
    "$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"
                    }
                ]
            }
        }
    }
}

Veamos primero la plantilla de transformación.

Plantilla de transformación

La Plantilla de transformación incluye dos parámetros que se pasan desde la plantilla de recopilador:

  • source es un objeto que recibe uno de los objetos de valor de propiedad de la matriz de propiedades. En nuestro ejemplo, los objetos de la matriz securityRules se pasarán uno a uno.
  • state es una matriz que recibe los resultados concatenados de todas las transformaciones anteriores. Esta es la colección de código JSON transformado.

Nuestros parámetros tienen este aspecto:

{
    "$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": []
        }
    },

Nuestra plantilla también define una variable denominada instance que transforma el objeto source en el esquema JSON necesario:

"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]"
            }
        }
    ]
}

Por último, el valor de output de nuestra plantilla concatena las transformaciones recopiladas de nuestro parámetro state con la transformación actual realizada por la variable instance:

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

A continuación, veamos nuestra plantilla de recopilador para ver cómo pasa los valores de parámetro.

Plantilla de recopilador

La plantilla de recopilador incluye tres parámetros:

  • source es la matriz completa de objetos de parámetro. La pasa la plantilla de llamadas. Tiene el mismo nombre que el parámetro source en nuestra plantilla de transformación, pero hay una diferencia clave: aunque es la matriz completa, solo pasamos un elemento de matriz a la vez a la plantilla de transformación.
  • transformTemplateUri es el URI de nuestra plantilla de transformación. Lo definimos como parámetro para poder volver a usar la plantilla.
  • state es una matriz inicialmente vacía que pasamos a nuestra plantilla de transformación. Almacena la colección de objetos de parámetro transformados cuando el bucle de copia finaliza.

Nuestros parámetros tienen este aspecto:

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

A continuación, definimos una variable llamada count. Su valor es la longitud de la matriz de objetos de parámetro source:

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

Lo usamos para el número de iteraciones en nuestro bucle de copia.

Ahora, echemos un vistazo a nuestros recursos. Se definen dos recursos:

  • loop-0 es el recurso de base cero de nuestro bucle de copia.
  • loop- se concatena con el resultado de la función copyIndex(1) para generar un nombre único basado en iteración para el recurso, comenzando por 1.

Nuestros recursos tienen este aspecto:

"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]" }
            }
        }
    }
]

Veamos más de cerca los parámetros que pasamos a nuestra plantilla de transformación, en la plantilla anidada. Recuerde que nuestro parámetro source pasa el objeto actual a la matriz de objetos de parámetro source. El parámetro state es donde ocurre la colección, porque toma la salida de la iteración anterior de nuestro bucle de copia y la pasa a la iteración actual. Observe que la función reference() usa la función copyIndex() sin ningún parámetro para hacer referencia al valor name de nuestro objeto de plantilla vinculado anterior.

Por último, el valor de output de la plantilla devuelve el valor de output de la última iteración de la plantilla de transformación:

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

Puede parecer extraño devolver el valor de output de la última iteración de la plantilla de transformación a la plantilla de llamada, porque parecería que lo estamos almacenando en el parámetro source. Sin embargo, la última iteración de la plantilla de transformación es la que contiene la matriz completa de los objetos de propiedad transformados, y eso es lo que queremos devolver.

Por último, echemos un vistazo a cómo llamar a la plantilla de recopilador desde la plantilla de llamada.

Plantilla de llamada

La plantilla de llamada define un único parámetro llamado networkSecurityGroupsSettings:

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

Después, la plantilla define una sola variable llamada collectorTemplateUri:

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

Este es el URI de la plantilla de recopilador que usará el recurso de plantilla vinculada:

{
    "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')]"
            }
        }
    }
}

Pasamos dos parámetros a la plantilla de recopilador:

  • source es la matriz de objetos de propiedad. En nuestro ejemplo, es el parámetro networkSecurityGroupsSettings.
  • transformTemplateUri es la variable que acabamos de definir con el URI de la plantilla de recopilador.

Por último, el recurso Microsoft.Network/networkSecurityGroups asigna directamente el valor de output del recurso de plantilla vinculada collector a su propiedad 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]"
    }
}

Prueba de la plantilla

GitHub tiene una plantilla de ejemplo a su disposición. Para implementar la plantilla, clone el repositorio y ejecute los siguientes comandos de la CLI de Azure:

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

Pasos siguientes