Exercice – Alimenter un compte de stockage et une base de données

Effectué

Vous avez mis à jour votre pipeline pour générer et déployer l’application de votre site web sur l’application Azure App Service définie dans votre fichier Bicep. Toutefois, la phase de test de détection de fumée échoue, car la base de données ne fonctionne pas encore. Dans cette unité, vous déployez une nouvelle base de données et un nouveau serveur logique Azure SQL, et vous configurez votre pipeline pour générer et déployer le schéma de la base de données. Vous mettez également à jour votre pipeline pour ajouter des exemples de données de produits pour votre environnement de test, afin que votre équipe puisse essayer le site web.

Au cours du processus, vous effectuez les tâches suivantes :

  • Ajouter un conteneur d’objets blob dans le compte de stockage Azure.
  • Ajouter une base de données et un serveur logique Azure SQL.
  • Mettre à jour la phase de génération pour générer le projet de base de données dans un fichier DACPAC.
  • Ajouter de nouvelles variables à votre groupe de variables pour la base de données et le serveur logique Azure SQL.
  • Mettre à jour vos phases de déploiement pour utiliser les nouvelles variables comme valeurs de paramètre.
  • Ajouter de nouvelles étapes de pipeline pour déployer votre fichier DACPAC.
  • Exécuter le pipeline et afficher le site web.

Ajouter un conteneur de stockage

Votre fichier Bicep définit déjà un compte de stockage, mais il ne définit pas de conteneur d’objets blob. Ici, vous ajoutez un conteneur d’objets blob à votre fichier Bicep. Vous fournissez également le nom du compte de stockage et du conteneur d’objets blob à l’application à l’aide de ses paramètres de configuration. De cette façon, l’application sait à quel compte de stockage accéder.

  1. Dans Visual Studio Code, ouvrez le fichier main.bicep dans le dossier deploy.

  2. Sous les variables qui définissent les noms des ressources (près de la ligne 27), ajoutez une nouvelle définition de variable pour le nom du conteneur de stockage d’objets blob :

    var storageAccountImagesBlobContainerName = 'toyimages'
    
  3. Mettez à jour la ressource storageAccount pour définir le conteneur d’objets blob :

    resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
      name: storageAccountName
      location: location
      kind: 'StorageV2'
      sku: environmentConfigurationMap[environmentType].storageAccount.sku
    
      resource blobService 'blobServices' = {
        name: 'default'
    
        resource storageAccountImagesBlobContainer 'containers' = {
          name: storageAccountImagesBlobContainerName
    
          properties: {
            publicAccess: 'Blob'
          }
        }
      }
    }
    
  4. Mettez à jour la propriété appSettings de l’application pour ajouter trois nouveaux paramètres d’application pour le nom du compte de stockage, le point de terminaison d’objet blob et le nom du conteneur d’objets blob :

    resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
      name: appServiceAppName
      location: location
      properties: {
        serverFarmId: appServicePlan.id
        httpsOnly: true
        siteConfig: {
          appSettings: [
            {
              name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
              value: applicationInsights.properties.InstrumentationKey
            }
            {
              name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
              value: applicationInsights.properties.ConnectionString
            }
            {
              name: 'ReviewApiUrl'
              value: reviewApiUrl
            }
            {
              name: 'ReviewApiKey'
              value: reviewApiKey
            }
            {
              name: 'StorageAccountName'
              value: storageAccount.name
            }
            {
              name: 'StorageAccountBlobEndpoint'
              value: storageAccount.properties.primaryEndpoints.blob
            }
            {
              name: 'StorageAccountImagesContainerName'
              value: storageAccount::blobService::storageAccountImagesBlobContainer.name
            }
          ]
        }
      }
    }
    
  5. À la fin du fichier, ajoutez de nouvelles sorties pour exposer les noms du compte de stockage et du conteneur d’objets blob :

    output storageAccountName string = storageAccount.name
    output storageAccountImagesBlobContainerName string = storageAccount::blobService::storageAccountImagesBlobContainer.name
    
  6. Enregistrez les modifications apportées au fichier.

  7. Validez vos modifications dans votre dépôt Git, mais ne les envoyez (push) pas encore. Dans le terminal Visual Studio Code, exécutez les commandes suivantes :

    git add .
    git commit -m "Add storage container"
    

Ajouter une base de données et un serveur logique Azure SQL

Votre fichier Bicep ne déploie pas actuellement une base de données ou un serveur logique Azure SQL. Dans cette section, vous allez ajouter ces ressources à votre fichier Bicep.

  1. Dans le fichier main.bicep, sous le paramètre reviewApiKey, ajoutez les deux paramètres suivants :

    @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
    
  2. Sous les variables qui définissent les noms des ressources, ajoutez de nouvelles variables pour définir les noms de votre base de données et de votre serveur logique Azure SQL :

    var sqlServerName = 'toy-website-${resourceNameSuffix}'
    var sqlDatabaseName = 'Toys'
    
  3. Sous les variables que vous venez d’ajouter, définissez une nouvelle variable qui crée une chaîne de connexion pour que l’application accède à la base de données :

    // Define the connection string to access Azure SQL.
    var sqlDatabaseConnectionString = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Initial Catalog=${sqlDatabase.name};Persist Security Info=False;User ID=${sqlServerAdministratorLogin};Password=${sqlServerAdministratorLoginPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'
    

    Notes

    Par souci de simplicité, l’application utilise la connexion et le mot de passe de l’administrateur pour accéder à la base de données. Toutefois, il ne s’agit pas là d’une bonne pratique pour une solution de production. Il est préférable d’utiliser une identité managée par App Service pour accéder à la base de données et d’accorder à cette identité managée les autorisations minimales requises par l’application. Nous proposons des liens vers des informations supplémentaires dans la page Récapitulatif.

  4. Près de la fin du fichier, au-dessus des sorties, ajoutez les ressources de base de données et de serveur logique Azure SQL :

    resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
      name: sqlServerName
      location: location
      properties: {
        administratorLogin: sqlServerAdministratorLogin
        administratorLoginPassword: sqlServerAdministratorLoginPassword
      }
    }
    
    resource sqlServerFirewallRule 'Microsoft.Sql/servers/firewallRules@2022-05-01-preview' = {
      parent: sqlServer
      name: 'AllowAllWindowsAzureIps'
      properties: {
        endIpAddress: '0.0.0.0'
        startIpAddress: '0.0.0.0'
      }
    }
    
    resource sqlDatabase 'Microsoft.Sql/servers/databases@2022-05-01-preview' = {
      parent: sqlServer
      name: sqlDatabaseName
      location: location
      sku: environmentConfigurationMap[environmentType].sqlDatabase.sku
    }
    
  5. Mettre à jour la variable environmentConfigurationMap pour définir les références les valeurs de sku à utiliser pour votre base de données pour chaque environnement :

    var environmentConfigurationMap = {
      Production: {
        appServicePlan: {
          sku: {
            name: 'S1'
            capacity: 1
          }
        }
        storageAccount: {
          sku: {
            name: 'Standard_LRS'
          }
        }
        sqlDatabase: {
          sku: {
            name: 'Standard'
            tier: 'Standard'
          }
        }
      }
      Test: {
        appServicePlan: {
          sku: {
            name: 'F1'
          }
        }
        storageAccount: {
          sku: {
            name: 'Standard_GRS'
          }
        }
        sqlDatabase: {
          sku: {
            name: 'Standard'
            tier: 'Standard'
          }
        }
      }
    }
    
  6. Ajoutez un autre paramètre d’application à votre application App Service pour la chaîne de connexion à la base de données :

    resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
      name: appServiceAppName
      location: location
      properties: {
        serverFarmId: appServicePlan.id
        httpsOnly: true
        siteConfig: {
          appSettings: [
            {
              name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
              value: applicationInsights.properties.InstrumentationKey
            }
            {
              name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
              value: applicationInsights.properties.ConnectionString
            }
            {
              name: 'ReviewApiUrl'
              value: reviewApiUrl
            }
            {
              name: 'ReviewApiKey'
              value: reviewApiKey
            }
            {
              name: 'StorageAccountName'
              value: storageAccount.name
            }
            {
              name: 'StorageAccountBlobEndpoint'
              value: storageAccount.properties.primaryEndpoints.blob
            }
            {
              name: 'StorageAccountImagesContainerName'
              value: storageAccount::blobService::storageAccountImagesBlobContainer.name
            }
            {
              name: 'SqlDatabaseConnectionString'
              value: sqlDatabaseConnectionString
            }
          ]
        }
      }
    }
    
  7. En bas du fichier, ajoutez des sorties pour exposer le nom d’hôte du serveur logique Azure SQL et le nom de la base de données :

    output appServiceAppName string = appServiceApp.name
    output appServiceAppHostName string = appServiceApp.properties.defaultHostName
    output storageAccountName string = storageAccount.name
    output storageAccountImagesBlobContainerName string = storageAccount::blobService::storageAccountImagesBlobContainer.name
    output sqlServerFullyQualifiedDomainName string = sqlServer.properties.fullyQualifiedDomainName
    output sqlDatabaseName string = sqlDatabase.name
    
  8. Enregistrez les modifications apportées au fichier.

Ajouter de nouvelles étapes de génération pour le projet de base de données

Les développeurs de votre site web ont préparé un projet de base de données Visual Studio qui déploie et configure la table de base de données de votre site web. Ici, vous mettez à jour la phase Générer de votre pipeline pour générer le projet de base de données dans un fichier DACPAC et le publier en tant qu’artefact de pipeline.

  1. Ouvrez le fichier build.yml dans le dossier deploy/pipeline-templates.

  2. Pour générer le projet de base de données Visual Studio, copiez le fichier DACPAC généré dans un dossier intermédiaire et publiez-le en tant qu’artefact de pipeline, puis ajoutez les étapes suivantes :

    jobs:
    - job: Build
      displayName: Build application and database
      pool:
        vmImage: windows-latest
    
      steps:
    
      # Build, copy, and publish the website.
      - task: DotNetCoreCLI@2
        displayName: Build publishable website
        inputs:
          command: 'publish'
          publishWebProjects: true
    
      - task: CopyFiles@2
        displayName: Copy publishable website
        inputs:
          sourceFolder: '$(Build.SourcesDirectory)/src/ToyCompany/ToyCompany.Website/bin'
          contents: '**/publish.zip'
          targetFolder: '$(Build.ArtifactStagingDirectory)/website'
          flattenFolders: true
    
      - task: PublishBuildArtifacts@1
        displayName: Publish website as pipeline artifact
        inputs:
          pathToPublish: '$(Build.ArtifactStagingDirectory)/website'
          artifactName: 'website'
    
      # Build, copy, and publish the DACPAC file.
      - task: VSBuild@1
        displayName: Build Visual Studio solution
        inputs:
          solution: '$(Build.SourcesDirectory)/src/ToyCompany/ToyCompany.Database/ToyCompany.Database.sqlproj'
    
      - task: CopyFiles@2
        displayName: Copy DACPAC
        inputs:
          sourceFolder: '$(Build.SourcesDirectory)/src/ToyCompany/ToyCompany.Database/bin'
          contents: '**/*.dacpac'
          targetFolder: '$(Build.ArtifactStagingDirectory)/database'
          flattenFolders: true
    
      - task: PublishBuildArtifacts@1
        displayName: Publish DACPAC as pipeline artifact
        inputs:
          pathToPublish: '$(Build.ArtifactStagingDirectory)/database'
          artifactName: 'database'
    
  3. Enregistrez les modifications apportées au fichier.

Ajouter des valeurs aux groupes de variables

  1. Dans votre navigateur, accédez à Pipelines>Bibliothèque.

  2. Sélectionnez le groupe de variables ToyWebsiteProduction.

    Screenshot of Azure DevOps showing the list of variable groups, with the ToyWebsiteProduction variable group highlighted.

  3. Ajoutez les variables suivantes au groupe de variables :

    Nom Valeur
    SqlServerAdministratorLogin ToyCompanyAdmin
    SqlServerAdministratorLoginPassword SecurePassword!111
  4. Sélectionnez l’icône de cadenas en regard de la variable SqlServerAdministratorLoginPassword. Cette fonctionnalité indique à Azure Pipelines de traiter la valeur de la variable de façon sécurisée.

    Screenshot of the production variable group, with the secret variable button highlighted.

  5. Enregistrez le groupe de variables.

    Screenshot of the production variable group, with the Save button highlighted.

  6. Répétez ce processus pour ajouter les variables suivantes au groupe de variables ToyWebsiteTest :

    Nom Valeur
    SqlServerAdministratorLogin TestToyCompanyAdmin
    SqlServerAdministratorLoginPassword SecurePassword!999

    Rappelez-vous de sélectionner l’icône de cadenas en regard de la variable SqlServerAdministratorLoginPassword et d’enregistrer le groupe de variables.

Ajouter des valeurs de paramètre aux phases de validation et de préversion

Le fichier Bicep comporte désormais deux nouveaux paramètres obligatoires : sqlServerAdministratorLogin et sqlServerAdministratorLoginPassword. Ici, vous propagez ces valeurs de paramètre à partir de votre groupe de variables, pour les deux phases Valider et Préversion.

  1. Dans Visual Studio Code, ouvrez le fichier deploy.yml dans le dossier deploy/pipeline-templates.

  2. Mettez à jour l’étape RunPreflightValidation de la phase Valider en ajoutant les nouveaux paramètres.

    - task: AzureResourceManagerTemplateDeployment@3
      name: RunPreflightValidation
      displayName: Run preflight validation
      inputs:
        connectedServiceName: ToyWebsite${{parameters.environmentType}}
        location: ${{parameters.deploymentDefaultLocation}}
        deploymentMode: Validation
        resourceGroupName: $(ResourceGroupName)
        csmFile: deploy/main.bicep
        overrideParameters: >
          -environmentType $(EnvironmentType)
          -reviewApiUrl $(ReviewApiUrl)
          -reviewApiKey $(ReviewApiKey)
          -sqlServerAdministratorLogin $(SqlServerAdministratorLogin)
          -sqlServerAdministratorLoginPassword $(SqlServerAdministratorLoginPassword)
    
  3. Mettre à jour l’étape RunWhatIf de la phase Aperçu en ajoutant les nouveaux paramètres.

    inlineScript: |
      az deployment group what-if \
        --resource-group $(ResourceGroupName) \
        --template-file deploy/main.bicep \
        --parameters environmentType=$(EnvironmentType) \
                     reviewApiUrl=$(ReviewApiUrl) \
                     reviewApiKey=$(ReviewApiKey) \
                     sqlServerAdministratorLogin=$(SqlServerAdministratorLogin) \
                     sqlServerAdministratorLoginPassword=$(SqlServerAdministratorLoginPassword)
    

    Important

    Veillez à ajouter une barre oblique inverse (\) à la fin de la ligne qui définit la valeur du paramètre reviewApiKey, et à la ligne suivante. Le caractère \ indique qu’il existe des lignes supplémentaires qui font partie de la même commande Azure CLI.

Ajouter des valeurs de paramètre à la phase Déployer

  1. Mettez à jour l’étape DeployBicepFile de la phase Déployer en ajoutant les nouveaux paramètres.

    - task: AzureResourceManagerTemplateDeployment@3
      name: DeployBicepFile
      displayName: Deploy Bicep file
      inputs:
        connectedServiceName: ToyWebsite${{parameters.environmentType}}
        deploymentName: $(Build.BuildNumber)
        location: ${{parameters.deploymentDefaultLocation}}
        resourceGroupName: $(ResourceGroupName)
        csmFile: deploy/main.bicep
        overrideParameters: >
          -environmentType $(EnvironmentType)
          -reviewApiUrl $(ReviewApiUrl)
          -reviewApiKey $(ReviewApiKey)
          -sqlServerAdministratorLogin $(SqlServerAdministratorLogin)
          -sqlServerAdministratorLoginPassword $(SqlServerAdministratorLoginPassword)
        deploymentOutputs: deploymentOutputs
    
  2. Créez des variables de pipeline qui contiennent les valeurs des sorties Bicep que vous avez ajoutées récemment pour le compte de stockage et les ressources Azure SQL :

    - bash: |
        echo "##vso[task.setvariable variable=appServiceAppName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppName.value')"
        echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
        echo "##vso[task.setvariable variable=storageAccountName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.storageAccountName.value')"
        echo "##vso[task.setvariable variable=storageAccountImagesBlobContainerName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.storageAccountImagesBlobContainerName.value')"
        echo "##vso[task.setvariable variable=sqlServerFullyQualifiedDomainName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.sqlServerFullyQualifiedDomainName.value')"
        echo "##vso[task.setvariable variable=sqlDatabaseName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.sqlDatabaseName.value')"
      name: SaveDeploymentOutputs
      displayName: Save deployment outputs into variables
      env:
        DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
    

Ajouter des étapes de déploiement de base de données

Dans cette section, vous allez définir les étapes nécessaires au déploiement des composants de base de données de votre site web. Tout d’abord, vous allez ajouter une étape pour déployer le fichier DACPAC précédemment généré par le pipeline. Ensuite, vous ajoutez des exemples de données dans la base de données et le compte de stockage, mais seulement pour les environnements hors production.

  1. Sous l’étape DeployWebsiteApp de la phase Déployer, ajoutez une nouvelle étape pour déployer le fichier DACPAC :

    - task: SqlAzureDacpacDeployment@1
      name: DeploySqlDatabaseDacpac
      displayName: Deploy DACPAC to database
      inputs:
        ConnectedServiceNameARM: ToyWebsite${{parameters.environmentType}}
        authenticationType: 'server'
        serverName: $(sqlServerFullyQualifiedDomainName)
        databaseName: $(sqlDatabaseName)
        sqlUsername: $(SqlServerAdministratorLogin)
        sqlPassword: $(SqlServerAdministratorLoginPassword)
        deployType: 'DacpacTask'
        deploymentAction: 'Publish'
        dacpacFile: '$(Pipeline.Workspace)/database/ToyCompany.Database.dacpac'
    
  2. Sous l’étape que vous venez d’ajouter, définissez une étape pour alimenter la base de données avec des exemples de données.

    - ${{ if ne(parameters.environmentType, 'Production') }}:
      - task: SqlAzureDacpacDeployment@1
        name: AddTestDataToDatabase
        displayName: Add test data to database
        inputs:
          ConnectedServiceNameARM: ToyWebsite${{parameters.environmentType}}
          authenticationType: 'server'
          serverName: $(sqlServerFullyQualifiedDomainName)
          databaseName: $(sqlDatabaseName)
          sqlUsername: $(SqlServerAdministratorLogin)
          sqlPassword: $(SqlServerAdministratorLoginPassword)
          deployType: 'sqlTask'
          sqlFile: 'deploy/sample-data/Toys.sql'
    

    Notez qu’une condition est appliquée à cette étape afin qu’elle s’exécute seulement pour les environnements hors production.

  3. Sous l’étape que vous venez d’ajouter et toujours dans l’étendue de la condition, ajoutez une étape pour charger des exemples d’images de jouets dans le conteneur d’objets blob en utilisant Azure CLI :

    - task: AzureCLI@2
      name: UploadSampleImages
      displayName: Upload sample images
      inputs:
        azureSubscription: ToyWebsite${{parameters.environmentType}}
        scriptType: 'bash'
        scriptLocation: 'inlineScript'
        inlineScript: |
          az storage blob upload-batch \
            --account-name $(storageAccountName) \
            --destination $(storageAccountImagesBlobContainerName) \
            --source 'deploy/sample-data/toyimages'
    

Vérifier les fichiers et valider vos modifications

  1. Vérifiez que votre fichier main.bicep ressemble à ceci :

    @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 URL to the product review API.')
    param reviewApiUrl string
    
    @secure()
    @description('The API key to use when accessing the product review API.')
    param reviewApiKey string
    
    @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
    
    // Define the names for resources.
    var appServiceAppName = 'toy-website-${resourceNameSuffix}'
    var appServicePlanName = 'toy-website'
    var logAnalyticsWorkspaceName = 'workspace-${resourceNameSuffix}'
    var applicationInsightsName = 'toywebsite'
    var storageAccountName = 'mystorage${resourceNameSuffix}'
    var storageAccountImagesBlobContainerName = 'toyimages'
    var sqlServerName = 'toy-website-${resourceNameSuffix}'
    var sqlDatabaseName = 'Toys'
    
    // Define the connection string to access Azure SQL.
    var sqlDatabaseConnectionString = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Initial Catalog=${sqlDatabase.name};Persist Security Info=False;User ID=${sqlServerAdministratorLogin};Password=${sqlServerAdministratorLoginPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'
    
    // Define the SKUs for each component based on the environment type.
    var environmentConfigurationMap = {
      Production: {
        appServicePlan: {
          sku: {
            name: 'S1'
            capacity: 1
          }
        }
        storageAccount: {
          sku: {
            name: 'Standard_LRS'
          }
        }
        sqlDatabase: {
          sku: {
            name: 'Standard'
            tier: 'Standard'
          }
        }
      }
      Test: {
        appServicePlan: {
          sku: {
            name: 'F1'
          }
        }
        storageAccount: {
          sku: {
            name: 'Standard_GRS'
          }
        }
        sqlDatabase: {
          sku: {
            name: 'Standard'
            tier: 'Standard'
          }
        }
      }
    }
    
    resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
      name: appServicePlanName
      location: location
      sku: environmentConfigurationMap[environmentType].appServicePlan.sku
    }
    
    resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
      name: appServiceAppName
      location: location
      properties: {
        serverFarmId: appServicePlan.id
        httpsOnly: true
        siteConfig: {
          appSettings: [
            {
              name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
              value: applicationInsights.properties.InstrumentationKey
            }
            {
              name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
              value: applicationInsights.properties.ConnectionString
            }
            {
              name: 'ReviewApiUrl'
              value: reviewApiUrl
            }
            {
              name: 'ReviewApiKey'
              value: reviewApiKey
            }
            {
              name: 'StorageAccountName'
              value: storageAccount.name
            }
            {
              name: 'StorageAccountBlobEndpoint'
              value: storageAccount.properties.primaryEndpoints.blob
            }
            {
              name: 'StorageAccountImagesContainerName'
              value: storageAccount::blobService::storageAccountImagesBlobContainer.name
            }
            {
              name: 'SqlDatabaseConnectionString'
              value: sqlDatabaseConnectionString
            }
          ]
        }
      }
    }
    
    resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
      name: logAnalyticsWorkspaceName
      location: location
    }
    
    resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
      name: applicationInsightsName
      location: location
      kind: 'web'
      properties: {
        Application_Type: 'web'
        Request_Source: 'rest'
        Flow_Type: 'Bluefield'
        WorkspaceResourceId: logAnalyticsWorkspace.id
      }
    }
    
    resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
      name: storageAccountName
      location: location
      kind: 'StorageV2'
      sku: environmentConfigurationMap[environmentType].storageAccount.sku
    
      resource blobService 'blobServices' = {
        name: 'default'
    
        resource storageAccountImagesBlobContainer 'containers' = {
          name: storageAccountImagesBlobContainerName
    
          properties: {
            publicAccess: 'Blob'
          }
        }
      }
    }
    
    resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
      name: sqlServerName
      location: location
      properties: {
        administratorLogin: sqlServerAdministratorLogin
        administratorLoginPassword: sqlServerAdministratorLoginPassword
      }
    }
    
    resource sqlServerFirewallRule 'Microsoft.Sql/servers/firewallRules@2022-05-01-preview' = {
      parent: sqlServer
      name: 'AllowAllWindowsAzureIps'
      properties: {
        endIpAddress: '0.0.0.0'
        startIpAddress: '0.0.0.0'
      }
    }
    
    resource sqlDatabase 'Microsoft.Sql/servers/databases@2022-05-01-preview' = {
      parent: sqlServer
      name: sqlDatabaseName
      location: location
      sku: environmentConfigurationMap[environmentType].sqlDatabase.sku
    }
    
    output appServiceAppName string = appServiceApp.name
    output appServiceAppHostName string = appServiceApp.properties.defaultHostName
    output storageAccountName string = storageAccount.name
    output storageAccountImagesBlobContainerName string = storageAccount::blobService::storageAccountImagesBlobContainer.name
    output sqlServerFullyQualifiedDomainName string = sqlServer.properties.fullyQualifiedDomainName
    output sqlDatabaseName string = sqlDatabase.name
    

    Si ce n’est pas le cas, mettez-le à jour pour qu’il corresponde au contenu du fichier.

  2. Vérifiez que votre fichier deploy.yml ressemble à ceci :

    parameters:
    - name: environmentType
      type: string
    - name: deploymentDefaultLocation
      type: string
      default: westus3
    
    stages:
    
    - ${{ if ne(parameters.environmentType, 'Production') }}:
      - stage: Validate_${{parameters.environmentType}}
        displayName: Validate (${{parameters.environmentType}} Environment)
        jobs:
        - job: ValidateBicepCode
          displayName: Validate Bicep code
          variables:
          - group: ToyWebsite${{parameters.environmentType}}
          steps:
            - task: AzureResourceManagerTemplateDeployment@3
              name: RunPreflightValidation
              displayName: Run preflight validation
              inputs:
                connectedServiceName: ToyWebsite${{parameters.environmentType}}
                location: ${{parameters.deploymentDefaultLocation}}
                deploymentMode: Validation
                resourceGroupName: $(ResourceGroupName)
                csmFile: deploy/main.bicep
                overrideParameters: >
                  -environmentType $(EnvironmentType)
                  -reviewApiUrl $(ReviewApiUrl)
                  -reviewApiKey $(ReviewApiKey)
                  -sqlServerAdministratorLogin $(SqlServerAdministratorLogin)
                  -sqlServerAdministratorLoginPassword $(SqlServerAdministratorLoginPassword)
    
    - ${{ if eq(parameters.environmentType, 'Production') }}:
      - stage: Preview_${{parameters.environmentType}}
        displayName: Preview (${{parameters.environmentType}} Environment)
        jobs:
        - job: PreviewAzureChanges
          displayName: Preview Azure changes
          variables:
          - group: ToyWebsite${{parameters.environmentType}}
          steps:
            - task: AzureCLI@2
              name: RunWhatIf
              displayName: Run what-if
              inputs:
                azureSubscription: ToyWebsite${{parameters.environmentType}}
                scriptType: 'bash'
                scriptLocation: 'inlineScript'
                inlineScript: |
                  az deployment group what-if \
                    --resource-group $(ResourceGroupName) \
                    --template-file deploy/main.bicep \
                    --parameters environmentType=$(EnvironmentType) \
                                 reviewApiUrl=$(ReviewApiUrl) \
                                 reviewApiKey=$(ReviewApiKey) \
                                 sqlServerAdministratorLogin=$(SqlServerAdministratorLogin) \
                                 sqlServerAdministratorLoginPassword=$(SqlServerAdministratorLoginPassword)
    
    - stage: Deploy_${{parameters.environmentType}}
      displayName: Deploy (${{parameters.environmentType}} Environment)
      jobs:
      - deployment: DeployWebsite
        displayName: Deploy website
        pool:
          vmImage: windows-latest
        variables:
        - group: ToyWebsite${{parameters.environmentType}}
        environment: ${{parameters.environmentType}}
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
    
                - task: AzureResourceManagerTemplateDeployment@3
                  name: DeployBicepFile
                  displayName: Deploy Bicep file
                  inputs:
                    connectedServiceName: ToyWebsite${{parameters.environmentType}}
                    deploymentName: $(Build.BuildNumber)
                    location: ${{parameters.deploymentDefaultLocation}}
                    resourceGroupName: $(ResourceGroupName)
                    csmFile: deploy/main.bicep
                    overrideParameters: >
                      -environmentType $(EnvironmentType)
                      -reviewApiUrl $(ReviewApiUrl)
                      -reviewApiKey $(ReviewApiKey)
                      -sqlServerAdministratorLogin $(SqlServerAdministratorLogin)
                      -sqlServerAdministratorLoginPassword $(SqlServerAdministratorLoginPassword)
                    deploymentOutputs: deploymentOutputs
    
                - bash: |
                    echo "##vso[task.setvariable variable=appServiceAppName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppName.value')"
                    echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
                    echo "##vso[task.setvariable variable=storageAccountName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.storageAccountName.value')"
                    echo "##vso[task.setvariable variable=storageAccountImagesBlobContainerName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.storageAccountImagesBlobContainerName.value')"
                    echo "##vso[task.setvariable variable=sqlServerFullyQualifiedDomainName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.sqlServerFullyQualifiedDomainName.value')"
                    echo "##vso[task.setvariable variable=sqlDatabaseName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.sqlDatabaseName.value')"
                  name: SaveDeploymentOutputs
                  displayName: Save deployment outputs into variables
                  env:
                    DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
    
                - task: AzureRmWebAppDeployment@4
                  name: DeployWebsiteApp
                  displayName: Deploy website
                  inputs:
                    appType: webApp
                    ConnectionType: AzureRM
                    azureSubscription: ToyWebsite${{parameters.environmentType}}
                    ResourceGroupName: $(ResourceGroupName)
                    WebAppName: $(appServiceAppName)
                    Package: '$(Pipeline.Workspace)/website/publish.zip'
    
                - task: SqlAzureDacpacDeployment@1
                  name: DeploySqlDatabaseDacpac
                  displayName: Deploy DACPAC to database
                  inputs:
                    ConnectedServiceNameARM: ToyWebsite${{parameters.environmentType}}
                    authenticationType: 'server'
                    serverName: $(sqlServerFullyQualifiedDomainName)
                    databaseName: $(sqlDatabaseName)
                    sqlUsername: $(SqlServerAdministratorLogin)
                    sqlPassword: $(SqlServerAdministratorLoginPassword)
                    deployType: 'DacpacTask'
                    deploymentAction: 'Publish'
                    dacpacFile: '$(Pipeline.Workspace)/database/ToyCompany.Database.dacpac'
    
                - ${{ if ne(parameters.environmentType, 'Production') }}:
                  - task: SqlAzureDacpacDeployment@1
                    name: AddTestDataToDatabase
                    displayName: Add test data to database
                    inputs:
                      ConnectedServiceNameARM: ToyWebsite${{parameters.environmentType}}
                      authenticationType: 'server'
                      serverName: $(sqlServerFullyQualifiedDomainName)
                      databaseName: $(sqlDatabaseName)
                      sqlUsername: $(SqlServerAdministratorLogin)
                      sqlPassword: $(SqlServerAdministratorLoginPassword)
                      deployType: 'sqlTask'
                      sqlFile: 'deploy/sample-data/Toys.sql'
    
                  - task: AzureCLI@2
                    name: UploadSampleImages
                    displayName: Upload sample images
                    inputs:
                      azureSubscription: ToyWebsite${{parameters.environmentType}}
                      scriptType: 'bash'
                      scriptLocation: 'inlineScript'
                      inlineScript: |
                        az storage blob upload-batch \
                          --account-name $(storageAccountName) \
                          --destination $(storageAccountImagesBlobContainerName) \
                          --source 'deploy/sample-data/toyimages'
    
    - stage: SmokeTest_${{parameters.environmentType}}
      displayName: Smoke Test (${{parameters.environmentType}} Environment)
      jobs:
      - job: SmokeTest
        displayName: Smoke test
        variables:
          appServiceAppHostName: $[ stageDependencies.Deploy_${{parameters.environmentType}}.DeployWebsite.outputs['DeployWebsite.SaveDeploymentOutputs.appServiceAppHostName'] ]
        steps:
          - task: PowerShell@2
            name: RunSmokeTests
            displayName: Run smoke tests
            inputs:
              targetType: inline
              script: |
                $container = New-PesterContainer `
                  -Path 'deploy/Website.Tests.ps1' `
                  -Data @{ HostName = '$(appServiceAppHostName)' }
                Invoke-Pester `
                  -Container $container `
                  -CI
    
          - task: PublishTestResults@2
            name: PublishTestResults
            displayName: Publish test results
            condition: always()
            inputs:
              testResultsFormat: NUnit
              testResultsFiles: 'testResults.xml'
    

    Si ce n’est pas le cas, mettez-le à jour pour qu’il corresponde au contenu du fichier.

  3. Enregistrez les modifications apportées au fichier.

  4. Validez et envoyez (push) vos modifications vers votre dépôt Git. Dans le terminal Visual Studio Code, exécutez les commandes suivantes :

    git add .
    git commit -m "Add SQL database"
    git push
    

Exécuter le pipeline

  1. Dans votre navigateur, accédez à Pipelines.

  2. Sélectionnez l’exécution la plus récente de votre pipeline.

    Attendez que toutes les phases de l’environnement de test se terminent correctement. Notez que le test de détection de fumée réussit également désormais.

    Screenshot of Azure DevOps showing the pipeline run's Smoke Test stage for the test environment. The status shows that the stage succeeded.

  3. Attendez que le pipeline soit à nouveau suspendu avant la phase Préversion (environnement de production), car il a besoin de l’autorisation pour un groupe de variables différent cette fois.

    Screenshot of Azure DevOps showing the pipeline run paused at the Deploy stage. Permission is required to continue. The View button is highlighted.

  4. Sélectionnez Afficher, puis Autoriser>Autoriser.

    La phase Préversion (environnement de production) se termine avec succès.

  5. Surveillez le pipeline à mesure qu’il termine les phases finales.

    La phase Déployer (environnement de production) se termine correctement et la phase Test de détection de fumée (environnement de production) se termine également correctement.

    Screenshot of Azure DevOps showing the pipeline run with all stages showing success.

Voir le site web

  1. Sélectionnez la phase Déployer (environnement de test) pour ouvrir le journal du pipeline.

  2. Sélectionnez l’étape Déployer le site web.

    Maintenez la touche Ctrl enfoncée ( sur macOS) et sélectionnez l’URL de l’application App Service pour l’ouvrir dans un nouvel onglet de navigateur.

    Screenshot of Azure DevOps showing the pipeline run log for the test environment's Deploy stage. The URL of the App Service app is highlighted.

  3. Sélectionnez Jouets.

    Screenshot of the toy company website homepage, with the Toys link highlighted.

    Notez que les exemples de données s’affichent dans l’environnement de test.

    Screenshot of the test website's toy page, with the sample toys displayed.

  4. Répétez le processus précédent pour l’application de la phase Déployer (environnement de production).

    Notez qu’aucun exemple de données ne s’affiche dans l’environnement de production.

    Screenshot of the production website's toy page, with no toys displayed.

Nettoyer les ressources

Maintenant que l’exercice est terminé, vous devez supprimer les ressources afin qu’elles ne vous soient pas facturées.

Dans le terminal Visual Studio Code, exécutez les commandes suivantes :

az group delete --resource-group ToyWebsiteTest --yes --no-wait
az group delete --resource-group ToyWebsiteProduction --yes --no-wait

Le groupe de ressources est supprimé en arrière-plan.

Remove-AzResourceGroup -Name ToyWebsiteTest -Force
Remove-AzResourceGroup -Name ToyWebsiteProduction -Force