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

完了

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

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

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

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

まず、テスト スクリプトを追加して、HTTPS が使用されているときに Web サイトにアクセス可能であり、セキュリティで保護されていない HTTP プロトコルが使用されている場合はアクセスできないかどうかを確認します。

  1. Visual Studio Code で、 配置 フォルダーに新しいファイルを作成します。 ファイル名をWebsite.Tests.ps1にします。

    Visual Studio Code Explorer のスクリーンショット。配置フォルダーとテスト ファイルが表示されます。

  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 サイトへの接続を試します。 サーバーが HTTP 応答状態コード (接続成功を示す 200 ~ 299) で応答した場合、テストは成功します。
    • HTTP 経由で Web サイトへの接続を試します。 サーバーから 300 以上の HTTP 応答状態コードが返される場合、テストは合格です。

    この演習では、テスト ファイルの詳細とそのしくみを理解する必要はありません。 関心がある場合は、モジュールの概要に一覧表示されているリソースを確認して詳細を確認できます。

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

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

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

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

    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 形式の出力をパイプライン変数に変換するには、デプロイ手順の下の次のスクリプト ステップを追加します。

      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 ステージの次の定義を追加します。

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

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

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

    - 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 ステージに追加します。

    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: Dafault
    
    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. Azure DevOps で、パイプラインに移動します。

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

    パイプラインが LintValidatePreview の各ステージを完了するまで待ちます。 Azure Pipelines は最新の状態でページを自動的に更新しますが、ページを時々更新することをお勧めします。

  3. [ 確認 ] ボタンを選択し、[承認] を選択 します

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

  4. デプロイ ステージが正常に完了したことを確認します。 SmokeTest ステージはエラーで終了します。

    パイプラインの実行ステージを示す Azure DevOps インターフェイスのスクリーンショット。SmokeTest ステージでエラーが報告されます。

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

    パイプラインの実行を示す Azure DevOps インターフェイスのスクリーンショット。[テスト] タブが強調表示されています。

  6. テストの概要に、2 つのテストが実行されたことが示されている点に注目してください。 1 つは成功し、もう 1 つは失敗しています。 失敗したテストは、 Toy Web サイトとして表示されます。HTTP 経由でページを提供しません

    パイプラインの実行のテスト結果を示す Azure DevOps インターフェイスのスクリーンショット。失敗したテストが強調表示されます。

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

Bicep ファイルを更新する

Bicep 定義がセキュリティ チームの要件を満たしていないことを確認したら、修正します。

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

  2. Azure App Service アプリの定義を見つけて、httpsOnly セクションに properties プロパティを含むように更新します。

    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. 最新の実行を選択します。

    パイプラインが LintValidatePreview の各ステージを完了するまで待ちます。 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/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e/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 ステージを含め、パイプライン全体が正常に完了していることに注意してください。 この結果は、両方のテストが成功したことを示します。

    パイプラインの実行が成功したことを示す Azure DevOps インターフェイスのスクリーンショット。

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

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

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

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

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

Remove-AzResourceGroup -Name ToyWebsiteTest -Force