diff --git a/.github/workflows/run-validation-oidc.yml b/.github/workflows/run-validation-oidc.yml new file mode 100644 index 00000000..e0d2344a --- /dev/null +++ b/.github/workflows/run-validation-oidc.yml @@ -0,0 +1,679 @@ +name: Run validation on action using OIDC + +on: + push: + branches: + - main + paths-ignore: + - '**.md' + pull_request: + branches: + - main + paths-ignore: + - '**.md' + +env: + TEST_FULL_ACR_NAME: ${{ vars.TEST_ACR_NAME }}.azurecr.io + TEST_IMAGE_REPOSITORY: github-actions/container-app + +jobs: + create-using-builder: + + name: 'Create app using builder' + runs-on: ubuntu-latest + permissions: + id-token: write #This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_IMAGE_TAG: 'bs-${{ github.run_id }}' + TEST_CONTAINER_APP_NAME: 'gh-ca-bs-${{ github.run_id }}' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + appSourcePath: '${{ github.workspace }}/oryx/tests/SampleApps/DotNetCore/NetCore6PreviewWebApp' + acrName: ${{ vars.TEST_ACR_NAME }} + acrUsername: ${{ secrets.TEST_REGISTRY_USERNAME }} + acrPassword: ${{ secrets.TEST_REGISTRY_PASSWORD }} + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + containerAppEnvironment: ${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + imageToBuild: ${{ env.TEST_FULL_ACR_NAME }}/${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + - name: Delete pushed image + if: ${{ always() }} + shell: bash + run: az acr repository delete -n ${{ vars.TEST_ACR_NAME }} -t ${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} -y + + + create-using-builder-and-internal-registry: + + name: 'Create app using builder and push to internal registry' + runs-on: ubuntu-latest + permissions: + id-token: write #This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_IMAGE_TAG: 'bs-${{ github.run_id }}' + TEST_CONTAINER_APP_NAME: 'gh-ca-bs-${{ github.run_id }}' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + appSourcePath: '${{ github.workspace }}/oryx/tests/SampleApps/DotNetCore/NetCore6PreviewWebApp' + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NO_ACR_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + + create-using-found-dockerfile: + + name: 'Create app using found Dockerfile' + runs-on: ubuntu-latest + permissions: + id-token: write #This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_IMAGE_TAG: 'fd-${{ github.run_id }}' + TEST_CONTAINER_APP_NAME: 'gh-ca-fd-${{ github.run_id }}' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + appSourcePath: '${{ github.workspace }}/oryx/tests/SampleApps/DotNetCore/Blazor_Function_Sample/blazor-sample-app' + acrName: ${{ vars.TEST_ACR_NAME }} + acrUsername: ${{ secrets.TEST_REGISTRY_USERNAME }} + acrPassword: ${{ secrets.TEST_REGISTRY_PASSWORD }} + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + containerAppEnvironment: ${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + imageToBuild: ${{ env.TEST_FULL_ACR_NAME }}/${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + - name: Delete pushed image + if: ${{ always() }} + shell: bash + run: az acr repository delete -n ${{ vars.TEST_ACR_NAME }} -t ${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} -y + + create-using-provided-dockerfile: + + name: 'Create app using provided Dockerfile' + runs-on: ubuntu-latest + permissions: + id-token: write #This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_IMAGE_TAG: 'pd-${{ github.run_id }}' + TEST_CONTAINER_APP_NAME: 'gh-ca-pd-${{ github.run_id }}' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + appSourcePath: '${{ github.workspace }}/oryx/tests/SampleApps/DotNetCore/Blazor_Function_Sample/blazor-sample-app' + dockerfilePath: 'Dockerfile' + acrName: ${{ vars.TEST_ACR_NAME }} + acrUsername: ${{ secrets.TEST_REGISTRY_USERNAME }} + acrPassword: ${{ secrets.TEST_REGISTRY_PASSWORD }} + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + containerAppEnvironment: ${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + imageToBuild: ${{ env.TEST_FULL_ACR_NAME }}/${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + - name: Delete pushed image + if: ${{ always() }} + shell: bash + run: az acr repository delete -n ${{ vars.TEST_ACR_NAME }} -t ${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} -y + + + create-using-image-linux: + + name: 'Create app using image on Linux runner' + runs-on: ubuntu-latest + permissions: + id-token: write #This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_CONTAINER_APP_NAME: 'gh-ca-is-lin-${{ github.run_id }}' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + imageToDeploy: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + containerAppEnvironment: ${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + create-using-image-windows: + + name: 'Create app using image on Windows runner' + runs-on: windows-latest + permissions: + id-token: write #This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_CONTAINER_APP_NAME: 'gh-ca-is-win-${{ github.run_id }}' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + imageToDeploy: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + containerAppEnvironment: ${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + create-using-image-new-env: + + name: 'Create app using image with new environment' + runs-on: ubuntu-latest + permissions: + id-token: write # This is required for requesting the OIDC JWT Token + timeout-minutes: 15 + + env: + TEST_CONTAINER_APP_NAME: 'gh-ca-is-ne-${{ github.run_id }}' + TEST_NEW_CONTAINER_APP_ENV: 'gh-ca-is-ne-${{ github.run_id }}-env' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + imageToDeploy: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + containerAppEnvironment: ${{ env.TEST_NEW_CONTAINER_APP_ENV }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + - name: Get customer ID for workspace to delete + if: ${{ always() }} + shell: bash + run: | + CUSTOMER_ID=$(az containerapp env show -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -n ${{ env.TEST_NEW_CONTAINER_APP_ENV }} --query 'properties.appLogsConfiguration.logAnalyticsConfiguration.customerId') + echo "CUSTOMER_ID=${CUSTOMER_ID}" >> $GITHUB_ENV + + - name: Get name of workspace to delete + if: ${{ always() }} + shell: bash + run: | + WORKSPACE_NAME=$(az monitor log-analytics workspace list -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} --query '[?customerId == `${{ env.CUSTOMER_ID }}`].name | [0]') + echo "WORKSPACE_NAME=${WORKSPACE_NAME}" >> $GITHUB_ENV + + - name: Delete created Azure Container App environment + if: ${{ always() }} + shell: bash + run: az containerapp env delete -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -n ${{ env.TEST_NEW_CONTAINER_APP_ENV }} -y + + - name: Delete created workspace + if: ${{ always() }} + shell: bash + run: az monitor log-analytics workspace delete -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -n ${{ env.WORKSPACE_NAME }} -y + + create-using-builder-yaml: + + name: 'Create app using builder with YAML configuration' + runs-on: ubuntu-latest + permissions: + id-token: write # This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_IMAGE_TAG: 'bs-yaml-${{ github.run_id }}' + TEST_CONTAINER_APP_NAME: 'gh-ca-bs-yaml-${{ github.run_id }}' + TEST_YAML_FILE_PATH: '${{ github.workspace }}/yaml-samples/create-with-builder-simple.yaml' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Update values in YAML configuration file + shell: pwsh + run: | + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$SUBSCRIPTION_ID$', '${{ vars.TEST_SUBSCRIPTION_ID }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$RESOURCE_GROUP$', '${{ vars.TEST_RESOURCE_GROUP_NAME }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$CONTAINER_APP_ENV$', '${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$FULL_ACR_NAME$', '${{ env.TEST_FULL_ACR_NAME }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$ACR_USERNAME$', '${{ secrets.TEST_REGISTRY_USERNAME }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$ACR_PASSWORD$', '${{ secrets.TEST_REGISTRY_PASSWORD }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$IMAGE_REPOSITORY$', '${{ env.TEST_IMAGE_REPOSITORY }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$IMAGE_TAG$', '${{ env.TEST_IMAGE_TAG }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + yamlConfigPath: ${{ env.TEST_YAML_FILE_PATH }} + appSourcePath: '${{ github.workspace }}/oryx/tests/SampleApps/DotNetCore/NetCore6PreviewWebApp' + acrName: ${{ vars.TEST_ACR_NAME }} + acrUsername: ${{ secrets.TEST_REGISTRY_USERNAME }} + acrPassword: ${{ secrets.TEST_REGISTRY_PASSWORD }} + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + imageToBuild: ${{ env.TEST_FULL_ACR_NAME }}/${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + - name: Delete pushed image + if: ${{ always() }} + shell: bash + run: az acr repository delete -n ${{ vars.TEST_ACR_NAME }} -t ${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} -y + + create-using-image-yaml-linux: + + name: 'Create app using image with YAML configuration on Linux runner' + runs-on: ubuntu-latest + permissions: + id-token: write #This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_CONTAINER_APP_NAME: 'gh-ca-bs-yaml-lin-${{ github.run_id }}' + TEST_YAML_FILE_PATH: '${{ github.workspace }}/yaml-samples/create-with-image-simple.yaml' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Update values in YAML configuration file + shell: pwsh + run: | + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$SUBSCRIPTION_ID$', '${{ vars.TEST_SUBSCRIPTION_ID }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$RESOURCE_GROUP$', '${{ vars.TEST_RESOURCE_GROUP_NAME }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$CONTAINER_APP_ENV$', '${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + yamlConfigPath: ${{ env.TEST_YAML_FILE_PATH }} + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + create-using-image-yaml-windows: + + name: 'Create app using image with YAML configuration on Windows runner' + runs-on: windows-latest + permissions: + id-token: write #This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_CONTAINER_APP_NAME: 'gh-ca-bs-yaml-win-${{ github.run_id }}' + TEST_YAML_FILE_PATH: 'yaml-samples/create-with-image-simple.yaml' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Update values in YAML configuration file + shell: pwsh + run: | + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$SUBSCRIPTION_ID$', '${{ vars.TEST_SUBSCRIPTION_ID }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$RESOURCE_GROUP$', '${{ vars.TEST_RESOURCE_GROUP_NAME }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$CONTAINER_APP_ENV$', '${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + yamlConfigPath: ${{ env.TEST_YAML_FILE_PATH }} + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + update-using-builder: + + name: 'Update existing app using builder' + runs-on: ubuntu-latest + permissions: + id-token: write #This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_IMAGE_TAG: 'bs-up-${{ github.run_id }}' + TEST_CONTAINER_APP_NAME: 'update-using-builder-app' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + appSourcePath: '${{ github.workspace }}/oryx/tests/SampleApps/DotNetCore/NetCore6PreviewWebApp' + acrName: ${{ vars.TEST_ACR_NAME }} + acrUsername: ${{ secrets.TEST_REGISTRY_USERNAME }} + acrPassword: ${{ secrets.TEST_REGISTRY_PASSWORD }} + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + containerAppEnvironment: ${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + imageToBuild: ${{ env.TEST_FULL_ACR_NAME }}/${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete pushed image + if: ${{ always() }} + shell: bash + run: az acr repository delete -n ${{ vars.TEST_ACR_NAME }} -t ${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} -y + + - name: Update Container App with existing image + if: ${{ always() }} + shell: bash + run: az containerapp update -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -i mcr.microsoft.com/azuredocs/containerapps-helloworld:latest + + update-using-builder-and-internal-registry: + + name: 'Update existing app using builder and push to internal registry' + runs-on: ubuntu-latest + permissions: + id-token: write #This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_IMAGE_TAG: 'bs-up-${{ github.run_id }}' + TEST_CONTAINER_APP_NAME: 'update-using-builder-app' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + appSourcePath: '${{ github.workspace }}/oryx/tests/SampleApps/DotNetCore/NetCore6PreviewWebApp' + containerAppName: ${{ env.TEST_EXISTING_CONTAINER_APP }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NO_ACR_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Update Container App with existing image + if: ${{ always() }} + shell: bash + run: az containerapp update -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -i mcr.microsoft.com/azuredocs/containerapps-helloworld:latest + + update-using-image: + + name: 'Update app using image' + runs-on: ubuntu-latest + permissions: + id-token: write # This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_CONTAINER_APP_NAME: 'update-using-image-app' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + imageToDeploy: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + containerAppEnvironment: ${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + update-using-image-yaml: + + name: 'Update app using image with YAML configuration' + runs-on: ubuntu-latest + permissions: + id-token: write # This is required for requesting the OIDC JWT Token + timeout-minutes: 10 + + env: + TEST_CONTAINER_APP_NAME: 'update-using-image-yaml-app' + TEST_YAML_FILE_PATH: '${{ github.workspace }}/yaml-samples/update-with-image-simple.yaml' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.TEST_CLIENT_ID }} + tenant-id: ${{ secrets.TEST_TENANT_ID }} + subscription-id: ${{ secrets.TEST_SUBSCRIPTION_ID }} + + - name: Update values in YAML configuration file + shell: pwsh + run: | + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$SUBSCRIPTION_ID$', '${{ vars.TEST_SUBSCRIPTION_ID }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$RESOURCE_GROUP$', '${{ vars.TEST_RESOURCE_GROUP_NAME }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + (Get-Content ${{ env.TEST_YAML_FILE_PATH }}).Replace('$CONTAINER_APP_ENV$', '${{ vars.TEST_EXISTING_CONTAINER_APP_ENV }}') | Set-Content ${{ env.TEST_YAML_FILE_PATH }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + yamlConfigPath: ${{ env.TEST_YAML_FILE_PATH }} + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} \ No newline at end of file diff --git a/.github/workflows/run-validation.yml b/.github/workflows/run-validation.yml index efb5b534..f708a330 100644 --- a/.github/workflows/run-validation.yml +++ b/.github/workflows/run-validation.yml @@ -18,6 +18,7 @@ env: jobs: create-using-builder: + name: 'Create app using builder' runs-on: ubuntu-latest timeout-minutes: 10 @@ -64,7 +65,48 @@ jobs: shell: bash run: az acr repository delete -n ${{ vars.TEST_ACR_NAME }} -t ${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} -y + + create-using-builder-and-internal-registry: + + name: 'Create app using builder and push to internal registry' + runs-on: ubuntu-latest + timeout-minutes: 10 + + env: + TEST_IMAGE_TAG: 'bs-${{ github.run_id }}' + TEST_CONTAINER_APP_NAME: 'gh-ca-bs-${{ github.run_id }}' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Log in to Azure + uses: azure/login@v1 + with: + creds: ${{ secrets.TEST_AZURE_CREDENTIALS }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + appSourcePath: '${{ github.workspace }}/oryx/tests/SampleApps/DotNetCore/NetCore6PreviewWebApp' + containerAppName: ${{ env.TEST_CONTAINER_APP_NAME }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NO_ACR_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Delete created Azure Container App + if: ${{ always() }} + shell: bash + run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y + + create-using-found-dockerfile: + name: 'Create app using found Dockerfile' runs-on: ubuntu-latest timeout-minutes: 10 @@ -112,6 +154,7 @@ jobs: run: az acr repository delete -n ${{ vars.TEST_ACR_NAME }} -t ${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} -y create-using-provided-dockerfile: + name: 'Create app using provided Dockerfile' runs-on: ubuntu-latest timeout-minutes: 10 @@ -159,6 +202,7 @@ jobs: shell: bash run: az acr repository delete -n ${{ vars.TEST_ACR_NAME }} -t ${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} -y + create-using-image-linux: name: 'Create app using image on Linux runner' @@ -280,7 +324,9 @@ jobs: shell: bash run: az monitor log-analytics workspace delete -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -n ${{ env.WORKSPACE_NAME }} -y + create-using-builder-yaml: + name: 'Create app using builder with YAML configuration' runs-on: ubuntu-latest timeout-minutes: 10 @@ -341,6 +387,7 @@ jobs: run: az acr repository delete -n ${{ vars.TEST_ACR_NAME }} -t ${{ env.TEST_IMAGE_REPOSITORY }}:${{ env.TEST_IMAGE_TAG }} -y create-using-image-yaml-linux: + name: 'Create app using image with YAML configuration on Linux runner' runs-on: ubuntu-latest timeout-minutes: 10 @@ -385,6 +432,7 @@ jobs: run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y create-using-image-yaml-windows: + name: 'Create app using image with YAML configuration on Windows runner' runs-on: windows-latest timeout-minutes: 10 @@ -429,6 +477,7 @@ jobs: run: az containerapp delete -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -y update-using-builder: + name: 'Update existing app using builder' runs-on: ubuntu-latest timeout-minutes: 10 @@ -475,6 +524,44 @@ jobs: shell: bash run: az containerapp update -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -i mcr.microsoft.com/azuredocs/containerapps-helloworld:latest + update-using-builder-and-internal-registry: + + name: 'Update existing app using builder and push to internal registry' + runs-on: ubuntu-latest + timeout-minutes: 10 + + env: + TEST_IMAGE_TAG: 'bs-up-${{ github.run_id }}' + TEST_CONTAINER_APP_NAME: 'update-using-builder-app' + + steps: + - name: Checkout action repository + uses: actions/checkout@v3 + + - name: Clone Oryx repository + uses: actions/checkout@v3 + with: + repository: microsoft/Oryx + path: oryx + + - name: Log in to Azure + uses: azure/login@v1 + with: + creds: ${{ secrets.TEST_AZURE_CREDENTIALS }} + + - name: Execute Azure Container Apps Build and Deploy Action + uses: ./ + with: + appSourcePath: '${{ github.workspace }}/oryx/tests/SampleApps/DotNetCore/NetCore6PreviewWebApp' + containerAppName: ${{ env.TEST_EXISTING_CONTAINER_APP }} + resourceGroup: ${{ vars.TEST_RESOURCE_GROUP_NO_ACR_NAME }} + disableTelemetry: ${{ vars.TEST_DISABLE_TELEMETRY }} + + - name: Update Container App with existing image + if: ${{ always() }} + shell: bash + run: az containerapp update -n ${{ env.TEST_CONTAINER_APP_NAME }} -g ${{ vars.TEST_RESOURCE_GROUP_NAME }} -i mcr.microsoft.com/azuredocs/containerapps-helloworld:latest + update-using-image: name: 'Update app using image' diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e82ec5cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# .gitignore +node_modules/ +*.js +!dist/index.js \ No newline at end of file diff --git a/README.md b/README.md index 8f721a95..162a2b5a 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ For more information on the structure of the YAML configuration file, please vis | `resourceGroup` | No | The existing resource group that the Azure Container App will be created in. If not provided, this value will be `-rg` and its existence will first be checked before attempting to create it. | | `containerAppEnvironment` | No | The name of the Container App environment to use with the application. If not provided, an existing environment in the resource group of the Container App will be used, otherwise, an environment will be created in the formation `-env`. | | `runtimeStack` | No | The platform version stack used in the final runnable application image that is deployed to the Container App. The value should be provided in the formation `:`. If not provided, this value is determined by Oryx based on the contents of the provided application. Please refer to [this document](https://github.com/microsoft/Oryx/blob/main/doc/supportedRuntimeVersions.md) for more information on supported runtime stacks for Oryx. | -| `builderStack` | No | The stack (OS) that should be used to build the provided application source and produce the runnable application image. You can provide a specific image tag for the stack, such as "debian-bookworm-20231004.1", or you can provide a supported stack name, such as "debian-bookworm" or "debian-bullseye", and the latest supported image tag for that stack will be used. If no stack is provided, this action will attempt to build the provided application source with each supported stack until there's a successful build. | +| `builderStack` | No | The stack (OS) that should be used to build the provided application source and produce the runnable application image. You can provide a specific image tag for the stack, such as "debian-bookworm-20231107.2", or you can provide a supported stack name, such as "debian-bookworm" or "debian-bullseye", and the latest supported image tag for that stack will be used. If no stack is provided, this action will attempt to build the provided application source with each supported stack until there's a successful build. | | `targetPort` | No | The designated port for the application to run on. If no value is provided and the builder is used to build the runnable application image, the target port will be set to 80 for Python applications and 8080 for all other platform applications. If no value is provided when creating a Container App, the target port will default to 80. Note: when using this action to update a Container App, the target port may be updated if not provided based on changes to the ingress property. | | `location` | No | The location that the Container App (and other created resources) will be deployed to. To view locations suitable for creating the Container App in, please run the following: `az provider show -n Microsoft.App --query "resourceTypes[?resourceType=='containerApps'].locations"` | | `environmentVariables` | No | A list of environment variable(s) for the container. Space-separated values in 'key=value' format. Empty string to clear existing values. Prefix value with 'secretref:' to reference a secret. | diff --git a/action.yml b/action.yml index cdff7e70..743c5c03 100644 --- a/action.yml +++ b/action.yml @@ -83,7 +83,7 @@ inputs: builderStack: description: | 'The stack (OS) that should be used to build the provided application source and produce the runnable application - image. You can provide a specific image tag for the stack, such as "debian-bullseye-20231004.1", or you can + image. You can provide a specific image tag for the stack, such as "debian-bullseye-20231107.2", or you can provide a supported stack name, such as "debian-bookworm" or "debian-bullseye", and the latest supported image tag for that stack will be used. If no stack is provided, this action will attempt to build the provided application source with each supported stack until there's a successful build.' @@ -134,595 +134,5 @@ inputs: default: false runs: - using: "composite" - steps: - - name: Start timer to track action execution length, in milliseconds - if: ${{ always() && inputs.disableTelemetry == 'false' }} - shell: bash - run: | - CA_GH_ACTION_START_MILLISECONDS=$(date +%s%N | cut -b1-13) - echo "CA_GH_ACTION_START_MILLISECONDS=${CA_GH_ACTION_START_MILLISECONDS}" >> $GITHUB_ENV - CA_GH_ACTION_RESULT_ARG="--property 'result=failed'" - echo "CA_GH_ACTION_RESULT_ARG=${CA_GH_ACTION_RESULT_ARG}" >> $GITHUB_ENV - - - name: Check that an ACR name and a registry URL are not provided together - if: ${{ inputs.acrName != '' && inputs.registryUrl != '' }} - shell: bash - run: | - echo "Both 'acrName' and 'registryUrl' arguments cannot be provided together." - exit 1 - - - name: Check for ACR name or any other registry url provided with application source path - if: ${{ inputs.appSourcePath != '' && inputs.acrName == '' && inputs.registryUrl == '' }} - shell: bash - run: | - echo "The 'acrName' argument or the 'registryUrl' argument must also be provided if the 'appSourcePath' argument is provided." - exit 1 - - - name: Check for application source path, a previously built image or a YAML configuration file - if: ${{ inputs.appSourcePath == '' && inputs.imageToDeploy == '' && inputs.yamlConfigPath == '' }} - shell: bash - run: | - echo "One of the following arguments must be provided: appSourcePath, imageToDeploy, yamlConfigPath" - exit 1 - - - name: Check if the YAML configuration file is provided - if: ${{ inputs.yamlConfigPath != '' }} - shell: bash - run: | - CA_GH_ACTION_YAML_PROVIDED=true - echo "CA_GH_ACTION_YAML_PROVIDED=${CA_GH_ACTION_YAML_PROVIDED}" >> $GITHUB_ENV - - - name: Check if only the YAML configuration file is being used - if: ${{ inputs.appSourcePath == '' && inputs.imageToDeploy == '' && inputs.yamlConfigPath != '' }} - shell: bash - run: | - CA_GH_ACTION_ONLY_YAML=true - echo "CA_GH_ACTION_ONLY_YAML=${CA_GH_ACTION_ONLY_YAML}" >> $GITHUB_ENV - - - name: Set Azure CLI to dynamically install missing extensions - shell: bash - run: az config set extension.use_dynamic_install=yes_without_prompt - - - name: Log in to Azure - if: ${{ inputs.azureCredentials != '' }} - uses: azure/login@v1 - with: - creds: ${{ inputs.azureCredentials }} - - - name: Log in to Azure Container Registry - uses: docker/login-action@v2.2.0 - if: ${{ inputs.acrName != '' && inputs.acrUsername != '' && inputs.acrPassword != '' }} - with: - registry: ${{ inputs.acrName }}.azurecr.io - username: ${{ inputs.acrUsername }} - password: ${{ inputs.acrPassword }} - - - name: Log in to Container Registry - uses: docker/login-action@v2.2.0 - if: ${{ inputs.registryUrl != '' && inputs.registryUsername != '' && inputs.registryPassword != '' }} - with: - registry: ${{ inputs.registryUrl }} - username: ${{ inputs.registryUsername }} - password: ${{ inputs.registryPassword }} - - - name: Export Container Registry information to environment variable - if: ${{ inputs.registryUrl != '' && inputs.registryUsername != '' && inputs.registryPassword != '' && env.CA_GH_ACTION_ONLY_YAML != 'true' }} - shell: bash - run: | - CA_GH_ACTION_REGISTRY_LOGIN_ARG="--registry-server ${{ inputs.registryUrl }} --registry-username ${{ inputs.registryUsername }} --registry-password ${{ inputs.registryPassword }}" - CA_GH_ACTION_REGISTRY_URL=${{ inputs.registryUrl }} - CA_GH_ACTION_REGISTRY_USERNAME=${{ inputs.registryUsername }} - CA_GH_ACTION_REGISTRY_PASSWORD=${{ inputs.registryPassword }} - echo "CA_GH_ACTION_REGISTRY_LOGIN_ARG=${CA_GH_ACTION_REGISTRY_LOGIN_ARG}" >> $GITHUB_ENV - echo "CA_GH_ACTION_REGISTRY_URL=${CA_GH_ACTION_REGISTRY_URL}" >> $GITHUB_ENV - echo "CA_GH_ACTION_REGISTRY_USERNAME=${CA_GH_ACTION_REGISTRY_USERNAME}" >> $GITHUB_ENV - echo "CA_GH_ACTION_REGISTRY_PASSWORD=${CA_GH_ACTION_REGISTRY_PASSWORD}" >> $GITHUB_ENV - - - name: Export Azure Container Registry information to environment variable for Azure CLI command - if: ${{ inputs.acrName != '' && inputs.acrUsername != '' && inputs.acrPassword != '' && env.CA_GH_ACTION_ONLY_YAML != 'true' }} - shell: bash - run: | - CA_GH_ACTION_REGISTRY_LOGIN_ARG="--registry-server ${{ inputs.acrName }}.azurecr.io --registry-username ${{ inputs.acrUsername }} --registry-password ${{ inputs.acrPassword }}" - CA_GH_ACTION_REGISTRY_URL=${{ inputs.acrName }}.azurecr.io - CA_GH_ACTION_REGISTRY_USERNAME=${{ inputs.acrUsername }} - CA_GH_ACTION_REGISTRY_PASSWORD=${{ inputs.acrPassword }} - echo "CA_GH_ACTION_REGISTRY_LOGIN_ARG=${CA_GH_ACTION_REGISTRY_LOGIN_ARG}" >> $GITHUB_ENV - echo "CA_GH_ACTION_REGISTRY_URL=${CA_GH_ACTION_REGISTRY_URL}" >> $GITHUB_ENV - echo "CA_GH_ACTION_REGISTRY_USERNAME=${CA_GH_ACTION_REGISTRY_USERNAME}" >> $GITHUB_ENV - echo "CA_GH_ACTION_REGISTRY_PASSWORD=${CA_GH_ACTION_REGISTRY_PASSWORD}" >> $GITHUB_ENV - - - name: Get access token to log in to Azure Container Registry - if: ${{ inputs.acrName != '' && (inputs.acrUsername == '' || inputs.acrPassword == '') }} - shell: bash - run: | - CA_GH_ACTION_ACR_ACCESS_TOKEN=$(az acr login --name ${{ inputs.acrName }} --output json --expose-token | jq -r '.accessToken') - echo "CA_GH_ACTION_ACR_ACCESS_TOKEN=${CA_GH_ACTION_ACR_ACCESS_TOKEN}" >> $GITHUB_ENV - docker login ${{ inputs.acrName }}.azurecr.io -u 00000000-0000-0000-0000-000000000000 -p $CA_GH_ACTION_ACR_ACCESS_TOKEN - CA_GH_ACTION_REGISTRY_URL=${{ inputs.acrName }}.azurecr.io - echo "CA_GH_ACTION_REGISTRY_URL=${CA_GH_ACTION_REGISTRY_URL}" >> $GITHUB_ENV - - - name: Export Dockerfile path to environment variable - if: ${{ inputs.appSourcePath != '' && inputs.dockerfilePath != '' }} - shell: bash - run: | - CA_GH_ACTION_DOCKERFILE_PATH="${{ inputs.appSourcePath }}/${{ inputs.dockerfilePath }}" - echo "CA_GH_ACTION_DOCKERFILE_PATH=${CA_GH_ACTION_DOCKERFILE_PATH}" >> $GITHUB_ENV - - - name: Check for existing Dockerfile in application source - if: ${{ inputs.appSourcePath != '' && inputs.dockerfilePath == '' }} - shell: bash - run: | - dockerfilePath=${{ inputs.appSourcePath }}/Dockerfile - if [ -f "$dockerfilePath" ]; then echo "CA_GH_ACTION_DOCKERFILE_PATH=${dockerfilePath}" >> $GITHUB_ENV; fi - - - name: Export name of image to build to environment variable - if: ${{ inputs.imageToBuild != '' && env.CA_GH_ACTION_ONLY_YAML != 'true' }} - shell: bash - run: | - CA_GH_ACTION_IMAGE_TO_BUILD="${{ inputs.imageToBuild }}" - echo "CA_GH_ACTION_IMAGE_TO_BUILD=${CA_GH_ACTION_IMAGE_TO_BUILD}" >> $GITHUB_ENV - - - name: Determine name of image to build if not provided - if: ${{ env.CA_GH_ACTION_REGISTRY_URL != '' && inputs.imageToBuild == '' && env.CA_GH_ACTION_ONLY_YAML != 'true' }} - shell: bash - run: | - CA_GH_ACTION_IMAGE_TO_BUILD="${{ env.CA_GH_ACTION_REGISTRY_URL }}/github-action/container-app:${{ github.run_id }}.${{ github.run_attempt }}" - echo "CA_GH_ACTION_IMAGE_TO_BUILD=${CA_GH_ACTION_IMAGE_TO_BUILD}" >> $GITHUB_ENV - - - name: Export name of image to deploy to environment variable - if: ${{ inputs.imageToDeploy != '' }} - shell: bash - run: | - CA_GH_ACTION_IMAGE_TO_DEPLOY="${{ inputs.imageToDeploy }}" - echo "CA_GH_ACTION_IMAGE_TO_DEPLOY=${CA_GH_ACTION_IMAGE_TO_DEPLOY}" >> $GITHUB_ENV - - - name: Update name of image to deploy if not provided - if: ${{ inputs.imageToDeploy == '' && env.CA_GH_ACTION_ONLY_YAML != 'true' }} - shell: bash - run: | - CA_GH_ACTION_IMAGE_TO_DEPLOY="${{ env.CA_GH_ACTION_IMAGE_TO_BUILD }}" - echo "CA_GH_ACTION_IMAGE_TO_DEPLOY=${CA_GH_ACTION_IMAGE_TO_DEPLOY}" >> $GITHUB_ENV - - - name: Export resource group to environment variable - if: ${{ inputs.resourceGroup != '' }} - shell: bash - run: | - CA_GH_ACTION_RESOURCE_GROUP="${{ inputs.resourceGroup }}" - echo "CA_GH_ACTION_RESOURCE_GROUP=${CA_GH_ACTION_RESOURCE_GROUP}" >> $GITHUB_ENV - - - name: Determine resource group if not provided - if: ${{ inputs.resourceGroup == '' }} - shell: bash - run: | - CA_GH_ACTION_RESOURCE_GROUP="${{ inputs.containerAppName }}-rg" - echo "CA_GH_ACTION_RESOURCE_GROUP=${CA_GH_ACTION_RESOURCE_GROUP}" >> $GITHUB_ENV - - - name: Check if the previously generated resource group exists - if: ${{ inputs.resourceGroup == '' }} - shell: bash - run: | - az group show \ - -n ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - --output none && CA_GH_ACTION_RG_EXISTS=true || CA_GH_ACTION_RG_EXISTS=false - echo "CA_GH_ACTION_RG_EXISTS=${CA_GH_ACTION_RG_EXISTS}" >> $GITHUB_ENV - - - name: Get default location for Container Apps to use if resource group doesn't exist and location wasn't provided - if: ${{ env.CA_GH_ACTION_RG_EXISTS == 'false' && inputs.location == '' }} - shell: bash - run: | - CA_GH_ACTION_DEFAULT_LOCATION=$(az provider show -n Microsoft.App --query "resourceTypes[?resourceType=='containerApps'].locations[] | [0]") - CA_GH_ACTION_DEFAULT_LOCATION=$(echo $CA_GH_ACTION_DEFAULT_LOCATION | sed 's/[ !()]//g') - CA_GH_ACTION_DEFAULT_LOCATION=${CA_GH_ACTION_DEFAULT_LOCATION,,} - echo "CA_GH_ACTION_DEFAULT_LOCATION=${CA_GH_ACTION_DEFAULT_LOCATION}" >> $GITHUB_ENV - - - name: Set default location for Container Apps if location was provided - if: ${{ env.CA_GH_ACTION_RG_EXISTS == 'false' && inputs.location != '' }} - shell: bash - run: | - CA_GH_ACTION_DEFAULT_LOCATION="${{ inputs.location }}" - echo "CA_GH_ACTION_DEFAULT_LOCATION=${CA_GH_ACTION_DEFAULT_LOCATION}" >> $GITHUB_ENV - - - name: Create resource group if it doesn't exist - if: ${{ env.CA_GH_ACTION_RG_EXISTS == 'false' }} - shell: bash - run: | - az group create \ - -n ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -l ${{ env.CA_GH_ACTION_DEFAULT_LOCATION }} - - - name: Export location argument to environment variable - if: ${{ inputs.location != '' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_CONTAINER_APP_LOCATION_ARG="--location ${{ inputs.location }}" - echo "CA_GH_ACTION_CONTAINER_APP_LOCATION_ARG=${CA_GH_ACTION_CONTAINER_APP_LOCATION_ARG}" >> $GITHUB_ENV - - - name: Check if the Container App already exists in the resource group - shell: bash - run: | - az containerapp show \ - -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -n ${{ inputs.containerAppName }} \ - --output none && CA_GH_ACTION_RESOURCE_EXISTS=true || CA_GH_ACTION_RESOURCE_EXISTS=false - echo "CA_GH_ACTION_RESOURCE_EXISTS=${CA_GH_ACTION_RESOURCE_EXISTS}" >> $GITHUB_ENV - - - name: Export environment to environment variable - if: ${{ inputs.containerAppEnvironment != '' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT="${{ inputs.containerAppEnvironment }}" - echo "CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT=${CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT}" >> $GITHUB_ENV - - - name: Determine environment name if not provided - if: ${{ inputs.containerAppEnvironment == '' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT="${{ inputs.containerAppName }}-env" - echo "CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT=${CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT}" >> $GITHUB_ENV - - - name: Check if Container App environment already exists with the provided/determined name - if: ${{ env.CA_GH_ACTION_RESOURCE_EXISTS == 'false' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - az containerapp env show \ - -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -n ${{ env.CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT }} \ - --output none && CA_GH_ACTION_ENVIRONMENT_EXISTS=true || CA_GH_ACTION_ENVIRONMENT_EXISTS=false - echo "CA_GH_ACTION_ENVIRONMENT_EXISTS=${CA_GH_ACTION_ENVIRONMENT_EXISTS}" >> $GITHUB_ENV - - - name: Reuse an existing Container App environment if found in the resource group - if: ${{ env.CA_GH_ACTION_ENVIRONMENT_EXISTS == 'false' && inputs.containerAppEnvironment == '' && env.CA_CH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_TEMP_ENV_NAME=$(az containerapp env list -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} --query '[0].name') - if [[ $CA_GH_ACTION_TEMP_ENV_NAME != '' ]]; then \ - echo "CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT=${CA_GH_ACTION_TEMP_ENV_NAME}" >> $GITHUB_ENV; \ - echo "CA_GH_ACTION_ENVIRONMENT_EXISTS=true" >> $GITHUB_ENV; \ - fi - - - name: Create Container App environment if the Container App environment doesn't exist - if: ${{ env.CA_GH_ACTION_ENVIRONMENT_EXISTS == 'false' }} - shell: bash - run: | - az containerapp env create \ - -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -n ${{ env.CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT }} \ - ${{ env.CA_GH_ACTION_CONTAINER_APP_LOCATION_ARG }} - - - name: Export disabled ingress value to environment variable - if: ${{ inputs.ingress == 'disabled' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_INGRESS_ENABLED="false" - echo "CA_GH_ACTION_INGRESS_ENABLED=${CA_GH_ACTION_INGRESS_ENABLED}" >> $GITHUB_ENV - - - name: Set default value of ingress to 'external' if not provided - if: ${{ inputs.ingress == '' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_INGRESS_ENABLED="true" - echo "CA_GH_ACTION_INGRESS_ENABLED=${CA_GH_ACTION_INGRESS_ENABLED}" >> $GITHUB_ENV - CA_GH_ACTION_INGRESS_ARG="--ingress external" - echo "CA_GH_ACTION_INGRESS_ARG=${CA_GH_ACTION_INGRESS_ARG}" >> $GITHUB_ENV - - - name: Export ingress value to environment variable - if: ${{ inputs.ingress != '' && inputs.ingress != 'disabled' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_INGRESS_ENABLED="true" - echo "CA_GH_ACTION_INGRESS_ENABLED=${CA_GH_ACTION_INGRESS_ENABLED}" >> $GITHUB_ENV - CA_GH_ACTION_INGRESS_ARG="--ingress ${{ inputs.ingress }}" - echo "CA_GH_ACTION_INGRESS_ARG=${CA_GH_ACTION_INGRESS_ARG}" >> $GITHUB_ENV - - - name: Export target port to environment variable - if: ${{ env.CA_GH_ACTION_INGRESS_ENABLED == 'true' && inputs.targetPort != '' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_TARGET_PORT="${{ inputs.targetPort }}" - echo "CA_GH_ACTION_TARGET_PORT=${CA_GH_ACTION_TARGET_PORT}" >> $GITHUB_ENV - - - name: Default to target port 80 if one wasn't provided and no Dockerfile provided/found with the application source - if: ${{ env.CA_GH_ACTION_INGRESS_ENABLED == 'true' && inputs.targetPort == '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' && inputs.appSourcePath != '' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_TARGET_PORT="80" - echo "CA_GH_ACTION_TARGET_PORT=${CA_GH_ACTION_TARGET_PORT}" >> $GITHUB_ENV - - - name: Default to target port 80 if one wasn't provided or found, and ingress is enabled, and the Container App doesn't exist (prevent overriding) - if: ${{ env.CA_GH_ACTION_INGRESS_ENABLED == 'true' && env.CA_GH_ACTION_RESOURCE_EXISTS == 'false' && env.CA_GH_ACTION_TARGET_PORT == '' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_TARGET_PORT="80" - echo "CA_GH_ACTION_TARGET_PORT=${CA_GH_ACTION_TARGET_PORT}" >> $GITHUB_ENV - - - name: Export target port information to environment variable for Azure CLI command - if: ${{ env.CA_GH_ACTION_INGRESS_ENABLED == 'true' && env.CA_GH_ACTION_TARGET_PORT != '' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_TARGET_PORT_ARG="--target-port ${{ env.CA_GH_ACTION_TARGET_PORT }}" - echo "CA_GH_ACTION_TARGET_PORT_ARG=${CA_GH_ACTION_TARGET_PORT_ARG}" >> $GITHUB_ENV - - - name: Export environment variables argument to environment variable if the Container App does not exist - if: ${{ inputs.environmentVariables != '' && env.CA_GH_ACTION_RESOURCE_EXISTS == 'false' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG="--env-vars ${{ inputs.environmentVariables }}" - echo "CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG=${CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG}" >> $GITHUB_ENV - - - name: Export environment variables argument to environment variable if the Container App does exist - if: ${{ inputs.environmentVariables != '' && env.CA_GH_ACTION_RESOURCE_EXISTS == 'true' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG="--replace-env-vars ${{ inputs.environmentVariables }}" - echo "CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG=${CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG}" >> $GITHUB_ENV - - - name: Install pack CLI on non-Windows runner - if: ${{ runner.os != 'Windows' && inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' }} - shell: bash - run: (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.31.0/pack-v0.31.0-${{ runner.os }}.tgz" | sudo tar -C /usr/local/bin/ --no-same-owner -xzv pack) - - - name: Install pack CLI on Windows runner - if: ${{ runner.os == 'Windows' && inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' }} - shell: bash - run: | - mkdir -p $PWD/pack && cd $PWD/pack - curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.31.0/pack-v0.31.0-windows.zip" -o "pack-windows.zip" - 7z x pack-windows.zip > /dev/null 2>&1 - rm pack-windows.zip && echo "$PWD" >> $GITHUB_PATH - - - name: Set Oryx++ Builder as default builder - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' }} - shell: bash - run: pack config default-builder mcr.microsoft.com/oryx/builder:debian-bullseye-20231004.1 - - - name: Enable experimental features for Oryx++ Builder - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' }} - shell: bash - run: pack config experimental true - - - name: Set telemetry for Oryx++ Builder - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' && inputs.disableTelemetry == 'false' }} - shell: bash - run: | - CA_GH_ACTION_ORYX_BUILDER_TELEMETRY_ARG='--env "CALLER_ID=github-actions-v1"' - echo "CA_GH_ACTION_ORYX_BUILDER_TELEMETRY_ARG=${CA_GH_ACTION_ORYX_BUILDER_TELEMETRY_ARG}" >> $GITHUB_ENV - - - name: Disable telemetry for Oryx++ Builder - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' && inputs.disableTelemetry == 'true' }} - shell: bash - run: | - CA_GH_ACTION_ORYX_BUILDER_TELEMETRY_ARG='--env "ORYX_DISABLE_TELEMETRY=true"' - echo "CA_GH_ACTION_ORYX_BUILDER_TELEMETRY_ARG=${CA_GH_ACTION_ORYX_BUILDER_TELEMETRY_ARG}" >> $GITHUB_ENV - - - name: Parse the given runtime stack input and export the platform and version to environment variables - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' && inputs.runtimeStack != '' }} - shell: bash - run: | - IFS=':' read -ra CA_GH_ACTION_RUNTIME_STACK <<< "${{ inputs.runtimeStack }}" - CA_GH_ACTION_RUNTIME_STACK_PLATFORM=${CA_GH_ACTION_RUNTIME_STACK[0]} - CA_GH_ACTION_RUNTIME_STACK_VERSION=${CA_GH_ACTION_RUNTIME_STACK[1]} - if [[ CA_GH_ACTION_RUNTIME_STACK_PLATFORM == "dotnetcore" ]]; then CA_GH_ACTION_RUNTIME_STACK_PLATFORM="dotnet"; fi - echo "CA_GH_ACTION_RUNTIME_STACK_PLATFORM=${CA_GH_ACTION_RUNTIME_STACK_PLATFORM}" >> $GITHUB_ENV - echo "CA_GH_ACTION_RUNTIME_STACK_VERSION=${CA_GH_ACTION_RUNTIME_STACK_VERSION}" >> $GITHUB_ENV - - - name: Set platform environment variables for builder invocation - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' && inputs.runtimeStack != '' }} - shell: bash - run: | - CA_GH_ACTION_ORYX_BUILDER_ENV_ARG="--env ORYX_PLATFORM_NAME=${{ env.CA_GH_ACTION_RUNTIME_STACK_PLATFORM }} --env ORYX_PLATFORM_VERSION=${{ env.CA_GH_ACTION_RUNTIME_STACK_VERSION }}" - echo "CA_GH_ACTION_ORYX_BUILDER_ENV_ARG=${CA_GH_ACTION_ORYX_BUILDER_ENV_ARG}" >> $GITHUB_ENV - - - name: Set port environment variable for builder invocation - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' && env.CA_GH_ACTION_TARGET_PORT != '' }} - shell: bash - run: | - CA_GH_ACTION_ORYX_BUILDER_ENV_ARG="${CA_GH_ACTION_ORYX_BUILDER_ENV_ARG} --env ORYX_RUNTIME_PORT=${{ env.CA_GH_ACTION_TARGET_PORT }}" - echo "CA_GH_ACTION_ORYX_BUILDER_ENV_ARG=${CA_GH_ACTION_ORYX_BUILDER_ENV_ARG}" >> $GITHUB_ENV - - - name: Create runnable application image using bullseye Oryx++ Builder - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' && (inputs.builderStack == '' || inputs.builderStack == 'debian-bullseye') }} - shell: bash - run: | - pack build \ - ${{ env.CA_GH_ACTION_IMAGE_TO_DEPLOY }} \ - --path ${{ inputs.appSourcePath }} \ - --builder mcr.microsoft.com/oryx/builder:debian-bullseye-20231004.1 \ - ${{ env.CA_GH_ACTION_ORYX_BUILDER_ENV_ARG }} \ - ${{ env.CA_GH_ACTION_ORYX_BUILDER_TELEMETRY_ARG }} && - echo "CA_GH_ACTION_BUILDER_FAILED=false" >> $GITHUB_ENV || \ - (echo "CA_GH_ACTION_BUILDER_FAILED=true" >> $GITHUB_ENV && \ - echo "CA_GH_ACTION_USE_BOOKWORM_BUILDER=true" >> $GITHUB_ENV) - - - name: Create runnable application image using bookworm Oryx++ Builder - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' && ((inputs.builderStack == '' && env.CA_GH_ACTION_USE_BOOKWORM_BUILDER == 'true') || inputs.builderStack == 'debian-bookworm') }} - shell: bash - run: | - pack build \ - ${{ env.CA_GH_ACTION_IMAGE_TO_DEPLOY }} \ - --path ${{ inputs.appSourcePath }} \ - --builder mcr.microsoft.com/oryx/builder:debian-bookworm-20231004.1 \ - ${{ env.CA_GH_ACTION_ORYX_BUILDER_ENV_ARG }} \ - ${{ env.CA_GH_ACTION_ORYX_BUILDER_TELEMETRY_ARG }} && \ - echo "CA_GH_ACTION_BUILDER_FAILED=false" >> $GITHUB_ENV || \ - echo "CA_GH_ACTION_BUILDER_FAILED=true" >> $GITHUB_ENV - - - name: Create runnable application image using builder version specified by input - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' && inputs.builderStack != '' && inputs.builderStack != 'debian-bookworm' && inputs.builderStack != 'debian-bullseye' }} - shell: bash - run: | - pack build \ - ${{ env.CA_GH_ACTION_IMAGE_TO_DEPLOY }} \ - --path ${{ inputs.appSourcePath }} \ - --builder mcr.microsoft.com/oryx/builder:${{ inputs.builderStack }} \ - ${{ env.CA_GH_ACTION_ORYX_BUILDER_ENV_ARG }} \ - ${{ env.CA_GH_ACTION_ORYX_BUILDER_TELEMETRY_ARG }} && \ - echo "CA_GH_ACTION_BUILDER_FAILED=false" >> $GITHUB_ENV || \ - echo "CA_GH_ACTION_BUILDER_FAILED=true" >> $GITHUB_ENV - - - name: Check if no builder was able to build the provided application source - if: ${{ env.CA_GH_ACTION_BUILDER_FAILED == 'true' }} - shell: bash - run: | - echo "No builder was able to build the provided application source. Please visit the following page for more information on supported platform versions: https://aka.ms/SourceToCloudSupportedVersions" - exit 1 - - - name: Create runnable application image using provided Dockerfile - if: ${{ inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH != '' }} - shell: bash - run: | - docker build \ - --tag ${{ env.CA_GH_ACTION_IMAGE_TO_DEPLOY }} \ - --file ${{ env.CA_GH_ACTION_DOCKERFILE_PATH }} \ - ${{ inputs.appSourcePath }} - - - name: Push image to Container Registry - if: ${{ inputs.appSourcePath != '' }} - shell: bash - run: docker push ${{ env.CA_GH_ACTION_IMAGE_TO_DEPLOY }} - - - name: Create the Container App from a YAML configuration file - if: ${{ env.CA_GH_ACTION_YAML_PROVIDED == 'true' && env.CA_GH_ACTION_RESOURCE_EXISTS == 'false' }} - shell: bash - run: | - az containerapp create \ - -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -n ${{ inputs.containerAppName }} \ - --yaml ${{ inputs.yamlConfigPath }} \ - --output none - - - name: Create the Container App from provided arguments - if: ${{ env.CA_GH_ACTION_YAML_PROVIDED != 'true' && env.CA_GH_ACTION_RESOURCE_EXISTS == 'false' }} - shell: bash - run: | - az containerapp create \ - -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -n ${{ inputs.containerAppName }} \ - -i ${{ env.CA_GH_ACTION_IMAGE_TO_DEPLOY }} \ - --environment ${{ env.CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT }} \ - ${{ env.CA_GH_ACTION_INGRESS_ARG }} \ - ${{ env.CA_GH_ACTION_TARGET_PORT_ARG }} \ - ${{ env.CA_GH_ACTION_REGISTRY_LOGIN_ARG }} \ - ${{ env.CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG }} \ - --output none - - - name: Update the existing Container App from a YAML configuration file - if: ${{ env.CA_GH_ACTION_YAML_PROVIDED == 'true' && env.CA_GH_ACTION_RESOURCE_EXISTS == 'true' }} - shell: bash - run: | - az containerapp update \ - -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -n ${{ inputs.containerAppName }} \ - --yaml ${{ inputs.yamlConfigPath }} \ - --output none - - - name: Determine whether or not 'update' or 'up' should be used - if: ${{ env.CA_GH_ACTION_YAML_PROVIDED != 'true' && env.CA_GH_ACTION_RESOURCE_EXISTS == 'true' && (inputs.targetPort != '' || inputs.ingress != '') }} - shell: bash - run: | - CA_GH_ACTION_USE_UP="true" - echo "CA_GH_ACTION_USE_UP=${CA_GH_ACTION_USE_UP}" >> $GITHUB_ENV - - - name: Update the Container Registry details on the existing Container App - if: ${{ env.CA_GH_ACTION_REGISTRY_LOGIN_ARG != '' && env.CA_GH_ACTION_USE_UP != 'true' && env.CA_GH_ACTION_RESOURCE_EXISTS == 'true' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - az containerapp registry set \ - -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -n ${{ inputs.containerAppName }} \ - --server ${{ env.CA_GH_ACTION_REGISTRY_URL }} \ - --username ${{ env.CA_GH_ACTION_REGISTRY_USERNAME }} \ - --password ${{ env.CA_GH_ACTION_REGISTRY_PASSWORD }} - - - name: Update the existing Container App from provided arguments via 'update' (no ingress values provided) - if: ${{ env.CA_GH_ACTION_USE_UP != 'true' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' && env.CA_GH_ACTION_RESOURCE_EXISTS == 'true' }} - shell: bash - run: | - az containerapp update \ - -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -n ${{ inputs.containerAppName }} \ - -i ${{ env.CA_GH_ACTION_IMAGE_TO_DEPLOY }} \ - ${{ env.CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG }} \ - --output none - - - name: Reset the ingress argument environment variable if it wasn't provided (use default ingress value) - if: ${{ inputs.ingress == '' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' && env.CA_GH_ACTION_RESOURCE_EXISTS == 'true' }} - shell: bash - run: | - CA_GH_ACTION_INGRESS_ARG="" - echo "CA_GH_ACTION_INGRESS_ARG=${CA_GH_ACTION_INGRESS_ARG}" >> $GITHUB_ENV - - - name: Reset the environment variables argument environment variable for the 'up' command - if: ${{ env.CA_GH_ACTION_USE_UP == 'true' && inputs.environmentVariables != '' }} - shell: bash - run: | - CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG="--env-vars ${{ inputs.environmentVariables }}" - echo "CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG=${CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG}" >> $GITHUB_ENV - - - name: Update the existing Container App from provided arguments via 'up' (ingress values provided) - if: ${{ env.CA_GH_ACTION_USE_UP == 'true' }} - shell: bash - run: | - az containerapp up \ - -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -n ${{ inputs.containerAppName }} \ - -i ${{ env.CA_GH_ACTION_IMAGE_TO_DEPLOY }} \ - ${{ env.CA_GH_ACTION_TARGET_PORT_ARG }} \ - ${{ env.CA_GH_ACTION_INGRESS_ARG }} \ - ${{ env.CA_GH_ACTION_CONTAINER_APP_ENVIRONMENT_VARIABLES_ARG }} \ - ${{ env.CA_GH_ACTION_REGISTRY_LOGIN_ARG }} - - - name: Disable ingress on the existing Container App - if: ${{ env.CA_GH_ACTION_RESOURCE_EXISTS == 'true' && inputs.ingress == 'disabled' && env.CA_GH_ACTION_YAML_PROVIDED != 'true' }} - shell: bash - run: | - az containerapp ingress disable \ - -g ${{ env.CA_GH_ACTION_RESOURCE_GROUP }} \ - -n ${{ inputs.containerAppName }} - - - name: Mark action as 'succeeded' for telemetry - if: ${{ inputs.disableTelemetry == 'false' }} - shell: bash - run: | - CA_GH_ACTION_RESULT_ARG="--property 'result=succeeded'" - echo "CA_GH_ACTION_RESULT_ARG=${CA_GH_ACTION_RESULT_ARG}" >> $GITHUB_ENV - - - name: End timer that's tracking action execution length, in milliseconds - if: ${{ always() && inputs.disableTelemetry == 'false' }} - shell: bash - run: | - CA_GH_ACTION_END_MILLISECONDS=$(date +%s%N | cut -b1-13) - CA_GH_ACTION_START_MILLISECONDS=${{ env.CA_GH_ACTION_START_MILLISECONDS }} - CA_GH_ACTION_LENGTH_MILLISECONDS_ARG="--processing-time '$((CA_GH_ACTION_END_MILLISECONDS-CA_GH_ACTION_START_MILLISECONDS))'" - echo "CA_GH_ACTION_LENGTH_MILLISECONDS_ARG=${CA_GH_ACTION_LENGTH_MILLISECONDS_ARG}" >> $GITHUB_ENV - - - name: Set telemetry for previously built image scenario - if: ${{ inputs.disableTelemetry == 'false' && inputs.appSourcePath == '' }} - shell: bash - run: | - CA_GH_ACTION_SCENARIO_ARG="--property 'scenario=used-image'" - echo "CA_GH_ACTION_SCENARIO_ARG=${CA_GH_ACTION_SCENARIO_ARG}" >> $GITHUB_ENV - - - name: Set telemetry for built Dockerfile scenario - if: ${{ inputs.disableTelemetry == 'false' && env.CA_GH_ACTION_DOCKERFILE_PATH != '' }} - shell: bash - run: | - CA_GH_ACTION_SCENARIO_ARG="--property 'scenario=used-dockerfile'" - echo "CA_GH_ACTION_SCENARIO_ARG=${CA_GH_ACTION_SCENARIO_ARG}" >> $GITHUB_ENV - - - name: Set telemetry for no Dockerfile scenario - if: ${{ inputs.disableTelemetry == 'false' && inputs.appSourcePath != '' && env.CA_GH_ACTION_DOCKERFILE_PATH == '' }} - shell: bash - run: | - CA_GH_ACTION_SCENARIO_ARG="--property 'scenario=used-builder'" - echo "CA_GH_ACTION_SCENARIO_ARG=${CA_GH_ACTION_SCENARIO_ARG}" >> $GITHUB_ENV - - - name: Log telemetry for action - if: ${{ always() && inputs.disableTelemetry == 'false' }} - shell: bash - run: | - docker run \ - --rm \ - mcr.microsoft.com/oryx/cli:builder-debian-bullseye-20230926.1 \ - /bin/bash \ - -c "oryx telemetry --event-name 'ContainerAppsGitHubAction' ${{ env.CA_GH_ACTION_LENGTH_MILLISECONDS_ARG }} ${{ env.CA_GH_ACTION_SCENARIO_ARG }} ${{ env.CA_GH_ACTION_RESULT_ARG }}" + using: 'node16' + main: 'dist/index.js' diff --git a/azurecontainerapps.ts b/azurecontainerapps.ts new file mode 100644 index 00000000..df74d0a1 --- /dev/null +++ b/azurecontainerapps.ts @@ -0,0 +1,622 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { ContainerAppHelper } from './src/ContainerAppHelper'; +import { ContainerRegistryHelper } from './src/ContainerRegistryHelper'; +import { TelemetryHelper } from './src/TelemetryHelper'; +import { Utility } from './src/Utility'; +import { GitHubActionsToolHelper } from './src/GitHubActionsToolHelper'; + +export class azurecontainerapps { + + public static async runMain(): Promise { + this.initializeHelpers(); + + try { + // Validate that the arguments provided can be used for one of the supported scenarios + this.validateSupportedScenarioArguments(); + + // Set up the Azure CLI to be used for this task + await this.setupAzureCli(); + + // Set up the resources required to deploy a Container App + await this.setupResources(); + + // If a Container Registry URL was provided, try to authenticate against it + if (!this.util.isNullOrEmpty(this.registryUrl)) { + await this.authenticateContainerRegistryAsync(); + } + + // If an Azure Container Registry name was provided, try to authenticate against it + if (!this.util.isNullOrEmpty(this.acrName)) { + await this.authenticateAzureContainerRegistryAsync(); + } + + // Set up property to determine if the internal registry should be used + this.useInternalRegistry = this.util.isNullOrEmpty(this.registryUrl); + + // Set up property to trigger cloud build with 'up' command + this.shouldCreateOrUpdateContainerAppWithUp = !this.util.isNullOrEmpty(this.appSourcePath) && this.useInternalRegistry; + + // If the application source was provided, build a runnable application image from it + if (!this.useInternalRegistry && !this.util.isNullOrEmpty(this.appSourcePath)) { + await this.buildAndPushImageAsync(); + } + + // If no application source was provided, set up the scenario for deploying an existing image + if (this.util.isNullOrEmpty(this.appSourcePath)) { + this.setupExistingImageScenario(); + } + + // If no YAML configuration file was provided, set up the Container App properties + if (this.util.isNullOrEmpty(this.yamlConfigPath)) { + this.setupContainerAppProperties(); + } + + // Create/update the Container App + await this.createOrUpdateContainerApp(); + + // If telemetry is enabled, log that the task completed successfully + this.telemetryHelper.setSuccessfulResult(); + } catch (err) { + this.toolHelper.setFailed(err.message); + this.telemetryHelper.setFailedResult(err.message); + } finally { + // If telemetry is enabled, will log metadata for this task run + await this.telemetryHelper.sendLogs(); + } + } + + // Build-specific properties + private static buildId: string; + private static buildNumber: string; + + // Supported scenario properties + private static appSourcePath: string; + private static acrName: string; + private static imageToDeploy: string; + private static yamlConfigPath: string; + + // Resource properties + private static containerAppName: string; + private static containerAppExists: boolean; + private static location: string; + private static resourceGroup: string; + private static containerAppEnvironment: string; + private static ingressEnabled: boolean; + + // Container Registry properties + private static registryUsername: string; + private static registryPassword: string; + private static registryUrl: string; + + // Command line arguments + private static commandLineArgs: string[]; + + // Helper properties + private static telemetryHelper: TelemetryHelper; + private static appHelper: ContainerAppHelper; + private static registryHelper: ContainerRegistryHelper; + private static util: Utility; + private static toolHelper: GitHubActionsToolHelper; + + // Miscellaneous properties + private static imageToBuild: string; + private static ingress: string; + private static targetPort: string; + private static shouldUseUpdateCommand: boolean; + private static useInternalRegistry: boolean; + private static shouldCreateOrUpdateContainerAppWithUp: boolean; + + /** + * Initializes the helpers used by this task. + * @param disableTelemetry - Whether or not to disable telemetry for this task. + */ + private static initializeHelpers() { + // Set up Utility for managing miscellaneous calls + this.util = new Utility(); + + // Set up toolHelper for managing calls to the GitHub Actions toolkit + this.toolHelper = new GitHubActionsToolHelper(); + + let disableTelemetry = this.toolHelper.getInput('disableTelemetry').toLowerCase() === 'true'; + + // Get buildId + this.buildId = this.toolHelper.getBuildId(); + + // Get buildNumber + this.buildNumber = this.toolHelper.getBuildNumber(); + + // Set up TelemetryHelper for managing telemetry calls + this.telemetryHelper = new TelemetryHelper(disableTelemetry); + + // Set up ContainerAppHelper for managing calls around the Container App + this.appHelper = new ContainerAppHelper(disableTelemetry); + + // Set up ContainerRegistryHelper for managing calls around the Container Registry + this.registryHelper = new ContainerRegistryHelper(); + } + + /** + * Validates the arguments provided to the task for supported scenarios. + * @throws Error if a valid combination of the support scenario arguments is not provided. + */ + private static validateSupportedScenarioArguments() { + + // Get the path to the application source to build and run, if provided + this.appSourcePath = this.toolHelper.getInput('appSourcePath', false) as string; + + // Get the name of the ACR instance to push images to, if provided + this.acrName = this.toolHelper.getInput('acrName', false) as string; + + // Get the name of the RegistryUrl to push images to, if provided + this.registryUrl = this.toolHelper.getInput('registryUrl', false) as string; + + // Get the previously built image to deploy, if provided + this.imageToDeploy = this.toolHelper.getInput('imageToDeploy', false) as string; + + // Get the YAML configuration file, if provided + this.yamlConfigPath = this.toolHelper.getInput('yamlConfigPath', false) as string; + + // Get the name of the image to build if it was provided, or generate it from build variables + this.imageToBuild = this.toolHelper.getInput('imageToBuild', false); + + // Ensure that one of appSourcePath, imageToDeploy, or yamlConfigPath is provided + if (this.util.isNullOrEmpty(this.appSourcePath) && this.util.isNullOrEmpty(this.imageToDeploy) && this.util.isNullOrEmpty(this.yamlConfigPath)) { + let requiredArgumentMessage = `One of the following arguments must be provided: 'appSourcePath', 'imageToDeploy', or 'yamlConfigPath'.`; + this.toolHelper.writeError(requiredArgumentMessage); + throw Error(requiredArgumentMessage); + } + + // Ensure that an ACR name and registry URL are not both provided + if (!this.util.isNullOrEmpty(this.acrName) && !this.util.isNullOrEmpty(this.registryUrl)) { + let conflictingArgumentsMessage = `The 'acrName' and 'registryUrl' arguments cannot both be provided.`; + this.toolHelper.writeError(conflictingArgumentsMessage); + throw Error(conflictingArgumentsMessage); + } + } + + /** + * Sets up the Azure CLI to be used for this task by logging in to Azure with the provided service connection and + * setting the Azure CLI to install missing extensions. + */ + private static async setupAzureCli() { + // Set the Azure CLI to install missing extensions + await this.util.installAzureCliExtension(); + } + + /** + * Sets up the resources required to deploy a Container App. This includes the following: + * - Getting or generating the Container App name + * - Getting or discovering the location to deploy resources to + * - Getting or creating the resource group + * - Getting or creating the Container App Environment + */ + private static async setupResources() { + // Get the Container App name if it was provided, or generate it from build variables + this.containerAppName = this.getContainerAppName(); + + // Get the location to deploy resources to, if provided, or use the default location + this.location = await this.getLocation(); + + // Get the resource group to deploy to if it was provided, or generate it from the Container App name + this.resourceGroup = await this.getOrCreateResourceGroup(this.containerAppName, this.location); + + // Determine if the Container Appp currently exists + this.containerAppExists = await this.appHelper.doesContainerAppExist(this.containerAppName, this.resourceGroup); + + // If the Container App doesn't exist, get/create the Container App Environment to use for the Container App + if (!this.containerAppExists) { + this.containerAppEnvironment = await this.getOrCreateContainerAppEnvironment(this.containerAppName, this.resourceGroup, this.location); + } + } + + /** + * Gets the name of the Container App to use for the task. If the 'containerAppName' argument is not provided, + * then a default name will be generated in the form 'gh-action-app--'. + * @returns The name of the Container App to use for the task. + */ + private static getContainerAppName(): string { + let containerAppName: string = this.toolHelper.getInput('containerAppName', false); + if (this.util.isNullOrEmpty(containerAppName)) { + return this.toolHelper.getDefaultContainerAppName(containerAppName); + } + + return containerAppName; + } + + /** + * Gets the location to deploy resources to. If the 'location' argument is not provided, then the default location + * for the Container App service will be used. + * @returns The location to deploy resources to. + */ + private static async getLocation(): Promise { + // Set deployment location, if provided + let location: string = this.toolHelper.getInput('location', false); + if (!this.util.isNullOrEmpty(location)) { + return location; + } + + // If no location was provided, attempt to discover the location of the existing Container App Environment linked to the Container App + // or Container App Environment provided in the resource group or use the default location. + // Get the resource group if it was provided + let resourceGroup: string = this.toolHelper.getInput('resourceGroup', false); + + if (!this.util.isNullOrEmpty(resourceGroup)) { + // Check if Container App exists in the resource group provided and get the location from the Container App Environment linked to it + let containerAppExists = await this.appHelper.doesContainerAppExist(this.containerAppName, resourceGroup); + if (containerAppExists) { + // Get the name of the Container App Environment linked to the Container App + var environmentName = await this.appHelper.getExistingContainerAppEnvironmentName(this.containerAppName, resourceGroup); + + // Check if environment exists in the resource group provided and get the location + var containerAppEnvironmentExistsInResourceGroup = !this.util.isNullOrEmpty(environmentName) ? await this.appHelper.doesContainerAppEnvironmentExist(environmentName, resourceGroup) : false; + if (containerAppEnvironmentExistsInResourceGroup) { + // Get the location of the Container App Environment linked to the Container App + location = await this.appHelper.getExistingContainerAppEnvironmentLocation(environmentName, resourceGroup); + return location; + } + } + + // Get the Container App Environment name if it was provided + let containerAppEnvironment: string = this.toolHelper.getInput('containerAppEnvironment', false); + + // Check if Container App Environment is provided and exits in the resource group provided and get the location + let containerAppEnvironmentExists = !this.util.isNullOrEmpty(containerAppEnvironment) ? await this.appHelper.doesContainerAppEnvironmentExist(containerAppEnvironment, resourceGroup) : false; + if (containerAppEnvironmentExists) { + location = await this.appHelper.getExistingContainerAppEnvironmentLocation(containerAppEnvironment, resourceGroup); + return location; + } + } + + // Get the default location if the Container App or Container App Environment was not found in the resource group provided. + location = await this.appHelper.getDefaultContainerAppLocation(); + return location; + + } + + /** + * Gets the name of the resource group to use for the task. If the 'resourceGroup' argument is not provided, + * then a default name will be generated in the form '-rg'. If the generated resource group does + * not exist, it will be created. + * @param containerAppName - The name of the Container App to use for the task. + * @param location - The location to deploy resources to. + * @returns The name of the resource group to use for the task. + */ + private static async getOrCreateResourceGroup(containerAppName: string, location: string): Promise { + // Get the resource group to deploy to if it was provided, or generate it from the Container App name + let resourceGroup: string = this.toolHelper.getInput('resourceGroup', false); + if (this.util.isNullOrEmpty(resourceGroup)) { + resourceGroup = `${containerAppName}-rg`; + this.toolHelper.writeInfo(`Default resource group name: ${resourceGroup}`); + + // Ensure that the resource group that the Container App will be created in exists + const resourceGroupExists = await this.appHelper.doesResourceGroupExist(resourceGroup); + if (!resourceGroupExists) { + await this.appHelper.createResourceGroup(resourceGroup, location); + } + } + + return resourceGroup; + } + + /** + * Gets the name of the Container App Environment to use for the task. If the 'containerAppEnvironment' argument + * is not provided, then the task will attempt to discover an existing Container App Environment in the resource + * group. If no existing Container App Environment is found, then a default name will be generated in the form + * '-env'. If the Container App Environment does not exist, it will be created. + * @param containerAppName - The name of the Container App to use for the task. + * @param resourceGroup - The name of the resource group to use for the task. + * @param location - The location to deploy resources to. + * @returns The name of the Container App Environment to use for the task. + */ + private static async getOrCreateContainerAppEnvironment( + containerAppName: string, + resourceGroup: string, + location: string): Promise { + // Get the Container App environment if it was provided + let containerAppEnvironment: string = this.toolHelper.getInput('containerAppEnvironment', false); + + // See if we can reuse an existing Container App environment found in the resource group + if (this.util.isNullOrEmpty(containerAppEnvironment)) { + const existingContainerAppEnvironment: string = await this.appHelper.getExistingContainerAppEnvironment(resourceGroup); + if (!this.util.isNullOrEmpty(existingContainerAppEnvironment)) { + this.toolHelper.writeInfo(`Existing Container App environment found in resource group: ${existingContainerAppEnvironment}`); + return existingContainerAppEnvironment + } + } + + // Generate the Container App environment name if it was not provided + if (this.util.isNullOrEmpty(containerAppEnvironment)) { + containerAppEnvironment = `${containerAppName}-env`; + this.toolHelper.writeInfo(`Default Container App environment name: ${containerAppEnvironment}`); + } + + // Determine if the Container App environment currently exists and create one if it doesn't + const containerAppEnvironmentExists: boolean = await this.appHelper.doesContainerAppEnvironmentExist(containerAppEnvironment, resourceGroup); + if (!containerAppEnvironmentExists) { + await this.appHelper.createContainerAppEnvironment(containerAppEnvironment, resourceGroup, location); + } + + return containerAppEnvironment; + } + + /** + * Authenticates calls to the provided Azure Container Registry. + */ + private static async authenticateAzureContainerRegistryAsync() { + this.registryUsername = this.toolHelper.getInput('acrUsername', false); + this.registryPassword = this.toolHelper.getInput('acrPassword', false); + this.registryUrl = `${this.acrName}.azurecr.io`; + + // Login to ACR if credentials were provided + if (!this.util.isNullOrEmpty(this.registryUsername) && !this.util.isNullOrEmpty(this.registryPassword)) { + this.toolHelper.writeInfo(`Logging in to ACR instance "${this.acrName}" with username and password credentials`); + await this.registryHelper.loginContainerRegistryWithUsernamePassword(this.registryUrl, this.registryUsername, this.registryPassword); + } else { + this.toolHelper.writeInfo(`No ACR credentials provided; attempting to log in to ACR instance "${this.acrName}" with access token`); + await this.registryHelper.loginAcrWithAccessTokenAsync(this.acrName); + } + } + + /** + * Authenticates calls to the provided Container Registry. + */ + private static async authenticateContainerRegistryAsync() { + this.registryUsername = this.toolHelper.getInput('registryUsername', false); + this.registryPassword = this.toolHelper.getInput('registryPassword', false); + + // Login to Container Registry if credentials were provided + if (!this.util.isNullOrEmpty(this.registryUsername) && !this.util.isNullOrEmpty(this.registryPassword)) { + this.toolHelper.writeInfo(`Logging in to Container Registry "${this.registryUrl}" with username and password credentials`); + await this.registryHelper.loginContainerRegistryWithUsernamePassword(this.registryUrl, this.registryUsername, this.registryPassword); + } + } + + /** + * Sets up the scenario where an existing image is used for the Container App. + */ + private static setupExistingImageScenario() { + // If telemetry is enabled, log that the previously built image scenario was targeted for this task + this.telemetryHelper.setImageScenario(); + } + + /** + * Builds a runnable application image using a Dockerfile or the builder and pushes it to the Container Registry. + */ + private static async buildAndPushImageAsync() { + // Get the name of the image to build if it was provided, or generate it from build variables + this.imageToBuild = this.toolHelper.getInput('imageToBuild', false); + + if (this.util.isNullOrEmpty(this.imageToBuild)) { + const imageRepository = this.toolHelper.getDefaultImageRepository() + // Constructs the image to build based on the provided registry URL, image repository, build ID, and build number. + this.imageToBuild = `${this.registryUrl}/${imageRepository}:${this.buildId}.${this.buildNumber}`; + this.toolHelper.writeInfo(`Default image to build: ${this.imageToBuild}`); + } + + // Get the name of the image to deploy if it was provided, or set it to the value of 'imageToBuild' + if (this.util.isNullOrEmpty(this.imageToDeploy)) { + this.imageToDeploy = this.imageToBuild; + this.toolHelper.writeInfo(`Default image to deploy: ${this.imageToDeploy}`); + } + + // Get Dockerfile to build, if provided, or check if one exists at the root of the provided application + let dockerfilePath: string = this.toolHelper.getInput('dockerfilePath', false); + if (this.util.isNullOrEmpty(dockerfilePath)) { + this.toolHelper.writeInfo(`No Dockerfile path provided; checking for Dockerfile at root of application source.`); + const rootDockerfilePath = path.join(this.appSourcePath, 'Dockerfile'); + if (fs.existsSync(rootDockerfilePath)) { + this.toolHelper.writeInfo(`Dockerfile found at root of application source.`) + dockerfilePath = rootDockerfilePath; + } else { + // No Dockerfile found or provided, build the image using the builder + await this.buildImageFromBuilderAsync(this.appSourcePath, this.imageToBuild); + } + } else { + dockerfilePath = path.join(this.appSourcePath, dockerfilePath); + } + + if (!this.util.isNullOrEmpty(dockerfilePath)) { + // Build the image from the provided/discovered Dockerfile + await this.buildImageFromDockerfile(this.appSourcePath, dockerfilePath, this.imageToBuild); + } + + // Push the image to the Container Registry + await this.registryHelper.pushImageToContainerRegistry(this.imageToBuild); + } + + /** + * Builds a runnable application image using the builder. + * @param appSourcePath - The path to the application source code. + * @param imageToBuild - The name of the image to build. + */ + private static async buildImageFromBuilderAsync(appSourcePath: string, imageToBuild: string) { + // Install the pack CLI + await this.appHelper.installPackCliAsync(); + this.toolHelper.writeInfo(`Successfully installed the pack CLI.`); + + // Enable experimental features for the pack CLI + await this.appHelper.enablePackCliExperimentalFeaturesAsync(); + this.toolHelper.writeInfo(`Successfully enabled experimental features for the pack CLI.`); + + // Define the environment variables that should be propagated to the builder + let environmentVariables: string[] = [] + + // Parse the given runtime stack input and export the platform and version to environment variables + const runtimeStack = this.toolHelper.getInput('runtimeStack', false); + if (!this.util.isNullOrEmpty(runtimeStack)) { + const runtimeStackSplit = runtimeStack.split(':'); + const platformName = runtimeStackSplit[0] == "dotnetcore" ? "dotnet" : runtimeStackSplit[0]; + const platformVersion = runtimeStackSplit[1]; + environmentVariables.push(`ORYX_PLATFORM_NAME=${platformName}`); + environmentVariables.push(`ORYX_PLATFORM_VERSION=${platformVersion}`); + } + + // Check if the user provided a builder stack to use + const builderStack = this.toolHelper.getInput('builderStack', false); + + // Set the target port on the image produced by the builder + if (!this.util.isNullOrEmpty(this.targetPort)) { + environmentVariables.push(`ORYX_RUNTIME_PORT=${this.targetPort}`); + } + + this.toolHelper.writeInfo(`Building image "${imageToBuild}" using the Oryx++ Builder`); + + // Set the Oryx++ Builder as the default builder locally + await this.appHelper.setDefaultBuilder(); + + // Create a runnable application image + await this.appHelper.createRunnableAppImage(imageToBuild, appSourcePath, environmentVariables, builderStack); + + // If telemetry is enabled, log that the builder scenario was targeted for this task + this.telemetryHelper.setBuilderScenario(); + } + + /** + * Builds a runnable application image using a provided or discovered Dockerfile. + * @param appSourcePath - The path to the application source code. + * @param dockerfilePath - The path to the Dockerfile to build. + * @param imageToBuild - The name of the image to build. + */ + private static async buildImageFromDockerfile( + appSourcePath: string, + dockerfilePath: string, + imageToBuild: string) { + this.toolHelper.writeInfo(`Building image "${imageToBuild}" using the provided Dockerfile`); + await this.appHelper.createRunnableAppImageFromDockerfile(imageToBuild, appSourcePath, dockerfilePath); + + // If telemetry is enabled, log that the Dockerfile scenario was targeted for this task + this.telemetryHelper.setDockerfileScenario(); + } + + /** + * Sets up the Container App properties that will be passed through to the Azure CLI when a YAML configuration + * file is not provided. + */ + private static setupContainerAppProperties() { + this.commandLineArgs = []; + + // Get the ingress inputs + this.ingress = this.toolHelper.getInput('ingress', false); + this.targetPort = this.toolHelper.getInput('targetPort', false); + + // If both ingress and target port were not provided for an existing Container App, or if ingress is to be disabled, + // use the 'update' command, otherwise we should use the 'up' command that performs a PATCH operation on the ingress properties. + this.shouldUseUpdateCommand = this.containerAppExists && + this.util.isNullOrEmpty(this.targetPort) && + (this.util.isNullOrEmpty(this.ingress) || this.ingress == 'disabled'); + + // Pass the Container Registry credentials when creating a Container App or updating a Container App via the 'up' command + if (!this.util.isNullOrEmpty(this.registryUrl) && !this.util.isNullOrEmpty(this.registryUsername) && !this.util.isNullOrEmpty(this.registryPassword) && + (!this.containerAppExists || (this.containerAppExists && !this.shouldUseUpdateCommand))) { + this.commandLineArgs.push( + `--registry-server ${this.registryUrl}`, + `--registry-username ${this.registryUsername}`, + `--registry-password ${this.registryPassword}`); + } + + // Determine default values only for the 'create' scenario to avoid overriding existing values for the 'update' scenario + if (!this.containerAppExists) { + this.ingressEnabled = true; + + // Set the ingress value to 'external' if it was not provided + if (this.util.isNullOrEmpty(this.ingress)) { + this.ingress = 'external'; + this.toolHelper.writeInfo(`Default ingress value: ${this.ingress}`); + } + + // Set the value of ingressEnabled to 'false' if ingress was provided as 'disabled' + if (this.ingress == 'disabled') { + this.ingressEnabled = false; + this.toolHelper.writeInfo(`Ingress is disabled for this Container App.`); + } + + // Handle setup for ingress values when enabled + if (this.ingressEnabled) { + // Get the target port if provided, or set it to the default value + this.targetPort = this.toolHelper.getInput('targetPort', false); + + // Set the target port to 80 if it was not provided + if (this.util.isNullOrEmpty(this.targetPort)) { + this.targetPort = '80'; + this.toolHelper.writeInfo(`Default target port: ${this.targetPort}`); + } + + // Add the ingress value and target port to the optional arguments array + // Note: this step should be skipped if we're updating an existing Container App (ingress is enabled via a separate command) + this.commandLineArgs.push(`--ingress ${this.ingress}`); + this.commandLineArgs.push(`--target-port ${this.targetPort}`); + } + } + + const environmentVariables: string = this.toolHelper.getInput('environmentVariables', false); + + // Add user-specified environment variables + if (!this.util.isNullOrEmpty(environmentVariables)) { + // The --replace-env-vars flag is only used for the 'update' command, + // otherwise --env-vars is used for 'create' and 'up' + if (this.shouldUseUpdateCommand) { + this.commandLineArgs.push(`--replace-env-vars ${environmentVariables}`); + } else { + this.commandLineArgs.push(`--env-vars ${environmentVariables}`); + } + } + + // Ensure '-i' argument and '--source' argument are not both provided + if (!this.util.isNullOrEmpty(this.imageToDeploy)) { + this.commandLineArgs.push(`-i ${this.imageToDeploy}`); + } else if (this.shouldCreateOrUpdateContainerAppWithUp) { + this.commandLineArgs.push(`--source ${this.appSourcePath}`); + this.commandLineArgs.push(`-l ${this.location}`); + } + + } + + /** + * Creates or updates the Container App. + */ + private static async createOrUpdateContainerApp() { + if (!this.containerAppExists) { + if (!this.util.isNullOrEmpty(this.yamlConfigPath)) { + // Create the Container App from the YAML configuration file + await this.appHelper.createContainerAppFromYaml(this.containerAppName, this.resourceGroup, this.yamlConfigPath); + } else if (this.shouldCreateOrUpdateContainerAppWithUp) { + await this.appHelper.createOrUpdateContainerAppWithUp(this.containerAppName, this.resourceGroup, this.commandLineArgs); + } else { + // Create the Container App from command line arguments + await this.appHelper.createContainerApp(this.containerAppName, this.resourceGroup, this.containerAppEnvironment, this.commandLineArgs); + } + + return; + } + + if (!this.util.isNullOrEmpty(this.yamlConfigPath)) { + // Update the Container App from the YAML configuration file + await this.appHelper.updateContainerAppFromYaml(this.containerAppName, this.resourceGroup, this.yamlConfigPath); + + return; + } + + if (this.shouldUseUpdateCommand && !this.shouldCreateOrUpdateContainerAppWithUp) { + // Update the Container Registry details on the existing Container App, if provided as an input + if (!this.util.isNullOrEmpty(this.registryUrl) && !this.util.isNullOrEmpty(this.registryUsername) && !this.util.isNullOrEmpty(this.registryPassword)) { + await this.appHelper.updateContainerAppRegistryDetails(this.containerAppName, this.resourceGroup, this.registryUrl, this.registryUsername, this.registryPassword); + } + + // Update the Container App using the 'update' command + await this.appHelper.updateContainerApp(this.containerAppName, this.resourceGroup, this.commandLineArgs); + } else if (this.shouldCreateOrUpdateContainerAppWithUp) { + await this.appHelper.createOrUpdateContainerAppWithUp(this.containerAppName, this.resourceGroup, this.commandLineArgs); + } else { + // Update the Container App using the 'up' command + await this.appHelper.updateContainerAppWithUp(this.containerAppName, this.resourceGroup, this.commandLineArgs, this.ingress, this.targetPort); + } + + // Disable ingress on the existing Container App, if provided as an input + if (this.ingress == 'disabled') { + await this.appHelper.disableContainerAppIngress(this.containerAppName, this.resourceGroup); + } + } +} + +azurecontainerapps.runMain(); \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 00000000..46764e26 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,6216 @@ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 3238: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.azurecontainerapps = void 0; +var fs = __nccwpck_require__(7147); +var path = __nccwpck_require__(1017); +var ContainerAppHelper_1 = __nccwpck_require__(2929); +var ContainerRegistryHelper_1 = __nccwpck_require__(4769); +var TelemetryHelper_1 = __nccwpck_require__(7166); +var Utility_1 = __nccwpck_require__(2135); +var GitHubActionsToolHelper_1 = __nccwpck_require__(3185); +var azurecontainerapps = /** @class */ (function () { + function azurecontainerapps() { + } + azurecontainerapps.runMain = function () { + return __awaiter(this, void 0, void 0, function () { + var err_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + this.initializeHelpers(); + _a.label = 1; + case 1: + _a.trys.push([1, 11, 12, 14]); + // Validate that the arguments provided can be used for one of the supported scenarios + this.validateSupportedScenarioArguments(); + // Set up the Azure CLI to be used for this task + return [4 /*yield*/, this.setupAzureCli()]; + case 2: + // Set up the Azure CLI to be used for this task + _a.sent(); + // Set up the resources required to deploy a Container App + return [4 /*yield*/, this.setupResources()]; + case 3: + // Set up the resources required to deploy a Container App + _a.sent(); + if (!!this.util.isNullOrEmpty(this.registryUrl)) return [3 /*break*/, 5]; + return [4 /*yield*/, this.authenticateContainerRegistryAsync()]; + case 4: + _a.sent(); + _a.label = 5; + case 5: + if (!!this.util.isNullOrEmpty(this.acrName)) return [3 /*break*/, 7]; + return [4 /*yield*/, this.authenticateAzureContainerRegistryAsync()]; + case 6: + _a.sent(); + _a.label = 7; + case 7: + // Set up property to determine if the internal registry should be used + this.useInternalRegistry = this.util.isNullOrEmpty(this.registryUrl); + // Set up property to trigger cloud build with 'up' command + this.shouldCreateOrUpdateContainerAppWithUp = !this.util.isNullOrEmpty(this.appSourcePath) && this.useInternalRegistry; + if (!(!this.useInternalRegistry && !this.util.isNullOrEmpty(this.appSourcePath))) return [3 /*break*/, 9]; + return [4 /*yield*/, this.buildAndPushImageAsync()]; + case 8: + _a.sent(); + _a.label = 9; + case 9: + // If no application source was provided, set up the scenario for deploying an existing image + if (this.util.isNullOrEmpty(this.appSourcePath)) { + this.setupExistingImageScenario(); + } + // If no YAML configuration file was provided, set up the Container App properties + if (this.util.isNullOrEmpty(this.yamlConfigPath)) { + this.setupContainerAppProperties(); + } + // Create/update the Container App + return [4 /*yield*/, this.createOrUpdateContainerApp()]; + case 10: + // Create/update the Container App + _a.sent(); + // If telemetry is enabled, log that the task completed successfully + this.telemetryHelper.setSuccessfulResult(); + return [3 /*break*/, 14]; + case 11: + err_1 = _a.sent(); + this.toolHelper.setFailed(err_1.message); + this.telemetryHelper.setFailedResult(err_1.message); + return [3 /*break*/, 14]; + case 12: + // If telemetry is enabled, will log metadata for this task run + return [4 /*yield*/, this.telemetryHelper.sendLogs()]; + case 13: + // If telemetry is enabled, will log metadata for this task run + _a.sent(); + return [7 /*endfinally*/]; + case 14: return [2 /*return*/]; + } + }); + }); + }; + /** + * Initializes the helpers used by this task. + * @param disableTelemetry - Whether or not to disable telemetry for this task. + */ + azurecontainerapps.initializeHelpers = function () { + // Set up Utility for managing miscellaneous calls + this.util = new Utility_1.Utility(); + // Set up toolHelper for managing calls to the GitHub Actions toolkit + this.toolHelper = new GitHubActionsToolHelper_1.GitHubActionsToolHelper(); + var disableTelemetry = this.toolHelper.getInput('disableTelemetry').toLowerCase() === 'true'; + // Get buildId + this.buildId = this.toolHelper.getBuildId(); + // Get buildNumber + this.buildNumber = this.toolHelper.getBuildNumber(); + // Set up TelemetryHelper for managing telemetry calls + this.telemetryHelper = new TelemetryHelper_1.TelemetryHelper(disableTelemetry); + // Set up ContainerAppHelper for managing calls around the Container App + this.appHelper = new ContainerAppHelper_1.ContainerAppHelper(disableTelemetry); + // Set up ContainerRegistryHelper for managing calls around the Container Registry + this.registryHelper = new ContainerRegistryHelper_1.ContainerRegistryHelper(); + }; + /** + * Validates the arguments provided to the task for supported scenarios. + * @throws Error if a valid combination of the support scenario arguments is not provided. + */ + azurecontainerapps.validateSupportedScenarioArguments = function () { + // Get the path to the application source to build and run, if provided + this.appSourcePath = this.toolHelper.getInput('appSourcePath', false); + // Get the name of the ACR instance to push images to, if provided + this.acrName = this.toolHelper.getInput('acrName', false); + // Get the name of the RegistryUrl to push images to, if provided + this.registryUrl = this.toolHelper.getInput('registryUrl', false); + // Get the previously built image to deploy, if provided + this.imageToDeploy = this.toolHelper.getInput('imageToDeploy', false); + // Get the YAML configuration file, if provided + this.yamlConfigPath = this.toolHelper.getInput('yamlConfigPath', false); + // Get the name of the image to build if it was provided, or generate it from build variables + this.imageToBuild = this.toolHelper.getInput('imageToBuild', false); + // Ensure that one of appSourcePath, imageToDeploy, or yamlConfigPath is provided + if (this.util.isNullOrEmpty(this.appSourcePath) && this.util.isNullOrEmpty(this.imageToDeploy) && this.util.isNullOrEmpty(this.yamlConfigPath)) { + var requiredArgumentMessage = "One of the following arguments must be provided: 'appSourcePath', 'imageToDeploy', or 'yamlConfigPath'."; + this.toolHelper.writeError(requiredArgumentMessage); + throw Error(requiredArgumentMessage); + } + // Ensure that an ACR name and registry URL are not both provided + if (!this.util.isNullOrEmpty(this.acrName) && !this.util.isNullOrEmpty(this.registryUrl)) { + var conflictingArgumentsMessage = "The 'acrName' and 'registryUrl' arguments cannot both be provided."; + this.toolHelper.writeError(conflictingArgumentsMessage); + throw Error(conflictingArgumentsMessage); + } + }; + /** + * Sets up the Azure CLI to be used for this task by logging in to Azure with the provided service connection and + * setting the Azure CLI to install missing extensions. + */ + azurecontainerapps.setupAzureCli = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + // Set the Azure CLI to install missing extensions + return [4 /*yield*/, this.util.installAzureCliExtension()]; + case 1: + // Set the Azure CLI to install missing extensions + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + /** + * Sets up the resources required to deploy a Container App. This includes the following: + * - Getting or generating the Container App name + * - Getting or discovering the location to deploy resources to + * - Getting or creating the resource group + * - Getting or creating the Container App Environment + */ + azurecontainerapps.setupResources = function () { + return __awaiter(this, void 0, void 0, function () { + var _a, _b, _c, _d; + return __generator(this, function (_e) { + switch (_e.label) { + case 0: + // Get the Container App name if it was provided, or generate it from build variables + this.containerAppName = this.getContainerAppName(); + // Get the location to deploy resources to, if provided, or use the default location + _a = this; + return [4 /*yield*/, this.getLocation()]; + case 1: + // Get the location to deploy resources to, if provided, or use the default location + _a.location = _e.sent(); + // Get the resource group to deploy to if it was provided, or generate it from the Container App name + _b = this; + return [4 /*yield*/, this.getOrCreateResourceGroup(this.containerAppName, this.location)]; + case 2: + // Get the resource group to deploy to if it was provided, or generate it from the Container App name + _b.resourceGroup = _e.sent(); + // Determine if the Container Appp currently exists + _c = this; + return [4 /*yield*/, this.appHelper.doesContainerAppExist(this.containerAppName, this.resourceGroup)]; + case 3: + // Determine if the Container Appp currently exists + _c.containerAppExists = _e.sent(); + if (!!this.containerAppExists) return [3 /*break*/, 5]; + _d = this; + return [4 /*yield*/, this.getOrCreateContainerAppEnvironment(this.containerAppName, this.resourceGroup, this.location)]; + case 4: + _d.containerAppEnvironment = _e.sent(); + _e.label = 5; + case 5: return [2 /*return*/]; + } + }); + }); + }; + /** + * Gets the name of the Container App to use for the task. If the 'containerAppName' argument is not provided, + * then a default name will be generated in the form 'gh-action-app--'. + * @returns The name of the Container App to use for the task. + */ + azurecontainerapps.getContainerAppName = function () { + var containerAppName = this.toolHelper.getInput('containerAppName', false); + if (this.util.isNullOrEmpty(containerAppName)) { + return this.toolHelper.getDefaultContainerAppName(containerAppName); + } + return containerAppName; + }; + /** + * Gets the location to deploy resources to. If the 'location' argument is not provided, then the default location + * for the Container App service will be used. + * @returns The location to deploy resources to. + */ + azurecontainerapps.getLocation = function () { + return __awaiter(this, void 0, void 0, function () { + var location, resourceGroup, containerAppExists, environmentName, containerAppEnvironmentExistsInResourceGroup, _a, containerAppEnvironment, containerAppEnvironmentExists, _b; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + location = this.toolHelper.getInput('location', false); + if (!this.util.isNullOrEmpty(location)) { + return [2 /*return*/, location]; + } + resourceGroup = this.toolHelper.getInput('resourceGroup', false); + if (!!this.util.isNullOrEmpty(resourceGroup)) return [3 /*break*/, 12]; + return [4 /*yield*/, this.appHelper.doesContainerAppExist(this.containerAppName, resourceGroup)]; + case 1: + containerAppExists = _c.sent(); + if (!containerAppExists) return [3 /*break*/, 7]; + return [4 /*yield*/, this.appHelper.getExistingContainerAppEnvironmentName(this.containerAppName, resourceGroup)]; + case 2: + environmentName = _c.sent(); + if (!!this.util.isNullOrEmpty(environmentName)) return [3 /*break*/, 4]; + return [4 /*yield*/, this.appHelper.doesContainerAppEnvironmentExist(environmentName, resourceGroup)]; + case 3: + _a = _c.sent(); + return [3 /*break*/, 5]; + case 4: + _a = false; + _c.label = 5; + case 5: + containerAppEnvironmentExistsInResourceGroup = _a; + if (!containerAppEnvironmentExistsInResourceGroup) return [3 /*break*/, 7]; + return [4 /*yield*/, this.appHelper.getExistingContainerAppEnvironmentLocation(environmentName, resourceGroup)]; + case 6: + // Get the location of the Container App Environment linked to the Container App + location = _c.sent(); + return [2 /*return*/, location]; + case 7: + containerAppEnvironment = this.toolHelper.getInput('containerAppEnvironment', false); + if (!!this.util.isNullOrEmpty(containerAppEnvironment)) return [3 /*break*/, 9]; + return [4 /*yield*/, this.appHelper.doesContainerAppEnvironmentExist(containerAppEnvironment, resourceGroup)]; + case 8: + _b = _c.sent(); + return [3 /*break*/, 10]; + case 9: + _b = false; + _c.label = 10; + case 10: + containerAppEnvironmentExists = _b; + if (!containerAppEnvironmentExists) return [3 /*break*/, 12]; + return [4 /*yield*/, this.appHelper.getExistingContainerAppEnvironmentLocation(containerAppEnvironment, resourceGroup)]; + case 11: + location = _c.sent(); + return [2 /*return*/, location]; + case 12: return [4 /*yield*/, this.appHelper.getDefaultContainerAppLocation()]; + case 13: + // Get the default location if the Container App or Container App Environment was not found in the resource group provided. + location = _c.sent(); + return [2 /*return*/, location]; + } + }); + }); + }; + /** + * Gets the name of the resource group to use for the task. If the 'resourceGroup' argument is not provided, + * then a default name will be generated in the form '-rg'. If the generated resource group does + * not exist, it will be created. + * @param containerAppName - The name of the Container App to use for the task. + * @param location - The location to deploy resources to. + * @returns The name of the resource group to use for the task. + */ + azurecontainerapps.getOrCreateResourceGroup = function (containerAppName, location) { + return __awaiter(this, void 0, void 0, function () { + var resourceGroup, resourceGroupExists; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + resourceGroup = this.toolHelper.getInput('resourceGroup', false); + if (!this.util.isNullOrEmpty(resourceGroup)) return [3 /*break*/, 3]; + resourceGroup = containerAppName + "-rg"; + this.toolHelper.writeInfo("Default resource group name: " + resourceGroup); + return [4 /*yield*/, this.appHelper.doesResourceGroupExist(resourceGroup)]; + case 1: + resourceGroupExists = _a.sent(); + if (!!resourceGroupExists) return [3 /*break*/, 3]; + return [4 /*yield*/, this.appHelper.createResourceGroup(resourceGroup, location)]; + case 2: + _a.sent(); + _a.label = 3; + case 3: return [2 /*return*/, resourceGroup]; + } + }); + }); + }; + /** + * Gets the name of the Container App Environment to use for the task. If the 'containerAppEnvironment' argument + * is not provided, then the task will attempt to discover an existing Container App Environment in the resource + * group. If no existing Container App Environment is found, then a default name will be generated in the form + * '-env'. If the Container App Environment does not exist, it will be created. + * @param containerAppName - The name of the Container App to use for the task. + * @param resourceGroup - The name of the resource group to use for the task. + * @param location - The location to deploy resources to. + * @returns The name of the Container App Environment to use for the task. + */ + azurecontainerapps.getOrCreateContainerAppEnvironment = function (containerAppName, resourceGroup, location) { + return __awaiter(this, void 0, void 0, function () { + var containerAppEnvironment, existingContainerAppEnvironment, containerAppEnvironmentExists; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + containerAppEnvironment = this.toolHelper.getInput('containerAppEnvironment', false); + if (!this.util.isNullOrEmpty(containerAppEnvironment)) return [3 /*break*/, 2]; + return [4 /*yield*/, this.appHelper.getExistingContainerAppEnvironment(resourceGroup)]; + case 1: + existingContainerAppEnvironment = _a.sent(); + if (!this.util.isNullOrEmpty(existingContainerAppEnvironment)) { + this.toolHelper.writeInfo("Existing Container App environment found in resource group: " + existingContainerAppEnvironment); + return [2 /*return*/, existingContainerAppEnvironment]; + } + _a.label = 2; + case 2: + // Generate the Container App environment name if it was not provided + if (this.util.isNullOrEmpty(containerAppEnvironment)) { + containerAppEnvironment = containerAppName + "-env"; + this.toolHelper.writeInfo("Default Container App environment name: " + containerAppEnvironment); + } + return [4 /*yield*/, this.appHelper.doesContainerAppEnvironmentExist(containerAppEnvironment, resourceGroup)]; + case 3: + containerAppEnvironmentExists = _a.sent(); + if (!!containerAppEnvironmentExists) return [3 /*break*/, 5]; + return [4 /*yield*/, this.appHelper.createContainerAppEnvironment(containerAppEnvironment, resourceGroup, location)]; + case 4: + _a.sent(); + _a.label = 5; + case 5: return [2 /*return*/, containerAppEnvironment]; + } + }); + }); + }; + /** + * Authenticates calls to the provided Azure Container Registry. + */ + azurecontainerapps.authenticateAzureContainerRegistryAsync = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + this.registryUsername = this.toolHelper.getInput('acrUsername', false); + this.registryPassword = this.toolHelper.getInput('acrPassword', false); + this.registryUrl = this.acrName + ".azurecr.io"; + if (!(!this.util.isNullOrEmpty(this.registryUsername) && !this.util.isNullOrEmpty(this.registryPassword))) return [3 /*break*/, 2]; + this.toolHelper.writeInfo("Logging in to ACR instance \"" + this.acrName + "\" with username and password credentials"); + return [4 /*yield*/, this.registryHelper.loginContainerRegistryWithUsernamePassword(this.registryUrl, this.registryUsername, this.registryPassword)]; + case 1: + _a.sent(); + return [3 /*break*/, 4]; + case 2: + this.toolHelper.writeInfo("No ACR credentials provided; attempting to log in to ACR instance \"" + this.acrName + "\" with access token"); + return [4 /*yield*/, this.registryHelper.loginAcrWithAccessTokenAsync(this.acrName)]; + case 3: + _a.sent(); + _a.label = 4; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Authenticates calls to the provided Container Registry. + */ + azurecontainerapps.authenticateContainerRegistryAsync = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + this.registryUsername = this.toolHelper.getInput('registryUsername', false); + this.registryPassword = this.toolHelper.getInput('registryPassword', false); + if (!(!this.util.isNullOrEmpty(this.registryUsername) && !this.util.isNullOrEmpty(this.registryPassword))) return [3 /*break*/, 2]; + this.toolHelper.writeInfo("Logging in to Container Registry \"" + this.registryUrl + "\" with username and password credentials"); + return [4 /*yield*/, this.registryHelper.loginContainerRegistryWithUsernamePassword(this.registryUrl, this.registryUsername, this.registryPassword)]; + case 1: + _a.sent(); + _a.label = 2; + case 2: return [2 /*return*/]; + } + }); + }); + }; + /** + * Sets up the scenario where an existing image is used for the Container App. + */ + azurecontainerapps.setupExistingImageScenario = function () { + // If telemetry is enabled, log that the previously built image scenario was targeted for this task + this.telemetryHelper.setImageScenario(); + }; + /** + * Builds a runnable application image using a Dockerfile or the builder and pushes it to the Container Registry. + */ + azurecontainerapps.buildAndPushImageAsync = function () { + return __awaiter(this, void 0, void 0, function () { + var imageRepository, dockerfilePath, rootDockerfilePath; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + // Get the name of the image to build if it was provided, or generate it from build variables + this.imageToBuild = this.toolHelper.getInput('imageToBuild', false); + if (this.util.isNullOrEmpty(this.imageToBuild)) { + imageRepository = this.toolHelper.getDefaultImageRepository(); + // Constructs the image to build based on the provided registry URL, image repository, build ID, and build number. + this.imageToBuild = this.registryUrl + "/" + imageRepository + ":" + this.buildId + "." + this.buildNumber; + this.toolHelper.writeInfo("Default image to build: " + this.imageToBuild); + } + // Get the name of the image to deploy if it was provided, or set it to the value of 'imageToBuild' + if (this.util.isNullOrEmpty(this.imageToDeploy)) { + this.imageToDeploy = this.imageToBuild; + this.toolHelper.writeInfo("Default image to deploy: " + this.imageToDeploy); + } + dockerfilePath = this.toolHelper.getInput('dockerfilePath', false); + if (!this.util.isNullOrEmpty(dockerfilePath)) return [3 /*break*/, 4]; + this.toolHelper.writeInfo("No Dockerfile path provided; checking for Dockerfile at root of application source."); + rootDockerfilePath = path.join(this.appSourcePath, 'Dockerfile'); + if (!fs.existsSync(rootDockerfilePath)) return [3 /*break*/, 1]; + this.toolHelper.writeInfo("Dockerfile found at root of application source."); + dockerfilePath = rootDockerfilePath; + return [3 /*break*/, 3]; + case 1: + // No Dockerfile found or provided, build the image using the builder + return [4 /*yield*/, this.buildImageFromBuilderAsync(this.appSourcePath, this.imageToBuild)]; + case 2: + // No Dockerfile found or provided, build the image using the builder + _a.sent(); + _a.label = 3; + case 3: return [3 /*break*/, 5]; + case 4: + dockerfilePath = path.join(this.appSourcePath, dockerfilePath); + _a.label = 5; + case 5: + if (!!this.util.isNullOrEmpty(dockerfilePath)) return [3 /*break*/, 7]; + // Build the image from the provided/discovered Dockerfile + return [4 /*yield*/, this.buildImageFromDockerfile(this.appSourcePath, dockerfilePath, this.imageToBuild)]; + case 6: + // Build the image from the provided/discovered Dockerfile + _a.sent(); + _a.label = 7; + case 7: + // Push the image to the Container Registry + return [4 /*yield*/, this.registryHelper.pushImageToContainerRegistry(this.imageToBuild)]; + case 8: + // Push the image to the Container Registry + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + /** + * Builds a runnable application image using the builder. + * @param appSourcePath - The path to the application source code. + * @param imageToBuild - The name of the image to build. + */ + azurecontainerapps.buildImageFromBuilderAsync = function (appSourcePath, imageToBuild) { + return __awaiter(this, void 0, void 0, function () { + var environmentVariables, runtimeStack, runtimeStackSplit, platformName, platformVersion, builderStack; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + // Install the pack CLI + return [4 /*yield*/, this.appHelper.installPackCliAsync()]; + case 1: + // Install the pack CLI + _a.sent(); + this.toolHelper.writeInfo("Successfully installed the pack CLI."); + // Enable experimental features for the pack CLI + return [4 /*yield*/, this.appHelper.enablePackCliExperimentalFeaturesAsync()]; + case 2: + // Enable experimental features for the pack CLI + _a.sent(); + this.toolHelper.writeInfo("Successfully enabled experimental features for the pack CLI."); + environmentVariables = []; + runtimeStack = this.toolHelper.getInput('runtimeStack', false); + if (!this.util.isNullOrEmpty(runtimeStack)) { + runtimeStackSplit = runtimeStack.split(':'); + platformName = runtimeStackSplit[0] == "dotnetcore" ? "dotnet" : runtimeStackSplit[0]; + platformVersion = runtimeStackSplit[1]; + environmentVariables.push("ORYX_PLATFORM_NAME=" + platformName); + environmentVariables.push("ORYX_PLATFORM_VERSION=" + platformVersion); + } + builderStack = this.toolHelper.getInput('builderStack', false); + // Set the target port on the image produced by the builder + if (!this.util.isNullOrEmpty(this.targetPort)) { + environmentVariables.push("ORYX_RUNTIME_PORT=" + this.targetPort); + } + this.toolHelper.writeInfo("Building image \"" + imageToBuild + "\" using the Oryx++ Builder"); + // Set the Oryx++ Builder as the default builder locally + return [4 /*yield*/, this.appHelper.setDefaultBuilder()]; + case 3: + // Set the Oryx++ Builder as the default builder locally + _a.sent(); + // Create a runnable application image + return [4 /*yield*/, this.appHelper.createRunnableAppImage(imageToBuild, appSourcePath, environmentVariables, builderStack)]; + case 4: + // Create a runnable application image + _a.sent(); + // If telemetry is enabled, log that the builder scenario was targeted for this task + this.telemetryHelper.setBuilderScenario(); + return [2 /*return*/]; + } + }); + }); + }; + /** + * Builds a runnable application image using a provided or discovered Dockerfile. + * @param appSourcePath - The path to the application source code. + * @param dockerfilePath - The path to the Dockerfile to build. + * @param imageToBuild - The name of the image to build. + */ + azurecontainerapps.buildImageFromDockerfile = function (appSourcePath, dockerfilePath, imageToBuild) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + this.toolHelper.writeInfo("Building image \"" + imageToBuild + "\" using the provided Dockerfile"); + return [4 /*yield*/, this.appHelper.createRunnableAppImageFromDockerfile(imageToBuild, appSourcePath, dockerfilePath)]; + case 1: + _a.sent(); + // If telemetry is enabled, log that the Dockerfile scenario was targeted for this task + this.telemetryHelper.setDockerfileScenario(); + return [2 /*return*/]; + } + }); + }); + }; + /** + * Sets up the Container App properties that will be passed through to the Azure CLI when a YAML configuration + * file is not provided. + */ + azurecontainerapps.setupContainerAppProperties = function () { + this.commandLineArgs = []; + // Get the ingress inputs + this.ingress = this.toolHelper.getInput('ingress', false); + this.targetPort = this.toolHelper.getInput('targetPort', false); + // If both ingress and target port were not provided for an existing Container App, or if ingress is to be disabled, + // use the 'update' command, otherwise we should use the 'up' command that performs a PATCH operation on the ingress properties. + this.shouldUseUpdateCommand = this.containerAppExists && + this.util.isNullOrEmpty(this.targetPort) && + (this.util.isNullOrEmpty(this.ingress) || this.ingress == 'disabled'); + // Pass the Container Registry credentials when creating a Container App or updating a Container App via the 'up' command + if (!this.util.isNullOrEmpty(this.registryUrl) && !this.util.isNullOrEmpty(this.registryUsername) && !this.util.isNullOrEmpty(this.registryPassword) && + (!this.containerAppExists || (this.containerAppExists && !this.shouldUseUpdateCommand))) { + this.commandLineArgs.push("--registry-server " + this.registryUrl, "--registry-username " + this.registryUsername, "--registry-password " + this.registryPassword); + } + // Determine default values only for the 'create' scenario to avoid overriding existing values for the 'update' scenario + if (!this.containerAppExists) { + this.ingressEnabled = true; + // Set the ingress value to 'external' if it was not provided + if (this.util.isNullOrEmpty(this.ingress)) { + this.ingress = 'external'; + this.toolHelper.writeInfo("Default ingress value: " + this.ingress); + } + // Set the value of ingressEnabled to 'false' if ingress was provided as 'disabled' + if (this.ingress == 'disabled') { + this.ingressEnabled = false; + this.toolHelper.writeInfo("Ingress is disabled for this Container App."); + } + // Handle setup for ingress values when enabled + if (this.ingressEnabled) { + // Get the target port if provided, or set it to the default value + this.targetPort = this.toolHelper.getInput('targetPort', false); + // Set the target port to 80 if it was not provided + if (this.util.isNullOrEmpty(this.targetPort)) { + this.targetPort = '80'; + this.toolHelper.writeInfo("Default target port: " + this.targetPort); + } + // Add the ingress value and target port to the optional arguments array + // Note: this step should be skipped if we're updating an existing Container App (ingress is enabled via a separate command) + this.commandLineArgs.push("--ingress " + this.ingress); + this.commandLineArgs.push("--target-port " + this.targetPort); + } + } + var environmentVariables = this.toolHelper.getInput('environmentVariables', false); + // Add user-specified environment variables + if (!this.util.isNullOrEmpty(environmentVariables)) { + // The --replace-env-vars flag is only used for the 'update' command, + // otherwise --env-vars is used for 'create' and 'up' + if (this.shouldUseUpdateCommand) { + this.commandLineArgs.push("--replace-env-vars " + environmentVariables); + } + else { + this.commandLineArgs.push("--env-vars " + environmentVariables); + } + } + // Ensure '-i' argument and '--source' argument are not both provided + if (!this.util.isNullOrEmpty(this.imageToDeploy)) { + this.commandLineArgs.push("-i " + this.imageToDeploy); + } + else if (this.shouldCreateOrUpdateContainerAppWithUp) { + this.commandLineArgs.push("--source " + this.appSourcePath); + this.commandLineArgs.push("-l " + this.location); + } + }; + /** + * Creates or updates the Container App. + */ + azurecontainerapps.createOrUpdateContainerApp = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!!this.containerAppExists) return [3 /*break*/, 7]; + if (!!this.util.isNullOrEmpty(this.yamlConfigPath)) return [3 /*break*/, 2]; + // Create the Container App from the YAML configuration file + return [4 /*yield*/, this.appHelper.createContainerAppFromYaml(this.containerAppName, this.resourceGroup, this.yamlConfigPath)]; + case 1: + // Create the Container App from the YAML configuration file + _a.sent(); + return [3 /*break*/, 6]; + case 2: + if (!this.shouldCreateOrUpdateContainerAppWithUp) return [3 /*break*/, 4]; + return [4 /*yield*/, this.appHelper.createOrUpdateContainerAppWithUp(this.containerAppName, this.resourceGroup, this.commandLineArgs)]; + case 3: + _a.sent(); + return [3 /*break*/, 6]; + case 4: + // Create the Container App from command line arguments + return [4 /*yield*/, this.appHelper.createContainerApp(this.containerAppName, this.resourceGroup, this.containerAppEnvironment, this.commandLineArgs)]; + case 5: + // Create the Container App from command line arguments + _a.sent(); + _a.label = 6; + case 6: return [2 /*return*/]; + case 7: + if (!!this.util.isNullOrEmpty(this.yamlConfigPath)) return [3 /*break*/, 9]; + // Update the Container App from the YAML configuration file + return [4 /*yield*/, this.appHelper.updateContainerAppFromYaml(this.containerAppName, this.resourceGroup, this.yamlConfigPath)]; + case 8: + // Update the Container App from the YAML configuration file + _a.sent(); + return [2 /*return*/]; + case 9: + if (!(this.shouldUseUpdateCommand && !this.shouldCreateOrUpdateContainerAppWithUp)) return [3 /*break*/, 13]; + if (!(!this.util.isNullOrEmpty(this.registryUrl) && !this.util.isNullOrEmpty(this.registryUsername) && !this.util.isNullOrEmpty(this.registryPassword))) return [3 /*break*/, 11]; + return [4 /*yield*/, this.appHelper.updateContainerAppRegistryDetails(this.containerAppName, this.resourceGroup, this.registryUrl, this.registryUsername, this.registryPassword)]; + case 10: + _a.sent(); + _a.label = 11; + case 11: + // Update the Container App using the 'update' command + return [4 /*yield*/, this.appHelper.updateContainerApp(this.containerAppName, this.resourceGroup, this.commandLineArgs)]; + case 12: + // Update the Container App using the 'update' command + _a.sent(); + return [3 /*break*/, 17]; + case 13: + if (!this.shouldCreateOrUpdateContainerAppWithUp) return [3 /*break*/, 15]; + return [4 /*yield*/, this.appHelper.createOrUpdateContainerAppWithUp(this.containerAppName, this.resourceGroup, this.commandLineArgs)]; + case 14: + _a.sent(); + return [3 /*break*/, 17]; + case 15: + // Update the Container App using the 'up' command + return [4 /*yield*/, this.appHelper.updateContainerAppWithUp(this.containerAppName, this.resourceGroup, this.commandLineArgs, this.ingress, this.targetPort)]; + case 16: + // Update the Container App using the 'up' command + _a.sent(); + _a.label = 17; + case 17: + if (!(this.ingress == 'disabled')) return [3 /*break*/, 19]; + return [4 /*yield*/, this.appHelper.disableContainerAppIngress(this.containerAppName, this.resourceGroup)]; + case 18: + _a.sent(); + _a.label = 19; + case 19: return [2 /*return*/]; + } + }); + }); + }; + return azurecontainerapps; +}()); +exports.azurecontainerapps = azurecontainerapps; +azurecontainerapps.runMain(); + + +/***/ }), + +/***/ 5688: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.issue = exports.issueCommand = void 0; +const os = __importStar(__nccwpck_require__(2037)); +const utils_1 = __nccwpck_require__(869); +/** + * Commands + * + * Command Format: + * ::name key=value,key=value::message + * + * Examples: + * ::warning::This is the message + * ::set-env name=MY_VAR::some value + */ +function issueCommand(command, properties, message) { + const cmd = new Command(command, properties, message); + process.stdout.write(cmd.toString() + os.EOL); +} +exports.issueCommand = issueCommand; +function issue(name, message = '') { + issueCommand(name, {}, message); +} +exports.issue = issue; +const CMD_STRING = '::'; +class Command { + constructor(command, properties, message) { + if (!command) { + command = 'missing.command'; + } + this.command = command; + this.properties = properties; + this.message = message; + } + toString() { + let cmdStr = CMD_STRING + this.command; + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += ' '; + let first = true; + for (const key in this.properties) { + if (this.properties.hasOwnProperty(key)) { + const val = this.properties[key]; + if (val) { + if (first) { + first = false; + } + else { + cmdStr += ','; + } + cmdStr += `${key}=${escapeProperty(val)}`; + } + } + } + } + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; + return cmdStr; + } +} +function escapeData(s) { + return utils_1.toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} +function escapeProperty(s) { + return utils_1.toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); +} +//# sourceMappingURL=command.js.map + +/***/ }), + +/***/ 3195: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getIDToken = exports.getState = exports.saveState = exports.group = exports.endGroup = exports.startGroup = exports.info = exports.notice = exports.warning = exports.error = exports.debug = exports.isDebug = exports.setFailed = exports.setCommandEcho = exports.setOutput = exports.getBooleanInput = exports.getMultilineInput = exports.getInput = exports.addPath = exports.setSecret = exports.exportVariable = exports.ExitCode = void 0; +const command_1 = __nccwpck_require__(5688); +const file_command_1 = __nccwpck_require__(3930); +const utils_1 = __nccwpck_require__(869); +const os = __importStar(__nccwpck_require__(2037)); +const path = __importStar(__nccwpck_require__(1017)); +const oidc_utils_1 = __nccwpck_require__(1755); +/** + * The code to exit an action + */ +var ExitCode; +(function (ExitCode) { + /** + * A code indicating that the action was successful + */ + ExitCode[ExitCode["Success"] = 0] = "Success"; + /** + * A code indicating that the action was a failure + */ + ExitCode[ExitCode["Failure"] = 1] = "Failure"; +})(ExitCode = exports.ExitCode || (exports.ExitCode = {})); +//----------------------------------------------------------------------- +// Variables +//----------------------------------------------------------------------- +/** + * Sets env variable for this action and future actions in the job + * @param name the name of the variable to set + * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function exportVariable(name, val) { + const convertedVal = utils_1.toCommandValue(val); + process.env[name] = convertedVal; + const filePath = process.env['GITHUB_ENV'] || ''; + if (filePath) { + return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val)); + } + command_1.issueCommand('set-env', { name }, convertedVal); +} +exports.exportVariable = exportVariable; +/** + * Registers a secret which will get masked from logs + * @param secret value of the secret + */ +function setSecret(secret) { + command_1.issueCommand('add-mask', {}, secret); +} +exports.setSecret = setSecret; +/** + * Prepends inputPath to the PATH (for this action and future actions) + * @param inputPath + */ +function addPath(inputPath) { + const filePath = process.env['GITHUB_PATH'] || ''; + if (filePath) { + file_command_1.issueFileCommand('PATH', inputPath); + } + else { + command_1.issueCommand('add-path', {}, inputPath); + } + process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; +} +exports.addPath = addPath; +/** + * Gets the value of an input. + * Unless trimWhitespace is set to false in InputOptions, the value is also trimmed. + * Returns an empty string if the value is not defined. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string + */ +function getInput(name, options) { + const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; + if (options && options.required && !val) { + throw new Error(`Input required and not supplied: ${name}`); + } + if (options && options.trimWhitespace === false) { + return val; + } + return val.trim(); +} +exports.getInput = getInput; +/** + * Gets the values of an multiline input. Each value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string[] + * + */ +function getMultilineInput(name, options) { + const inputs = getInput(name, options) + .split('\n') + .filter(x => x !== ''); + if (options && options.trimWhitespace === false) { + return inputs; + } + return inputs.map(input => input.trim()); +} +exports.getMultilineInput = getMultilineInput; +/** + * Gets the input value of the boolean type in the YAML 1.2 "core schema" specification. + * Support boolean input list: `true | True | TRUE | false | False | FALSE` . + * The return value is also in boolean type. + * ref: https://yaml.org/spec/1.2/spec.html#id2804923 + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns boolean + */ +function getBooleanInput(name, options) { + const trueValue = ['true', 'True', 'TRUE']; + const falseValue = ['false', 'False', 'FALSE']; + const val = getInput(name, options); + if (trueValue.includes(val)) + return true; + if (falseValue.includes(val)) + return false; + throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` + + `Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); +} +exports.getBooleanInput = getBooleanInput; +/** + * Sets the value of an output. + * + * @param name name of the output to set + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function setOutput(name, value) { + const filePath = process.env['GITHUB_OUTPUT'] || ''; + if (filePath) { + return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value)); + } + process.stdout.write(os.EOL); + command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value)); +} +exports.setOutput = setOutput; +/** + * Enables or disables the echoing of commands into stdout for the rest of the step. + * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. + * + */ +function setCommandEcho(enabled) { + command_1.issue('echo', enabled ? 'on' : 'off'); +} +exports.setCommandEcho = setCommandEcho; +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- +/** + * Sets the action status to failed. + * When the action exits it will be with an exit code of 1 + * @param message add error issue message + */ +function setFailed(message) { + process.exitCode = ExitCode.Failure; + error(message); +} +exports.setFailed = setFailed; +//----------------------------------------------------------------------- +// Logging Commands +//----------------------------------------------------------------------- +/** + * Gets whether Actions Step Debug is on or not + */ +function isDebug() { + return process.env['RUNNER_DEBUG'] === '1'; +} +exports.isDebug = isDebug; +/** + * Writes debug message to user log + * @param message debug message + */ +function debug(message) { + command_1.issueCommand('debug', {}, message); +} +exports.debug = debug; +/** + * Adds an error issue + * @param message error issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. + */ +function error(message, properties = {}) { + command_1.issueCommand('error', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message); +} +exports.error = error; +/** + * Adds a warning issue + * @param message warning issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. + */ +function warning(message, properties = {}) { + command_1.issueCommand('warning', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message); +} +exports.warning = warning; +/** + * Adds a notice issue + * @param message notice issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. + */ +function notice(message, properties = {}) { + command_1.issueCommand('notice', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message); +} +exports.notice = notice; +/** + * Writes info to log with console.log. + * @param message info message + */ +function info(message) { + process.stdout.write(message + os.EOL); +} +exports.info = info; +/** + * Begin an output group. + * + * Output until the next `groupEnd` will be foldable in this group + * + * @param name The name of the output group + */ +function startGroup(name) { + command_1.issue('group', name); +} +exports.startGroup = startGroup; +/** + * End an output group. + */ +function endGroup() { + command_1.issue('endgroup'); +} +exports.endGroup = endGroup; +/** + * Wrap an asynchronous function call in a group. + * + * Returns the same type as the function itself. + * + * @param name The name of the group + * @param fn The function to wrap in the group + */ +function group(name, fn) { + return __awaiter(this, void 0, void 0, function* () { + startGroup(name); + let result; + try { + result = yield fn(); + } + finally { + endGroup(); + } + return result; + }); +} +exports.group = group; +//----------------------------------------------------------------------- +// Wrapper action state +//----------------------------------------------------------------------- +/** + * Saves state for current action, the state can only be retrieved by this action's post job execution. + * + * @param name name of the state to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function saveState(name, value) { + const filePath = process.env['GITHUB_STATE'] || ''; + if (filePath) { + return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value)); + } + command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value)); +} +exports.saveState = saveState; +/** + * Gets the value of an state set by this action's main execution. + * + * @param name name of the state to get + * @returns string + */ +function getState(name) { + return process.env[`STATE_${name}`] || ''; +} +exports.getState = getState; +function getIDToken(aud) { + return __awaiter(this, void 0, void 0, function* () { + return yield oidc_utils_1.OidcClient.getIDToken(aud); + }); +} +exports.getIDToken = getIDToken; +/** + * Summary exports + */ +var summary_1 = __nccwpck_require__(8606); +Object.defineProperty(exports, "summary", ({ enumerable: true, get: function () { return summary_1.summary; } })); +/** + * @deprecated use core.summary + */ +var summary_2 = __nccwpck_require__(8606); +Object.defineProperty(exports, "markdownSummary", ({ enumerable: true, get: function () { return summary_2.markdownSummary; } })); +/** + * Path exports + */ +var path_utils_1 = __nccwpck_require__(397); +Object.defineProperty(exports, "toPosixPath", ({ enumerable: true, get: function () { return path_utils_1.toPosixPath; } })); +Object.defineProperty(exports, "toWin32Path", ({ enumerable: true, get: function () { return path_utils_1.toWin32Path; } })); +Object.defineProperty(exports, "toPlatformPath", ({ enumerable: true, get: function () { return path_utils_1.toPlatformPath; } })); +//# sourceMappingURL=core.js.map + +/***/ }), + +/***/ 3930: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +// For internal use, subject to change. +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.prepareKeyValueMessage = exports.issueFileCommand = void 0; +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +const fs = __importStar(__nccwpck_require__(7147)); +const os = __importStar(__nccwpck_require__(2037)); +const uuid_1 = __nccwpck_require__(5814); +const utils_1 = __nccwpck_require__(869); +function issueFileCommand(command, message) { + const filePath = process.env[`GITHUB_${command}`]; + if (!filePath) { + throw new Error(`Unable to find environment variable for file command ${command}`); + } + if (!fs.existsSync(filePath)) { + throw new Error(`Missing file at path: ${filePath}`); + } + fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, { + encoding: 'utf8' + }); +} +exports.issueFileCommand = issueFileCommand; +function prepareKeyValueMessage(key, value) { + const delimiter = `ghadelimiter_${uuid_1.v4()}`; + const convertedValue = utils_1.toCommandValue(value); + // These should realistically never happen, but just in case someone finds a + // way to exploit uuid generation let's not allow keys or values that contain + // the delimiter. + if (key.includes(delimiter)) { + throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); + } + if (convertedValue.includes(delimiter)) { + throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); + } + return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`; +} +exports.prepareKeyValueMessage = prepareKeyValueMessage; +//# sourceMappingURL=file-command.js.map + +/***/ }), + +/***/ 1755: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.OidcClient = void 0; +const http_client_1 = __nccwpck_require__(9780); +const auth_1 = __nccwpck_require__(8833); +const core_1 = __nccwpck_require__(3195); +class OidcClient { + static createHttpClient(allowRetry = true, maxRetry = 10) { + const requestOptions = { + allowRetries: allowRetry, + maxRetries: maxRetry + }; + return new http_client_1.HttpClient('actions/oidc-client', [new auth_1.BearerCredentialHandler(OidcClient.getRequestToken())], requestOptions); + } + static getRequestToken() { + const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']; + if (!token) { + throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable'); + } + return token; + } + static getIDTokenUrl() { + const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL']; + if (!runtimeUrl) { + throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable'); + } + return runtimeUrl; + } + static getCall(id_token_url) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const httpclient = OidcClient.createHttpClient(); + const res = yield httpclient + .getJson(id_token_url) + .catch(error => { + throw new Error(`Failed to get ID Token. \n + Error Code : ${error.statusCode}\n + Error Message: ${error.message}`); + }); + const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value; + if (!id_token) { + throw new Error('Response json body do not have ID Token field'); + } + return id_token; + }); + } + static getIDToken(audience) { + return __awaiter(this, void 0, void 0, function* () { + try { + // New ID Token is requested from action service + let id_token_url = OidcClient.getIDTokenUrl(); + if (audience) { + const encodedAudience = encodeURIComponent(audience); + id_token_url = `${id_token_url}&audience=${encodedAudience}`; + } + core_1.debug(`ID token url is ${id_token_url}`); + const id_token = yield OidcClient.getCall(id_token_url); + core_1.setSecret(id_token); + return id_token; + } + catch (error) { + throw new Error(`Error message: ${error.message}`); + } + }); + } +} +exports.OidcClient = OidcClient; +//# sourceMappingURL=oidc-utils.js.map + +/***/ }), + +/***/ 397: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.toPlatformPath = exports.toWin32Path = exports.toPosixPath = void 0; +const path = __importStar(__nccwpck_require__(1017)); +/** + * toPosixPath converts the given path to the posix form. On Windows, \\ will be + * replaced with /. + * + * @param pth. Path to transform. + * @return string Posix path. + */ +function toPosixPath(pth) { + return pth.replace(/[\\]/g, '/'); +} +exports.toPosixPath = toPosixPath; +/** + * toWin32Path converts the given path to the win32 form. On Linux, / will be + * replaced with \\. + * + * @param pth. Path to transform. + * @return string Win32 path. + */ +function toWin32Path(pth) { + return pth.replace(/[/]/g, '\\'); +} +exports.toWin32Path = toWin32Path; +/** + * toPlatformPath converts the given path to a platform-specific path. It does + * this by replacing instances of / and \ with the platform-specific path + * separator. + * + * @param pth The path to platformize. + * @return string The platform-specific path. + */ +function toPlatformPath(pth) { + return pth.replace(/[/\\]/g, path.sep); +} +exports.toPlatformPath = toPlatformPath; +//# sourceMappingURL=path-utils.js.map + +/***/ }), + +/***/ 8606: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0; +const os_1 = __nccwpck_require__(2037); +const fs_1 = __nccwpck_require__(7147); +const { access, appendFile, writeFile } = fs_1.promises; +exports.SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'; +exports.SUMMARY_DOCS_URL = 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'; +class Summary { + constructor() { + this._buffer = ''; + } + /** + * Finds the summary file path from the environment, rejects if env var is not found or file does not exist + * Also checks r/w permissions. + * + * @returns step summary file path + */ + filePath() { + return __awaiter(this, void 0, void 0, function* () { + if (this._filePath) { + return this._filePath; + } + const pathFromEnv = process.env[exports.SUMMARY_ENV_VAR]; + if (!pathFromEnv) { + throw new Error(`Unable to find environment variable for $${exports.SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`); + } + try { + yield access(pathFromEnv, fs_1.constants.R_OK | fs_1.constants.W_OK); + } + catch (_a) { + throw new Error(`Unable to access summary file: '${pathFromEnv}'. Check if the file has correct read/write permissions.`); + } + this._filePath = pathFromEnv; + return this._filePath; + }); + } + /** + * Wraps content in an HTML tag, adding any HTML attributes + * + * @param {string} tag HTML tag to wrap + * @param {string | null} content content within the tag + * @param {[attribute: string]: string} attrs key-value list of HTML attributes to add + * + * @returns {string} content wrapped in HTML element + */ + wrap(tag, content, attrs = {}) { + const htmlAttrs = Object.entries(attrs) + .map(([key, value]) => ` ${key}="${value}"`) + .join(''); + if (!content) { + return `<${tag}${htmlAttrs}>`; + } + return `<${tag}${htmlAttrs}>${content}`; + } + /** + * Writes text in the buffer to the summary buffer file and empties buffer. Will append by default. + * + * @param {SummaryWriteOptions} [options] (optional) options for write operation + * + * @returns {Promise} summary instance + */ + write(options) { + return __awaiter(this, void 0, void 0, function* () { + const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite); + const filePath = yield this.filePath(); + const writeFunc = overwrite ? writeFile : appendFile; + yield writeFunc(filePath, this._buffer, { encoding: 'utf8' }); + return this.emptyBuffer(); + }); + } + /** + * Clears the summary buffer and wipes the summary file + * + * @returns {Summary} summary instance + */ + clear() { + return __awaiter(this, void 0, void 0, function* () { + return this.emptyBuffer().write({ overwrite: true }); + }); + } + /** + * Returns the current summary buffer as a string + * + * @returns {string} string of summary buffer + */ + stringify() { + return this._buffer; + } + /** + * If the summary buffer is empty + * + * @returns {boolen} true if the buffer is empty + */ + isEmptyBuffer() { + return this._buffer.length === 0; + } + /** + * Resets the summary buffer without writing to summary file + * + * @returns {Summary} summary instance + */ + emptyBuffer() { + this._buffer = ''; + return this; + } + /** + * Adds raw text to the summary buffer + * + * @param {string} text content to add + * @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false) + * + * @returns {Summary} summary instance + */ + addRaw(text, addEOL = false) { + this._buffer += text; + return addEOL ? this.addEOL() : this; + } + /** + * Adds the operating system-specific end-of-line marker to the buffer + * + * @returns {Summary} summary instance + */ + addEOL() { + return this.addRaw(os_1.EOL); + } + /** + * Adds an HTML codeblock to the summary buffer + * + * @param {string} code content to render within fenced code block + * @param {string} lang (optional) language to syntax highlight code + * + * @returns {Summary} summary instance + */ + addCodeBlock(code, lang) { + const attrs = Object.assign({}, (lang && { lang })); + const element = this.wrap('pre', this.wrap('code', code), attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML list to the summary buffer + * + * @param {string[]} items list of items to render + * @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false) + * + * @returns {Summary} summary instance + */ + addList(items, ordered = false) { + const tag = ordered ? 'ol' : 'ul'; + const listItems = items.map(item => this.wrap('li', item)).join(''); + const element = this.wrap(tag, listItems); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML table to the summary buffer + * + * @param {SummaryTableCell[]} rows table rows + * + * @returns {Summary} summary instance + */ + addTable(rows) { + const tableBody = rows + .map(row => { + const cells = row + .map(cell => { + if (typeof cell === 'string') { + return this.wrap('td', cell); + } + const { header, data, colspan, rowspan } = cell; + const tag = header ? 'th' : 'td'; + const attrs = Object.assign(Object.assign({}, (colspan && { colspan })), (rowspan && { rowspan })); + return this.wrap(tag, data, attrs); + }) + .join(''); + return this.wrap('tr', cells); + }) + .join(''); + const element = this.wrap('table', tableBody); + return this.addRaw(element).addEOL(); + } + /** + * Adds a collapsable HTML details element to the summary buffer + * + * @param {string} label text for the closed state + * @param {string} content collapsable content + * + * @returns {Summary} summary instance + */ + addDetails(label, content) { + const element = this.wrap('details', this.wrap('summary', label) + content); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML image tag to the summary buffer + * + * @param {string} src path to the image you to embed + * @param {string} alt text description of the image + * @param {SummaryImageOptions} options (optional) addition image attributes + * + * @returns {Summary} summary instance + */ + addImage(src, alt, options) { + const { width, height } = options || {}; + const attrs = Object.assign(Object.assign({}, (width && { width })), (height && { height })); + const element = this.wrap('img', null, Object.assign({ src, alt }, attrs)); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML section heading element + * + * @param {string} text heading text + * @param {number | string} [level=1] (optional) the heading level, default: 1 + * + * @returns {Summary} summary instance + */ + addHeading(text, level) { + const tag = `h${level}`; + const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag) + ? tag + : 'h1'; + const element = this.wrap(allowedTag, text); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML thematic break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addSeparator() { + const element = this.wrap('hr', null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML line break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addBreak() { + const element = this.wrap('br', null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML blockquote to the summary buffer + * + * @param {string} text quote text + * @param {string} cite (optional) citation url + * + * @returns {Summary} summary instance + */ + addQuote(text, cite) { + const attrs = Object.assign({}, (cite && { cite })); + const element = this.wrap('blockquote', text, attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML anchor tag to the summary buffer + * + * @param {string} text link text/content + * @param {string} href hyperlink + * + * @returns {Summary} summary instance + */ + addLink(text, href) { + const element = this.wrap('a', text, { href }); + return this.addRaw(element).addEOL(); + } +} +const _summary = new Summary(); +/** + * @deprecated use `core.summary` + */ +exports.markdownSummary = _summary; +exports.summary = _summary; +//# sourceMappingURL=summary.js.map + +/***/ }), + +/***/ 869: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.toCommandProperties = exports.toCommandValue = void 0; +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); +} +exports.toCommandValue = toCommandValue; +/** + * + * @param annotationProperties + * @returns The command properties to send with the actual annotation command + * See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646 + */ +function toCommandProperties(annotationProperties) { + if (!Object.keys(annotationProperties).length) { + return {}; + } + return { + title: annotationProperties.title, + file: annotationProperties.file, + line: annotationProperties.startLine, + endLine: annotationProperties.endLine, + col: annotationProperties.startColumn, + endColumn: annotationProperties.endColumn + }; +} +exports.toCommandProperties = toCommandProperties; +//# sourceMappingURL=utils.js.map + +/***/ }), + +/***/ 9714: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getExecOutput = exports.exec = void 0; +const string_decoder_1 = __nccwpck_require__(1576); +const tr = __importStar(__nccwpck_require__(5315)); +/** + * Exec a command. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param commandLine command to execute (can include additional args). Must be correctly escaped. + * @param args optional arguments for tool. Escaping is handled by the lib. + * @param options optional exec options. See ExecOptions + * @returns Promise exit code + */ +function exec(commandLine, args, options) { + return __awaiter(this, void 0, void 0, function* () { + const commandArgs = tr.argStringToArray(commandLine); + if (commandArgs.length === 0) { + throw new Error(`Parameter 'commandLine' cannot be null or empty.`); + } + // Path to tool to execute should be first arg + const toolPath = commandArgs[0]; + args = commandArgs.slice(1).concat(args || []); + const runner = new tr.ToolRunner(toolPath, args, options); + return runner.exec(); + }); +} +exports.exec = exec; +/** + * Exec a command and get the output. + * Output will be streamed to the live console. + * Returns promise with the exit code and collected stdout and stderr + * + * @param commandLine command to execute (can include additional args). Must be correctly escaped. + * @param args optional arguments for tool. Escaping is handled by the lib. + * @param options optional exec options. See ExecOptions + * @returns Promise exit code, stdout, and stderr + */ +function getExecOutput(commandLine, args, options) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + let stdout = ''; + let stderr = ''; + //Using string decoder covers the case where a mult-byte character is split + const stdoutDecoder = new string_decoder_1.StringDecoder('utf8'); + const stderrDecoder = new string_decoder_1.StringDecoder('utf8'); + const originalStdoutListener = (_a = options === null || options === void 0 ? void 0 : options.listeners) === null || _a === void 0 ? void 0 : _a.stdout; + const originalStdErrListener = (_b = options === null || options === void 0 ? void 0 : options.listeners) === null || _b === void 0 ? void 0 : _b.stderr; + const stdErrListener = (data) => { + stderr += stderrDecoder.write(data); + if (originalStdErrListener) { + originalStdErrListener(data); + } + }; + const stdOutListener = (data) => { + stdout += stdoutDecoder.write(data); + if (originalStdoutListener) { + originalStdoutListener(data); + } + }; + const listeners = Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.listeners), { stdout: stdOutListener, stderr: stdErrListener }); + const exitCode = yield exec(commandLine, args, Object.assign(Object.assign({}, options), { listeners })); + //flush any remaining characters + stdout += stdoutDecoder.end(); + stderr += stderrDecoder.end(); + return { + exitCode, + stdout, + stderr + }; + }); +} +exports.getExecOutput = getExecOutput; +//# sourceMappingURL=exec.js.map + +/***/ }), + +/***/ 5315: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.argStringToArray = exports.ToolRunner = void 0; +const os = __importStar(__nccwpck_require__(2037)); +const events = __importStar(__nccwpck_require__(2361)); +const child = __importStar(__nccwpck_require__(2081)); +const path = __importStar(__nccwpck_require__(1017)); +const io = __importStar(__nccwpck_require__(9529)); +const ioUtil = __importStar(__nccwpck_require__(7821)); +const timers_1 = __nccwpck_require__(9512); +/* eslint-disable @typescript-eslint/unbound-method */ +const IS_WINDOWS = process.platform === 'win32'; +/* + * Class for running command line tools. Handles quoting and arg parsing in a platform agnostic way. + */ +class ToolRunner extends events.EventEmitter { + constructor(toolPath, args, options) { + super(); + if (!toolPath) { + throw new Error("Parameter 'toolPath' cannot be null or empty."); + } + this.toolPath = toolPath; + this.args = args || []; + this.options = options || {}; + } + _debug(message) { + if (this.options.listeners && this.options.listeners.debug) { + this.options.listeners.debug(message); + } + } + _getCommandString(options, noPrefix) { + const toolPath = this._getSpawnFileName(); + const args = this._getSpawnArgs(options); + let cmd = noPrefix ? '' : '[command]'; // omit prefix when piped to a second tool + if (IS_WINDOWS) { + // Windows + cmd file + if (this._isCmdFile()) { + cmd += toolPath; + for (const a of args) { + cmd += ` ${a}`; + } + } + // Windows + verbatim + else if (options.windowsVerbatimArguments) { + cmd += `"${toolPath}"`; + for (const a of args) { + cmd += ` ${a}`; + } + } + // Windows (regular) + else { + cmd += this._windowsQuoteCmdArg(toolPath); + for (const a of args) { + cmd += ` ${this._windowsQuoteCmdArg(a)}`; + } + } + } + else { + // OSX/Linux - this can likely be improved with some form of quoting. + // creating processes on Unix is fundamentally different than Windows. + // on Unix, execvp() takes an arg array. + cmd += toolPath; + for (const a of args) { + cmd += ` ${a}`; + } + } + return cmd; + } + _processLineBuffer(data, strBuffer, onLine) { + try { + let s = strBuffer + data.toString(); + let n = s.indexOf(os.EOL); + while (n > -1) { + const line = s.substring(0, n); + onLine(line); + // the rest of the string ... + s = s.substring(n + os.EOL.length); + n = s.indexOf(os.EOL); + } + return s; + } + catch (err) { + // streaming lines to console is best effort. Don't fail a build. + this._debug(`error processing line. Failed with error ${err}`); + return ''; + } + } + _getSpawnFileName() { + if (IS_WINDOWS) { + if (this._isCmdFile()) { + return process.env['COMSPEC'] || 'cmd.exe'; + } + } + return this.toolPath; + } + _getSpawnArgs(options) { + if (IS_WINDOWS) { + if (this._isCmdFile()) { + let argline = `/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`; + for (const a of this.args) { + argline += ' '; + argline += options.windowsVerbatimArguments + ? a + : this._windowsQuoteCmdArg(a); + } + argline += '"'; + return [argline]; + } + } + return this.args; + } + _endsWith(str, end) { + return str.endsWith(end); + } + _isCmdFile() { + const upperToolPath = this.toolPath.toUpperCase(); + return (this._endsWith(upperToolPath, '.CMD') || + this._endsWith(upperToolPath, '.BAT')); + } + _windowsQuoteCmdArg(arg) { + // for .exe, apply the normal quoting rules that libuv applies + if (!this._isCmdFile()) { + return this._uvQuoteCmdArg(arg); + } + // otherwise apply quoting rules specific to the cmd.exe command line parser. + // the libuv rules are generic and are not designed specifically for cmd.exe + // command line parser. + // + // for a detailed description of the cmd.exe command line parser, refer to + // http://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/7970912#7970912 + // need quotes for empty arg + if (!arg) { + return '""'; + } + // determine whether the arg needs to be quoted + const cmdSpecialChars = [ + ' ', + '\t', + '&', + '(', + ')', + '[', + ']', + '{', + '}', + '^', + '=', + ';', + '!', + "'", + '+', + ',', + '`', + '~', + '|', + '<', + '>', + '"' + ]; + let needsQuotes = false; + for (const char of arg) { + if (cmdSpecialChars.some(x => x === char)) { + needsQuotes = true; + break; + } + } + // short-circuit if quotes not needed + if (!needsQuotes) { + return arg; + } + // the following quoting rules are very similar to the rules that by libuv applies. + // + // 1) wrap the string in quotes + // + // 2) double-up quotes - i.e. " => "" + // + // this is different from the libuv quoting rules. libuv replaces " with \", which unfortunately + // doesn't work well with a cmd.exe command line. + // + // note, replacing " with "" also works well if the arg is passed to a downstream .NET console app. + // for example, the command line: + // foo.exe "myarg:""my val""" + // is parsed by a .NET console app into an arg array: + // [ "myarg:\"my val\"" ] + // which is the same end result when applying libuv quoting rules. although the actual + // command line from libuv quoting rules would look like: + // foo.exe "myarg:\"my val\"" + // + // 3) double-up slashes that precede a quote, + // e.g. hello \world => "hello \world" + // hello\"world => "hello\\""world" + // hello\\"world => "hello\\\\""world" + // hello world\ => "hello world\\" + // + // technically this is not required for a cmd.exe command line, or the batch argument parser. + // the reasons for including this as a .cmd quoting rule are: + // + // a) this is optimized for the scenario where the argument is passed from the .cmd file to an + // external program. many programs (e.g. .NET console apps) rely on the slash-doubling rule. + // + // b) it's what we've been doing previously (by deferring to node default behavior) and we + // haven't heard any complaints about that aspect. + // + // note, a weakness of the quoting rules chosen here, is that % is not escaped. in fact, % cannot be + // escaped when used on the command line directly - even though within a .cmd file % can be escaped + // by using %%. + // + // the saving grace is, on the command line, %var% is left as-is if var is not defined. this contrasts + // the line parsing rules within a .cmd file, where if var is not defined it is replaced with nothing. + // + // one option that was explored was replacing % with ^% - i.e. %var% => ^%var^%. this hack would + // often work, since it is unlikely that var^ would exist, and the ^ character is removed when the + // variable is used. the problem, however, is that ^ is not removed when %* is used to pass the args + // to an external program. + // + // an unexplored potential solution for the % escaping problem, is to create a wrapper .cmd file. + // % can be escaped within a .cmd file. + let reverse = '"'; + let quoteHit = true; + for (let i = arg.length; i > 0; i--) { + // walk the string in reverse + reverse += arg[i - 1]; + if (quoteHit && arg[i - 1] === '\\') { + reverse += '\\'; // double the slash + } + else if (arg[i - 1] === '"') { + quoteHit = true; + reverse += '"'; // double the quote + } + else { + quoteHit = false; + } + } + reverse += '"'; + return reverse + .split('') + .reverse() + .join(''); + } + _uvQuoteCmdArg(arg) { + // Tool runner wraps child_process.spawn() and needs to apply the same quoting as + // Node in certain cases where the undocumented spawn option windowsVerbatimArguments + // is used. + // + // Since this function is a port of quote_cmd_arg from Node 4.x (technically, lib UV, + // see https://github.com/nodejs/node/blob/v4.x/deps/uv/src/win/process.c for details), + // pasting copyright notice from Node within this function: + // + // Copyright Joyent, Inc. and other Node contributors. All rights reserved. + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to + // deal in the Software without restriction, including without limitation the + // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + // sell copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in + // all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + // IN THE SOFTWARE. + if (!arg) { + // Need double quotation for empty argument + return '""'; + } + if (!arg.includes(' ') && !arg.includes('\t') && !arg.includes('"')) { + // No quotation needed + return arg; + } + if (!arg.includes('"') && !arg.includes('\\')) { + // No embedded double quotes or backslashes, so I can just wrap + // quote marks around the whole thing. + return `"${arg}"`; + } + // Expected input/output: + // input : hello"world + // output: "hello\"world" + // input : hello""world + // output: "hello\"\"world" + // input : hello\world + // output: hello\world + // input : hello\\world + // output: hello\\world + // input : hello\"world + // output: "hello\\\"world" + // input : hello\\"world + // output: "hello\\\\\"world" + // input : hello world\ + // output: "hello world\\" - note the comment in libuv actually reads "hello world\" + // but it appears the comment is wrong, it should be "hello world\\" + let reverse = '"'; + let quoteHit = true; + for (let i = arg.length; i > 0; i--) { + // walk the string in reverse + reverse += arg[i - 1]; + if (quoteHit && arg[i - 1] === '\\') { + reverse += '\\'; + } + else if (arg[i - 1] === '"') { + quoteHit = true; + reverse += '\\'; + } + else { + quoteHit = false; + } + } + reverse += '"'; + return reverse + .split('') + .reverse() + .join(''); + } + _cloneExecOptions(options) { + options = options || {}; + const result = { + cwd: options.cwd || process.cwd(), + env: options.env || process.env, + silent: options.silent || false, + windowsVerbatimArguments: options.windowsVerbatimArguments || false, + failOnStdErr: options.failOnStdErr || false, + ignoreReturnCode: options.ignoreReturnCode || false, + delay: options.delay || 10000 + }; + result.outStream = options.outStream || process.stdout; + result.errStream = options.errStream || process.stderr; + return result; + } + _getSpawnOptions(options, toolPath) { + options = options || {}; + const result = {}; + result.cwd = options.cwd; + result.env = options.env; + result['windowsVerbatimArguments'] = + options.windowsVerbatimArguments || this._isCmdFile(); + if (options.windowsVerbatimArguments) { + result.argv0 = `"${toolPath}"`; + } + return result; + } + /** + * Exec a tool. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param tool path to tool to exec + * @param options optional exec options. See ExecOptions + * @returns number + */ + exec() { + return __awaiter(this, void 0, void 0, function* () { + // root the tool path if it is unrooted and contains relative pathing + if (!ioUtil.isRooted(this.toolPath) && + (this.toolPath.includes('/') || + (IS_WINDOWS && this.toolPath.includes('\\')))) { + // prefer options.cwd if it is specified, however options.cwd may also need to be rooted + this.toolPath = path.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath); + } + // if the tool is only a file name, then resolve it from the PATH + // otherwise verify it exists (add extension on Windows if necessary) + this.toolPath = yield io.which(this.toolPath, true); + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + this._debug(`exec tool: ${this.toolPath}`); + this._debug('arguments:'); + for (const arg of this.args) { + this._debug(` ${arg}`); + } + const optionsNonNull = this._cloneExecOptions(this.options); + if (!optionsNonNull.silent && optionsNonNull.outStream) { + optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os.EOL); + } + const state = new ExecState(optionsNonNull, this.toolPath); + state.on('debug', (message) => { + this._debug(message); + }); + if (this.options.cwd && !(yield ioUtil.exists(this.options.cwd))) { + return reject(new Error(`The cwd: ${this.options.cwd} does not exist!`)); + } + const fileName = this._getSpawnFileName(); + const cp = child.spawn(fileName, this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(this.options, fileName)); + let stdbuffer = ''; + if (cp.stdout) { + cp.stdout.on('data', (data) => { + if (this.options.listeners && this.options.listeners.stdout) { + this.options.listeners.stdout(data); + } + if (!optionsNonNull.silent && optionsNonNull.outStream) { + optionsNonNull.outStream.write(data); + } + stdbuffer = this._processLineBuffer(data, stdbuffer, (line) => { + if (this.options.listeners && this.options.listeners.stdline) { + this.options.listeners.stdline(line); + } + }); + }); + } + let errbuffer = ''; + if (cp.stderr) { + cp.stderr.on('data', (data) => { + state.processStderr = true; + if (this.options.listeners && this.options.listeners.stderr) { + this.options.listeners.stderr(data); + } + if (!optionsNonNull.silent && + optionsNonNull.errStream && + optionsNonNull.outStream) { + const s = optionsNonNull.failOnStdErr + ? optionsNonNull.errStream + : optionsNonNull.outStream; + s.write(data); + } + errbuffer = this._processLineBuffer(data, errbuffer, (line) => { + if (this.options.listeners && this.options.listeners.errline) { + this.options.listeners.errline(line); + } + }); + }); + } + cp.on('error', (err) => { + state.processError = err.message; + state.processExited = true; + state.processClosed = true; + state.CheckComplete(); + }); + cp.on('exit', (code) => { + state.processExitCode = code; + state.processExited = true; + this._debug(`Exit code ${code} received from tool '${this.toolPath}'`); + state.CheckComplete(); + }); + cp.on('close', (code) => { + state.processExitCode = code; + state.processExited = true; + state.processClosed = true; + this._debug(`STDIO streams have closed for tool '${this.toolPath}'`); + state.CheckComplete(); + }); + state.on('done', (error, exitCode) => { + if (stdbuffer.length > 0) { + this.emit('stdline', stdbuffer); + } + if (errbuffer.length > 0) { + this.emit('errline', errbuffer); + } + cp.removeAllListeners(); + if (error) { + reject(error); + } + else { + resolve(exitCode); + } + }); + if (this.options.input) { + if (!cp.stdin) { + throw new Error('child process missing stdin'); + } + cp.stdin.end(this.options.input); + } + })); + }); + } +} +exports.ToolRunner = ToolRunner; +/** + * Convert an arg string to an array of args. Handles escaping + * + * @param argString string of arguments + * @returns string[] array of arguments + */ +function argStringToArray(argString) { + const args = []; + let inQuotes = false; + let escaped = false; + let arg = ''; + function append(c) { + // we only escape double quotes. + if (escaped && c !== '"') { + arg += '\\'; + } + arg += c; + escaped = false; + } + for (let i = 0; i < argString.length; i++) { + const c = argString.charAt(i); + if (c === '"') { + if (!escaped) { + inQuotes = !inQuotes; + } + else { + append(c); + } + continue; + } + if (c === '\\' && escaped) { + append(c); + continue; + } + if (c === '\\' && inQuotes) { + escaped = true; + continue; + } + if (c === ' ' && !inQuotes) { + if (arg.length > 0) { + args.push(arg); + arg = ''; + } + continue; + } + append(c); + } + if (arg.length > 0) { + args.push(arg.trim()); + } + return args; +} +exports.argStringToArray = argStringToArray; +class ExecState extends events.EventEmitter { + constructor(options, toolPath) { + super(); + this.processClosed = false; // tracks whether the process has exited and stdio is closed + this.processError = ''; + this.processExitCode = 0; + this.processExited = false; // tracks whether the process has exited + this.processStderr = false; // tracks whether stderr was written to + this.delay = 10000; // 10 seconds + this.done = false; + this.timeout = null; + if (!toolPath) { + throw new Error('toolPath must not be empty'); + } + this.options = options; + this.toolPath = toolPath; + if (options.delay) { + this.delay = options.delay; + } + } + CheckComplete() { + if (this.done) { + return; + } + if (this.processClosed) { + this._setResult(); + } + else if (this.processExited) { + this.timeout = timers_1.setTimeout(ExecState.HandleTimeout, this.delay, this); + } + } + _debug(message) { + this.emit('debug', message); + } + _setResult() { + // determine whether there is an error + let error; + if (this.processExited) { + if (this.processError) { + error = new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`); + } + else if (this.processExitCode !== 0 && !this.options.ignoreReturnCode) { + error = new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`); + } + else if (this.processStderr && this.options.failOnStdErr) { + error = new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`); + } + } + // clear the timeout + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } + this.done = true; + this.emit('done', error, this.processExitCode); + } + static HandleTimeout(state) { + if (state.done) { + return; + } + if (!state.processClosed && state.processExited) { + const message = `The STDIO streams did not close within ${state.delay / + 1000} seconds of the exit event from process '${state.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`; + state._debug(message); + } + state._setResult(); + } +} +//# sourceMappingURL=toolrunner.js.map + +/***/ }), + +/***/ 8833: +/***/ (function(__unused_webpack_module, exports) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0; +class BasicCredentialHandler { + constructor(username, password) { + this.username = username; + this.password = password; + } + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.BasicCredentialHandler = BasicCredentialHandler; +class BearerCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Bearer ${this.token}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.BearerCredentialHandler = BearerCredentialHandler; +class PersonalAccessTokenCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler; +//# sourceMappingURL=auth.js.map + +/***/ }), + +/***/ 9780: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0; +const http = __importStar(__nccwpck_require__(3685)); +const https = __importStar(__nccwpck_require__(5687)); +const pm = __importStar(__nccwpck_require__(4492)); +const tunnel = __importStar(__nccwpck_require__(9041)); +var HttpCodes; +(function (HttpCodes) { + HttpCodes[HttpCodes["OK"] = 200] = "OK"; + HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices"; + HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently"; + HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved"; + HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther"; + HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified"; + HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy"; + HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy"; + HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect"; + HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect"; + HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest"; + HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized"; + HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired"; + HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden"; + HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound"; + HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed"; + HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable"; + HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired"; + HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout"; + HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict"; + HttpCodes[HttpCodes["Gone"] = 410] = "Gone"; + HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests"; + HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError"; + HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented"; + HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway"; + HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable"; + HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout"; +})(HttpCodes = exports.HttpCodes || (exports.HttpCodes = {})); +var Headers; +(function (Headers) { + Headers["Accept"] = "accept"; + Headers["ContentType"] = "content-type"; +})(Headers = exports.Headers || (exports.Headers = {})); +var MediaTypes; +(function (MediaTypes) { + MediaTypes["ApplicationJson"] = "application/json"; +})(MediaTypes = exports.MediaTypes || (exports.MediaTypes = {})); +/** + * Returns the proxy URL, depending upon the supplied url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ +function getProxyUrl(serverUrl) { + const proxyUrl = pm.getProxyUrl(new URL(serverUrl)); + return proxyUrl ? proxyUrl.href : ''; +} +exports.getProxyUrl = getProxyUrl; +const HttpRedirectCodes = [ + HttpCodes.MovedPermanently, + HttpCodes.ResourceMoved, + HttpCodes.SeeOther, + HttpCodes.TemporaryRedirect, + HttpCodes.PermanentRedirect +]; +const HttpResponseRetryCodes = [ + HttpCodes.BadGateway, + HttpCodes.ServiceUnavailable, + HttpCodes.GatewayTimeout +]; +const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD']; +const ExponentialBackoffCeiling = 10; +const ExponentialBackoffTimeSlice = 5; +class HttpClientError extends Error { + constructor(message, statusCode) { + super(message); + this.name = 'HttpClientError'; + this.statusCode = statusCode; + Object.setPrototypeOf(this, HttpClientError.prototype); + } +} +exports.HttpClientError = HttpClientError; +class HttpClientResponse { + constructor(message) { + this.message = message; + } + readBody() { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + let output = Buffer.alloc(0); + this.message.on('data', (chunk) => { + output = Buffer.concat([output, chunk]); + }); + this.message.on('end', () => { + resolve(output.toString()); + }); + })); + }); + } + readBodyBuffer() { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + const chunks = []; + this.message.on('data', (chunk) => { + chunks.push(chunk); + }); + this.message.on('end', () => { + resolve(Buffer.concat(chunks)); + }); + })); + }); + } +} +exports.HttpClientResponse = HttpClientResponse; +function isHttps(requestUrl) { + const parsedUrl = new URL(requestUrl); + return parsedUrl.protocol === 'https:'; +} +exports.isHttps = isHttps; +class HttpClient { + constructor(userAgent, handlers, requestOptions) { + this._ignoreSslError = false; + this._allowRedirects = true; + this._allowRedirectDowngrade = false; + this._maxRedirects = 50; + this._allowRetries = false; + this._maxRetries = 1; + this._keepAlive = false; + this._disposed = false; + this.userAgent = userAgent; + this.handlers = handlers || []; + this.requestOptions = requestOptions; + if (requestOptions) { + if (requestOptions.ignoreSslError != null) { + this._ignoreSslError = requestOptions.ignoreSslError; + } + this._socketTimeout = requestOptions.socketTimeout; + if (requestOptions.allowRedirects != null) { + this._allowRedirects = requestOptions.allowRedirects; + } + if (requestOptions.allowRedirectDowngrade != null) { + this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade; + } + if (requestOptions.maxRedirects != null) { + this._maxRedirects = Math.max(requestOptions.maxRedirects, 0); + } + if (requestOptions.keepAlive != null) { + this._keepAlive = requestOptions.keepAlive; + } + if (requestOptions.allowRetries != null) { + this._allowRetries = requestOptions.allowRetries; + } + if (requestOptions.maxRetries != null) { + this._maxRetries = requestOptions.maxRetries; + } + } + } + options(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}); + }); + } + get(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('GET', requestUrl, null, additionalHeaders || {}); + }); + } + del(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('DELETE', requestUrl, null, additionalHeaders || {}); + }); + } + post(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('POST', requestUrl, data, additionalHeaders || {}); + }); + } + patch(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PATCH', requestUrl, data, additionalHeaders || {}); + }); + } + put(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PUT', requestUrl, data, additionalHeaders || {}); + }); + } + head(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('HEAD', requestUrl, null, additionalHeaders || {}); + }); + } + sendStream(verb, requestUrl, stream, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request(verb, requestUrl, stream, additionalHeaders); + }); + } + /** + * Gets a typed object from an endpoint + * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise + */ + getJson(requestUrl, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + const res = yield this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + postJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + putJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + patchJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.patch(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + /** + * Makes a raw http request. + * All other methods such as get, post, patch, and request ultimately call this. + * Prefer get, del, post and patch + */ + request(verb, requestUrl, data, headers) { + return __awaiter(this, void 0, void 0, function* () { + if (this._disposed) { + throw new Error('Client has already been disposed.'); + } + const parsedUrl = new URL(requestUrl); + let info = this._prepareRequest(verb, parsedUrl, headers); + // Only perform retries on reads since writes may not be idempotent. + const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) + ? this._maxRetries + 1 + : 1; + let numTries = 0; + let response; + do { + response = yield this.requestRaw(info, data); + // Check if it's an authentication challenge + if (response && + response.message && + response.message.statusCode === HttpCodes.Unauthorized) { + let authenticationHandler; + for (const handler of this.handlers) { + if (handler.canHandleAuthentication(response)) { + authenticationHandler = handler; + break; + } + } + if (authenticationHandler) { + return authenticationHandler.handleAuthentication(this, info, data); + } + else { + // We have received an unauthorized response but have no handlers to handle it. + // Let the response return to the caller. + return response; + } + } + let redirectsRemaining = this._maxRedirects; + while (response.message.statusCode && + HttpRedirectCodes.includes(response.message.statusCode) && + this._allowRedirects && + redirectsRemaining > 0) { + const redirectUrl = response.message.headers['location']; + if (!redirectUrl) { + // if there's no location to redirect to, we won't + break; + } + const parsedRedirectUrl = new URL(redirectUrl); + if (parsedUrl.protocol === 'https:' && + parsedUrl.protocol !== parsedRedirectUrl.protocol && + !this._allowRedirectDowngrade) { + throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.'); + } + // we need to finish reading the response before reassigning response + // which will leak the open socket. + yield response.readBody(); + // strip authorization header if redirected to a different hostname + if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { + for (const header in headers) { + // header names are case insensitive + if (header.toLowerCase() === 'authorization') { + delete headers[header]; + } + } + } + // let's make the request with the new redirectUrl + info = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = yield this.requestRaw(info, data); + redirectsRemaining--; + } + if (!response.message.statusCode || + !HttpResponseRetryCodes.includes(response.message.statusCode)) { + // If not a retry code, return immediately instead of retrying + return response; + } + numTries += 1; + if (numTries < maxTries) { + yield response.readBody(); + yield this._performExponentialBackoff(numTries); + } + } while (numTries < maxTries); + return response; + }); + } + /** + * Needs to be called if keepAlive is set to true in request options. + */ + dispose() { + if (this._agent) { + this._agent.destroy(); + } + this._disposed = true; + } + /** + * Raw request. + * @param info + * @param data + */ + requestRaw(info, data) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + function callbackForResult(err, res) { + if (err) { + reject(err); + } + else if (!res) { + // If `err` is not passed, then `res` must be passed. + reject(new Error('Unknown error')); + } + else { + resolve(res); + } + } + this.requestRawWithCallback(info, data, callbackForResult); + }); + }); + } + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + requestRawWithCallback(info, data, onResult) { + if (typeof data === 'string') { + if (!info.options.headers) { + info.options.headers = {}; + } + info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); + } + let callbackCalled = false; + function handleResult(err, res) { + if (!callbackCalled) { + callbackCalled = true; + onResult(err, res); + } + } + const req = info.httpModule.request(info.options, (msg) => { + const res = new HttpClientResponse(msg); + handleResult(undefined, res); + }); + let socket; + req.on('socket', sock => { + socket = sock; + }); + // If we ever get disconnected, we want the socket to timeout eventually + req.setTimeout(this._socketTimeout || 3 * 60000, () => { + if (socket) { + socket.end(); + } + handleResult(new Error(`Request timeout: ${info.options.path}`)); + }); + req.on('error', function (err) { + // err has statusCode property + // res should have headers + handleResult(err); + }); + if (data && typeof data === 'string') { + req.write(data, 'utf8'); + } + if (data && typeof data !== 'string') { + data.on('close', function () { + req.end(); + }); + data.pipe(req); + } + else { + req.end(); + } + } + /** + * Gets an http agent. This function is useful when you need an http agent that handles + * routing through a proxy server - depending upon the url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ + getAgent(serverUrl) { + const parsedUrl = new URL(serverUrl); + return this._getAgent(parsedUrl); + } + _prepareRequest(method, requestUrl, headers) { + const info = {}; + info.parsedUrl = requestUrl; + const usingSsl = info.parsedUrl.protocol === 'https:'; + info.httpModule = usingSsl ? https : http; + const defaultPort = usingSsl ? 443 : 80; + info.options = {}; + info.options.host = info.parsedUrl.hostname; + info.options.port = info.parsedUrl.port + ? parseInt(info.parsedUrl.port) + : defaultPort; + info.options.path = + (info.parsedUrl.pathname || '') + (info.parsedUrl.search || ''); + info.options.method = method; + info.options.headers = this._mergeHeaders(headers); + if (this.userAgent != null) { + info.options.headers['user-agent'] = this.userAgent; + } + info.options.agent = this._getAgent(info.parsedUrl); + // gives handlers an opportunity to participate + if (this.handlers) { + for (const handler of this.handlers) { + handler.prepareRequest(info.options); + } + } + return info; + } + _mergeHeaders(headers) { + if (this.requestOptions && this.requestOptions.headers) { + return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {})); + } + return lowercaseKeys(headers || {}); + } + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; + } + return additionalHeaders[header] || clientHeader || _default; + } + _getAgent(parsedUrl) { + let agent; + const proxyUrl = pm.getProxyUrl(parsedUrl); + const useProxy = proxyUrl && proxyUrl.hostname; + if (this._keepAlive && useProxy) { + agent = this._proxyAgent; + } + if (this._keepAlive && !useProxy) { + agent = this._agent; + } + // if agent is already assigned use that agent. + if (agent) { + return agent; + } + const usingSsl = parsedUrl.protocol === 'https:'; + let maxSockets = 100; + if (this.requestOptions) { + maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets; + } + // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis. + if (proxyUrl && proxyUrl.hostname) { + const agentOptions = { + maxSockets, + keepAlive: this._keepAlive, + proxy: Object.assign(Object.assign({}, ((proxyUrl.username || proxyUrl.password) && { + proxyAuth: `${proxyUrl.username}:${proxyUrl.password}` + })), { host: proxyUrl.hostname, port: proxyUrl.port }) + }; + let tunnelAgent; + const overHttps = proxyUrl.protocol === 'https:'; + if (usingSsl) { + tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp; + } + else { + tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp; + } + agent = tunnelAgent(agentOptions); + this._proxyAgent = agent; + } + // if reusing agent across request and tunneling agent isn't assigned create a new agent + if (this._keepAlive && !agent) { + const options = { keepAlive: this._keepAlive, maxSockets }; + agent = usingSsl ? new https.Agent(options) : new http.Agent(options); + this._agent = agent; + } + // if not using private agent and tunnel agent isn't setup then use global agent + if (!agent) { + agent = usingSsl ? https.globalAgent : http.globalAgent; + } + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + agent.options = Object.assign(agent.options || {}, { + rejectUnauthorized: false + }); + } + return agent; + } + _performExponentialBackoff(retryNumber) { + return __awaiter(this, void 0, void 0, function* () { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); + const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); + return new Promise(resolve => setTimeout(() => resolve(), ms)); + }); + } + _processResponse(res, options) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + const statusCode = res.message.statusCode || 0; + const response = { + statusCode, + result: null, + headers: {} + }; + // not found leads to null obj returned + if (statusCode === HttpCodes.NotFound) { + resolve(response); + } + // get the result from the body + function dateTimeDeserializer(key, value) { + if (typeof value === 'string') { + const a = new Date(value); + if (!isNaN(a.valueOf())) { + return a; + } + } + return value; + } + let obj; + let contents; + try { + contents = yield res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) { + obj = JSON.parse(contents, dateTimeDeserializer); + } + else { + obj = JSON.parse(contents); + } + response.result = obj; + } + response.headers = res.message.headers; + } + catch (err) { + // Invalid resource (contents not json); leaving result obj null + } + // note that 3xx redirects are handled by the http layer. + if (statusCode > 299) { + let msg; + // if exception/error in body, attempt to get better error + if (obj && obj.message) { + msg = obj.message; + } + else if (contents && contents.length > 0) { + // it may be the case that the exception is in the body message as string + msg = contents; + } + else { + msg = `Failed request: (${statusCode})`; + } + const err = new HttpClientError(msg, statusCode); + err.result = response.result; + reject(err); + } + else { + resolve(response); + } + })); + }); + } +} +exports.HttpClient = HttpClient; +const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}); +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 4492: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.checkBypass = exports.getProxyUrl = void 0; +function getProxyUrl(reqUrl) { + const usingSsl = reqUrl.protocol === 'https:'; + if (checkBypass(reqUrl)) { + return undefined; + } + const proxyVar = (() => { + if (usingSsl) { + return process.env['https_proxy'] || process.env['HTTPS_PROXY']; + } + else { + return process.env['http_proxy'] || process.env['HTTP_PROXY']; + } + })(); + if (proxyVar) { + try { + return new URL(proxyVar); + } + catch (_a) { + if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://')) + return new URL(`http://${proxyVar}`); + } + } + else { + return undefined; + } +} +exports.getProxyUrl = getProxyUrl; +function checkBypass(reqUrl) { + if (!reqUrl.hostname) { + return false; + } + const reqHost = reqUrl.hostname; + if (isLoopbackAddress(reqHost)) { + return true; + } + const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''; + if (!noProxy) { + return false; + } + // Determine the request port + let reqPort; + if (reqUrl.port) { + reqPort = Number(reqUrl.port); + } + else if (reqUrl.protocol === 'http:') { + reqPort = 80; + } + else if (reqUrl.protocol === 'https:') { + reqPort = 443; + } + // Format the request hostname and hostname with port + const upperReqHosts = [reqUrl.hostname.toUpperCase()]; + if (typeof reqPort === 'number') { + upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); + } + // Compare request host against noproxy + for (const upperNoProxyItem of noProxy + .split(',') + .map(x => x.trim().toUpperCase()) + .filter(x => x)) { + if (upperNoProxyItem === '*' || + upperReqHosts.some(x => x === upperNoProxyItem || + x.endsWith(`.${upperNoProxyItem}`) || + (upperNoProxyItem.startsWith('.') && + x.endsWith(`${upperNoProxyItem}`)))) { + return true; + } + } + return false; +} +exports.checkBypass = checkBypass; +function isLoopbackAddress(host) { + const hostLower = host.toLowerCase(); + return (hostLower === 'localhost' || + hostLower.startsWith('127.') || + hostLower.startsWith('[::1]') || + hostLower.startsWith('[0:0:0:0:0:0:0:1]')); +} +//# sourceMappingURL=proxy.js.map + +/***/ }), + +/***/ 7821: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var _a; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getCmdPath = exports.tryGetExecutablePath = exports.isRooted = exports.isDirectory = exports.exists = exports.READONLY = exports.UV_FS_O_EXLOCK = exports.IS_WINDOWS = exports.unlink = exports.symlink = exports.stat = exports.rmdir = exports.rm = exports.rename = exports.readlink = exports.readdir = exports.open = exports.mkdir = exports.lstat = exports.copyFile = exports.chmod = void 0; +const fs = __importStar(__nccwpck_require__(7147)); +const path = __importStar(__nccwpck_require__(1017)); +_a = fs.promises +// export const {open} = 'fs' +, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.open = _a.open, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rm = _a.rm, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink; +// export const {open} = 'fs' +exports.IS_WINDOWS = process.platform === 'win32'; +// See https://github.com/nodejs/node/blob/d0153aee367422d0858105abec186da4dff0a0c5/deps/uv/include/uv/win.h#L691 +exports.UV_FS_O_EXLOCK = 0x10000000; +exports.READONLY = fs.constants.O_RDONLY; +function exists(fsPath) { + return __awaiter(this, void 0, void 0, function* () { + try { + yield exports.stat(fsPath); + } + catch (err) { + if (err.code === 'ENOENT') { + return false; + } + throw err; + } + return true; + }); +} +exports.exists = exists; +function isDirectory(fsPath, useStat = false) { + return __awaiter(this, void 0, void 0, function* () { + const stats = useStat ? yield exports.stat(fsPath) : yield exports.lstat(fsPath); + return stats.isDirectory(); + }); +} +exports.isDirectory = isDirectory; +/** + * On OSX/Linux, true if path starts with '/'. On Windows, true for paths like: + * \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases). + */ +function isRooted(p) { + p = normalizeSeparators(p); + if (!p) { + throw new Error('isRooted() parameter "p" cannot be empty'); + } + if (exports.IS_WINDOWS) { + return (p.startsWith('\\') || /^[A-Z]:/i.test(p) // e.g. \ or \hello or \\hello + ); // e.g. C: or C:\hello + } + return p.startsWith('/'); +} +exports.isRooted = isRooted; +/** + * Best effort attempt to determine whether a file exists and is executable. + * @param filePath file path to check + * @param extensions additional file extensions to try + * @return if file exists and is executable, returns the file path. otherwise empty string. + */ +function tryGetExecutablePath(filePath, extensions) { + return __awaiter(this, void 0, void 0, function* () { + let stats = undefined; + try { + // test file exists + stats = yield exports.stat(filePath); + } + catch (err) { + if (err.code !== 'ENOENT') { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); + } + } + if (stats && stats.isFile()) { + if (exports.IS_WINDOWS) { + // on Windows, test for valid extension + const upperExt = path.extname(filePath).toUpperCase(); + if (extensions.some(validExt => validExt.toUpperCase() === upperExt)) { + return filePath; + } + } + else { + if (isUnixExecutable(stats)) { + return filePath; + } + } + } + // try each extension + const originalFilePath = filePath; + for (const extension of extensions) { + filePath = originalFilePath + extension; + stats = undefined; + try { + stats = yield exports.stat(filePath); + } + catch (err) { + if (err.code !== 'ENOENT') { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); + } + } + if (stats && stats.isFile()) { + if (exports.IS_WINDOWS) { + // preserve the case of the actual file (since an extension was appended) + try { + const directory = path.dirname(filePath); + const upperName = path.basename(filePath).toUpperCase(); + for (const actualName of yield exports.readdir(directory)) { + if (upperName === actualName.toUpperCase()) { + filePath = path.join(directory, actualName); + break; + } + } + } + catch (err) { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine the actual case of the file '${filePath}': ${err}`); + } + return filePath; + } + else { + if (isUnixExecutable(stats)) { + return filePath; + } + } + } + } + return ''; + }); +} +exports.tryGetExecutablePath = tryGetExecutablePath; +function normalizeSeparators(p) { + p = p || ''; + if (exports.IS_WINDOWS) { + // convert slashes on Windows + p = p.replace(/\//g, '\\'); + // remove redundant slashes + return p.replace(/\\\\+/g, '\\'); + } + // remove redundant slashes + return p.replace(/\/\/+/g, '/'); +} +// on Mac/Linux, test the execute bit +// R W X R W X R W X +// 256 128 64 32 16 8 4 2 1 +function isUnixExecutable(stats) { + return ((stats.mode & 1) > 0 || + ((stats.mode & 8) > 0 && stats.gid === process.getgid()) || + ((stats.mode & 64) > 0 && stats.uid === process.getuid())); +} +// Get the path of cmd.exe in windows +function getCmdPath() { + var _a; + return (_a = process.env['COMSPEC']) !== null && _a !== void 0 ? _a : `cmd.exe`; +} +exports.getCmdPath = getCmdPath; +//# sourceMappingURL=io-util.js.map + +/***/ }), + +/***/ 9529: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.findInPath = exports.which = exports.mkdirP = exports.rmRF = exports.mv = exports.cp = void 0; +const assert_1 = __nccwpck_require__(9491); +const path = __importStar(__nccwpck_require__(1017)); +const ioUtil = __importStar(__nccwpck_require__(7821)); +/** + * Copies a file or folder. + * Based off of shelljs - https://github.com/shelljs/shelljs/blob/9237f66c52e5daa40458f94f9565e18e8132f5a6/src/cp.js + * + * @param source source path + * @param dest destination path + * @param options optional. See CopyOptions. + */ +function cp(source, dest, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + const { force, recursive, copySourceDirectory } = readCopyOptions(options); + const destStat = (yield ioUtil.exists(dest)) ? yield ioUtil.stat(dest) : null; + // Dest is an existing file, but not forcing + if (destStat && destStat.isFile() && !force) { + return; + } + // If dest is an existing directory, should copy inside. + const newDest = destStat && destStat.isDirectory() && copySourceDirectory + ? path.join(dest, path.basename(source)) + : dest; + if (!(yield ioUtil.exists(source))) { + throw new Error(`no such file or directory: ${source}`); + } + const sourceStat = yield ioUtil.stat(source); + if (sourceStat.isDirectory()) { + if (!recursive) { + throw new Error(`Failed to copy. ${source} is a directory, but tried to copy without recursive flag.`); + } + else { + yield cpDirRecursive(source, newDest, 0, force); + } + } + else { + if (path.relative(source, newDest) === '') { + // a file cannot be copied to itself + throw new Error(`'${newDest}' and '${source}' are the same file`); + } + yield copyFile(source, newDest, force); + } + }); +} +exports.cp = cp; +/** + * Moves a path. + * + * @param source source path + * @param dest destination path + * @param options optional. See MoveOptions. + */ +function mv(source, dest, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + if (yield ioUtil.exists(dest)) { + let destExists = true; + if (yield ioUtil.isDirectory(dest)) { + // If dest is directory copy src into dest + dest = path.join(dest, path.basename(source)); + destExists = yield ioUtil.exists(dest); + } + if (destExists) { + if (options.force == null || options.force) { + yield rmRF(dest); + } + else { + throw new Error('Destination already exists'); + } + } + } + yield mkdirP(path.dirname(dest)); + yield ioUtil.rename(source, dest); + }); +} +exports.mv = mv; +/** + * Remove a path recursively with force + * + * @param inputPath path to remove + */ +function rmRF(inputPath) { + return __awaiter(this, void 0, void 0, function* () { + if (ioUtil.IS_WINDOWS) { + // Check for invalid characters + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + if (/[*"<>|]/.test(inputPath)) { + throw new Error('File path must not contain `*`, `"`, `<`, `>` or `|` on Windows'); + } + } + try { + // note if path does not exist, error is silent + yield ioUtil.rm(inputPath, { + force: true, + maxRetries: 3, + recursive: true, + retryDelay: 300 + }); + } + catch (err) { + throw new Error(`File was unable to be removed ${err}`); + } + }); +} +exports.rmRF = rmRF; +/** + * Make a directory. Creates the full path with folders in between + * Will throw if it fails + * + * @param fsPath path to create + * @returns Promise + */ +function mkdirP(fsPath) { + return __awaiter(this, void 0, void 0, function* () { + assert_1.ok(fsPath, 'a path argument must be provided'); + yield ioUtil.mkdir(fsPath, { recursive: true }); + }); +} +exports.mkdirP = mkdirP; +/** + * Returns path of a tool had the tool actually been invoked. Resolves via paths. + * If you check and the tool does not exist, it will throw. + * + * @param tool name of the tool + * @param check whether to check if tool exists + * @returns Promise path to tool + */ +function which(tool, check) { + return __awaiter(this, void 0, void 0, function* () { + if (!tool) { + throw new Error("parameter 'tool' is required"); + } + // recursive when check=true + if (check) { + const result = yield which(tool, false); + if (!result) { + if (ioUtil.IS_WINDOWS) { + throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`); + } + else { + throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`); + } + } + return result; + } + const matches = yield findInPath(tool); + if (matches && matches.length > 0) { + return matches[0]; + } + return ''; + }); +} +exports.which = which; +/** + * Returns a list of all occurrences of the given tool on the system path. + * + * @returns Promise the paths of the tool + */ +function findInPath(tool) { + return __awaiter(this, void 0, void 0, function* () { + if (!tool) { + throw new Error("parameter 'tool' is required"); + } + // build the list of extensions to try + const extensions = []; + if (ioUtil.IS_WINDOWS && process.env['PATHEXT']) { + for (const extension of process.env['PATHEXT'].split(path.delimiter)) { + if (extension) { + extensions.push(extension); + } + } + } + // if it's rooted, return it if exists. otherwise return empty. + if (ioUtil.isRooted(tool)) { + const filePath = yield ioUtil.tryGetExecutablePath(tool, extensions); + if (filePath) { + return [filePath]; + } + return []; + } + // if any path separators, return empty + if (tool.includes(path.sep)) { + return []; + } + // build the list of directories + // + // Note, technically "where" checks the current directory on Windows. From a toolkit perspective, + // it feels like we should not do this. Checking the current directory seems like more of a use + // case of a shell, and the which() function exposed by the toolkit should strive for consistency + // across platforms. + const directories = []; + if (process.env.PATH) { + for (const p of process.env.PATH.split(path.delimiter)) { + if (p) { + directories.push(p); + } + } + } + // find all matches + const matches = []; + for (const directory of directories) { + const filePath = yield ioUtil.tryGetExecutablePath(path.join(directory, tool), extensions); + if (filePath) { + matches.push(filePath); + } + } + return matches; + }); +} +exports.findInPath = findInPath; +function readCopyOptions(options) { + const force = options.force == null ? true : options.force; + const recursive = Boolean(options.recursive); + const copySourceDirectory = options.copySourceDirectory == null + ? true + : Boolean(options.copySourceDirectory); + return { force, recursive, copySourceDirectory }; +} +function cpDirRecursive(sourceDir, destDir, currentDepth, force) { + return __awaiter(this, void 0, void 0, function* () { + // Ensure there is not a run away recursive copy + if (currentDepth >= 255) + return; + currentDepth++; + yield mkdirP(destDir); + const files = yield ioUtil.readdir(sourceDir); + for (const fileName of files) { + const srcFile = `${sourceDir}/${fileName}`; + const destFile = `${destDir}/${fileName}`; + const srcFileStat = yield ioUtil.lstat(srcFile); + if (srcFileStat.isDirectory()) { + // Recurse + yield cpDirRecursive(srcFile, destFile, currentDepth, force); + } + else { + yield copyFile(srcFile, destFile, force); + } + } + // Change the mode for the newly created directory + yield ioUtil.chmod(destDir, (yield ioUtil.stat(sourceDir)).mode); + }); +} +// Buffered file copy +function copyFile(srcFile, destFile, force) { + return __awaiter(this, void 0, void 0, function* () { + if ((yield ioUtil.lstat(srcFile)).isSymbolicLink()) { + // unlink/re-link it + try { + yield ioUtil.lstat(destFile); + yield ioUtil.unlink(destFile); + } + catch (e) { + // Try to override file permission + if (e.code === 'EPERM') { + yield ioUtil.chmod(destFile, '0666'); + yield ioUtil.unlink(destFile); + } + // other errors = it doesn't exist, no work to do + } + // Copy over symlink + const symlinkFull = yield ioUtil.readlink(srcFile); + yield ioUtil.symlink(symlinkFull, destFile, ioUtil.IS_WINDOWS ? 'junction' : null); + } + else if (!(yield ioUtil.exists(destFile)) || force) { + yield ioUtil.copyFile(srcFile, destFile); + } + }); +} +//# sourceMappingURL=io.js.map + +/***/ }), + +/***/ 9041: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +module.exports = __nccwpck_require__(7111); + + +/***/ }), + +/***/ 7111: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var net = __nccwpck_require__(1808); +var tls = __nccwpck_require__(4404); +var http = __nccwpck_require__(3685); +var https = __nccwpck_require__(5687); +var events = __nccwpck_require__(2361); +var assert = __nccwpck_require__(9491); +var util = __nccwpck_require__(3837); + + +exports.httpOverHttp = httpOverHttp; +exports.httpsOverHttp = httpsOverHttp; +exports.httpOverHttps = httpOverHttps; +exports.httpsOverHttps = httpsOverHttps; + + +function httpOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + return agent; +} + +function httpsOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; +} + +function httpOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + return agent; +} + +function httpsOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; +} + + +function TunnelingAgent(options) { + var self = this; + self.options = options || {}; + self.proxyOptions = self.options.proxy || {}; + self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets; + self.requests = []; + self.sockets = []; + + self.on('free', function onFree(socket, host, port, localAddress) { + var options = toOptions(host, port, localAddress); + for (var i = 0, len = self.requests.length; i < len; ++i) { + var pending = self.requests[i]; + if (pending.host === options.host && pending.port === options.port) { + // Detect the request to connect same origin server, + // reuse the connection. + self.requests.splice(i, 1); + pending.request.onSocket(socket); + return; + } + } + socket.destroy(); + self.removeSocket(socket); + }); +} +util.inherits(TunnelingAgent, events.EventEmitter); + +TunnelingAgent.prototype.addRequest = function addRequest(req, host, port, localAddress) { + var self = this; + var options = mergeOptions({request: req}, self.options, toOptions(host, port, localAddress)); + + if (self.sockets.length >= this.maxSockets) { + // We are over limit so we'll add it to the queue. + self.requests.push(options); + return; + } + + // If we are under maxSockets create a new one. + self.createSocket(options, function(socket) { + socket.on('free', onFree); + socket.on('close', onCloseOrRemove); + socket.on('agentRemove', onCloseOrRemove); + req.onSocket(socket); + + function onFree() { + self.emit('free', socket, options); + } + + function onCloseOrRemove(err) { + self.removeSocket(socket); + socket.removeListener('free', onFree); + socket.removeListener('close', onCloseOrRemove); + socket.removeListener('agentRemove', onCloseOrRemove); + } + }); +}; + +TunnelingAgent.prototype.createSocket = function createSocket(options, cb) { + var self = this; + var placeholder = {}; + self.sockets.push(placeholder); + + var connectOptions = mergeOptions({}, self.proxyOptions, { + method: 'CONNECT', + path: options.host + ':' + options.port, + agent: false, + headers: { + host: options.host + ':' + options.port + } + }); + if (options.localAddress) { + connectOptions.localAddress = options.localAddress; + } + if (connectOptions.proxyAuth) { + connectOptions.headers = connectOptions.headers || {}; + connectOptions.headers['Proxy-Authorization'] = 'Basic ' + + new Buffer(connectOptions.proxyAuth).toString('base64'); + } + + debug('making CONNECT request'); + var connectReq = self.request(connectOptions); + connectReq.useChunkedEncodingByDefault = false; // for v0.6 + connectReq.once('response', onResponse); // for v0.6 + connectReq.once('upgrade', onUpgrade); // for v0.6 + connectReq.once('connect', onConnect); // for v0.7 or later + connectReq.once('error', onError); + connectReq.end(); + + function onResponse(res) { + // Very hacky. This is necessary to avoid http-parser leaks. + res.upgrade = true; + } + + function onUpgrade(res, socket, head) { + // Hacky. + process.nextTick(function() { + onConnect(res, socket, head); + }); + } + + function onConnect(res, socket, head) { + connectReq.removeAllListeners(); + socket.removeAllListeners(); + + if (res.statusCode !== 200) { + debug('tunneling socket could not be established, statusCode=%d', + res.statusCode); + socket.destroy(); + var error = new Error('tunneling socket could not be established, ' + + 'statusCode=' + res.statusCode); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + return; + } + if (head.length > 0) { + debug('got illegal response body from proxy'); + socket.destroy(); + var error = new Error('got illegal response body from proxy'); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + return; + } + debug('tunneling connection has established'); + self.sockets[self.sockets.indexOf(placeholder)] = socket; + return cb(socket); + } + + function onError(cause) { + connectReq.removeAllListeners(); + + debug('tunneling socket could not be established, cause=%s\n', + cause.message, cause.stack); + var error = new Error('tunneling socket could not be established, ' + + 'cause=' + cause.message); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + } +}; + +TunnelingAgent.prototype.removeSocket = function removeSocket(socket) { + var pos = this.sockets.indexOf(socket) + if (pos === -1) { + return; + } + this.sockets.splice(pos, 1); + + var pending = this.requests.shift(); + if (pending) { + // If we have pending requests and a socket gets closed a new one + // needs to be created to take over in the pool for the one that closed. + this.createSocket(pending, function(socket) { + pending.request.onSocket(socket); + }); + } +}; + +function createSecureSocket(options, cb) { + var self = this; + TunnelingAgent.prototype.createSocket.call(self, options, function(socket) { + var hostHeader = options.request.getHeader('host'); + var tlsOptions = mergeOptions({}, self.options, { + socket: socket, + servername: hostHeader ? hostHeader.replace(/:.*$/, '') : options.host + }); + + // 0 is dummy port for v0.6 + var secureSocket = tls.connect(0, tlsOptions); + self.sockets[self.sockets.indexOf(socket)] = secureSocket; + cb(secureSocket); + }); +} + + +function toOptions(host, port, localAddress) { + if (typeof host === 'string') { // since v0.10 + return { + host: host, + port: port, + localAddress: localAddress + }; + } + return host; // for v0.11 or later +} + +function mergeOptions(target) { + for (var i = 1, len = arguments.length; i < len; ++i) { + var overrides = arguments[i]; + if (typeof overrides === 'object') { + var keys = Object.keys(overrides); + for (var j = 0, keyLen = keys.length; j < keyLen; ++j) { + var k = keys[j]; + if (overrides[k] !== undefined) { + target[k] = overrides[k]; + } + } + } + } + return target; +} + + +var debug; +if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { + debug = function() { + var args = Array.prototype.slice.call(arguments); + if (typeof args[0] === 'string') { + args[0] = 'TUNNEL: ' + args[0]; + } else { + args.unshift('TUNNEL:'); + } + console.error.apply(console, args); + } +} else { + debug = function() {}; +} +exports.debug = debug; // for test + + +/***/ }), + +/***/ 5814: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +Object.defineProperty(exports, "v1", ({ + enumerable: true, + get: function () { + return _v.default; + } +})); +Object.defineProperty(exports, "v3", ({ + enumerable: true, + get: function () { + return _v2.default; + } +})); +Object.defineProperty(exports, "v4", ({ + enumerable: true, + get: function () { + return _v3.default; + } +})); +Object.defineProperty(exports, "v5", ({ + enumerable: true, + get: function () { + return _v4.default; + } +})); +Object.defineProperty(exports, "NIL", ({ + enumerable: true, + get: function () { + return _nil.default; + } +})); +Object.defineProperty(exports, "version", ({ + enumerable: true, + get: function () { + return _version.default; + } +})); +Object.defineProperty(exports, "validate", ({ + enumerable: true, + get: function () { + return _validate.default; + } +})); +Object.defineProperty(exports, "stringify", ({ + enumerable: true, + get: function () { + return _stringify.default; + } +})); +Object.defineProperty(exports, "parse", ({ + enumerable: true, + get: function () { + return _parse.default; + } +})); + +var _v = _interopRequireDefault(__nccwpck_require__(6471)); + +var _v2 = _interopRequireDefault(__nccwpck_require__(3384)); + +var _v3 = _interopRequireDefault(__nccwpck_require__(5940)); + +var _v4 = _interopRequireDefault(__nccwpck_require__(9193)); + +var _nil = _interopRequireDefault(__nccwpck_require__(8654)); + +var _version = _interopRequireDefault(__nccwpck_require__(2362)); + +var _validate = _interopRequireDefault(__nccwpck_require__(9815)); + +var _stringify = _interopRequireDefault(__nccwpck_require__(5183)); + +var _parse = _interopRequireDefault(__nccwpck_require__(5108)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/***/ }), + +/***/ 9313: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _crypto = _interopRequireDefault(__nccwpck_require__(6113)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function md5(bytes) { + if (Array.isArray(bytes)) { + bytes = Buffer.from(bytes); + } else if (typeof bytes === 'string') { + bytes = Buffer.from(bytes, 'utf8'); + } + + return _crypto.default.createHash('md5').update(bytes).digest(); +} + +var _default = md5; +exports["default"] = _default; + +/***/ }), + +/***/ 8654: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _default = '00000000-0000-0000-0000-000000000000'; +exports["default"] = _default; + +/***/ }), + +/***/ 5108: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _validate = _interopRequireDefault(__nccwpck_require__(9815)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function parse(uuid) { + if (!(0, _validate.default)(uuid)) { + throw TypeError('Invalid UUID'); + } + + let v; + const arr = new Uint8Array(16); // Parse ########-....-....-....-............ + + arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24; + arr[1] = v >>> 16 & 0xff; + arr[2] = v >>> 8 & 0xff; + arr[3] = v & 0xff; // Parse ........-####-....-....-............ + + arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8; + arr[5] = v & 0xff; // Parse ........-....-####-....-............ + + arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8; + arr[7] = v & 0xff; // Parse ........-....-....-####-............ + + arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8; + arr[9] = v & 0xff; // Parse ........-....-....-....-############ + // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) + + arr[10] = (v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000 & 0xff; + arr[11] = v / 0x100000000 & 0xff; + arr[12] = v >>> 24 & 0xff; + arr[13] = v >>> 16 & 0xff; + arr[14] = v >>> 8 & 0xff; + arr[15] = v & 0xff; + return arr; +} + +var _default = parse; +exports["default"] = _default; + +/***/ }), + +/***/ 1629: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; +exports["default"] = _default; + +/***/ }), + +/***/ 9271: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = rng; + +var _crypto = _interopRequireDefault(__nccwpck_require__(6113)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const rnds8Pool = new Uint8Array(256); // # of random values to pre-allocate + +let poolPtr = rnds8Pool.length; + +function rng() { + if (poolPtr > rnds8Pool.length - 16) { + _crypto.default.randomFillSync(rnds8Pool); + + poolPtr = 0; + } + + return rnds8Pool.slice(poolPtr, poolPtr += 16); +} + +/***/ }), + +/***/ 2017: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _crypto = _interopRequireDefault(__nccwpck_require__(6113)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function sha1(bytes) { + if (Array.isArray(bytes)) { + bytes = Buffer.from(bytes); + } else if (typeof bytes === 'string') { + bytes = Buffer.from(bytes, 'utf8'); + } + + return _crypto.default.createHash('sha1').update(bytes).digest(); +} + +var _default = sha1; +exports["default"] = _default; + +/***/ }), + +/***/ 5183: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _validate = _interopRequireDefault(__nccwpck_require__(9815)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Convert array of 16 byte values to UUID string format of the form: + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + */ +const byteToHex = []; + +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 0x100).toString(16).substr(1)); +} + +function stringify(arr, offset = 0) { + // Note: Be careful editing this code! It's been tuned for performance + // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 + const uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); // Consistency check for valid UUID. If this throws, it's likely due to one + // of the following: + // - One or more input array values don't map to a hex octet (leading to + // "undefined" in the uuid) + // - Invalid input values for the RFC `version` or `variant` fields + + if (!(0, _validate.default)(uuid)) { + throw TypeError('Stringified UUID is invalid'); + } + + return uuid; +} + +var _default = stringify; +exports["default"] = _default; + +/***/ }), + +/***/ 6471: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _rng = _interopRequireDefault(__nccwpck_require__(9271)); + +var _stringify = _interopRequireDefault(__nccwpck_require__(5183)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// **`v1()` - Generate time-based UUID** +// +// Inspired by https://github.com/LiosK/UUID.js +// and http://docs.python.org/library/uuid.html +let _nodeId; + +let _clockseq; // Previous uuid creation time + + +let _lastMSecs = 0; +let _lastNSecs = 0; // See https://github.com/uuidjs/uuid for API details + +function v1(options, buf, offset) { + let i = buf && offset || 0; + const b = buf || new Array(16); + options = options || {}; + let node = options.node || _nodeId; + let clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; // node and clockseq need to be initialized to random values if they're not + // specified. We do this lazily to minimize issues related to insufficient + // system entropy. See #189 + + if (node == null || clockseq == null) { + const seedBytes = options.random || (options.rng || _rng.default)(); + + if (node == null) { + // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) + node = _nodeId = [seedBytes[0] | 0x01, seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5]]; + } + + if (clockseq == null) { + // Per 4.2.2, randomize (14 bit) clockseq + clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff; + } + } // UUID timestamps are 100 nano-second units since the Gregorian epoch, + // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so + // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs' + // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00. + + + let msecs = options.msecs !== undefined ? options.msecs : Date.now(); // Per 4.2.1.2, use count of uuid's generated during the current clock + // cycle to simulate higher resolution clock + + let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; // Time since last uuid creation (in msecs) + + const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; // Per 4.2.1.2, Bump clockseq on clock regression + + if (dt < 0 && options.clockseq === undefined) { + clockseq = clockseq + 1 & 0x3fff; + } // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new + // time interval + + + if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) { + nsecs = 0; + } // Per 4.2.1.2 Throw error if too many uuids are requested + + + if (nsecs >= 10000) { + throw new Error("uuid.v1(): Can't create more than 10M uuids/sec"); + } + + _lastMSecs = msecs; + _lastNSecs = nsecs; + _clockseq = clockseq; // Per 4.1.4 - Convert from unix epoch to Gregorian epoch + + msecs += 12219292800000; // `time_low` + + const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; + b[i++] = tl >>> 24 & 0xff; + b[i++] = tl >>> 16 & 0xff; + b[i++] = tl >>> 8 & 0xff; + b[i++] = tl & 0xff; // `time_mid` + + const tmh = msecs / 0x100000000 * 10000 & 0xfffffff; + b[i++] = tmh >>> 8 & 0xff; + b[i++] = tmh & 0xff; // `time_high_and_version` + + b[i++] = tmh >>> 24 & 0xf | 0x10; // include version + + b[i++] = tmh >>> 16 & 0xff; // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant) + + b[i++] = clockseq >>> 8 | 0x80; // `clock_seq_low` + + b[i++] = clockseq & 0xff; // `node` + + for (let n = 0; n < 6; ++n) { + b[i + n] = node[n]; + } + + return buf || (0, _stringify.default)(b); +} + +var _default = v1; +exports["default"] = _default; + +/***/ }), + +/***/ 3384: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _v = _interopRequireDefault(__nccwpck_require__(5717)); + +var _md = _interopRequireDefault(__nccwpck_require__(9313)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const v3 = (0, _v.default)('v3', 0x30, _md.default); +var _default = v3; +exports["default"] = _default; + +/***/ }), + +/***/ 5717: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = _default; +exports.URL = exports.DNS = void 0; + +var _stringify = _interopRequireDefault(__nccwpck_require__(5183)); + +var _parse = _interopRequireDefault(__nccwpck_require__(5108)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function stringToBytes(str) { + str = unescape(encodeURIComponent(str)); // UTF8 escape + + const bytes = []; + + for (let i = 0; i < str.length; ++i) { + bytes.push(str.charCodeAt(i)); + } + + return bytes; +} + +const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; +exports.DNS = DNS; +const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; +exports.URL = URL; + +function _default(name, version, hashfunc) { + function generateUUID(value, namespace, buf, offset) { + if (typeof value === 'string') { + value = stringToBytes(value); + } + + if (typeof namespace === 'string') { + namespace = (0, _parse.default)(namespace); + } + + if (namespace.length !== 16) { + throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)'); + } // Compute hash of namespace and value, Per 4.3 + // Future: Use spread syntax when supported on all platforms, e.g. `bytes = + // hashfunc([...namespace, ... value])` + + + let bytes = new Uint8Array(16 + value.length); + bytes.set(namespace); + bytes.set(value, namespace.length); + bytes = hashfunc(bytes); + bytes[6] = bytes[6] & 0x0f | version; + bytes[8] = bytes[8] & 0x3f | 0x80; + + if (buf) { + offset = offset || 0; + + for (let i = 0; i < 16; ++i) { + buf[offset + i] = bytes[i]; + } + + return buf; + } + + return (0, _stringify.default)(bytes); + } // Function#name is not settable on some platforms (#270) + + + try { + generateUUID.name = name; // eslint-disable-next-line no-empty + } catch (err) {} // For CommonJS default export support + + + generateUUID.DNS = DNS; + generateUUID.URL = URL; + return generateUUID; +} + +/***/ }), + +/***/ 5940: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _rng = _interopRequireDefault(__nccwpck_require__(9271)); + +var _stringify = _interopRequireDefault(__nccwpck_require__(5183)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function v4(options, buf, offset) { + options = options || {}; + + const rnds = options.random || (options.rng || _rng.default)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + + + rnds[6] = rnds[6] & 0x0f | 0x40; + rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided + + if (buf) { + offset = offset || 0; + + for (let i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + + return buf; + } + + return (0, _stringify.default)(rnds); +} + +var _default = v4; +exports["default"] = _default; + +/***/ }), + +/***/ 9193: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _v = _interopRequireDefault(__nccwpck_require__(5717)); + +var _sha = _interopRequireDefault(__nccwpck_require__(2017)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const v5 = (0, _v.default)('v5', 0x50, _sha.default); +var _default = v5; +exports["default"] = _default; + +/***/ }), + +/***/ 9815: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _regex = _interopRequireDefault(__nccwpck_require__(1629)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function validate(uuid) { + return typeof uuid === 'string' && _regex.default.test(uuid); +} + +var _default = validate; +exports["default"] = _default; + +/***/ }), + +/***/ 2362: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _validate = _interopRequireDefault(__nccwpck_require__(9815)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function version(uuid) { + if (!(0, _validate.default)(uuid)) { + throw TypeError('Invalid UUID'); + } + + return parseInt(uuid.substr(14, 1), 16); +} + +var _default = version; +exports["default"] = _default; + +/***/ }), + +/***/ 2929: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.ContainerAppHelper = void 0; +var path = __nccwpck_require__(1017); +var os = __nccwpck_require__(2037); +var Utility_1 = __nccwpck_require__(2135); +var GitHubActionsToolHelper_1 = __nccwpck_require__(3185); +var fs = __nccwpck_require__(7147); +var ORYX_CLI_IMAGE = 'mcr.microsoft.com/oryx/cli:builder-debian-bullseye-20230926.1'; +var ORYX_BULLSEYE_BUILDER_IMAGE = 'mcr.microsoft.com/oryx/builder:debian-bullseye-20231107.2'; +var ORYX_BOOKWORM_BUILDER_IMAGE = 'mcr.microsoft.com/oryx/builder:debian-bookworm-20231107.2'; +var ORYX_BUILDER_IMAGES = [ORYX_BULLSEYE_BUILDER_IMAGE, ORYX_BOOKWORM_BUILDER_IMAGE]; +var IS_WINDOWS_AGENT = os.platform() == 'win32'; +var PACK_CMD = IS_WINDOWS_AGENT ? path.join(os.tmpdir(), 'pack') : 'pack'; +var toolHelper = new GitHubActionsToolHelper_1.GitHubActionsToolHelper(); +var util = new Utility_1.Utility(); +var ContainerAppHelper = /** @class */ (function () { + function ContainerAppHelper(disableTelemetry) { + this.disableTelemetry = false; + this.disableTelemetry = disableTelemetry; + } + /** + * Creates an Azure Container App. + * @param containerAppName - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + * @param environment - the Container App Environment that will be associated with the Container App + * @param optionalCmdArgs - a set of optional command line arguments + */ + ContainerAppHelper.prototype.createContainerApp = function (containerAppName, resourceGroup, environment, optionalCmdArgs) { + return __awaiter(this, void 0, void 0, function () { + var command_1, err_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to create Container App with name \"" + containerAppName + "\" in resource group \"" + resourceGroup + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command_1 = "az containerapp create -n " + containerAppName + " -g " + resourceGroup + " --environment " + environment + " --output none"; + optionalCmdArgs.forEach(function (val) { + command_1 += " " + val; + }); + return [4 /*yield*/, util.execute(command_1)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_1 = _a.sent(); + toolHelper.writeError(err_1.message); + throw err_1; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Creates an Azure Container App. + * @param containerAppName - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + * @param optionalCmdArgs - a set of optional command line arguments + */ + ContainerAppHelper.prototype.createOrUpdateContainerAppWithUp = function (containerAppName, resourceGroup, optionalCmdArgs) { + return __awaiter(this, void 0, void 0, function () { + var command_2, err_2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to create Container App with name \"" + containerAppName + "\" in resource group \"" + resourceGroup + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command_2 = "az containerapp up -n " + containerAppName + " -g " + resourceGroup + " --debug"; + optionalCmdArgs.forEach(function (val) { + command_2 += " " + val; + }); + return [4 /*yield*/, util.execute(command_2)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_2 = _a.sent(); + toolHelper.writeError(err_2.message); + throw err_2; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Creates an Azure Container App based from a YAML configuration file. + * @param containerAppName - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + * @param yamlConfigPath - the path to the YAML configuration file that the Container App properties will be based from + */ + ContainerAppHelper.prototype.createContainerAppFromYaml = function (containerAppName, resourceGroup, yamlConfigPath) { + return __awaiter(this, void 0, void 0, function () { + var command, err_3; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to create Container App with name \"" + containerAppName + "\" in resource group \"" + resourceGroup + "\" from provided YAML \"" + yamlConfigPath + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az containerapp create -n " + containerAppName + " -g " + resourceGroup + " --yaml " + yamlConfigPath + " --output none"; + return [4 /*yield*/, util.execute(command)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_3 = _a.sent(); + toolHelper.writeError(err_3.message); + throw err_3; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Updates an existing Azure Container App based from an image that was previously built. + * @param containerAppName - the name of the existing Container App + * @param resourceGroup - the resource group that the existing Container App is found in + * @param optionalCmdArgs - a set of optional command line arguments + */ + ContainerAppHelper.prototype.updateContainerApp = function (containerAppName, resourceGroup, optionalCmdArgs) { + return __awaiter(this, void 0, void 0, function () { + var command_3, err_4; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to update Container App with name \"" + containerAppName + "\" in resource group \"" + resourceGroup + "\" "); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command_3 = "az containerapp update -n " + containerAppName + " -g " + resourceGroup + " --output none"; + optionalCmdArgs.forEach(function (val) { + command_3 += " " + val; + }); + return [4 /*yield*/, util.execute(command_3)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_4 = _a.sent(); + toolHelper.writeError(err_4.message); + throw err_4; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Updates an existing Azure Container App using the 'az containerapp up' command. + * @param containerAppName - the name of the existing Container App + * @param resourceGroup - the resource group that the existing Container App is found in + * @param optionalCmdArgs - a set of optional command line arguments + * @param ingress - the ingress that the Container App will be exposed on + * @param targetPort - the target port that the Container App will be exposed on + */ + ContainerAppHelper.prototype.updateContainerAppWithUp = function (containerAppName, resourceGroup, optionalCmdArgs, ingress, targetPort) { + return __awaiter(this, void 0, void 0, function () { + var command_4, err_5; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to update Container App with name \"" + containerAppName + "\" in resource group \"" + resourceGroup + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command_4 = "az containerapp up -n " + containerAppName + " -g " + resourceGroup; + optionalCmdArgs.forEach(function (val) { + command_4 += " " + val; + }); + if (!util.isNullOrEmpty(ingress)) { + command_4 += " --ingress " + ingress; + } + if (!util.isNullOrEmpty(targetPort)) { + command_4 += " --target-port " + targetPort; + } + return [4 /*yield*/, util.execute(command_4)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_5 = _a.sent(); + toolHelper.writeError(err_5.message); + throw err_5; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Updates an existing Azure Container App based from a YAML configuration file. + * @param containerAppName - the name of the existing Container App + * @param resourceGroup - the resource group that the existing Container App is found in + * @param yamlConfigPath - the path to the YAML configuration file that the Container App properties will be based from + */ + ContainerAppHelper.prototype.updateContainerAppFromYaml = function (containerAppName, resourceGroup, yamlConfigPath) { + return __awaiter(this, void 0, void 0, function () { + var command, err_6; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to update Container App with name \"" + containerAppName + "\" in resource group \"" + resourceGroup + "\" from provided YAML \"" + yamlConfigPath + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az containerapp update -n " + containerAppName + " -g " + resourceGroup + " --yaml " + yamlConfigPath + " --output none"; + return [4 /*yield*/, util.execute(command)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_6 = _a.sent(); + toolHelper.writeError(err_6.message); + throw err_6; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Determines if the provided Container App exists in the provided resource group. + * @param containerAppName - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + * @returns true if the Container App exists, false otherwise + */ + ContainerAppHelper.prototype.doesContainerAppExist = function (containerAppName, resourceGroup) { + return __awaiter(this, void 0, void 0, function () { + var command, executionResult, err_7; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to determine if Container App with name \"" + containerAppName + "\" exists in resource group \"" + resourceGroup + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az containerapp show -n " + containerAppName + " -g " + resourceGroup + " -o none"; + return [4 /*yield*/, util.execute(command)]; + case 2: + executionResult = _a.sent(); + return [2 /*return*/, executionResult.exitCode === 0]; + case 3: + err_7 = _a.sent(); + toolHelper.writeInfo(err_7.message); + return [2 /*return*/, false]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Determines if the provided Container App Environment exists in the provided resource group. + * @param containerAppEnvironment - the name of the Container App Environment + * @param resourceGroup - the resource group that the Container App Environment is found in + * @returns true if the Container App Environment exists, false otherwise + */ + ContainerAppHelper.prototype.doesContainerAppEnvironmentExist = function (containerAppEnvironment, resourceGroup) { + return __awaiter(this, void 0, void 0, function () { + var command, executionResult, err_8; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to determine if Container App Environment with name \"" + containerAppEnvironment + "\" exists in resource group \"" + resourceGroup + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az containerapp env show -o none -g " + resourceGroup + " -n " + containerAppEnvironment; + return [4 /*yield*/, util.execute(command)]; + case 2: + executionResult = _a.sent(); + return [2 /*return*/, executionResult.exitCode === 0]; + case 3: + err_8 = _a.sent(); + toolHelper.writeInfo(err_8.message); + return [2 /*return*/, false]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Determines if the provided resource group exists. + * @param resourceGroup - the name of the resource group + * @returns true if the resource group exists, false otherwise + */ + ContainerAppHelper.prototype.doesResourceGroupExist = function (resourceGroup) { + return __awaiter(this, void 0, void 0, function () { + var command, executionResult, err_9; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to determine if resource group \"" + resourceGroup + "\" exists"); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az group show -n " + resourceGroup + " -o none"; + return [4 /*yield*/, util.execute(command)]; + case 2: + executionResult = _a.sent(); + return [2 /*return*/, executionResult.exitCode === 0]; + case 3: + err_9 = _a.sent(); + toolHelper.writeInfo(err_9.message); + return [2 /*return*/, false]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Gets the default location for the Container App provider. + * @returns the default location if found, otherwise 'eastus2' + */ + ContainerAppHelper.prototype.getDefaultContainerAppLocation = function () { + return __awaiter(this, void 0, void 0, function () { + var command, executionResult, err_10; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to get the default location for the Container App service for the subscription."); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az provider show -n Microsoft.App --query \"resourceTypes[?resourceType=='containerApps'].locations[] | [0]\""; + return [4 /*yield*/, util.execute(command)]; + case 2: + executionResult = _a.sent(); + // If successful, strip out double quotes, spaces and parentheses from the first location returned + return [2 /*return*/, executionResult.exitCode === 0 ? executionResult.stdout.toLowerCase().replace(/["() ]/g, "").trim() : "eastus2"]; + case 3: + err_10 = _a.sent(); + toolHelper.writeInfo(err_10.message); + return [2 /*return*/, "eastus2"]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Creates a new resource group in the provided location. + * @param name - the name of the resource group to create + * @param location - the location to create the resource group in + */ + ContainerAppHelper.prototype.createResourceGroup = function (name, location) { + return __awaiter(this, void 0, void 0, function () { + var command, err_11; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to create resource group \"" + name + "\" in location \"" + location + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az group create -n " + name + " -l " + location; + return [4 /*yield*/, util.execute(command)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_11 = _a.sent(); + toolHelper.writeError(err_11.message); + throw err_11; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Gets the name of an existing Container App Environment in the provided resource group. + * @param resourceGroup - the resource group to check for an existing Container App Environment + * @returns the name of the existing Container App Environment, null if none exists + */ + ContainerAppHelper.prototype.getExistingContainerAppEnvironment = function (resourceGroup) { + return __awaiter(this, void 0, void 0, function () { + var command, executionResult, err_12; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to get the existing Container App Environment in resource group \"" + resourceGroup + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az containerapp env list -g " + resourceGroup + " --query \"[0].name\""; + return [4 /*yield*/, util.execute(command)]; + case 2: + executionResult = _a.sent(); + return [2 /*return*/, executionResult.exitCode === 0 ? executionResult.stdout : null]; + case 3: + err_12 = _a.sent(); + toolHelper.writeInfo(err_12.message); + return [2 /*return*/, null]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Gets the location of an existing Container App Environment + * @param environmentName - the name of the Container App Environment + * @param resourceGroup - the resource group that the Container App Environment is found in + */ + ContainerAppHelper.prototype.getExistingContainerAppEnvironmentLocation = function (environmentName, resourceGroup) { + return __awaiter(this, void 0, void 0, function () { + var command, executionResult, err_13; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + command = "az containerapp env show -g " + resourceGroup + " --query location -n " + environmentName; + return [4 /*yield*/, util.execute(command)]; + case 1: + executionResult = _a.sent(); + return [2 /*return*/, executionResult.exitCode === 0 ? executionResult.stdout.toLowerCase().replace(/["() ]/g, "").trim() : null]; + case 2: + err_13 = _a.sent(); + toolHelper.writeInfo(err_13.message); + return [2 /*return*/, null]; + case 3: return [2 /*return*/]; + } + }); + }); + }; + /** + * Gets the environment name of an existing Container App + * @param containerAppName - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + */ + ContainerAppHelper.prototype.getExistingContainerAppEnvironmentName = function (containerAppName, resourceGroup) { + return __awaiter(this, void 0, void 0, function () { + var command, executionResult, containerappEnvironmentId, err_14; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + command = "az containerapp show -n " + containerAppName + " -g " + resourceGroup + " --query properties.environmentId"; + return [4 /*yield*/, util.execute(command)]; + case 1: + executionResult = _a.sent(); + containerappEnvironmentId = executionResult.stdout.trim(); + //Remove trailing slash if it exists + if (!util.isNullOrEmpty(containerappEnvironmentId)) { + containerappEnvironmentId = containerappEnvironmentId.endsWith("/") ? containerappEnvironmentId.slice(0, -1) : containerappEnvironmentId; + } + return [2 /*return*/, executionResult.exitCode === 0 ? containerappEnvironmentId.split("/").pop().trim() : null]; + case 2: + err_14 = _a.sent(); + toolHelper.writeInfo(err_14.message); + return [2 /*return*/, null]; + case 3: return [2 /*return*/]; + } + }); + }); + }; + /** + * Creates a new Azure Container App Environment in the provided resource group. + * @param name - the name of the Container App Environment + * @param resourceGroup - the resource group that the Container App Environment will be created in + * @param location - the location that the Container App Environment will be created in + */ + ContainerAppHelper.prototype.createContainerAppEnvironment = function (name, resourceGroup, location) { + return __awaiter(this, void 0, void 0, function () { + var util, command, err_15; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + util = new Utility_1.Utility(); + toolHelper.writeDebug("Attempting to create Container App Environment with name \"" + name + "\" in resource group \"" + resourceGroup + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az containerapp env create -n " + name + " -g " + resourceGroup; + if (!util.isNullOrEmpty(location)) { + command += " -l " + location; + } + return [4 /*yield*/, util.execute(command)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_15 = _a.sent(); + toolHelper.writeError(err_15.message); + throw err_15; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Disables ingress on an existing Container App. + * @param name - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + */ + ContainerAppHelper.prototype.disableContainerAppIngress = function (name, resourceGroup) { + return __awaiter(this, void 0, void 0, function () { + var command, err_16; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to disable ingress for Container App with name \"" + name + "\" in resource group \"" + resourceGroup + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az containerapp ingress disable -n " + name + " -g " + resourceGroup; + return [4 /*yield*/, util.execute(command)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_16 = _a.sent(); + toolHelper.writeError(err_16.message); + throw err_16; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Updates the Container Registry details on an existing Container App. + * @param name - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + * @param registryUrl - the name of the Container Registry + * @param registryUsername - the username used to authenticate with the Container Registry + * @param registryPassword - the password used to authenticate with the Container Registry + */ + ContainerAppHelper.prototype.updateContainerAppRegistryDetails = function (name, resourceGroup, registryUrl, registryUsername, registryPassword) { + return __awaiter(this, void 0, void 0, function () { + var command, err_17; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to set the Container Registry details for Container App with name \"" + name + "\" in resource group \"" + resourceGroup + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "az containerapp registry set -n " + name + " -g " + resourceGroup + " --server " + registryUrl + " --username " + registryUsername + " --password " + registryPassword; + return [4 /*yield*/, util.execute(command)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_17 = _a.sent(); + toolHelper.writeError(err_17.message); + throw err_17; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Using the Oryx++ Builder, creates a runnable application image from the provided application source. + * @param imageToDeploy - the name of the runnable application image that is created and can be later deployed + * @param appSourcePath - the path to the application source on the machine + * @param environmentVariables - an array of environment variables that should be provided to the builder via the `--env` flag + * @param builderStack - the stack to use when building the provided application source + */ + ContainerAppHelper.prototype.createRunnableAppImage = function (imageToDeploy, appSourcePath, environmentVariables, builderStack) { + return __awaiter(this, void 0, void 0, function () { + var telemetryArg, couldBuildImage, _loop_1, _i, ORYX_BUILDER_IMAGES_1, builderImage, state_1, errorMessage; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + telemetryArg = toolHelper.getTelemetryArg(); + if (this.disableTelemetry) { + telemetryArg = "ORYX_DISABLE_TELEMETRY=true"; + } + couldBuildImage = false; + _loop_1 = function (builderImage) { + var command_5, err_18; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!util.isNullOrEmpty(builderStack) && !builderImage.includes(builderStack)) { + return [2 /*return*/, "continue"]; + } + toolHelper.writeDebug("Attempting to create a runnable application image with name \"" + imageToDeploy + "\" using the Oryx++ Builder \"" + builderImage + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command_5 = "build " + imageToDeploy + " --path " + appSourcePath + " --builder " + builderImage + " --env " + telemetryArg; + environmentVariables.forEach(function (envVar) { + command_5 += " --env " + envVar; + }); + return [4 /*yield*/, util.execute(PACK_CMD + " " + command_5)]; + case 2: + _a.sent(); + couldBuildImage = true; + return [2 /*return*/, "break"]; + case 3: + err_18 = _a.sent(); + toolHelper.writeWarning("Unable to run 'pack build' command to produce runnable application image: " + err_18.message); + return [3 /*break*/, 4]; + case 4: return [2 /*return*/]; + } + }); + }; + _i = 0, ORYX_BUILDER_IMAGES_1 = ORYX_BUILDER_IMAGES; + _a.label = 1; + case 1: + if (!(_i < ORYX_BUILDER_IMAGES_1.length)) return [3 /*break*/, 4]; + builderImage = ORYX_BUILDER_IMAGES_1[_i]; + return [5 /*yield**/, _loop_1(builderImage)]; + case 2: + state_1 = _a.sent(); + if (state_1 === "break") + return [3 /*break*/, 4]; + _a.label = 3; + case 3: + _i++; + return [3 /*break*/, 1]; + case 4: + ; + // If none of the builder images were able to build the provided application source, throw an error. + if (!couldBuildImage) { + errorMessage = "No builder was able to build the provided application source. Please visit the following page for more information on supported platform versions: https://aka.ms/SourceToCloudSupportedVersions"; + toolHelper.writeError(errorMessage); + throw new Error(errorMessage); + } + return [2 /*return*/]; + } + }); + }); + }; + /** + * Using a Dockerfile that was provided or found at the root of the application source, + * creates a runable application image. + * @param imageToDeploy - the name of the runnable application image that is created and can be later deployed + * @param appSourcePath - the path to the application source on the machine + * @param dockerfilePath - the path to the Dockerfile to build and tag with the provided image name + */ + ContainerAppHelper.prototype.createRunnableAppImageFromDockerfile = function (imageToDeploy, appSourcePath, dockerfilePath) { + return __awaiter(this, void 0, void 0, function () { + var command, err_19; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to create a runnable application image from the provided/found Dockerfile \"" + dockerfilePath + "\" with image name \"" + imageToDeploy + "\""); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "docker build --file " + dockerfilePath + " " + appSourcePath + " --tag " + imageToDeploy; + return [4 /*yield*/, util.execute(command)]; + case 2: + _a.sent(); + toolHelper.writeDebug("Successfully created runnable application image from the provided/found Dockerfile \"" + dockerfilePath + "\" with image name \"" + imageToDeploy + "\""); + return [3 /*break*/, 4]; + case 3: + err_19 = _a.sent(); + toolHelper.writeError(err_19.message); + throw err_19; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Determines the runtime stack to use for the runnable application image. + * @param appSourcePath - the path to the application source on the machine + * @returns a string representing the runtime stack that can be used for the Oryx MCR runtime images + */ + ContainerAppHelper.prototype.determineRuntimeStackAsync = function (appSourcePath) { + return __awaiter(this, void 0, void 0, function () { + var command, oryxRuntimeTxtPath_1, runtimeStack, err_20; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug('Attempting to determine the runtime stack needed for the provided application source'); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "docker run --rm -v " + appSourcePath + ":/app " + ORYX_CLI_IMAGE + " /bin/bash -c \"oryx dockerfile /app | head -n 1 | sed 's/ARG RUNTIME=//' >> /app/oryx-runtime.txt\""; + return [4 /*yield*/, util.execute(command) + // Read the temp file to get the runtime stack into a variable + ]; + case 2: + _a.sent(); + oryxRuntimeTxtPath_1 = path.join(appSourcePath, 'oryx-runtime.txt'); + runtimeStack = fs.promises.readFile(oryxRuntimeTxtPath_1, 'utf8').then(function (data) { + var lines = data.split('\n'); + return lines[0]; + })["catch"](function (err) { + toolHelper.writeError(err.message); + throw err; + }); + // Delete the temp file + fs.unlink(oryxRuntimeTxtPath_1, function (err) { + if (err) { + toolHelper.writeWarning("Unable to delete the temporary file \"" + oryxRuntimeTxtPath_1 + "\". Error: " + err.message); + } + }); + return [2 /*return*/, runtimeStack]; + case 3: + err_20 = _a.sent(); + toolHelper.writeError(err_20.message); + throw err_20; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Sets the default builder on the machine to the Oryx++ Builder to prevent an exception from being thrown due + * to no default builder set. + */ + ContainerAppHelper.prototype.setDefaultBuilder = function () { + return __awaiter(this, void 0, void 0, function () { + var command, err_21; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeInfo('Setting the Oryx++ Builder as the default builder via the pack CLI'); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = "config default-builder " + ORYX_BUILDER_IMAGES[0]; + return [4 /*yield*/, util.execute(PACK_CMD + " " + command)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_21 = _a.sent(); + toolHelper.writeError(err_21.message); + throw err_21; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Installs the pack CLI that will be used to build a runnable application image. + * For more Information about the pack CLI can be found here: https://buildpacks.io/docs/tools/pack/ + */ + ContainerAppHelper.prototype.installPackCliAsync = function () { + return __awaiter(this, void 0, void 0, function () { + var command, commandLine, packZipDownloadUri, packZipDownloadFilePath, tgzSuffix, err_22; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug('Attempting to install the pack CLI'); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = ''; + commandLine = ''; + if (IS_WINDOWS_AGENT) { + packZipDownloadUri = 'https://github.com/buildpacks/pack/releases/download/v0.31.0/pack-v0.31.0-windows.zip'; + packZipDownloadFilePath = path.join(PACK_CMD, 'pack-windows.zip'); + command = "New-Item -ItemType Directory -Path " + PACK_CMD + " -Force | Out-Null; Invoke-WebRequest -Uri " + packZipDownloadUri + " -OutFile " + packZipDownloadFilePath + "; Expand-Archive -LiteralPath " + packZipDownloadFilePath + " -DestinationPath " + PACK_CMD + "; Remove-Item -Path " + packZipDownloadFilePath; + commandLine = 'pwsh'; + } + else { + tgzSuffix = os.platform() == 'darwin' ? 'macos' : 'linux'; + command = "(curl -sSL \"https://github.com/buildpacks/pack/releases/download/v0.31.0/pack-v0.31.0-" + tgzSuffix + ".tgz\" | " + + 'tar -C /usr/local/bin/ --no-same-owner -xzv pack)'; + commandLine = 'bash'; + } + return [4 /*yield*/, util.execute(commandLine + " -c \"" + command + "\"")]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_22 = _a.sent(); + toolHelper.writeError("Unable to install the pack CLI. Error: " + err_22.message); + throw err_22; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Enables experimental features for the pack CLI, such as extension support. + */ + ContainerAppHelper.prototype.enablePackCliExperimentalFeaturesAsync = function () { + return __awaiter(this, void 0, void 0, function () { + var command, err_23; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug('Attempting to enable experimental features for the pack CLI'); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + command = PACK_CMD + " config experimental true"; + return [4 /*yield*/, util.execute(command)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_23 = _a.sent(); + toolHelper.writeError("Unable to enable experimental features for the pack CLI: " + err_23.message); + throw err_23; + case 4: return [2 /*return*/]; + } + }); + }); + }; + return ContainerAppHelper; +}()); +exports.ContainerAppHelper = ContainerAppHelper; + + +/***/ }), + +/***/ 4769: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.ContainerRegistryHelper = void 0; +var os = __nccwpck_require__(2037); +var Utility_1 = __nccwpck_require__(2135); +var GitHubActionsToolHelper_1 = __nccwpck_require__(3185); +var toolHelper = new GitHubActionsToolHelper_1.GitHubActionsToolHelper(); +var util = new Utility_1.Utility(); +var ContainerRegistryHelper = /** @class */ (function () { + function ContainerRegistryHelper() { + } + /** + * Authorizes Docker to make calls to the provided Container Registry instance using username and password. + * @param registryUrl - the name of the Container Registry instance to authenticate calls to + * @param registryUsername - the username for authentication + * @param registryPassword - the password for authentication + */ + ContainerRegistryHelper.prototype.loginContainerRegistryWithUsernamePassword = function (registryUrl, registryUsername, registryPassword) { + return __awaiter(this, void 0, void 0, function () { + var err_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to log in to Container Registry instance\"" + registryUrl + "\" with username and password credentials"); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, util.execute("docker login --password-stdin --username " + registryUsername + " " + registryUrl, [], Buffer.from(registryPassword))]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_1 = _a.sent(); + toolHelper.writeError("Failed to log in to Container Registry instance \"" + registryUrl + "\" with username and password credentials"); + throw err_1; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Authorizes Docker to make calls to the provided ACR instance using an access token that is generated via + * the 'az acr login --expose-token' command. + * @param acrName - the name of the ACR instance to authenticate calls to. + */ + ContainerRegistryHelper.prototype.loginAcrWithAccessTokenAsync = function (acrName) { + return __awaiter(this, void 0, void 0, function () { + var commandLine, err_2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to log in to ACR instance \"" + acrName + "\" with access token"); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + commandLine = os.platform() === 'win32' ? 'pwsh' : 'bash'; + return [4 /*yield*/, util.execute(commandLine + " -c \"CA_ADO_TASK_ACR_ACCESS_TOKEN=$(az acr login --name " + acrName + " --output json --expose-token --only-show-errors | jq -r '.accessToken'); docker login " + acrName + ".azurecr.io -u 00000000-0000-0000-0000-000000000000 -p $CA_ADO_TASK_ACR_ACCESS_TOKEN > /dev/null 2>&1\"")]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_2 = _a.sent(); + toolHelper.writeError("Failed to log in to ACR instance \"" + acrName + "\" with access token"); + throw err_2; + case 4: return [2 /*return*/]; + } + }); + }); + }; + /** + * Pushes an image to the Container Registry instance that was previously authenticated against. + * @param imageToPush - the name of the image to push to the Container Registry instance + */ + ContainerRegistryHelper.prototype.pushImageToContainerRegistry = function (imageToPush) { + return __awaiter(this, void 0, void 0, function () { + var err_3; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + toolHelper.writeDebug("Attempting to push image \"" + imageToPush + "\" to Container Registry"); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, util.execute("docker push " + imageToPush)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_3 = _a.sent(); + toolHelper.writeError("Failed to push image \"" + imageToPush + "\" to Container Registry. Error: " + err_3.message); + throw err_3; + case 4: return [2 /*return*/]; + } + }); + }); + }; + return ContainerRegistryHelper; +}()); +exports.ContainerRegistryHelper = ContainerRegistryHelper; + + +/***/ }), + +/***/ 3185: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.GitHubActionsToolHelper = void 0; +var core = __nccwpck_require__(3195); +var io = __nccwpck_require__(9529); +var exec = __nccwpck_require__(9714); +var GitHubActionsToolHelper = /** @class */ (function () { + function GitHubActionsToolHelper() { + } + GitHubActionsToolHelper.prototype.getBuildId = function () { + return process.env['GITHUB_RUN_ID'] || ''; + }; + GitHubActionsToolHelper.prototype.getBuildNumber = function () { + return process.env['GITHUB_RUN_NUMBER'] || ''; + }; + GitHubActionsToolHelper.prototype.writeInfo = function (message) { + core.info(message); + }; + GitHubActionsToolHelper.prototype.writeError = function (message) { + core.error(message); + }; + GitHubActionsToolHelper.prototype.writeWarning = function (message) { + core.warning(message); + }; + GitHubActionsToolHelper.prototype.writeDebug = function (message) { + core.debug(message); + }; + GitHubActionsToolHelper.prototype.exec = function (commandLine, args, inputOptions) { + return __awaiter(this, void 0, void 0, function () { + var stdout_1, stderr_1, options, exitCode_1, err_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + stdout_1 = ''; + stderr_1 = ''; + options = { + listeners: { + stdout: function (data) { + stdout_1 += data.toString(); + }, + stderr: function (data) { + stderr_1 += data.toString(); + } + }, + input: inputOptions + }; + return [4 /*yield*/, exec.exec(commandLine, args, options)]; + case 1: + exitCode_1 = _a.sent(); + return [2 /*return*/, new Promise(function (resolve, reject) { + var executionResult = { + exitCode: exitCode_1, + stdout: stdout_1, + stderr: stderr_1 + }; + resolve(executionResult); + })]; + case 2: + err_1 = _a.sent(); + throw err_1; + case 3: return [2 /*return*/]; + } + }); + }); + }; + GitHubActionsToolHelper.prototype.getInput = function (name, required) { + var options = { + required: required + }; + return core.getInput(name, options); + }; + GitHubActionsToolHelper.prototype.setFailed = function (message) { + core.setFailed(message); + }; + GitHubActionsToolHelper.prototype.which = function (tool, check) { + return io.which(tool, check); + }; + GitHubActionsToolHelper.prototype.getDefaultContainerAppName = function (containerAppName) { + containerAppName = "gh-action-app-" + this.getBuildId() + "-" + this.getBuildNumber(); + // Replace all '.' characters with '-' characters in the Container App name + containerAppName = containerAppName.replace(/\./gi, "-"); + this.writeInfo("Default Container App name: " + containerAppName); + return containerAppName; + }; + GitHubActionsToolHelper.prototype.getTelemetryArg = function () { + return "CALLER_ID=github-actions-v2"; + }; + GitHubActionsToolHelper.prototype.getEventName = function () { + return "ContainerAppsGitHubActionV2"; + }; + GitHubActionsToolHelper.prototype.getDefaultImageRepository = function () { + return "gh-action/container-app"; + }; + return GitHubActionsToolHelper; +}()); +exports.GitHubActionsToolHelper = GitHubActionsToolHelper; + + +/***/ }), + +/***/ 7166: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.TelemetryHelper = void 0; +var Utility_1 = __nccwpck_require__(2135); +var GitHubActionsToolHelper_1 = __nccwpck_require__(3185); +var ORYX_CLI_IMAGE = "mcr.microsoft.com/oryx/cli:debian-buster-20230207.2"; +var SUCCESSFUL_RESULT = "succeeded"; +var FAILED_RESULT = "failed"; +var BUILDER_SCENARIO = "used-builder"; +var DOCKERFILE_SCENARIO = "used-dockerfile"; +var IMAGE_SCENARIO = "used-image"; +var util = new Utility_1.Utility(); +var toolHelper = new GitHubActionsToolHelper_1.GitHubActionsToolHelper(); +var TelemetryHelper = /** @class */ (function () { + function TelemetryHelper(disableTelemetry) { + this.disableTelemetry = disableTelemetry; + this.taskStartMilliseconds = Date.now(); + } + /** + * Marks that the task was successful in telemetry. + */ + TelemetryHelper.prototype.setSuccessfulResult = function () { + this.result = SUCCESSFUL_RESULT; + }; + /** + * Marks that the task failed in telemetry. + */ + TelemetryHelper.prototype.setFailedResult = function (errorMessage) { + this.result = FAILED_RESULT; + this.errorMessage = errorMessage; + }; + /** + * Marks that the task used the builder scenario. + */ + TelemetryHelper.prototype.setBuilderScenario = function () { + this.scenario = BUILDER_SCENARIO; + }; + /** + * Marks that the task used the Dockerfile scenario. + */ + TelemetryHelper.prototype.setDockerfileScenario = function () { + this.scenario = DOCKERFILE_SCENARIO; + }; + /** + * Marks that the task used the previously built image scenario. + */ + TelemetryHelper.prototype.setImageScenario = function () { + this.scenario = IMAGE_SCENARIO; + }; + /** + * If telemetry is enabled, uses the "oryx telemetry" command to log metadata about this task execution. + */ + TelemetryHelper.prototype.sendLogs = function () { + return __awaiter(this, void 0, void 0, function () { + var taskLengthMilliseconds, resultArg, scenarioArg, errorMessageArg, eventName, err_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + taskLengthMilliseconds = Date.now() - this.taskStartMilliseconds; + if (!!this.disableTelemetry) return [3 /*break*/, 4]; + toolHelper.writeInfo("Telemetry enabled; logging metadata about task result, length and scenario targeted."); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + resultArg = ''; + if (!util.isNullOrEmpty(this.result)) { + resultArg = "--property result=" + this.result; + } + scenarioArg = ''; + if (!util.isNullOrEmpty(this.scenario)) { + scenarioArg = "--property scenario=" + this.scenario; + } + errorMessageArg = ''; + if (!util.isNullOrEmpty(this.errorMessage)) { + errorMessageArg = "--property errorMessage=" + this.errorMessage; + } + eventName = toolHelper.getEventName(); + return [4 /*yield*/, util.execute("docker run --rm " + ORYX_CLI_IMAGE + " /bin/bash -c \"oryx telemetry --event-name " + eventName + " --processing-time " + taskLengthMilliseconds + " " + resultArg + " " + scenarioArg + " " + errorMessageArg + "\"")]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + err_1 = _a.sent(); + toolHelper.writeWarning("Skipping telemetry logging due to the following exception: " + err_1.message); + return [3 /*break*/, 4]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + return TelemetryHelper; +}()); +exports.TelemetryHelper = TelemetryHelper; + + +/***/ }), + +/***/ 2135: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.Utility = void 0; +// Note: This file is used to define utility functions that can be used across the project. +var GitHubActionsToolHelper_1 = __nccwpck_require__(3185); +var toolHelper = new GitHubActionsToolHelper_1.GitHubActionsToolHelper(); +var Utility = /** @class */ (function () { + function Utility() { + } + /** + * @param commandLine - the command to execute + * @param args - the arguments to pass to the command + * @param continueOnError - whether or not to continue execution if the command fails + */ + Utility.prototype.execute = function (commandLine, args, inputOptions) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, toolHelper.exec(commandLine, args, inputOptions)]; + case 1: return [2 /*return*/, _a.sent()]; + } + }); + }); + }; + /** + * Sets the Azure CLI to install the containerapp extension. + */ + Utility.prototype.installAzureCliExtension = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.execute("az extension add --name containerapp --upgrade")]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + /** + * Checks whether or not the provided string is null, undefined or empty. + * @param str - the string to validate + * @returns true if the string is null, undefined or empty, false otherwise + */ + Utility.prototype.isNullOrEmpty = function (str) { + return str === null || str === undefined || str === ""; + }; + return Utility; +}()); +exports.Utility = Utility; + + +/***/ }), + +/***/ 9491: +/***/ ((module) => { + +"use strict"; +module.exports = require("assert"); + +/***/ }), + +/***/ 2081: +/***/ ((module) => { + +"use strict"; +module.exports = require("child_process"); + +/***/ }), + +/***/ 6113: +/***/ ((module) => { + +"use strict"; +module.exports = require("crypto"); + +/***/ }), + +/***/ 2361: +/***/ ((module) => { + +"use strict"; +module.exports = require("events"); + +/***/ }), + +/***/ 7147: +/***/ ((module) => { + +"use strict"; +module.exports = require("fs"); + +/***/ }), + +/***/ 3685: +/***/ ((module) => { + +"use strict"; +module.exports = require("http"); + +/***/ }), + +/***/ 5687: +/***/ ((module) => { + +"use strict"; +module.exports = require("https"); + +/***/ }), + +/***/ 1808: +/***/ ((module) => { + +"use strict"; +module.exports = require("net"); + +/***/ }), + +/***/ 2037: +/***/ ((module) => { + +"use strict"; +module.exports = require("os"); + +/***/ }), + +/***/ 1017: +/***/ ((module) => { + +"use strict"; +module.exports = require("path"); + +/***/ }), + +/***/ 1576: +/***/ ((module) => { + +"use strict"; +module.exports = require("string_decoder"); + +/***/ }), + +/***/ 9512: +/***/ ((module) => { + +"use strict"; +module.exports = require("timers"); + +/***/ }), + +/***/ 4404: +/***/ ((module) => { + +"use strict"; +module.exports = require("tls"); + +/***/ }), + +/***/ 3837: +/***/ ((module) => { + +"use strict"; +module.exports = require("util"); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __nccwpck_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ var threw = true; +/******/ try { +/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __nccwpck_require__); +/******/ threw = false; +/******/ } finally { +/******/ if(threw) delete __webpack_module_cache__[moduleId]; +/******/ } +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat */ +/******/ +/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/"; +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ var __webpack_exports__ = __nccwpck_require__(3238); +/******/ module.exports = __webpack_exports__; +/******/ +/******/ })() +; \ No newline at end of file diff --git a/dist/licenses.txt b/dist/licenses.txt new file mode 100644 index 00000000..d7718469 --- /dev/null +++ b/dist/licenses.txt @@ -0,0 +1,97 @@ +@actions/core +MIT +The MIT License (MIT) + +Copyright 2019 GitHub + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +@actions/exec +MIT +The MIT License (MIT) + +Copyright 2019 GitHub + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +@actions/http-client +MIT +Actions Http Client for Node.js + +Copyright (c) GitHub, Inc. + +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +@actions/io +MIT +The MIT License (MIT) + +Copyright 2019 GitHub + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +tunnel +MIT +The MIT License (MIT) + +Copyright (c) 2012 Koichi Kobayashi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +uuid +MIT +The MIT License (MIT) + +Copyright (c) 2010-2020 Robert Kieffer and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..32e76251 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,491 @@ +{ + "name": "container-apps-deploy-action", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "container-apps-deploy-action", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@actions/core": "^1.10.0", + "@actions/exec": "^1.1.1", + "@actions/github": "^5.1.1", + "typescript": "^5.2.2" + }, + "devDependencies": { + "@types/node": "^20.6.0" + } + }, + "node_modules/@actions/core": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz", + "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==", + "dependencies": { + "@actions/http-client": "^2.0.1", + "uuid": "^8.3.2" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/github": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz", + "integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==", + "dependencies": { + "@actions/http-client": "^2.0.1", + "@octokit/core": "^3.6.0", + "@octokit/plugin-paginate-rest": "^2.17.0", + "@octokit/plugin-rest-endpoint-methods": "^5.13.0" + } + }, + "node_modules/@actions/http-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.1.tgz", + "integrity": "sha512-qhrkRMB40bbbLo7gF+0vu+X+UawOvQQqNAA/5Unx774RS8poaOhThDOG6BGmxvAnxhQnDp2BG/ZUm65xZILTpw==", + "dependencies": { + "tunnel": "^0.0.6" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==" + }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", + "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "dependencies": { + "@octokit/types": "^6.40.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.16.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", + "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", + "dependencies": { + "@octokit/types": "^6.39.0", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", + "dependencies": { + "@octokit/openapi-types": "^12.11.0" + } + }, + "node_modules/@types/node": { + "version": "20.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", + "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==", + "dev": true + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + }, + "dependencies": { + "@actions/core": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz", + "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==", + "requires": { + "@actions/http-client": "^2.0.1", + "uuid": "^8.3.2" + } + }, + "@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "requires": { + "@actions/io": "^1.0.1" + } + }, + "@actions/github": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz", + "integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==", + "requires": { + "@actions/http-client": "^2.0.1", + "@octokit/core": "^3.6.0", + "@octokit/plugin-paginate-rest": "^2.17.0", + "@octokit/plugin-rest-endpoint-methods": "^5.13.0" + } + }, + "@actions/http-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.1.tgz", + "integrity": "sha512-qhrkRMB40bbbLo7gF+0vu+X+UawOvQQqNAA/5Unx774RS8poaOhThDOG6BGmxvAnxhQnDp2BG/ZUm65xZILTpw==", + "requires": { + "tunnel": "^0.0.6" + } + }, + "@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==" + }, + "@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "requires": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "requires": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" + }, + "@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", + "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "requires": { + "@octokit/types": "^6.40.0" + } + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "5.16.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", + "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", + "requires": { + "@octokit/types": "^6.39.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", + "requires": { + "@octokit/openapi-types": "^12.11.0" + } + }, + "@types/node": { + "version": "20.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", + "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==", + "dev": true + }, + "before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==" + }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..6b9c3dc4 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "container-apps-deploy-action", + "version": "1.0.0", + "description": "This action allows users to easily deploy their application source to an [Azure Container App](https://azure.microsoft.com/en-us/services/container-apps/) in their GitHub workflow by either providing a previously built image, a Dockerfile that an image can be built from, or using a builder to create a runnable application image for the user.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Azure/container-apps-deploy-action.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/Azure/container-apps-deploy-action/issues" + }, + "homepage": "https://github.com/Azure/container-apps-deploy-action#readme", + "dependencies": { + "@actions/core": "^1.10.0", + "@actions/exec": "^1.1.1", + "@actions/github": "^5.1.1", + "typescript": "^5.2.2" + }, + "devDependencies": { + "@types/node": "^20.6.0" + } +} diff --git a/src/ContainerAppHelper.ts b/src/ContainerAppHelper.ts new file mode 100644 index 00000000..f56c43e0 --- /dev/null +++ b/src/ContainerAppHelper.ts @@ -0,0 +1,532 @@ +import * as path from 'path'; +import * as os from 'os'; +import { Utility } from './Utility'; +import { GitHubActionsToolHelper } from './GitHubActionsToolHelper' +import fs = require('fs'); + +const ORYX_CLI_IMAGE: string = 'mcr.microsoft.com/oryx/cli:builder-debian-bullseye-20230926.1'; +const ORYX_BULLSEYE_BUILDER_IMAGE: string = 'mcr.microsoft.com/oryx/builder:debian-bullseye-20231107.2' +const ORYX_BOOKWORM_BUILDER_IMAGE: string = 'mcr.microsoft.com/oryx/builder:debian-bookworm-20231107.2' +const ORYX_BUILDER_IMAGES: string[] = [ORYX_BULLSEYE_BUILDER_IMAGE, ORYX_BOOKWORM_BUILDER_IMAGE]; +const IS_WINDOWS_AGENT: boolean = os.platform() == 'win32'; +const PACK_CMD: string = IS_WINDOWS_AGENT ? path.join(os.tmpdir(), 'pack') : 'pack'; +const toolHelper = new GitHubActionsToolHelper(); +const util = new Utility(); + +export class ContainerAppHelper { + readonly disableTelemetry: boolean = false; + + constructor(disableTelemetry: boolean) { + this.disableTelemetry = disableTelemetry; + } + + /** + * Creates an Azure Container App. + * @param containerAppName - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + * @param environment - the Container App Environment that will be associated with the Container App + * @param optionalCmdArgs - a set of optional command line arguments + */ + public async createContainerApp( + containerAppName: string, + resourceGroup: string, + environment: string, + optionalCmdArgs: string[]) { + toolHelper.writeDebug(`Attempting to create Container App with name "${containerAppName}" in resource group "${resourceGroup}"`); + try { + let command = `az containerapp create -n ${containerAppName} -g ${resourceGroup} --environment ${environment} --output none`; + optionalCmdArgs.forEach(function (val: string) { + command += ` ${val}`; + }); + await util.execute(command); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Creates an Azure Container App. + * @param containerAppName - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + * @param optionalCmdArgs - a set of optional command line arguments + */ + public async createOrUpdateContainerAppWithUp( + containerAppName: string, + resourceGroup: string, + optionalCmdArgs: string[]) { + toolHelper.writeDebug(`Attempting to create Container App with name "${containerAppName}" in resource group "${resourceGroup}"`); + try { + let command = `az containerapp up -n ${containerAppName} -g ${resourceGroup} --debug`; + optionalCmdArgs.forEach(function (val: string) { + command += ` ${val}`; + }); + await util.execute(command); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Creates an Azure Container App based from a YAML configuration file. + * @param containerAppName - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + * @param yamlConfigPath - the path to the YAML configuration file that the Container App properties will be based from + */ + public async createContainerAppFromYaml( + containerAppName: string, + resourceGroup: string, + yamlConfigPath: string) { + toolHelper.writeDebug(`Attempting to create Container App with name "${containerAppName}" in resource group "${resourceGroup}" from provided YAML "${yamlConfigPath}"`); + try { + let command = `az containerapp create -n ${containerAppName} -g ${resourceGroup} --yaml ${yamlConfigPath} --output none`; + await util.execute(command); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Updates an existing Azure Container App based from an image that was previously built. + * @param containerAppName - the name of the existing Container App + * @param resourceGroup - the resource group that the existing Container App is found in + * @param optionalCmdArgs - a set of optional command line arguments + */ + public async updateContainerApp( + containerAppName: string, + resourceGroup: string, + optionalCmdArgs: string[]) { + toolHelper.writeDebug(`Attempting to update Container App with name "${containerAppName}" in resource group "${resourceGroup}" `); + try { + let command = `az containerapp update -n ${containerAppName} -g ${resourceGroup} --output none`; + optionalCmdArgs.forEach(function (val: string) { + command += ` ${val}`; + }); + await util.execute(command); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Updates an existing Azure Container App using the 'az containerapp up' command. + * @param containerAppName - the name of the existing Container App + * @param resourceGroup - the resource group that the existing Container App is found in + * @param optionalCmdArgs - a set of optional command line arguments + * @param ingress - the ingress that the Container App will be exposed on + * @param targetPort - the target port that the Container App will be exposed on + */ + public async updateContainerAppWithUp( + containerAppName: string, + resourceGroup: string, + optionalCmdArgs: string[], + ingress?: string, + targetPort?: string) { + toolHelper.writeDebug(`Attempting to update Container App with name "${containerAppName}" in resource group "${resourceGroup}"`); + try { + let command = `az containerapp up -n ${containerAppName} -g ${resourceGroup}`; + optionalCmdArgs.forEach(function (val: string) { + command += ` ${val}`; + }); + + if (!util.isNullOrEmpty(ingress)) { + command += ` --ingress ${ingress}`; + } + + if (!util.isNullOrEmpty(targetPort)) { + command += ` --target-port ${targetPort}`; + } + + await util.execute(command); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Updates an existing Azure Container App based from a YAML configuration file. + * @param containerAppName - the name of the existing Container App + * @param resourceGroup - the resource group that the existing Container App is found in + * @param yamlConfigPath - the path to the YAML configuration file that the Container App properties will be based from + */ + public async updateContainerAppFromYaml( + containerAppName: string, + resourceGroup: string, + yamlConfigPath: string) { + toolHelper.writeDebug(`Attempting to update Container App with name "${containerAppName}" in resource group "${resourceGroup}" from provided YAML "${yamlConfigPath}"`); + try { + let command = `az containerapp update -n ${containerAppName} -g ${resourceGroup} --yaml ${yamlConfigPath} --output none`; + await util.execute(command); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Determines if the provided Container App exists in the provided resource group. + * @param containerAppName - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + * @returns true if the Container App exists, false otherwise + */ + public async doesContainerAppExist(containerAppName: string, resourceGroup: string): Promise { + toolHelper.writeDebug(`Attempting to determine if Container App with name "${containerAppName}" exists in resource group "${resourceGroup}"`); + try { + let command = `az containerapp show -n ${containerAppName} -g ${resourceGroup} -o none`; + let executionResult = await util.execute(command); + return executionResult.exitCode === 0; + } catch (err) { + toolHelper.writeInfo(err.message); + return false; + } + } + + /** + * Determines if the provided Container App Environment exists in the provided resource group. + * @param containerAppEnvironment - the name of the Container App Environment + * @param resourceGroup - the resource group that the Container App Environment is found in + * @returns true if the Container App Environment exists, false otherwise + */ + public async doesContainerAppEnvironmentExist(containerAppEnvironment: string, resourceGroup: string): Promise { + toolHelper.writeDebug(`Attempting to determine if Container App Environment with name "${containerAppEnvironment}" exists in resource group "${resourceGroup}"`); + try { + let command = `az containerapp env show -o none -g ${resourceGroup} -n ${containerAppEnvironment}`; + let executionResult = await util.execute(command); + return executionResult.exitCode === 0; + } catch (err) { + toolHelper.writeInfo(err.message); + return false; + } + } + + /** + * Determines if the provided resource group exists. + * @param resourceGroup - the name of the resource group + * @returns true if the resource group exists, false otherwise + */ + public async doesResourceGroupExist(resourceGroup: string): Promise { + toolHelper.writeDebug(`Attempting to determine if resource group "${resourceGroup}" exists`); + try { + let command = `az group show -n ${resourceGroup} -o none`; + let executionResult = await util.execute(command); + return executionResult.exitCode === 0; + } catch (err) { + toolHelper.writeInfo(err.message); + return false; + } + } + + /** + * Gets the default location for the Container App provider. + * @returns the default location if found, otherwise 'eastus2' + */ + public async getDefaultContainerAppLocation(): Promise { + toolHelper.writeDebug(`Attempting to get the default location for the Container App service for the subscription.`); + try { + let command = `az provider show -n Microsoft.App --query "resourceTypes[?resourceType=='containerApps'].locations[] | [0]"` + let executionResult = await util.execute(command); + // If successful, strip out double quotes, spaces and parentheses from the first location returned + return executionResult.exitCode === 0 ? executionResult.stdout.toLowerCase().replace(/["() ]/g, "").trim() : `eastus2`; + } catch (err) { + toolHelper.writeInfo(err.message); + return `eastus2`; + } + } + + /** + * Creates a new resource group in the provided location. + * @param name - the name of the resource group to create + * @param location - the location to create the resource group in + */ + public async createResourceGroup(name: string, location: string) { + toolHelper.writeDebug(`Attempting to create resource group "${name}" in location "${location}"`); + try { + let command = `az group create -n ${name} -l ${location}`; + await util.execute(command); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Gets the name of an existing Container App Environment in the provided resource group. + * @param resourceGroup - the resource group to check for an existing Container App Environment + * @returns the name of the existing Container App Environment, null if none exists + */ + public async getExistingContainerAppEnvironment(resourceGroup: string) { + toolHelper.writeDebug(`Attempting to get the existing Container App Environment in resource group "${resourceGroup}"`); + try { + let command = `az containerapp env list -g ${resourceGroup} --query "[0].name"` + let executionResult = await util.execute(command); + return executionResult.exitCode === 0 ? executionResult.stdout : null; + } catch (err) { + toolHelper.writeInfo(err.message); + return null; + } + } + + /** + * Gets the location of an existing Container App Environment + * @param environmentName - the name of the Container App Environment + * @param resourceGroup - the resource group that the Container App Environment is found in + */ + public async getExistingContainerAppEnvironmentLocation(environmentName: string, resourceGroup: string) { + try { + let command = `az containerapp env show -g ${resourceGroup} --query location -n ${environmentName}`; + let executionResult = await util.execute(command); + return executionResult.exitCode === 0 ? executionResult.stdout.toLowerCase().replace(/["() ]/g, "").trim() : null; + } catch (err) { + toolHelper.writeInfo(err.message); + return null; + } + } + + /** + * Gets the environment name of an existing Container App + * @param containerAppName - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + */ + public async getExistingContainerAppEnvironmentName(containerAppName: string, resourceGroup: string) { + try { + let command = `az containerapp show -n ${containerAppName} -g ${resourceGroup} --query properties.environmentId`; + let executionResult = await util.execute(command); + let containerappEnvironmentId = executionResult.stdout.trim(); + + //Remove trailing slash if it exists + if (!util.isNullOrEmpty(containerappEnvironmentId)) { + containerappEnvironmentId = containerappEnvironmentId.endsWith("/") ? containerappEnvironmentId.slice(0, -1) : containerappEnvironmentId; + } + + return executionResult.exitCode === 0 ? containerappEnvironmentId.split("/").pop().trim() : null; + } catch (err) { + toolHelper.writeInfo(err.message); + return null; + } + } + + /** + * Creates a new Azure Container App Environment in the provided resource group. + * @param name - the name of the Container App Environment + * @param resourceGroup - the resource group that the Container App Environment will be created in + * @param location - the location that the Container App Environment will be created in + */ + public async createContainerAppEnvironment(name: string, resourceGroup: string, location?: string) { + const util = new Utility(); + toolHelper.writeDebug(`Attempting to create Container App Environment with name "${name}" in resource group "${resourceGroup}"`); + try { + let command = `az containerapp env create -n ${name} -g ${resourceGroup}`; + if (!util.isNullOrEmpty(location)) { + command += ` -l ${location}`; + } + await util.execute(command); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Disables ingress on an existing Container App. + * @param name - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + */ + public async disableContainerAppIngress(name: string, resourceGroup: string) { + toolHelper.writeDebug(`Attempting to disable ingress for Container App with name "${name}" in resource group "${resourceGroup}"`); + try { + let command = `az containerapp ingress disable -n ${name} -g ${resourceGroup}`; + await util.execute(command); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Updates the Container Registry details on an existing Container App. + * @param name - the name of the Container App + * @param resourceGroup - the resource group that the Container App is found in + * @param registryUrl - the name of the Container Registry + * @param registryUsername - the username used to authenticate with the Container Registry + * @param registryPassword - the password used to authenticate with the Container Registry + */ + public async updateContainerAppRegistryDetails(name: string, resourceGroup: string, registryUrl: string, registryUsername: string, registryPassword: string) { + toolHelper.writeDebug(`Attempting to set the Container Registry details for Container App with name "${name}" in resource group "${resourceGroup}"`); + try { + let command = `az containerapp registry set -n ${name} -g ${resourceGroup} --server ${registryUrl} --username ${registryUsername} --password ${registryPassword}`; + await util.execute(command); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Using the Oryx++ Builder, creates a runnable application image from the provided application source. + * @param imageToDeploy - the name of the runnable application image that is created and can be later deployed + * @param appSourcePath - the path to the application source on the machine + * @param environmentVariables - an array of environment variables that should be provided to the builder via the `--env` flag + * @param builderStack - the stack to use when building the provided application source + */ + public async createRunnableAppImage( + imageToDeploy: string, + appSourcePath: string, + environmentVariables: string[], + builderStack?: string) { + + let telemetryArg = toolHelper.getTelemetryArg(); + if (this.disableTelemetry) { + telemetryArg = `ORYX_DISABLE_TELEMETRY=true`; + } + + let couldBuildImage = false; + + for (const builderImage of ORYX_BUILDER_IMAGES) { + if (!util.isNullOrEmpty(builderStack) && !builderImage.includes(builderStack)) { + continue; + } + + toolHelper.writeDebug(`Attempting to create a runnable application image with name "${imageToDeploy}" using the Oryx++ Builder "${builderImage}"`); + + try { + let command = `build ${imageToDeploy} --path ${appSourcePath} --builder ${builderImage} --env ${telemetryArg}`; + environmentVariables.forEach(function (envVar: string) { + command += ` --env ${envVar}`; + }); + + await util.execute(`${PACK_CMD} ${command}`); + couldBuildImage = true; + break; + } catch (err) { + toolHelper.writeWarning(`Unable to run 'pack build' command to produce runnable application image: ${err.message}`); + } + }; + + // If none of the builder images were able to build the provided application source, throw an error. + if (!couldBuildImage) { + const errorMessage = `No builder was able to build the provided application source. Please visit the following page for more information on supported platform versions: https://aka.ms/SourceToCloudSupportedVersions`; + toolHelper.writeError(errorMessage); + throw new Error(errorMessage); + } + } + + /** + * Using a Dockerfile that was provided or found at the root of the application source, + * creates a runable application image. + * @param imageToDeploy - the name of the runnable application image that is created and can be later deployed + * @param appSourcePath - the path to the application source on the machine + * @param dockerfilePath - the path to the Dockerfile to build and tag with the provided image name + */ + public async createRunnableAppImageFromDockerfile( + imageToDeploy: string, + appSourcePath: string, + dockerfilePath: string) { + toolHelper.writeDebug(`Attempting to create a runnable application image from the provided/found Dockerfile "${dockerfilePath}" with image name "${imageToDeploy}"`); + try { + let command = `docker build --file ${dockerfilePath} ${appSourcePath} --tag ${imageToDeploy}`; + await util.execute(command); + toolHelper.writeDebug(`Successfully created runnable application image from the provided/found Dockerfile "${dockerfilePath}" with image name "${imageToDeploy}"`); + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Determines the runtime stack to use for the runnable application image. + * @param appSourcePath - the path to the application source on the machine + * @returns a string representing the runtime stack that can be used for the Oryx MCR runtime images + */ + public async determineRuntimeStackAsync(appSourcePath: string): Promise { + toolHelper.writeDebug('Attempting to determine the runtime stack needed for the provided application source'); + try { + // Use 'oryx dockerfile' command to determine the runtime stack to use and write it to a temp file + let command = `docker run --rm -v ${appSourcePath}:/app ${ORYX_CLI_IMAGE} /bin/bash -c "oryx dockerfile /app | head -n 1 | sed 's/ARG RUNTIME=//' >> /app/oryx-runtime.txt"` + await util.execute(command) + + // Read the temp file to get the runtime stack into a variable + let oryxRuntimeTxtPath = path.join(appSourcePath, 'oryx-runtime.txt'); + + let runtimeStack = fs.promises.readFile(oryxRuntimeTxtPath, 'utf8').then((data) => { + let lines = data.split('\n'); + return lines[0]; + }).catch((err) => { + toolHelper.writeError(err.message); + throw err; + }); + + // Delete the temp file + fs.unlink(oryxRuntimeTxtPath, (err) => { + if (err) { + toolHelper.writeWarning(`Unable to delete the temporary file "${oryxRuntimeTxtPath}". Error: ${err.message}`); + } + }); + + return runtimeStack; + } catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Sets the default builder on the machine to the Oryx++ Builder to prevent an exception from being thrown due + * to no default builder set. + */ + public async setDefaultBuilder() { + toolHelper.writeInfo('Setting the Oryx++ Builder as the default builder via the pack CLI'); + try { + let command = `config default-builder ${ORYX_BUILDER_IMAGES[0]}` + await util.execute(`${PACK_CMD} ${command}`); + } + catch (err) { + toolHelper.writeError(err.message); + throw err; + } + } + + /** + * Installs the pack CLI that will be used to build a runnable application image. + * For more Information about the pack CLI can be found here: https://buildpacks.io/docs/tools/pack/ + */ + public async installPackCliAsync() { + toolHelper.writeDebug('Attempting to install the pack CLI'); + try { + let command: string = ''; + let commandLine = ''; + if (IS_WINDOWS_AGENT) { + let packZipDownloadUri: string = 'https://github.com/buildpacks/pack/releases/download/v0.31.0/pack-v0.31.0-windows.zip'; + let packZipDownloadFilePath: string = path.join(PACK_CMD, 'pack-windows.zip'); + command = `New-Item -ItemType Directory -Path ${PACK_CMD} -Force | Out-Null; Invoke-WebRequest -Uri ${packZipDownloadUri} -OutFile ${packZipDownloadFilePath}; Expand-Archive -LiteralPath ${packZipDownloadFilePath} -DestinationPath ${PACK_CMD}; Remove-Item -Path ${packZipDownloadFilePath}`; + commandLine = 'pwsh'; + } else { + let tgzSuffix = os.platform() == 'darwin' ? 'macos' : 'linux'; + command = `(curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.31.0/pack-v0.31.0-${tgzSuffix}.tgz" | ` + + 'tar -C /usr/local/bin/ --no-same-owner -xzv pack)'; + commandLine = 'bash'; + } + await util.execute(`${commandLine} -c "${command}"`); + } catch (err) { + toolHelper.writeError(`Unable to install the pack CLI. Error: ${err.message}`); + throw err; + } + } + + /** + * Enables experimental features for the pack CLI, such as extension support. + */ + public async enablePackCliExperimentalFeaturesAsync() { + toolHelper.writeDebug('Attempting to enable experimental features for the pack CLI'); + try { + let command = `${PACK_CMD} config experimental true`; + await util.execute(command); + } catch (err) { + toolHelper.writeError(`Unable to enable experimental features for the pack CLI: ${err.message}`); + throw err; + } + } +} \ No newline at end of file diff --git a/src/ContainerRegistryHelper.ts b/src/ContainerRegistryHelper.ts new file mode 100644 index 00000000..e6f7a136 --- /dev/null +++ b/src/ContainerRegistryHelper.ts @@ -0,0 +1,54 @@ +import * as os from 'os'; +import { Utility } from './Utility'; +import { GitHubActionsToolHelper } from './GitHubActionsToolHelper'; + +const toolHelper = new GitHubActionsToolHelper(); +const util = new Utility(); + +export class ContainerRegistryHelper { + /** + * Authorizes Docker to make calls to the provided Container Registry instance using username and password. + * @param registryUrl - the name of the Container Registry instance to authenticate calls to + * @param registryUsername - the username for authentication + * @param registryPassword - the password for authentication + */ + public async loginContainerRegistryWithUsernamePassword(registryUrl: string, registryUsername: string, registryPassword: string) { + toolHelper.writeDebug(`Attempting to log in to Container Registry instance"${registryUrl}" with username and password credentials`); + try { + await util.execute(`docker login --password-stdin --username ${registryUsername} ${registryUrl}`, [], Buffer.from(registryPassword)); + } catch (err) { + toolHelper.writeError(`Failed to log in to Container Registry instance "${registryUrl}" with username and password credentials`); + throw err; + } + } + + /** + * Authorizes Docker to make calls to the provided ACR instance using an access token that is generated via + * the 'az acr login --expose-token' command. + * @param acrName - the name of the ACR instance to authenticate calls to. + */ + public async loginAcrWithAccessTokenAsync(acrName: string) { + toolHelper.writeDebug(`Attempting to log in to ACR instance "${acrName}" with access token`); + try { + let commandLine = os.platform() === 'win32' ? 'pwsh' : 'bash'; + await util.execute(`${commandLine} -c "CA_ADO_TASK_ACR_ACCESS_TOKEN=$(az acr login --name ${acrName} --output json --expose-token --only-show-errors | jq -r '.accessToken'); docker login ${acrName}.azurecr.io -u 00000000-0000-0000-0000-000000000000 -p $CA_ADO_TASK_ACR_ACCESS_TOKEN > /dev/null 2>&1"`); + } catch (err) { + toolHelper.writeError(`Failed to log in to ACR instance "${acrName}" with access token`) + throw err; + } + } + + /** + * Pushes an image to the Container Registry instance that was previously authenticated against. + * @param imageToPush - the name of the image to push to the Container Registry instance + */ + public async pushImageToContainerRegistry(imageToPush: string) { + toolHelper.writeDebug(`Attempting to push image "${imageToPush}" to Container Registry`); + try { + await util.execute(`docker push ${imageToPush}`); + } catch (err) { + toolHelper.writeError(`Failed to push image "${imageToPush}" to Container Registry. Error: ${err.message}`); + throw err; + } + } +} \ No newline at end of file diff --git a/src/GithubActionsToolHelper.ts b/src/GithubActionsToolHelper.ts new file mode 100644 index 00000000..8dcfa03d --- /dev/null +++ b/src/GithubActionsToolHelper.ts @@ -0,0 +1,96 @@ +import * as core from '@actions/core'; +import * as io from '@actions/io'; +import * as exec from '@actions/exec'; + +export class GitHubActionsToolHelper { + + public getBuildId(): string { + return process.env['GITHUB_RUN_ID'] || ''; + } + + public getBuildNumber(): string { + return process.env['GITHUB_RUN_NUMBER'] || ''; + } + + public writeInfo(message: string): void { + core.info(message); + } + + public writeError(message: string): void { + core.error(message); + } + + public writeWarning(message: string): void { + core.warning(message); + } + + public writeDebug(message: string): void { + core.debug(message); + } + + public async exec(commandLine: string, args?: string[], inputOptions?: Buffer): Promise<{ exitCode: number, stdout: string, stderr: string }> { + try{ + let stdout = ''; + let stderr = ''; + + const options: exec.ExecOptions = { + listeners: { + stdout: (data: Buffer) => { + stdout += data.toString(); + }, + stderr: (data: Buffer) => { + stderr += data.toString(); + }, + }, + input: inputOptions + }; + + let exitCode = await exec.exec(commandLine, args, options); + return new Promise((resolve, reject) => { + let executionResult = { + exitCode: exitCode, + stdout: stdout, + stderr: stderr + } + resolve(executionResult); + }); + }catch(err){ + throw err; + } + } + + public getInput(name: string, required?: boolean): string { + const options: core.InputOptions = { + required:required + } + return core.getInput(name, options); + } + + public setFailed(message: string): void { + core.setFailed(message); + } + + public which(tool: string, check?: boolean): Promise { + return io.which(tool, check); + } + + public getDefaultContainerAppName(containerAppName: string): string { + containerAppName = `gh-action-app-${this.getBuildId()}-${this.getBuildNumber()}`; + // Replace all '.' characters with '-' characters in the Container App name + containerAppName = containerAppName.replace(/\./gi, "-"); + this.writeInfo(`Default Container App name: ${containerAppName}`); + return containerAppName; + } + + public getTelemetryArg(): string { + return `CALLER_ID=github-actions-v2`; + } + + public getEventName(): string { + return `ContainerAppsGitHubActionV2`; + } + + public getDefaultImageRepository(): string { + return `gh-action/container-app`; + } +} \ No newline at end of file diff --git a/src/TelemetryHelper.ts b/src/TelemetryHelper.ts new file mode 100644 index 00000000..141fbcf0 --- /dev/null +++ b/src/TelemetryHelper.ts @@ -0,0 +1,95 @@ +import { Utility } from './Utility'; +import { GitHubActionsToolHelper } from './GitHubActionsToolHelper'; + +const ORYX_CLI_IMAGE: string = "mcr.microsoft.com/oryx/cli:debian-buster-20230207.2"; + +const SUCCESSFUL_RESULT: string = "succeeded"; +const FAILED_RESULT: string = "failed"; + +const BUILDER_SCENARIO: string = "used-builder"; +const DOCKERFILE_SCENARIO: string = "used-dockerfile"; +const IMAGE_SCENARIO: string = "used-image"; + +const util = new Utility(); +const toolHelper = new GitHubActionsToolHelper(); + +export class TelemetryHelper { + readonly disableTelemetry: boolean; + + private scenario: string; + private result: string; + private errorMessage: string; + private taskStartMilliseconds: number; + + constructor(disableTelemetry: boolean) { + this.disableTelemetry = disableTelemetry; + this.taskStartMilliseconds = Date.now(); + } + + /** + * Marks that the task was successful in telemetry. + */ + public setSuccessfulResult() { + this.result = SUCCESSFUL_RESULT; + } + + /** + * Marks that the task failed in telemetry. + */ + public setFailedResult(errorMessage: string) { + this.result = FAILED_RESULT; + this.errorMessage = errorMessage; + } + + /** + * Marks that the task used the builder scenario. + */ + public setBuilderScenario() { + this.scenario = BUILDER_SCENARIO; + } + + /** + * Marks that the task used the Dockerfile scenario. + */ + public setDockerfileScenario() { + this.scenario = DOCKERFILE_SCENARIO; + } + + /** + * Marks that the task used the previously built image scenario. + */ + public setImageScenario() { + this.scenario = IMAGE_SCENARIO; + } + + /** + * If telemetry is enabled, uses the "oryx telemetry" command to log metadata about this task execution. + */ + public async sendLogs() { + let taskLengthMilliseconds: number = Date.now() - this.taskStartMilliseconds; + if (!this.disableTelemetry) { + toolHelper.writeInfo(`Telemetry enabled; logging metadata about task result, length and scenario targeted.`); + try { + let resultArg: string = ''; + if (!util.isNullOrEmpty(this.result)) { + resultArg = `--property result=${this.result}`; + } + + let scenarioArg: string = ''; + if (!util.isNullOrEmpty(this.scenario)) { + scenarioArg = `--property scenario=${this.scenario}`; + } + + let errorMessageArg: string = ''; + if (!util.isNullOrEmpty(this.errorMessage)) { + errorMessageArg = `--property errorMessage=${this.errorMessage}`; + } + + let eventName = toolHelper.getEventName(); + await util.execute(`docker run --rm ${ORYX_CLI_IMAGE} /bin/bash -c "oryx telemetry --event-name ${eventName} --processing-time ${taskLengthMilliseconds} ${resultArg} ${scenarioArg} ${errorMessageArg}"`); + } catch (err) { + toolHelper.writeWarning(`Skipping telemetry logging due to the following exception: ${err.message}`); + } + } + } +} \ No newline at end of file diff --git a/src/Utility.ts b/src/Utility.ts new file mode 100644 index 00000000..b5e2659b --- /dev/null +++ b/src/Utility.ts @@ -0,0 +1,32 @@ +// Note: This file is used to define utility functions that can be used across the project. +import { GitHubActionsToolHelper } from './GitHubActionsToolHelper'; + +const toolHelper = new GitHubActionsToolHelper(); + +export class Utility { + /** + * @param commandLine - the command to execute + * @param args - the arguments to pass to the command + * @param continueOnError - whether or not to continue execution if the command fails + */ + + public async execute(commandLine: string, args?: string[], inputOptions?:Buffer): Promise<{ exitCode: number, stdout: string, stderr: string }> { + return await toolHelper.exec(commandLine, args, inputOptions); + } + + /** + * Sets the Azure CLI to install the containerapp extension. + */ + public async installAzureCliExtension() { + await this.execute(`az extension add --name containerapp --upgrade`); + } + + /** + * Checks whether or not the provided string is null, undefined or empty. + * @param str - the string to validate + * @returns true if the string is null, undefined or empty, false otherwise + */ + public isNullOrEmpty(str: string): boolean { + return str === null || str === undefined || str === ""; + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..deab28d2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ES2016", + "module": "CommonJS", + "moduleResolution": "node", + "lib": ["es2018", "dom"], + } +}