Desplegar código en un App Service con private endpoint desde fuera de su red con GitHub Actions

Hace unos días compartí contigo cómo desplegar código en un App Service con private endpoint desde fuera de su red usando la extensión Onedeploy Hoy quiero mostrarte cómo integrar esta implementación en un flujo de GitHub Actions.

El flujo

Para este ejemplo he utilizado el mismo código de ejemplo que usé en el artículo anterior, pero puedes obviamente usar cualquier otro. En su repositorio he creado el siguiente flujo:

name: Build and deploy in internalweb
on:
  push:
    branches:
      - main
  workflow_dispatch:
env:
  PACKAGE_NAME: todo-web
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/[email protected]
      - name: Set up .NET Core
        uses: actions/[email protected]
        with:
          dotnet-version: '6.0.x'
          include-prerelease: true
      - name: Build with dotnet
        run: dotnet build --configuration Release
      - name: dotnet publish
        run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp
      - name: Upload artifact for deployment job
        uses: actions/[email protected]
        with:
          name: .net-app
          path: ${{env.DOTNET_ROOT}}/myapp
  deploy:
    runs-on: ubuntu-latest
    needs: build   
    steps:
      - name: Download artifact from build job
        uses: actions/[email protected]
        with:
          name: .net-app
          path: myapp
      - name: Azure Login
        uses: azure/[email protected]
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}
      - name: Zip up the app
        run: | 
          cd myapp
          zip -r ../myapp.zip .
      - name: Upload the zip to Azure
        uses: azure/[email protected]
        with:
          azcliversion: 2.33.1
          inlineScript: |
            FULL_PACKAGE_NAME=${{ env.PACKAGE_NAME }}-${{ github.sha }}
            echo "Package name: $FULL_PACKAGE_NAME"
            az storage blob upload --account-name ${{ secrets.STORAGE_ACCOUNT_NAME }} --container-name packages --name $FULL_PACKAGE_NAME.zip --file myapp.zip
      - name: Get date after 30 minutes
        id: date
        run: |           
          echo "::set-output name=date::$(date -d "30 minutes" +%Y-%m-%dT%H:%MZ)"
      - name: Azure CLI script
        id: deployment
        uses: azure/[email protected]
        with:
          azcliversion: 2.33.1
          inlineScript: |
            FULL_PACKAGE_NAME=${{ env.PACKAGE_NAME }}-${{ github.sha }}
            STORAGE_ACCOUNT_KEY=$(az storage account keys list --account-name ${{ secrets.STORAGE_ACCOUNT_NAME }} --resource-group ${{ secrets.RESOURCE_GROUP }} --query "[0].value" --output tsv)            
            SAS=$(az storage account generate-sas --permissions rl --account-name ${{ secrets.STORAGE_ACCOUNT_NAME }} --account-key $STORAGE_ACCOUNT_KEY --services b --resource-types co --expiry ${{ steps.date.outputs.date }} -o tsv)
            ZIP_URL="https://${{ secrets.STORAGE_ACCOUNT_NAME }}.blob.core.windows.net/packages/$FULL_PACKAGE_NAME.zip?$SAS"
            SUBSCRIPTION_ID=$(az account show --query id --output tsv)
            SITE_URI="https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${{ secrets.RESOURCE_GROUP }}/providers/Microsoft.Web/sites/${{ secrets.WEBAPP_NAME }}/extensions/onedeploy?api-version=2020-12-01"
            az rest --method PUT \
            --uri $SITE_URI \
            --body '{ 
               "properties": { 
                "packageUri": "'"${ZIP_URL}"'",                
                "type": "zip", 
                "ignorestack": false,
                "clean": true,
                "restart": false
              }
            }'
            echo "::set-output name=deployment::$SITE_URI"
      - name: Check if the deployment success
        uses: azure/[email protected]
        with:
          azcliversion: 2.33.1
          inlineScript: |
            echo "Install jq"
            apk add jq
            while true; do
              STATUS=$(az rest --method GET --uri ${{ steps.deployment.outputs.deployment }} | jq '.value[0].properties.provisioningState')
              
              if [[ "$STATUS" == "\"Succeeded\"" ]]; then
                  echo "Deployment succeeded"
                  break
              elif [[ "$STATUS" == "\"Failed\"" ]]; then
                echo "Deployment failed"
                exit 1
              else
                echo "Deployment state: $STATUS..."
                sleep 5
              fi
            done

Como ves, existe un primer trabajo, build, donde genero el paquete que quiero desplegar y en el apartado deploy es donde uso el mismo mecanismo mostrado en el artículo anterior, pero adaptado a GitHub Actions:

  1. Descargo el paquete generado en el paso anterior.
  2. Inicio sesión en Azure, con las credenciales almacenadas previamente en el secreto AZURE_CREDENTIALS.
  3. Hago un zip con el contenido del paquete.
  4. Lo subo a la cuenta de almacenamiento desde la que la extensión lo descargará.
  5. Genero una fecha con 30 minutos de más.
  6. Genero un SAS token, lo adjunto a la URL del paquete y lo utilizo como parte del body con el que llamo a la API REST para ejecutar el despliegue.
  7. Compruebo cada 5 segundos si el despliegue ha terminado satisfactoriamente, si ha fallado o continua.

¡Saludos!