Exercício - Semear uma conta de armazenamento e um banco de dados

Concluído

Você atualizou seu fluxo de trabalho para criar e implantar o aplicativo do seu site no aplicativo do Serviço de Aplicativo do Azure definido em seu arquivo Bicep, mas o trabalho de teste de fumaça está falhando porque o banco de dados ainda não está funcionando. Nesta unidade, você implantará um novo servidor lógico e banco de dados SQL do Azure e configurará seu fluxo de trabalho para criar e implantar o esquema do banco de dados. Você também atualizará seu fluxo de trabalho para adicionar alguns dados de produto de exemplo para seu ambiente de teste para que sua equipe possa experimentar o site.

No processo, você:

  • Adicione um contêiner de blob à conta de armazenamento do Azure.
  • Adicione um servidor lógico SQL do Azure e um banco de dados.
  • Atualize o trabalho de compilação para criar o projeto de banco de dados em um arquivo DACPAC.
  • Adicione novas variáveis e segredos para o servidor lógico e o banco de dados SQL do Azure.
  • Atualize seu fluxo de trabalho para usar as novas variáveis e segredos.
  • Adicione novas etapas de fluxo de trabalho para implantar seu arquivo DACPAC.
  • Execute o fluxo de trabalho e visualize o site.

Adicionar um recipiente de armazenamento

Seu arquivo Bicep já define uma conta de armazenamento, mas não define um contêiner de blob. Aqui, você adiciona um contêiner de blob ao seu arquivo Bicep. Você também fornece o nome da conta de armazenamento e do contêiner de blob para o aplicativo usando suas definições de configuração. Dessa forma, o aplicativo sabe qual conta de armazenamento acessar.

  1. No Visual Studio Code, abra o arquivo main.bicep na pasta deploy .

  2. Abaixo das variáveis que definem nomes de recursos (perto da linha 27), adicione uma nova definição de variável para o nome do contêiner de armazenamento de blob:

    var storageAccountImagesBlobContainerName = 'toyimages'
    
  3. Atualize o storageAccount recurso para definir o contêiner de 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. Atualize a propriedade do aplicativo para adicionar duas novas configurações de appSettings aplicativo, uma para o nome da conta de armazenamento e outra para o nome do contêiner 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. No final do conteúdo do arquivo, adicione novas saídas para expor os nomes da conta de armazenamento e do contêiner de blob:

    output storageAccountName string = storageAccount.name
    output storageAccountImagesBlobContainerName string = storageAccount::blobService::storageAccountImagesBlobContainer.name
    
  6. Guarde as alterações ao ficheiro.

  7. Confirme suas alterações no repositório Git, mas não as envie por push ainda. No terminal de código do Visual Studio, execute os seguintes comandos:

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

Adicionar um servidor lógico SQL do Azure e um banco de dados

Atualmente, seu arquivo Bicep não implanta um servidor lógico ou banco de dados SQL do Azure. Nesta seção, você adiciona esses recursos ao seu arquivo Bicep.

  1. No arquivo main.bicep, adicione dois novos parâmetros abaixo do reviewApiKey parâmetro perto da parte superior do arquivo:

    @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. Abaixo das variáveis que definem nomes de recursos, adicione novas variáveis para definir os nomes do seu servidor lógico SQL do Azure e do banco de dados:

    var sqlServerName = 'toy-website-${resourceNameSuffix}'
    var sqlDatabaseName = 'Toys'
    
  3. Abaixo das variáveis que você acabou de adicionar, defina uma nova variável que crie uma cadeia de conexão para o aplicativo acessar o banco de dados:

    // 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;'
    

    Nota

    Para simplificar, o aplicativo usa o login e senha de administrador para acessar o banco de dados. No entanto, isso não é uma boa prática para uma solução de produção. É melhor usar uma identidade gerenciada pelo Serviço de Aplicativo para acessar o banco de dados e conceder à identidade gerenciada as permissões mínimas necessárias para o aplicativo. Temos links para mais informações na página de Resumo deste módulo.

  4. Perto do final do conteúdo do arquivo, acima das saídas, adicione o servidor lógico SQL do Azure e os recursos do banco de dados:

    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. Atualize a environmentConfigurationMap variável para definir as SKUs a serem usadas para seu banco de dados para cada ambiente:

    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. Adicione outra configuração de aplicativo ao seu aplicativo do Serviço de Aplicativo para a cadeia de conexão do banco de dados:

    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. Na parte inferior do arquivo, adicione saídas para expor o nome do host do servidor lógico SQL do Azure e o nome do banco de dados:

    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. Guarde as alterações ao ficheiro.

Adicionar novas etapas de compilação para o projeto de banco de dados

Os desenvolvedores do site prepararam um projeto de banco de dados do Visual Studio que implanta e configura a tabela do banco de dados do site. Aqui, você atualiza sua compilação de fluxo de trabalho chamada fluxo de trabalho para criar o projeto de banco de dados em um arquivo DACPAC e carregá-lo como um artefato de fluxo de trabalho.

  1. Abra o arquivo build.yml na pasta .github/workflows .

  2. Para criar o projeto de banco de dados do Visual Studio e carregar o arquivo DACPAC gerado como um artefato de fluxo de trabalho, adicione o trabalho de banco de dados de compilação:

    name: build-website
    
    on:
      workflow_call:
    
    jobs:
      build-application:
        name: Build application
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3
    
        - name: Install .NET Core
          uses: actions/setup-dotnet@v3
          with:
            dotnet-version: 3.1
    
        - name: Build publishable website
          run: |
            dotnet publish --configuration Release
          working-directory: ./src/ToyCompany/ToyCompany.Website
    
        - name: Zip publishable website
          run: |
            zip -r publish.zip .
          working-directory: ./src/ToyCompany/ToyCompany.Website/bin/Release/netcoreapp3.1/publish
    
        - name: Upload website as workflow artifact
          uses: actions/upload-artifact@v3
          with:
            name: website
            path: ./src/ToyCompany/ToyCompany.Website/bin/Release/netcoreapp3.1/publish/publish.zip
    
      build-database:
        name: Build database
        runs-on: windows-latest
        steps:
        - uses: actions/checkout@v3
    
        - name: Prepare MSBuild
          uses: microsoft/setup-msbuild@v1.1
    
        - name: Build database project
          working-directory: ./src/ToyCompany/ToyCompany.Database
          run: MSBuild.exe ToyCompany.Database.sqlproj -property:Configuration=Release
    
        - name: Upload website as workflow artifact
          uses: actions/upload-artifact@v3
          with:
            name: database
            path: ./src/ToyCompany/ToyCompany.Database/bin/Release/ToyCompany.Database.dacpac
    

    O trabalho de compilar banco de dados usa um executor do Windows. Atualmente, os projetos de banco de dados do Visual Studio devem ser criados no sistema operacional Windows.

  3. Guarde as alterações ao ficheiro.

Definir os segredos

Você precisa armazenar com segurança a senha de administrador do servidor lógico SQL do Azure para cada ambiente. Você decide usar os segredos do GitHub para proteger as informações.

  1. No seu browser, aceda a Definições>Segredos e variáveis>Ações.

    Captura de tela do GitHub que mostra o item de menu Segredos na categoria Configurações.

  2. Selecione o botão Novo segredo do repositório.

  3. Digite SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_TEST como o nome secreto e SecurePassword!111 como o valor.

    Captura de tela do GitHub mostrando um novo segredo.

  4. Selecione Add secret (Adicionar segredo).

  5. Repita o processo para adicionar outro segredo chamado SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_PRODUCTION como o nome secreto e SecurePassword!999 como o valor. Selecione Add secret (Adicionar segredo).

Adicione os segredos e entradas ao seu fluxo de trabalho

  1. No Visual Studio Code, abra o arquivo deploy.yml na pasta .github/workflows .

  2. Na parte superior do arquivo, defina uma nova entrada nomeada sqlServerAdministratorLogin e um novo segredo chamado sqlServerAdministratorLoginPassword:

    name: deploy
    
    on:
      workflow_call:
        inputs:
          environmentType:
            required: true
            type: string
          resourceGroupName:
            required: true
            type: string
          reviewApiUrl:
            required: true
            type: string
          sqlServerAdministratorLogin:
            required: true
            type: string
        secrets:
          AZURE_CLIENT_ID:
            required: true
          AZURE_TENANT_ID:
            required: true
          AZURE_SUBSCRIPTION_ID:
            required: true
          reviewApiKey:
            required: true
          sqlServerAdministratorLoginPassword:
            required: true
    
  3. Guarde as alterações ao ficheiro.

  4. Abra o arquivo workflow.yml .

  5. Na definição deploy-test, defina um valor para a sqlServerAdministratorLogin entrada e propague o valor para o sqlServerAdministratorLoginPassword segredo:

    # Deploy to the test environment.
    deploy-test:
      uses: ./.github/workflows/deploy.yml
      needs: [build, lint]
      with:
        environmentType: Test
        resourceGroupName: ToyWebsiteTest
        reviewApiUrl: https://sandbox.contoso.com/reviews
        sqlServerAdministratorLogin: TestToyCompanyAdmin
      secrets:
        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_TEST }}
        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        reviewApiKey: ${{ secrets.REVIEW_API_KEY_TEST }}
        sqlServerAdministratorLoginPassword: ${{ secrets.SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_TEST }}
    
  6. Repita o processo na definição deploy-production com os valores do ambiente de produção:

    # Deploy to the production environment.
    deploy-production:
      uses: ./.github/workflows/deploy.yml
      needs:
      - lint
      - build
      - deploy-test
      with:
        environmentType: Production
        resourceGroupName: ToyWebsiteProduction
        reviewApiUrl: https://api.contoso.com/reviews
        sqlServerAdministratorLogin: ToyCompanyAdmin
      secrets:
        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_PRODUCTION }}
        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        reviewApiKey: ${{ secrets.REVIEW_API_KEY_PRODUCTION }}
        sqlServerAdministratorLoginPassword: ${{ secrets.SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_PRODUCTION }}
    
  7. Guarde as alterações ao ficheiro.

Adicionar valores de parâmetros e saídas

O arquivo Bicep agora tem dois novos parâmetros obrigatórios: sqlServerAdministratorLogin e sqlServerAdministratorLoginPassword. Aqui, você propaga esses valores de parâmetro a partir de suas entradas e segredos do fluxo de trabalho, para validar e implantar trabalhos. Você também propaga as saídas das implantações do Bicep para as saídas do trabalho.

  1. Abra o arquivo deploy.yml .

  2. Atualize a etapa de validação Executar comprovação do trabalho de validação para adicionar os novos parâmetros:

    jobs:
      validate:
         runs-on: ubuntu-latest
         steps:
         - uses: actions/checkout@v3
         - uses: azure/login@v1
           name: Sign in to Azure
           with:
             client-id: ${{ secrets.AZURE_CLIENT_ID }}
             tenant-id: ${{ secrets.AZURE_TENANT_ID }}
             subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
         - if: inputs.environmentType != 'Production'
           uses: azure/arm-deploy@v1
           name: Run preflight validation
           with:
             deploymentName: ${{ github.run_number }}
             resourceGroupName: ${{ inputs.resourceGroupName }}
             template: ./deploy/main.bicep
             parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
               sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
               sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
             deploymentMode: Validate
    
  3. Atualize a etapa Executar hipóteses para adicionar os novos parâmetros:

    - if: inputs.environmentType == 'Production'
      uses: azure/arm-deploy@v1
      name: Run what-if
      with:
        failOnStdErr: false
        resourceGroupName: ${{ inputs.resourceGroupName }}
        template: ./deploy/main.bicep
        parameters: >
          environmentType=${{ inputs.environmentType }}
          reviewApiUrl=${{ inputs.reviewApiUrl }}
          reviewApiKey=${{ secrets.reviewApiKey }}
          sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
          sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
        additionalArguments: --what-if
    
  4. Atualize a etapa do arquivo Implantar Bicep do trabalho de implantação para adicionar os novos parâmetros:

    deploy:
      needs: validate
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      outputs:
        appServiceAppName: ${{ steps.deploy.outputs.appServiceAppName }}
        appServiceAppHostName: ${{ steps.deploy.outputs.appServiceAppHostName }}
      steps:
      - uses: actions/checkout@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - uses: azure/arm-deploy@v1
        id: deploy
        name: Deploy Bicep file
        with:
          failOnStdErr: false
          deploymentName: ${{ github.run_number }}
          resourceGroupName: ${{ inputs.resourceGroupName }}
          template: ./deploy/main.bicep
          parameters: >
             environmentType=${{ inputs.environmentType }}
             reviewApiUrl=${{ inputs.reviewApiUrl }}
             reviewApiKey=${{ secrets.reviewApiKey }}
             sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
             sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
    
  5. Na definição do trabalho de implantação , adicione novas saídas para as saídas do arquivo Bicep:

    deploy:
      needs: validate
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      outputs:
        appServiceAppName: ${{ steps.deploy.outputs.appServiceAppName }}
        appServiceAppHostName: ${{ steps.deploy.outputs.appServiceAppHostName }}
        storageAccountName: ${{ steps.deploy.outputs.storageAccountName }}
        storageAccountImagesBlobContainerName: ${{ steps.deploy.outputs.storageAccountImagesBlobContainerName }}
        sqlServerFullyQualifiedDomainName: ${{ steps.deploy.outputs.sqlServerFullyQualifiedDomainName }}
        sqlDatabaseName: ${{ steps.deploy.outputs.sqlDatabaseName }}
    

Adicionar trabalhos de banco de dados e semente de dados

Nesta seção, você define as etapas necessárias para implantar os componentes de banco de dados do seu site. Primeiro, você adiciona uma etapa para implantar o arquivo DACPAC que o fluxo de trabalho criou anteriormente. Em seguida, você adiciona dados de exemplo ao banco de dados e à conta de armazenamento, mas apenas para ambientes que não são de produção.

  1. Abaixo do trabalho do site de implantação, adicione um novo trabalho para implantar o arquivo DACPAC:

    deploy-database:
      needs: deploy
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      steps:
      - uses: actions/download-artifact@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - uses: azure/sql-action@v1.2
        name: Deploy DACPAC to database
        with:
          server-name: ${{ needs.deploy.outputs.sqlServerFullyQualifiedDomainName }}
          connection-string: ${{ format('Server={0};Initial Catalog={1};User Id={2};Password={3};', needs.deploy.outputs.sqlServerFullyQualifiedDomainName, needs.deploy.outputs.sqlDatabaseName, inputs.sqlServerAdministratorLogin, secrets.sqlServerAdministratorLoginPassword) }}
          dacpac-package: database/ToyCompany.Database.dacpac
    
  2. Abaixo do trabalho que você acabou de adicionar, e acima do trabalho de teste de fumaça, defina um novo trabalho para semear o banco de dados com dados de exemplo.

    seed-database:
      needs:
      - deploy
      - deploy-database
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      steps:
      - uses: actions/checkout@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - if: inputs.environmentType != 'Production'
        uses: azure/sql-action@v1.2
        name: Add test data to database
        with:
          server-name: ${{ needs.deploy.outputs.sqlServerFullyQualifiedDomainName }}
          connection-string: ${{ format('Server={0};Initial Catalog={1};User Id={2};Password={3};', needs.deploy.outputs.sqlServerFullyQualifiedDomainName, needs.deploy.outputs.sqlDatabaseName, inputs.sqlServerAdministratorLogin, secrets.sqlServerAdministratorLoginPassword) }}
          sql-file: 'deploy/sample-data/Toys.sql'
    

    Observe que a etapa Adicionar dados de teste ao banco de dados tem uma condição aplicada a ela. Ou seja, ele é executado apenas para ambientes que não são de produção. A condição é aplicada à etapa, não a todo o trabalho, de modo que trabalhos posteriores possam depender desse trabalho, independentemente do tipo de ambiente.

  3. Abaixo do trabalho que você acabou de adicionar e acima do trabalho de teste de fumaça, defina outro trabalho para carregar algumas imagens de brinquedo de exemplo no contêiner de blob usando a CLI do Azure:

    seed-storage-account:
      needs: deploy
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      steps:
      - uses: actions/checkout@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - if: inputs.environmentType != 'Production'
        uses: azure/CLI@v1
        name: Upload sample images
        with:
          inlineScript: |
              az storage blob upload-batch \
                --account-name ${{ needs.deploy.outputs.storageAccountName }} \
                --destination ${{ needs.deploy.outputs.storageAccountImagesBlobContainerName }} \
                --source 'deploy/sample-data/toyimages'
    

    Observe que esse trabalho usa um corredor do Ubuntu, porque a ação requer que o azure/cli Linux seja executado, mas o trabalho definido anteriormente usa um corredor do build-database Windows para criar o projeto de banco de dados. Este fluxo de trabalho é um bom exemplo da utilização de vários sistemas operativos para atingir os seus requisitos.

Atualizar as dependências para o trabalho de teste de fumaça

  1. Atualize as dependências do trabalho de teste de fumaça para garantir que ele seja executado depois que todas as etapas de implantação forem concluídas:

    smoke-test:
      runs-on: ubuntu-latest
      needs:
      - deploy
      - deploy-website
      - deploy-database
      - seed-database
      - seed-storage-account
      steps:
      - uses: actions/checkout@v3
      - run: |
          $container = New-PesterContainer `
            -Path 'deploy/Website.Tests.ps1' `
            -Data @{ HostName = '${{needs.deploy.outputs.appServiceAppHostName}}' }
          Invoke-Pester `
            -Container $container `
            -CI
        name: Run smoke tests
        shell: pwsh
    
  2. Guarde as alterações ao ficheiro.

Verificar arquivos e confirmar suas alterações

  1. Verifique se o arquivo main.bicep tem esta aparência:

    @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
    

    Caso contrário, atualize-o para corresponder ao conteúdo do arquivo.

  2. Verifique se o arquivo deploy.yml tem esta aparência:

    name: deploy
    
    on:
      workflow_call:
        inputs:
          environmentType:
            required: true
            type: string
          resourceGroupName:
            required: true
            type: string
          reviewApiUrl:
            required: true
            type: string
          sqlServerAdministratorLogin:
            required: true
            type: string
        secrets:
          AZURE_CLIENT_ID:
            required: true
          AZURE_TENANT_ID:
            required: true
          AZURE_SUBSCRIPTION_ID:
            required: true
          reviewApiKey:
            required: true
          sqlServerAdministratorLoginPassword:
            required: true
    
    jobs:
      validate:
         runs-on: ubuntu-latest
         steps:
         - uses: actions/checkout@v3
         - uses: azure/login@v1
           name: Sign in to Azure
           with:
             client-id: ${{ secrets.AZURE_CLIENT_ID }}
             tenant-id: ${{ secrets.AZURE_TENANT_ID }}
             subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
         - if: inputs.environmentType != 'Production'
           uses: azure/arm-deploy@v1
           name: Run preflight validation
           with:
             deploymentName: ${{ github.run_number }}
             resourceGroupName: ${{ inputs.resourceGroupName }}
             template: ./deploy/main.bicep
             parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
               sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
               sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
             deploymentMode: Validate
         - if: inputs.environmentType == 'Production'
           uses: azure/arm-deploy@v1
           name: Run what-if
           with:
             failOnStdErr: false
             resourceGroupName: ${{ inputs.resourceGroupName }}
             template: ./deploy/main.bicep
             parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
               sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
               sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
             additionalArguments: --what-if
    
      deploy:
        needs: validate
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        outputs:
          appServiceAppName: ${{ steps.deploy.outputs.appServiceAppName }}
          appServiceAppHostName: ${{ steps.deploy.outputs.appServiceAppHostName }}
          storageAccountName: ${{ steps.deploy.outputs.storageAccountName }}
          storageAccountImagesBlobContainerName: ${{ steps.deploy.outputs.storageAccountImagesBlobContainerName }}
          sqlServerFullyQualifiedDomainName: ${{ steps.deploy.outputs.sqlServerFullyQualifiedDomainName }}
          sqlDatabaseName: ${{ steps.deploy.outputs.sqlDatabaseName }}
        steps:
        - uses: actions/checkout@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - uses: azure/arm-deploy@v1
          id: deploy
          name: Deploy Bicep file
          with:
            failOnStdErr: false
            deploymentName: ${{ github.run_number }}
            resourceGroupName: ${{ inputs.resourceGroupName }}
            template: ./deploy/main.bicep
            parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
               sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
               sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
    
      deploy-website:
        needs: deploy
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        steps:
        - uses: actions/download-artifact@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - uses: azure/webapps-deploy@v2
          name: Deploy website
          with:
            app-name: ${{ needs.deploy.outputs.appServiceAppName }}
            package: website/publish.zip
    
      deploy-database:
        needs: deploy
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        steps:
        - uses: actions/download-artifact@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - uses: azure/sql-action@v1.2
          name: Deploy DACPAC to database
          with:
            server-name: ${{ needs.deploy.outputs.sqlServerFullyQualifiedDomainName }}
            connection-string: ${{ format('Server={0};Initial Catalog={1};User Id={2};Password={3};', needs.deploy.outputs.sqlServerFullyQualifiedDomainName, needs.deploy.outputs.sqlDatabaseName, inputs.sqlServerAdministratorLogin, secrets.sqlServerAdministratorLoginPassword) }}
            dacpac-package: database/ToyCompany.Database.dacpac
    
      seed-database:
        needs:
        - deploy
        - deploy-database
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - if: inputs.environmentType != 'Production'
          uses: azure/sql-action@v1.2
          name: Add test data to database
          with:
            server-name: ${{ needs.deploy.outputs.sqlServerFullyQualifiedDomainName }}
            connection-string: ${{ format('Server={0};Initial Catalog={1};User Id={2};Password={3};', needs.deploy.outputs.sqlServerFullyQualifiedDomainName, needs.deploy.outputs.sqlDatabaseName, inputs.sqlServerAdministratorLogin, secrets.sqlServerAdministratorLoginPassword) }}
            sql-file: 'deploy/sample-data/Toys.sql'
    
      seed-storage-account:
        needs: deploy
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - if: inputs.environmentType != 'Production'
          uses: azure/CLI@v1
          name: Upload sample images
          with:
            inlineScript: |
                az storage blob upload-batch \
                  --account-name ${{ needs.deploy.outputs.storageAccountName }} \
                  --destination ${{ needs.deploy.outputs.storageAccountImagesBlobContainerName }} \
                  --source 'deploy/sample-data/toyimages'
    
      smoke-test:
        runs-on: ubuntu-latest
        needs:
        - deploy
        - deploy-website
        - deploy-database
        - seed-database
        - seed-storage-account
        steps:
        - uses: actions/checkout@v3
        - run: |
            $container = New-PesterContainer `
              -Path 'deploy/Website.Tests.ps1' `
              -Data @{ HostName = '${{needs.deploy.outputs.appServiceAppHostName}}' }
            Invoke-Pester `
              -Container $container `
              -CI
          name: Run smoke tests
          shell: pwsh
    

    Caso contrário, atualize-o para corresponder ao conteúdo do arquivo.

  3. Guarde as alterações ao ficheiro.

  4. Confirme e envie suas alterações para o repositório Git. No terminal de código do Visual Studio, execute os seguintes comandos:

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

Executar o fluxo de trabalho

  1. No navegador, acesse as execuções do fluxo de trabalho.

  2. Selecione a execução mais recente.

    Aguarde até que todos os trabalhos para o ambiente de teste terminem com êxito. Observe que o teste de fumaça agora também é bem-sucedido.

    Captura de tela das Ações do GitHub mostrando o trabalho de Teste de Fumaça da execução do fluxo de trabalho para o ambiente de teste. O status mostra que o trabalho foi bem-sucedido.

  3. Aguarde até que o fluxo de trabalho seja concluído com êxito, incluindo a implantação de produção.

Ver o site

  1. Selecione o trabalho deploy-test / deploy-website para abrir o log do fluxo de trabalho.

  2. Selecione a etapa Implantar site .

    Mantenha pressionada a tecla Ctrl ( no macOS) e selecione o URL do aplicativo do Serviço de Aplicativo para abri-lo em uma nova guia do navegador.

    Captura de tela de Ações do GitHub mostrando o log do fluxo de trabalho para o trabalho de implantação do site de teste. A URL do aplicativo do Serviço de Aplicativo está realçada.

  3. Selecione Brinquedos.

    Captura de tela da página inicial do site da empresa de brinquedos, com o link Brinquedos em destaque.

    Observe que os dados de exemplo são exibidos no ambiente de teste.

    Captura de ecrã da página de brinquedos do sítio Web de teste, com os exemplos de brinquedos apresentados.

  4. Repita o processo anterior para o aplicativo do trabalho deploy-production/deploy-website .

    Observe que nenhum dado de exemplo é exibido no ambiente de produção.

    Captura de tela da página de brinquedos do site de produção, sem brinquedos exibidos.

Clean up resources (Limpar recursos)

Agora que você concluiu o exercício, convém remover os recursos do Azure para que não seja cobrado por eles.

No terminal de código do Visual Studio, execute os seguintes comandos:

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

O grupo de recursos é excluído em segundo plano.

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