Exercice – Refactoriser votre fichier Bicep

Effectué

Après avez passé en revue votre modèle avec vos collègues, vous décidez de refactoriser le fichier pour leur en faciliter l’utilisation. Dans cet exercice, vous allez appliquer les bonnes pratiques que vous avez apprises dans les unités précédentes.

Votre tâche

Passez en revue le modèle Bicep que vous avez enregistré précédemment. Rappelez-vous du conseil que vous avez lu concernant la structuration des modèles. Essayez de mettre à jour votre modèle pour permettre à vos collègues de mieux le comprendre.

Dans les sections suivantes, vous trouverez des éléments d’information sur certaines parties du modèle ainsi que des indications sur ce que vous pouvez modifier. Nous proposons une solution suggérée, mais votre modèle peut se présenter différemment, ce qui est tout à fait concevable !

Conseil

À mesure que vous progresserez dans le processus de refactorisation, vous avez tout intérêt à vérifier que votre fichier Bicep est valide et que vous n’avez pas introduit d’erreurs par inadvertance. À cet effet, l’extension Bicep pour Visual Studio Code peut vous être utile. La présence de lignes ondulées rouges ou jaunes en dessous de votre code indique la présence d’une erreur ou d’un avertissement. Vous pouvez également examiner la liste des problèmes dans votre fichier en sélectionnant Affichage>Problèmes.

Mettre à jour les paramètres

  1. Certains paramètres de votre modèle ne sont pas clairs. Par exemple, examinez les paramètres suivants :

    @allowed([
      'F1'
      'D1'
      'B1'
      'B2'
      'B3'
      'S1'
      'S2'
      'S3'
      'P1'
      'P2'
      'P3'
      'P4'
    ])
    param skuName string = 'F1'
    
    @minValue(1)
    param skuCapacity int = 1
    

    À quoi servent-ils ?

    Conseil

    Si vous ne comprenez pas la fonction d’un paramètre, Visual Studio Code peut vous aider. Sélectionnez un nom de paramètre en maintenant le bouton enfoncé (ou cliquez dessus avec le bouton droit) dans m’importe quel emplacement de votre fichier, puis sélectionnez Rechercher toutes les références.

    Le modèle doit-il indiquer la liste des valeurs autorisées pour le paramètre skuName ? Quelles ressources sont affectées par le choix de valeurs différentes pour ces paramètres ? Pourriez-vous donner de meilleurs noms aux paramètres ?

    Conseil

    Quand vous renommez des identificateurs, veillez à le faire de façon cohérente dans toutes les parties de votre modèle. Ceci est particulièrement important pour les paramètres, les variables et les ressources auxquels vous faites référence dans votre modèle.

    Visual Studio Code offre un moyen pratique de renommer les symboles : sélectionnez l’identificateur que vous souhaitez renommer, appuyez sur F2, entrez un nouveau nom, puis appuyez sur Entrée :

    Screenshot from Visual Studio Code that shows how to rename a symbol.

    Ces étapes renomment l’identificateur et mettent automatiquement à jour toutes les références à ce dernier.

  2. Le paramètre managedIdentityName n’a pas de valeur par défaut. Pourriez-vous corriger cela ou, mieux encore, pourriez-vous créer le nom automatiquement dans le modèle ?

  3. Examinez la définition du paramètre roleDefinitionId :

    param roleDefinitionId string = 'b24988ac-6180-42a0-ab88-20f7382dd24c'
    

    Pourquoi la valeur par défaut b24988ac-6180-42a0-ab88-20f7382dd24c existe-t-elle ? Que signifie la présence de cet identificateur long ? Comment un utilisateur sait-il si la valeur par défaut doit être utilisée ou si elle doit être remplacée ? Que pourriez-vous faire pour améliorer l’identificateur ? Est-il même utile de faire de cela un paramètre ?

    Conseil

    Cet identificateur est l’ID de définition du rôle Contributeur pour Azure. Comment pouvez-vous utiliser ces informations pour améliorer le modèle ?

  4. Quand le modèle sera déployé, comment les utilisateurs sauront-ils à quoi sert chaque paramètre ? Pouvez-vous ajouter des descriptions pour aider les utilisateurs de votre modèle ?

Ajouter un jeu de configuration

  1. Suite à la discussion que vous avez eue avec vos collègues, vous décidez d’utiliser des références SKU spécifiques pour chaque ressource, en fonction de l’environnement déployé. Voici les références SKU que vous choisissez pour chacune de vos ressources :

    Ressource Référence SKU pour la production Référence SKU pour la non-production
    Plan App Service S1, deux instances F1, une instance
    Compte de stockage GRS LRS
    Base de données SQL S1 De base
  2. Pouvez-vous utiliser un jeu de configuration pour simplifier les définitions de paramètres ?

Mettre à jour les noms symboliques

Examinez les noms symboliques des ressources présentes dans le modèle. Que pourriez-vous faire pour les améliorer ?

  1. Votre modèle Bicep contient des ressources dont les noms symboliques utilisent divers styles typographiques, à savoir :

    • storageAccount et webSite, qui utilisent les règles typographiques « camelCase ».
    • roleassignment et sqlserver, qui utilisent les règles typographiques « flat case ».
    • sqlserverName_databaseName et AppInsights_webSiteName, qui utilisent les règles typographiques « snake case ».

    Pouvez-vous les corriger de façon à utiliser un seul et même style ?

  2. Examinez cette ressource d’attribution de rôle :

    resource roleassignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
      name: guid(roleDefinitionId, resourceGroup().id)
    
      properties: {
        principalType: 'ServicePrincipal'
        roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
        principalId: msi.properties.principalId
      }
    }
    

    Le nom symbolique est-il suffisamment descriptif pour faciliter l’utilisation de ce modèle ?

    Conseil

    Si l’identité a besoin d’une attribution de rôle, c’est parce que l’application web utilise son identité managée pour se connecter au serveur de base de données. Cela vous aide-t-il à clarifier cela dans le modèle ?

  3. Certaines ressources ont des noms symboliques qui ne reflètent pas les noms actuels de ressources Azure :

    resource hostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = {
      // ...
    }
    resource webSite 'Microsoft.Web/sites@2020-06-01' = {
      // ...
    }
    resource msi 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
      // ...
    }
    

    Auparavant, les identités managées étaient appelées MSI, les plans App Service étaient des plans d’hébergement et les applications App Service étaient des sites web.

    Pour éviter toute confusion à l’avenir, pouvez-vous les mettre à jour en leur donnant les noms les plus récents ?

Simplifier les définitions de conteneurs d’objets blob

  1. Examinez la façon dont les conteneurs d’objets blob sont définis :

    resource container1 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01' = {
      parent: storageAccount::blobServices
      name: container1Name
    }
    
    resource productmanuals 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01' = {
      name: '${storageAccount.name}/default/${productmanualsName}'
    }
    

    L’un d’entre eux utilise la propriété parent, mais pas l’autre. Pouvez-vous les harmoniser ?

  2. Les noms de conteneurs blob ne changent pas d’un environnement à l’autre. Pensez-vous que les noms doivent être spécifiés à l’aide de paramètres ?

  3. Il existe deux conteneurs d’objets blob. Pourraient-ils être déployés en utilisant une boucle ?

Mettre à jour les noms de ressources

  1. Certains paramètres définissent explicitement les noms de ressources :

    param managedIdentityName string
    param roleDefinitionId string = 'b24988ac-6180-42a0-ab88-20f7382dd24c'
    param webSiteName string = 'webSite${uniqueString(resourceGroup().id)}'
    param container1Name string = 'productspecs'
    param productmanualsName string = 'productmanuals'
    

    Existe-t-il une autre façon de procéder ?

    Attention

    N’oubliez pas que les ressources ne peuvent pas être renommées une fois déployées. Quand vous modifiez un modèle qui est déjà utilisé, faites attention lorsque vous changez la façon dont il crée les noms de ressources. Si le modèle est redéployé et que la ressource a un nouveau nom, Azure créera une autre ressource. L’ancienne ressource risque même d’être supprimée si vous la déployez en mode Complet.

    Il est inutile de s’en inquiéter ici, car il ne s’agit que d’un exemple.

  2. Le nom de ressource de votre serveur logique SQL est défini à l’aide d’une variable, même s’il a besoin d’un nom global unique :

    var sqlserverName = 'toywebsite${uniqueString(resourceGroup().id)}'
    

    Comment pourriez-vous améliorer cela ?

Mettre à jour les dépendances et les ressources enfants

  1. Voici une de vos ressources, qui comprend une propriété dependsOn. En a-t-elle vraiment besoin ?

    resource sqlserverName_AllowAllAzureIPs 'Microsoft.Sql/servers/firewallRules@2014-04-01' = {
      name: '${sqlserver.name}/AllowAllAzureIPs'
      properties: {
        endIpAddress: '0.0.0.0'
        startIpAddress: '0.0.0.0'
      }
      dependsOn: [
        sqlserver
      ]
    }
    
  2. Remarquez comment ces ressources enfants sont déclarées dans votre modèle :

    resource sqlserverName_databaseName 'Microsoft.Sql/servers/databases@2020-08-01-preview' = {
      name: '${sqlserver.name}/${databaseName}'
      location: location
      sku: {
        name: 'Basic'
      }
      properties: {
        collation: 'SQL_Latin1_General_CP1_CI_AS'
        maxSizeBytes: 1073741824
      }
    }
    
    resource sqlserverName_AllowAllAzureIPs 'Microsoft.Sql/servers/firewallRules@2014-04-01' = {
      name: '${sqlserver.name}/AllowAllAzureIPs'
      properties: {
        endIpAddress: '0.0.0.0'
        startIpAddress: '0.0.0.0'
      }
      dependsOn: [
        sqlserver
      ]
    }
    

    Comment pourriez-vous modifier la façon dont ces ressources sont déclarées ? Y a-t-il d’autres ressources dans le modèle qui doivent aussi être mises à jour ?

Mettre à jour les valeurs des propriétés

  1. Examinez les propriétés de la ressource de base de données SQL :

    resource sqlserverName_databaseName 'Microsoft.Sql/servers/databases@2020-08-01-preview' = {
      name: '${sqlserver.name}/${databaseName}'
      location: location
      sku: {
        name: 'Basic'
      }
      properties: {
        collation: 'SQL_Latin1_General_CP1_CI_AS'
        maxSizeBytes: 1073741824
      }
    }
    

    Est-il judicieux de coder de manière irréversible la valeur de la propriété name de la référence SKU ? Et quelles sont ces valeurs inhabituelles pour les propriétés collation et maxSizeBytes ?

    Conseil

    Les propriétés collation et maxSizeBytes sont définies avec les valeurs par défaut. Si vous ne spécifiez pas vous-même les valeurs, les valeurs par défaut sont utilisées. Cela vous aide-t-il à prendre une décision les concernant ?

  2. Pouvez-vous changer la façon dont la chaîne de connexion de stockage est définie de sorte que l’expression complexe ne soit pas définie en fonction de la ressource ?

    resource webSite 'Microsoft.Web/sites@2020-06-01' = {
      name: webSiteName
      location: location
      properties: {
        serverFarmId: hostingPlan.id
        siteConfig: {
          appSettings: [
            {
              name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
              value: AppInsights_webSiteName.properties.InstrumentationKey
            }
            {
              name: 'StorageAccountConnectionString'
              value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}'
            }
          ]
        }
      }
      identity: {
        type: 'UserAssigned'
        userAssignedIdentities: {
          '${msi.id}': {}
        }
      }
    }
    

Ordre des éléments

  1. Êtes-vous satisfait de l’ordre des éléments dans le fichier ? Comment pourriez-vous améliorer la lisibilité du fichier en déplaçant des éléments ?

  2. Examinez la variable databaseName. Est-elle à sa place ?

    var databaseName = 'ToyCompanyWebsite'
    resource sqlserverName_databaseName 'Microsoft.Sql/servers/databases@2020-08-01-preview' = {
      name: '${sqlserver.name}/${databaseName}'
      location: location
      sku: {
        name: 'Basic'
      }
      properties: {
        collation: 'SQL_Latin1_General_CP1_CI_AS'
        maxSizeBytes: 1073741824
      }
    }
    
  3. Avez-vous repéré la ressource commentée, webSiteConnectionStrings ? Selon vous, est-il nécessaire que cela figure dans le fichier ?

Ajouter des commentaires, des étiquettes et d’autres métadonnées

Réfléchissez aux éléments du modèle qui ne sont pas forcément évidents et qui auraient besoin d’être davantage expliqués. Pouvez-vous ajouter des commentaires pour permettre aux personnes qui ouvriront le fichier de mieux les comprendre ?

  1. Examinez la propriété identity de la ressource webSite :

    resource webSite 'Microsoft.Web/sites@2020-06-01' = {
      name: webSiteName
      location: location
      properties: {
        serverFarmId: hostingPlan.id
        siteConfig: {
          appSettings: [
            {
              name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
              value: AppInsights_webSiteName.properties.InstrumentationKey
            }
            {
              name: 'StorageAccountConnectionString'
              value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}'
            }
          ]
        }
      }
      identity: {
        type: 'UserAssigned'
        userAssignedIdentities: {
          '${msi.id}': {}
        }
      }
    }
    

    Cette syntaxe vous interpelle, n’est-ce pas ? Vous pensez qu’il est nécessaire de l’expliquer au moyen d’un commentaire ?

  2. Examinez la ressource d’attribution de rôle :

    resource roleassignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
      name: guid(roleDefinitionId, resourceGroup().id)
    
      properties: {
        principalType: 'ServicePrincipal'
        roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
        principalId: msi.properties.principalId
      }
    }
    

    Le nom de la ressource utilise la fonction guid(). Serait-il utile de l’expliquer ? Pourquoi ?

  3. Pouvez-vous ajouter une description à l’attribution de rôle ?

  4. Pouvez-vous ajouter un ensemble d’étiquettes à chaque ressource ?

Solution suggérée

Voici par exemple comment vous pourriez refactoriser le modèle. Il est possible que votre modèle n’ait pas exactement le même aspect, peut-être parce que votre style est différent.

@description('The location into which your Azure resources should be deployed.')
param location string = resourceGroup().location

@description('Select the type of environment you want to provision. Allowed values are Production and Test.')
@allowed([
  'Production'
  'Test'
])
param environmentType string

@description('A unique suffix to add to resource names that need to be globally unique.')
@maxLength(13)
param resourceNameSuffix string = uniqueString(resourceGroup().id)

@description('The administrator login username for the SQL server.')
param sqlServerAdministratorLogin string

@secure()
@description('The administrator login password for the SQL server.')
param sqlServerAdministratorLoginPassword string

@description('The tags to apply to each resource.')
param tags object = {
  CostCenter: 'Marketing'
  DataClassification: 'Public'
  Owner: 'WebsiteTeam'
  Environment: 'Production'
}

// Define the names for resources.
var appServiceAppName = 'webSite${resourceNameSuffix}'
var appServicePlanName = 'AppServicePLan'
var sqlServerName = 'sqlserver${resourceNameSuffix}'
var sqlDatabaseName = 'ToyCompanyWebsite'
var managedIdentityName = 'WebSite'
var applicationInsightsName = 'AppInsights'
var storageAccountName = 'toywebsite${resourceNameSuffix}'
var blobContainerNames = [
  'productspecs'
  'productmanuals'
]

@description('Define the SKUs for each component based on the environment type.')
var environmentConfigurationMap = {
  Production: {
    appServicePlan: {
      sku: {
        name: 'S1'
        capacity: 2
      }
    }
    storageAccount: {
      sku: {
        name: 'Standard_GRS'
      }
    }
    sqlDatabase: {
      sku: {
        name: 'S1'
        tier: 'Standard'
      }
    }
  }
  Test: {
    appServicePlan: {
      sku: {
        name: 'F1'
        capacity: 1
      }
    }
    storageAccount: {
      sku: {
        name: 'Standard_LRS'
      }
    }
    sqlDatabase: {
      sku: {
        name: 'Basic'
      }
    }
  }
}

@description('The role definition ID of the built-in Azure \'Contributor\' role.')
var contributorRoleDefinitionId = 'b24988ac-6180-42a0-ab88-20f7382dd24c'
var storageAccountConnectionString = 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}'

resource sqlServer 'Microsoft.Sql/servers@2019-06-01-preview' = {
  name: sqlServerName
  location: location
  tags: tags
  properties: {
    administratorLogin: sqlServerAdministratorLogin
    administratorLoginPassword: sqlServerAdministratorLoginPassword
    version: '12.0'
  }
}

resource sqlDatabase 'Microsoft.Sql/servers/databases@2020-08-01-preview' = {
  parent: sqlServer
  name: sqlDatabaseName
  location: location
  sku: environmentConfigurationMap[environmentType].sqlDatabase.sku
  tags: tags
}

resource sqlFirewallRuleAllowAllAzureIPs 'Microsoft.Sql/servers/firewallRules@2014-04-01' = {
  parent: sqlServer
  name: 'AllowAllAzureIPs'
  properties: {
    endIpAddress: '0.0.0.0'
    startIpAddress: '0.0.0.0'
  }
}

resource appServicePlan 'Microsoft.Web/serverfarms@2020-06-01' = {
  name: appServicePlanName
  location: location
  sku: environmentConfigurationMap[environmentType].appServicePlan.sku
  tags: tags
}

resource appServiceApp 'Microsoft.Web/sites@2020-06-01' = {
  name: appServiceAppName
  location: location
  tags: tags
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      appSettings: [
        {
          name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
          value: applicationInsights.properties.InstrumentationKey
        }
        {
          name: 'StorageAccountConnectionString'
          value: storageAccountConnectionString
        }
      ]
    }
  }
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${managedIdentity.id}': {} // This format is required when working with user-assigned managed identities.
    }
  }
}

resource storageAccount 'Microsoft.Storage/storageAccounts@2019-06-01' = {
  name: storageAccountName
  location: location
  sku: environmentConfigurationMap[environmentType].storageAccount.sku
  kind: 'StorageV2'
  properties: {
    accessTier: 'Hot'
  }

  resource blobServices 'blobServices' existing = {
    name: 'default'

    resource containers 'containers' = [for blobContainerName in blobContainerNames: {
      name: blobContainerName
    }]
  }
}

@description('A user-assigned managed identity that is used by the App Service app to communicate with a storage account.')
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
  name: managedIdentityName
  location: location
  tags: tags
}

@description('Grant the \'Contributor\' role to the user-assigned managed identity, at the scope of the resource group.')
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
  name: guid(contributorRoleDefinitionId, resourceGroup().id) // Create a GUID based on the role definition ID and scope (resource group ID). This will return the same GUID every time the template is deployed to the same resource group.
  properties: {
    principalType: 'ServicePrincipal'
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', contributorRoleDefinitionId)
    principalId: managedIdentity.properties.principalId
    description: 'Grant the "Contributor" role to the user-assigned managed identity so it can access the storage account.'
  }
}

resource applicationInsights 'Microsoft.Insights/components@2018-05-01-preview' = {
  name: applicationInsightsName
  location: location
  kind: 'web'
  tags: tags
  properties: {
    Application_Type: 'web'
  }
}

Conseil

Si vous travaillez avec vos collègues en utilisant GitHub ou Azure Repos, ce serait le moment idéal de soumettre une demande de tirage (pull request) pour intégrer vos modifications dans la branche primaire. Il est judicieux de soumettre des demandes de tirage (pull request) de tirage après une opération de refactorisation.