fjps
fjps

Reputation: 23

How to send slack message using ADO YAML Pipeline post successful and failed builds?

I'm trying to send a Slack message to a specified channel post-build, all branch policies are successful, and all status checks pass in a Azure DevOps YAML pipeline. But it seems I am having a few individual issues. This is fairly new to me so any help is appreciated.

Concept:

  1. Post-Build Success, All Branch Policies & Status Checks Pass -> Send Slack Notification to specified channel.
  2. If the build fails or any branch policies fail, Direct Message the Pull Requests Creator.
  3. If any new commits are made to the branch while the pull request is active, or any comments are resolved on the pull request, the pipeline will rerun and follow the same previous steps.

What I Tried:

  1. I tried a bunch of different combination of conditions but they didn't work.
  2. For example, a plain succeeded() and failed() without dependencies, or succeeded('Test') as stage level or succeeded('UnitTest') as job level.
  3. I also attempted having stages to build and restore but that was not necessary at this point in time. 4.In most cases, they send the success message even if they failed, and failed does not send the direct message, and messages that do send do not contain any successful variables.

Problems:

  1. I can't find a proper condition setting to distinguish between success and failures.
  2. I cannot find the Slack User Id as well as other variables to populate the Slack Messages.
  3. Trouble with the message formatting in slack properly
  4. I also am having trouble having this run as a final step only after all branch policies pass - less "At least 1 reviewer must approve." The branch policies are inherited.
  5. Adding condition for Status Checks.

I have included some reference images below as well.

trigger:
  - none  # Disable automatic trigger for commits. Trigger only for PRs.

resources:
  repositories:
    - repository: templates
      type: git
      name: Hagerty/azure-pipelines-templates

pr:
  branches:
    include:
      - main  # Only trigger for PRs targeting 'main'

pool:
  vmImage: 'windows-latest'

variables:
  - group: billing_pr_slack_notification  # ADO variable group

stages:
  - stage: BuildAndNotify
    displayName: 'Billing PR Slack Notifications'
    # Ensure this stage runs only after all PR policies succeed
    condition: and(succeeded(), eq(coalesce(variables['System.PullRequest.IsDraft'], false), false), startsWith(variables['Build.SourceBranch'], 'refs/pull/'))
    jobs:
      - job: LogVariables
        displayName: 'Log Pipeline Variables'
        steps:
          - script: |
              echo '=== Debugging Variables ==='
              echo 'AZURE_DEVOPS_ORG: $(AZURE_DEVOPS_ORG)'
              echo 'AZURE_DEVOPS_PROJECT: $(AZURE_DEVOPS_PROJECT)'
              echo 'SLACK_API_URL: $(SLACK_API_URL)'
              echo 'SLACK_BOT_TOKEN: $(SLACK_BOT_TOKEN)'
              echo 'SLACK_CHANNEL: $(SLACK_CHANNEL)'
              echo 'PRODUCT_BILLING_DEV_WEBHOOK_URL: $(PRODUCT_BILLING_DEV_WEBHOOK_URL)'
              echo 'System.PullRequest.IsDraft: $(System.PullRequest.IsDraft)'
              echo 'System.PullRequest.SourceBranch: $(System.PullRequest.SourceBranch)'
              echo 'System.PullRequest.TargetBranch: $(System.PullRequest.TargetBranch)'
            displayName: 'Output Debugging Variables'

      - job: FetchSlackUserID
        displayName: 'Fetch Slack User ID'
        dependsOn: LogVariables
        steps:
          - powershell: |
              $creatorEmail = $env:BUILD_REQUESTEDFOR
              if (-not $creatorEmail) {
                  Write-Host "##vso[task.logissue type=error]The Build.RequestedFor email is empty. Cannot proceed to fetch Slack User ID."
                  exit 1
              }
              $slackToken = "$env:SLACK_BOT_TOKEN"
              $response = Invoke-RestMethod -Uri "https://slack.com/api/users.lookupByEmail?email=$creatorEmail" -Headers @{Authorization="Bearer $slackToken"}

              if ($response.ok -eq $true -and $response.user.id) {
                  $userId = $response.user.id
                  Write-Host "##vso[task.setvariable variable=userId]$userId"
                  Write-Host "Successfully retrieved Slack User ID: $userId"
                  Write-Host "Debug: Slack User ID is $userId"
              } else {
                  Write-Host "Defaulting to Slack channel $env:SLACK_CHANNEL."
                  Write-Host "##vso[task.setvariable variable=userId]$env:SLACK_CHANNEL"
              }
            displayName: 'Fetch Slack User ID'

      - job: SlackNotification
        displayName: 'Send Slack Notifications'
        dependsOn: FetchSlackUserID
        steps:
          # Send a channel notification only on success
          - powershell: |
              $webhookUrl = "$env:PRODUCT_BILLING_DEV_WEBHOOK_URL"
              $createdBy = $env:SYSTEM_PULLREQUEST_CREATEDBY
              $prTitle = $env:SYSTEM_PULLREQUEST_TITLE
              $prDescription = $env:SYSTEM_PULLREQUEST_DESCRIPTION
              $prUrl = $env:SYSTEM_PULLREQUEST_URL

              # Handle unset variables by providing default values
              if (-not $createdBy) { $createdBy = "Unknown User" }
              if (-not $prTitle) { $prTitle = "Untitled PR" }
              if (-not $prDescription) { $prDescription = "No description provided." }
              if (-not $prUrl) { $prUrl = "No link available." }

              $message = @{
                  text = "<@here> [$createdBy] has created a new Pull Request & is ready for Code Review:\n**Title**: $prTitle\n**Description**: $prDescription\n[View PR]($prUrl)"
              } | ConvertTo-Json -Depth 10

              Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $message -ContentType 'application/json'
              Write-Host "Slack channel notification sent successfully."
            displayName: 'Send Slack Notification'
            condition: and(succeeded(), endsWith(variables['System.PullRequest.TargetBranch'], '/main'))

          # Send a direct message only on failure
          - powershell: |
              $gifUrls = @(
                  "https://media.giphy.com/media/Ju7l5y9osyymQ/giphy.gif",
                  "https://media.giphy.com/media/26AHONQ79FdWZhAI0/giphy.gif",
                  "https://media.giphy.com/media/3o6Zt481isNVuQI1l6/giphy.gif"
              )
              $randomGif = $gifUrls | Get-Random
              $slackToken = "$env:SLACK_BOT_TOKEN"
              $userId = "$env:userId"

              $prTitle = $env:SYSTEM_PULLREQUEST_TITLE
              $prUrl = $env:SYSTEM_PULLREQUEST_URL

              # Handle unset variables by providing default values
              if (-not $prTitle) { $prTitle = "Untitled PR" }
              if (-not $prUrl) { $prUrl = "No link available." }

              $message = if ($env:BUILD_STATUS -eq "Failed") {
                  "Your Pull Request **$prTitle** failed to build. Please review your branch.\n[View PR]($prUrl)\n\n![Failure GIF]($randomGif)"
              } else {
                  "Your Pull Request **$prTitle** failed the following policy check: $env:BUILD_DEFINITIONNAME. Please review your branch.\n[View PR]($prUrl)\n\n![Failure GIF]($randomGif)"
              }

              $body = @{
                  channel = $userId
                  text    = $message
              } | ConvertTo-Json -Depth 10

              Invoke-RestMethod -Uri "https://slack.com/api/chat.postMessage" -Method Post -Headers @{Authorization="Bearer $slackToken"} -Body $body
              Write-Host "Slack DM sent successfully to user $userId."
            displayName: 'Send Slack DM if Build or Policy Fails'
            condition: failed()

BranchPolicies StatusChecks SlackFormatting

Upvotes: 0

Views: 28

Answers (1)

bryanbcook
bryanbcook

Reputation: 17953

There's a few concerns with your pipeline.

The first is a common misunderstanding about jobs and variable scope. Each job in the Microsoft cloud-hosted agents (windows-latest) runs on a different agent, so any environment variables that you create within that job are not available to subsequent jobs. Jobs should be large units of work where you group related things together. In your scenario you're actually running three similar activities in different jobs, which in addition to the scope issues will slow down the pipeline considerably as the system will spend a fair bit of time to allocate new agents. You'd be better off with a single job with multiple steps as the variables you create in early steps will always be available in subsequent steps (plus a minor reduction in execution time).

The second concern is related to your conditions. The function succeeded() and failed refers to the current job's execution. As you've written it, you have conditions on the tasks within the SlackNotification job. The succeeded() condition on the first task has no effect because it's the first task in the job, and the failed() condition on the second task only executes if the 'Send Slack Notification' powershell task fails, which presumably is not what you're after.

I'm assuming that you want the succeeded or failed conditions applied based on the outcome of the build activity. You haven't shown the build activity in your pipeline, but assuming it's been omitted for clarity you can achieve the desired effect with two jobs:

trigger: none

jobs:
- job: build
  steps:
  ... steps to build are here

- job: slacknotify
  displayName: 'Send Slack Notification'
  dependsOn:
  - build
  # specify a condition that looks at the "build" job's final result
  condition: |
    and(
      in(dependencies.build.result, 'Succeeded', 'Failed'),
      eq(variables['Build.Reason'],'PullRequest'),
      eq(variables['System.PullRequest.TargetBranch'], 'ref/heads/main')
    )
  variables:
    # create a job scoped variable that holds the outcome of the build result
    buildResult: $[ dependencies.build.result ]
  steps:
  - powershell: |
      ...
    displayName: Resolve Slack User ID

  - powershell: |
      ...
    displayName: Send Success Message
    condition: eq(variables['buildResult'], 'Success')

  - powershell: |
      ...
    displayName: Send Failure Message
    condition: eq(variables['buildResult'], 'Failed')

Related to your variables:

  • SYSTEM_PULLREQUEST_CREATEDBY is not valid. use $(Build.RequestedBy)
  • SYSTEM_PULLREQUEST_URL is not valid. use (System.PullRequest.SourceRepositoryURI) and $(System.PullRequest.PullRequestId) to construct the URL.
  • SYSTEM_PULLREQUEST_TITLE and SYSTEM_PULLREQUEST_DESCRIPTION are not valid. You'd have to use the REST API to fetch the Pull Request details.

As for determine branch policy completion criteria, you'd have to use the REST API to fetch the Pull Request to evaluate which conditions have been met.

Lastly, the pr trigger you've specified has no effect. Because you're using Azure Repository (as evidenced by the Branch Policy), pr triggers can only be used with GitHub or BitBucket Cloud. This pipeline will only be triggered by a pull request if it's configured in the Build Validation of the Branch Policy.

Upvotes: 0

Related Questions