演習 - パイプラインにテスト ステージを追加する

完了

あなたは、勤務先の玩具会社のセキュリティ チームから、あなたの Web サイトが HTTPS を使用した場合にのみアクセスできることを確認するように依頼されました。 この演習では、セキュリティ チームの要求を確認するスモーク テストを実行するようにパイプラインを構成します。

このプロセスでは、次のことを行います。

  • テスト スクリプトをリポジトリに追加します。
  • パイプラインの定義を更新して、テスト ステージを追加します。
  • パイプラインを実行して、テストが失敗することを確認します。
  • Bicep ファイルを修正し、パイプラインが正常に実行されることを確認します。

テスト スクリプトを追加する

ここでは、HTTPS を使用したときには Web サイトにアクセスできること、および安全ではない HTTP プロトコルを使用したときにはアクセスできないことを確認するテスト スクリプトを追加します。

  1. Visual Studio Code の deploy フォルダー内に Website.Tests.ps1 という名前の新しいファイルを作成します。

    Screenshot of Visual Studio Code Explorer, with the deploy folder and the test file shown.

  2. 次のテスト コードをファイルに貼り付けます。

    param(
      [Parameter(Mandatory)]
      [ValidateNotNullOrEmpty()]
      [string] $HostName
    )
    
    Describe 'Toy Website' {
    
        It 'Serves pages over HTTPS' {
          $request = [System.Net.WebRequest]::Create("https://$HostName/")
          $request.AllowAutoRedirect = $false
          $request.GetResponse().StatusCode |
            Should -Be 200 -Because "the website requires HTTPS"
        }
    
        It 'Does not serves pages over HTTP' {
          $request = [System.Net.WebRequest]::Create("http://$HostName/")
          $request.AllowAutoRedirect = $false
          $request.GetResponse().StatusCode | 
            Should -BeGreaterOrEqual 300 -Because "HTTP is not secure"
        }
    
    }
    

    このコードは Pester のテスト ファイルです。 これには、$HostName というパラメーターが必要です。 ホスト名に対して 2 つのテストを実行します。

    • HTTPS 経由で Web サイトへの接続を試します。 サーバーから 200 から 299 の HTTP 応答状態コードが返される場合、接続の成功を示すので、テストは合格です。
    • HTTP 経由で Web サイトへの接続を試します。 サーバーから 300 以上の HTTP 応答状態コードが返される場合、テストは合格です。

    この演習では、テスト ファイルの詳細とそのしくみを理解することは重要ではありません。 関心をお持ちの場合は詳細を確認できるように、「まとめ」でリンクを提供します。

Bicep ファイルの出力をステージの出力変数として発行する

前のステップで作成したテスト スクリプトには、テストするホスト名が必要です。 Bicep ファイルには既に出力が含まれていますが、スモーク テストで使用する前に、ステージ出力変数として発行する必要があります。

  1. Visual Studio Code で、deploy フォルダー内の azure-pipelines.yml ファイルを開きます。

  2. デプロイ ステージで、デプロイ ステップを更新して、出力を変数に発行します。

    - task: AzureResourceManagerTemplateDeployment@3
      name: DeployBicepFile
      displayName: Deploy Bicep file
      inputs:
        connectedServiceName: $(ServiceConnectionName)
        deploymentName: $(Build.BuildNumber)
        location: $(deploymentDefaultLocation)
        resourceGroupName: $(ResourceGroupName)
        csmFile: deploy/main.bicep
        overrideParameters: >
          -environmentType $(EnvironmentType)
        deploymentOutputs: deploymentOutputs
    

    これで、デプロイ プロセスでは以前と同じタスクが引き続き使用されますが、デプロイからの出力はdeploymentOutputs という名前のパイプライン変数に格納されます。 出力変数は JSON として書式設定されます。

  3. JSON 形式の出力をパイプライン変数に変換するには、デプロイ手順の下の次のスクリプト ステップを追加します。

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

    デプロイが正常に完了すると、スクリプトは Bicep デプロイから各出力値にアクセスします。 このスクリプトでは、jq ツールを使用し、JSON 出力の関連部分にアクセスします。 次に、Bicep デプロイ出力と同じ名前のステージ出力変数に値が発行されます。

    注意

    Microsoft がホストする Azure Pipelines のエージェントにプレインストールされています。 スクリプト ステップでそれらを使用するために特別な操作を行う必要はありません。

  4. ファイルを保存します。

パイプラインにスモーク テスト ステージを追加する

これで、テストを実行するスモーク テスト ステージを追加できます。

  1. ファイルの末尾に、SmokeTest ステージの次のモジュール定義を追加します。

    - stage: SmokeTest
      jobs:
      - job: SmokeTest
        displayName: Smoke test
        variables:
          appServiceAppHostName: $[ stageDependencies.Deploy.DeployWebsite.outputs['DeployWebsite.SaveDeploymentOutputs.appServiceAppHostName'] ]
    

    このコードではステージとジョブが定義されます。 また、appServiceAppHostName という名前のジョブに変数が作成されます。 この変数は、前のセクションで作成した出力変数から値を取得します。

  2. ファイルの末尾で、SmokeTest ステージに次のステップ定義を追加します。

    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
    

    このステップでは PowerShell スクリプトを実行し、Pester テスト ツールを使用して先ほど記述したテスト スクリプトを実行します。

  3. ファイルの末尾で、SmokeTest ステージに次のステップ定義を追加します。

    - task: PublishTestResults@2
      name: PublishTestResults
      displayName: Publish test results
      condition: always()
      inputs:
        testResultsFormat: NUnit
        testResultsFiles: 'testResults.xml'
    

    このステップでは、Pester によって作成されるテスト結果ファイルを取得し、それをパイプライン テストの結果として発行します。 すぐに結果がどのように表示されるかが表示されます。

    ステップ定義に condition: always() が含まれていることに注目してください。 この状況では、前のステップが失敗した場合でも、常にテスト結果を発行する必要があることが Azure Pipelines に示されます。 テストに失敗するとテスト ステップが失敗し、通常は失敗したステップの後にパイプラインで実行が停止されるため、この条件は重要です。

  4. ファイルを保存します。

パイプライン定義を確認してコミットする

  1. azure-pipelines.yml ファイルが次のコードのようになっていることを確認します。

    trigger:
      batch: true
      branches:
        include:
        - main
    
    pool:
      vmImage: ubuntu-latest
    
    variables:
      - name: deploymentDefaultLocation
        value: westus3
    
    stages:
    
    - stage: Lint
      jobs:
      - job: LintCode
        displayName: Lint code
        steps:
          - script: |
              az bicep build --file deploy/main.bicep
            name: LintBicepCode
            displayName: Run Bicep linter
    
    - stage: Validate
      jobs:
      - job: ValidateBicepCode
        displayName: Validate Bicep code
        steps:
          - task: AzureResourceManagerTemplateDeployment@3
            name: RunPreflightValidation
            displayName: Run preflight validation
            inputs:
              connectedServiceName: $(ServiceConnectionName)
              location: $(deploymentDefaultLocation)
              deploymentMode: Validation
              resourceGroupName: $(ResourceGroupName)
              csmFile: deploy/main.bicep
              overrideParameters: >
                -environmentType $(EnvironmentType)
    
    - stage: Preview
      jobs:
      - job: PreviewAzureChanges
        displayName: Preview Azure changes
        steps:
          - task: AzureCLI@2
            name: RunWhatIf
            displayName: Run what-if
            inputs:
              azureSubscription: $(ServiceConnectionName)
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                az deployment group what-if \
                  --resource-group $(ResourceGroupName) \
                  --template-file deploy/main.bicep \
                  --parameters environmentType=$(EnvironmentType)
    
    - stage: Deploy
      jobs:
      - deployment: DeployWebsite
        displayName: Deploy website
        environment: Website
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
                - task: AzureResourceManagerTemplateDeployment@3
                  name: DeployBicepFile
                  displayName: Deploy Bicep file
                  inputs:
                    connectedServiceName: $(ServiceConnectionName)
                    deploymentName: $(Build.BuildNumber)
                    location: $(deploymentDefaultLocation)
                    resourceGroupName: $(ResourceGroupName)
                    csmFile: deploy/main.bicep
                    overrideParameters: >
                      -environmentType $(EnvironmentType)
                    deploymentOutputs: deploymentOutputs
    
                - bash: |
                    echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
                  name: SaveDeploymentOutputs
                  displayName: Save deployment outputs into variables
                  env:
                    DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
    
    - stage: SmokeTest
      jobs:
      - job: SmokeTest
        displayName: Smoke test
        variables:
          appServiceAppHostName: $[ stageDependencies.Deploy.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'
    

    そうでない場合は、この例に一致するように更新してから保存してください。

  2. Visual Studio Code ターミナルで次のコマンドを実行し、変更をコミットして Git リポジトリにプッシュします。

    git add .
    git commit -m "Add test stage"
    git push
    

パイプラインを実行してテスト結果を確認する

  1. ブラウザーでパイプラインに移動します。

  2. パイプラインの最新の実行を選択します。

    パイプラインでリント検証プレビューの各ステージが完了するまで待ちます。 Azure Pipelines によってページが最新の状態で自動的に更新されますが、ページを随時更新することをお勧めします。

  3. [確認] ボタンを選択してから、[承認] を選びます。

    パイプラインの実行が終了するまで待ちます。

  4. デプロイ ステージが正常に終了することに注目してください。 SmokeTest ステージはエラーで終了しています。

    Screenshot of the Azure DevOps interface that shows the pipeline run stages. The SmokeTest stage reports failure.

  5. [テスト] タブを選択します。

    Screenshot of the Azure DevOps interface that shows the pipeline run, with the Tests tab highlighted.

  6. テストの概要に、2 つのテストが実行されたことが示されている点に注目してください。 1 つは成功し、もう 1 つは失敗しています。 失敗したテストは、Toy Website.Does not serve pages over HTTP として一覧表示されています。

    Screenshot of the Azure DevOps interface that shows the pipeline run's test results, with the failed test highlighted.

    このテキストは、セキュリティ チームの要件を満たすために Web サイトが正しく構成されていないことを示しています。

Bicep ファイルを更新する

これで、Bicep 定義がセキュリティ チームの要件を満たしていないことがわかったので、それを修正します。

  1. Visual Studio Code で、deploy フォルダーの main.bicep ファイルを開きます。

  2. Azure App Service アプリの定義を見つけ、それを更新して、その properties 領域に httpsOnly プロパティが含まれるようにします。

    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
            }
          ]
        }
      }
    }
    
  3. ファイルを保存します。

  4. Visual Studio Code ターミナルで次のコマンドを実行し、変更をコミットして Git リポジトリにプッシュします。

    git add .
    git commit -m "Configure HTTPS on website"
    git push
    

もう一度パイプラインを実行する

  1. ブラウザーでパイプラインに移動します。

  2. 最新の実行を選択します。

    パイプラインでリント検証プレビューの各ステージが完了するまで待ちます。 Azure Pipelines によってページが最新の状態で自動的に更新されますが、ページを随時更新することをお勧めします。

  3. [プレビュー] ステージを選択し、もう一度 what-if の結果を確認します。

    what-if コマンドによって、httpsOnly プロパティの値の変更が検出されていることに注目してください。

    Resource and property changes are indicated with these symbols:
      + Create
      ~ Modify
      = Nochange
    
    The deployment will update the following scope:
    
    Scope: /subscriptions/f0750bbe-ea75-4ae5-b24d-a92ca601da2c/resourceGroups/ToyWebsiteTest
    
      ~ Microsoft.Web/sites/toy-website-nbfnedv766snk [2021-01-15]
        + properties.siteConfig.localMySqlEnabled:   false
        + properties.siteConfig.netFrameworkVersion: "v4.6"
        ~ properties.httpsOnly:                      false => true
    
      = Microsoft.Insights/components/toywebsite [2020-02-02]
      = Microsoft.Storage/storageAccounts/mystoragenbfnedv766snk [2021-04-01]
      = Microsoft.Web/serverfarms/toy-website [2021-01-15]
    
    Resource changes: 1 to modify, 3 no change.
    
  4. パイプラインの実行に戻ります。

  5. [確認] ボタンを選択してから、[承認] を選びます。

    パイプラインの実行が終了するまで待ちます。

  6. SmokeTest ステージを含め、パイプライン全体が正常に終了していることに注目してください。 これは、両方のテストが成功したことを示します。

    Screenshot of the Azure DevOps interface that shows a successful pipeline run.

リソースのクリーンアップ

これで演習が完了したので、課金されないようにリソースを削除しましょう。

Visual Studio Code ターミナルで、次のコマンドを実行します。

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

バックグラウンドでリソース グループが削除されます。

Remove-AzResourceGroup -Name ToyWebsiteTest -Force