├── check ├── in ├── images ├── teams2.png ├── webhook.png ├── connector.png └── pipeline.png ├── renovate.json ├── Dockerfile ├── testing ├── README.md ├── docker-compose.yml ├── hello-world.yml ├── hello-world-proxy.yml ├── hello-world-failing.yml └── hello-world-pwd.yml ├── .github └── workflows │ ├── lic_upt.yml │ └── ci.yml ├── card.jq ├── LICENSE ├── out └── README.md /check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "[]" 4 | 5 | -------------------------------------------------------------------------------- /in: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo '{"version":{"ref":"none"}}' 4 | 5 | -------------------------------------------------------------------------------- /images/teams2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navicore/teams-notification-resource/HEAD/images/teams2.png -------------------------------------------------------------------------------- /images/webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navicore/teams-notification-resource/HEAD/images/webhook.png -------------------------------------------------------------------------------- /images/connector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navicore/teams-notification-resource/HEAD/images/connector.png -------------------------------------------------------------------------------- /images/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navicore/teams-notification-resource/HEAD/images/pipeline.png -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "schedule": [ 4 | "before 3am on the first day of the month" 5 | ], 6 | "extends": [ 7 | "config:base" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | RUN apt-get update && \ 4 | DEBIAN_FRONTEND=noninteractive apt-get install -y curl jq && \ 5 | rm -rf /var/lib/apt/lists/* 6 | 7 | COPY check /opt/resource/check 8 | COPY in /opt/resource/in 9 | COPY out /opt/resource/out 10 | COPY card.jq /opt/resource/card.jq 11 | 12 | RUN chmod +x /opt/resource/out /opt/resource/in /opt/resource/check 13 | -------------------------------------------------------------------------------- /testing/README.md: -------------------------------------------------------------------------------- 1 | # DEVELOPMENT ENVIRONMENT 2 | 3 | I use these pipelines and docker-compose to test the Teams connector. 4 | 5 | Feel free to contribute examples of pipelines that you use and for which you 6 | would like to ensure regression protection. 7 | 8 | ``` 9 | docker-compose up -d 10 | ``` 11 | 12 | User:pwd is `test:test`. I shouldn't have to say don't expose this to a network. 13 | -------------------------------------------------------------------------------- /.github/workflows/lic_upt.yml: -------------------------------------------------------------------------------- 1 | name: Update copyright year(s) in license file 2 | 3 | on: 4 | schedule: 5 | - cron: "0 3 1 1 *" 6 | 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | - uses: FantasticFiasco/action-update-license-year@v3 15 | with: 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /card.jq: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "attachments": [ 4 | { 5 | "contentType": "application/vnd.microsoft.card.adaptive", 6 | "contentUrl": null, 7 | "content": { 8 | "type": "AdaptiveCard", 9 | "body": [ 10 | { 11 | "type": "Container", 12 | "items": [ 13 | { 14 | "type": "TextBlock", 15 | "text": $title, 16 | "wrap": true, 17 | "size": "Large", 18 | "weight": "Bolder" 19 | } 20 | ], 21 | "style": $style 22 | }, 23 | { 24 | "type": "TextBlock", 25 | "text": $text, 26 | "wrap": true 27 | } 28 | ], 29 | "actions": [ 30 | { 31 | "type": "Action.OpenUrl", 32 | "title": $actionName, 33 | "url": $actionTarget 34 | } 35 | ], 36 | "msteams": { 37 | "width": "Full" 38 | }, 39 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 40 | "version": "1.5" 41 | } 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2025 Ed Sweeney 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /testing/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | concourse-db: 5 | image: postgres 6 | environment: 7 | POSTGRES_DB: concourse 8 | POSTGRES_PASSWORD: concourse_pass 9 | POSTGRES_USER: concourse_user 10 | PGDATA: /database 11 | 12 | concourse: 13 | image: concourse/concourse 14 | command: quickstart 15 | privileged: true 16 | depends_on: [concourse-db] 17 | ports: ["8080:8080"] 18 | environment: 19 | CONCOURSE_POSTGRES_HOST: concourse-db 20 | CONCOURSE_POSTGRES_USER: concourse_user 21 | CONCOURSE_POSTGRES_PASSWORD: concourse_pass 22 | CONCOURSE_POSTGRES_DATABASE: concourse 23 | CONCOURSE_EXTERNAL_URL: http://localhost:8080 24 | CONCOURSE_ADD_LOCAL_USER: test:test 25 | CONCOURSE_MAIN_TEAM_LOCAL_USER: test 26 | # instead of relying on the default "detect" 27 | CONCOURSE_WORKER_BAGGAGECLAIM_DRIVER: overlay 28 | CONCOURSE_CLIENT_SECRET: Y29uY291cnNlLXdlYgo= 29 | CONCOURSE_TSA_CLIENT_SECRET: Y29uY291cnNlLXdvcmtlcgo= 30 | CONCOURSE_X_FRAME_OPTIONS: allow 31 | CONCOURSE_CONTENT_SECURITY_POLICY: "*" 32 | CONCOURSE_CLUSTER_NAME: tutorial 33 | CONCOURSE_WORKER_CONTAINERD_DNS_SERVER: "8.8.8.8" 34 | CONCOURSE_WORKER_RUNTIME: "containerd" 35 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | tags: 11 | - v* 12 | 13 | jobs: 14 | publish: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Prepare 20 | id: prepare 21 | run: | 22 | TAG=${GITHUB_REF##*/} 23 | echo ::set-output name=tag_name::${TAG} 24 | - name: Set up QEMU 25 | uses: docker/setup-qemu-action@v3 26 | - name: Set up Docker Buildx 27 | uses: docker/setup-buildx-action@v3 28 | - name: Login do docker.io 29 | if: startsWith(github.ref, 'refs/tags/') 30 | run: docker login -u navicore -p ${{ secrets.DOCKER_TOKEN }} 31 | - name: build and publish image 32 | if: startsWith(github.ref, 'refs/tags/') 33 | id: docker_build 34 | uses: docker/build-push-action@v5 35 | with: 36 | context: . 37 | file: ./Dockerfile 38 | platforms: linux/amd64,linux/arm64 39 | push: ${{ startsWith(github.ref, 'refs/tags/') }} 40 | tags: | 41 | navicore/teams-notification-resource:${{ steps.prepare.outputs.tag_name }} 42 | navicore/teams-notification-resource:latest 43 | -------------------------------------------------------------------------------- /testing/hello-world.yml: -------------------------------------------------------------------------------- 1 | task-config: &task-config 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: busybox } 6 | resources: 7 | - name: alert 8 | type: teams-notification 9 | source: 10 | url: "PUT YOUR WEBHOOK HERE" 11 | resource_types: 12 | - name: teams-notification 13 | type: docker-image 14 | source: 15 | repository: navicore/teams-notification-resource 16 | tag: dev 17 | jobs: 18 | - name: hello-world-job 19 | plan: 20 | - task: hello-world-task 21 | config: 22 | # Tells Concourse which type of worker this task should run on 23 | platform: linux 24 | # This is one way of telling Concourse which container image to use for a 25 | # task. We'll explain this more when talking about resources 26 | image_resource: 27 | type: registry-image 28 | source: 29 | repository: busybox # images are pulled from docker hub by default 30 | # The command Concourse will run inside the container 31 | # echo "Hello world!" 32 | run: 33 | path: echo 34 | args: ["Hello world!"] 35 | on_success: 36 | task: job-success 37 | config: 38 | << : *task-config 39 | run: 40 | path: echo 41 | args: ["This job succeeded!"] 42 | on_success: 43 | put: alert 44 | params: 45 | text: | 46 | task tested: with no errors 47 | title: hello-world tested good 48 | actionName: hello-world Pipeline 49 | actionTarget: $ATC_EXTERNAL_URL/teams/$BUILD_TEAM_NAME/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME 50 | 51 | -------------------------------------------------------------------------------- /testing/hello-world-proxy.yml: -------------------------------------------------------------------------------- 1 | task-config: &task-config 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: busybox } 6 | resources: 7 | - name: alert 8 | type: teams-notification 9 | source: 10 | url: "PUT YOUR WEBHOOK HERE" 11 | proxy_url: http://192.168.0.47 12 | proxy_port: 1234 13 | resource_types: 14 | - name: teams-notification 15 | type: docker-image 16 | source: 17 | repository: navicore/teams-notification-resource 18 | tag: dev 19 | jobs: 20 | - name: hello-world-proxy-job 21 | plan: 22 | - task: hello-world-proxy-task 23 | config: 24 | # Tells Concourse which type of worker this task should run on 25 | platform: linux 26 | # This is one way of telling Concourse which container image to use for a 27 | # task. We'll explain this more when talking about resources 28 | image_resource: 29 | type: registry-image 30 | source: 31 | repository: busybox # images are pulled from docker hub by default 32 | # The command Concourse will run inside the container 33 | # echo "Hello world!" 34 | run: 35 | path: echo 36 | args: ["Hello world!"] 37 | on_success: 38 | task: job-success 39 | config: 40 | << : *task-config 41 | run: 42 | path: echo 43 | args: ["This job succeeded!"] 44 | on_success: 45 | put: alert 46 | params: 47 | text: | 48 | task tested: with no errors 49 | title: hello-world-proxy tested good 50 | actionName: hello-world-proxy Pipeline 51 | actionTarget: $ATC_EXTERNAL_URL/teams/$BUILD_TEAM_NAME/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME 52 | 53 | -------------------------------------------------------------------------------- /testing/hello-world-failing.yml: -------------------------------------------------------------------------------- 1 | task-config: &task-config 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: navicore/navibox } 6 | resources: 7 | - name: alert 8 | type: teams-notification 9 | source: 10 | url: "PUT YOUR WEBHOOK HERE" 11 | resource_types: 12 | - name: teams-notification 13 | type: docker-image 14 | source: 15 | repository: navicore/teams-notification-resource 16 | tag: dev 17 | jobs: 18 | - name: hello-world-failing-job 19 | plan: 20 | - task: hello-world-failing-task 21 | config: 22 | platform: linux 23 | image_resource: 24 | type: registry-image 25 | source: 26 | repository: navicore/navibox 27 | run: 28 | path: ifail # 'ifail' signals on_failure. for on_success, use 'isucceed' 29 | on_success: 30 | task: job-success 31 | config: 32 | << : *task-config 33 | run: 34 | path: echo 35 | args: ["This job succeeded!"] 36 | on_success: 37 | do: 38 | - put: alert 39 | params: 40 | text: | 41 | task tested: with no errors 42 | title: hello-world-failing tested good 43 | actionName: hello-world-failing Pipeline 44 | actionTarget: $ATC_EXTERNAL_URL/teams/$BUILD_TEAM_NAME/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME 45 | on_failure: 46 | do: 47 | - put: alert 48 | params: 49 | color: FF0000 50 | text: | 51 | something failed in env. 52 | title: something error 53 | actionName: hello-world-failing Pipeline 54 | actionTarget: $ATC_EXTERNAL_URL/teams/$BUILD_TEAM_NAME/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME 55 | -------------------------------------------------------------------------------- /testing/hello-world-pwd.yml: -------------------------------------------------------------------------------- 1 | task-config: &task-config 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: busybox } 6 | resources: 7 | - name: alert 8 | type: teams-notification 9 | source: 10 | url: "PUT YOUR WEBHOOK HERE" 11 | proxy_url: http://192.168.0.47 12 | proxy_port: 1234 13 | proxy_username: myapp 14 | proxy_password: mysecret 15 | resource_types: 16 | - name: teams-notification 17 | type: docker-image 18 | source: 19 | repository: navicore/teams-notification-resource 20 | tag: dev 21 | jobs: 22 | - name: hello-world-pwd-job 23 | plan: 24 | - task: hello-world-pwd-task 25 | config: 26 | # Tells Concourse which type of worker this task should run on 27 | platform: linux 28 | # This is one way of telling Concourse which container image to use for a 29 | # task. We'll explain this more when talking about resources 30 | image_resource: 31 | type: registry-image 32 | source: 33 | repository: busybox # images are pulled from docker hub by default 34 | # The command Concourse will run inside the container 35 | # echo "Hello world!" 36 | run: 37 | path: echo 38 | args: ["Hello world!"] 39 | on_success: 40 | task: job-success 41 | config: 42 | << : *task-config 43 | run: 44 | path: echo 45 | args: ["This job succeeded!"] 46 | on_success: 47 | put: alert 48 | params: 49 | text: | 50 | task tested: with no errors 51 | title: hello-world-pwd tested good 52 | actionName: hello-world-pwd Pipeline 53 | actionTarget: $ATC_EXTERNAL_URL/teams/$BUILD_TEAM_NAME/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME 54 | 55 | -------------------------------------------------------------------------------- /out: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CURL_OPTION="" 4 | 5 | set -e 6 | exec 3>&1 7 | exec 1>&2 8 | #set -x 9 | 10 | function evaluate { 11 | __var=$1 12 | # escape ( and ) from markdown 13 | __var="${__var//\(/\\(}" 14 | __var="${__var//\)/\\)}" 15 | __var="${__var//\:/\\:}" 16 | __var=`eval echo $__var` 17 | echo $__var 18 | } 19 | 20 | payload=$(mktemp /tmp/resource-in.XXXXXX) 21 | cat > "${payload}" <&0 22 | 23 | format="markdown" 24 | 25 | actionName=$(evaluate "$(jq -r '.params.actionName // "Open Concourse"' < "${payload}")") 26 | 27 | actionTarget=$(evaluate "$(jq -r '.params.actionTarget // "https://concourse-ci.org/"' < "${payload}")") 28 | 29 | title=$(evaluate "$(jq -r '.params.title // "Concourse CI"' < "${payload}")") 30 | 31 | text=$(evaluate "$(jq -r '.params.text // ""' < "${payload}")") 32 | text_file="$1/"$(evaluate "$(jq -r '.params.text_file // ""' < "${payload}")") 33 | 34 | # - 'text' always overrides 'text_file' 35 | # - '_(NO MSG)_' being the default if neither are provded 36 | # - If text file is missing: _(NO MSG FILE)_ 37 | # - If text file is blank: _(EMPTY MSG FILE)_" 38 | text_output="_(NO MSG)_" 39 | if [[ -n "${text_file}" ]]; then 40 | if [[ ! -f "${text_file}" ]]; then 41 | text_output="_(NO MSG FILE)_" 42 | else 43 | text_output="$(cat "${text_file}")" && \ 44 | [[ -z "${text_output}" ]] && \ 45 | text_output="_(EMPTY MSG FILE)_" 46 | fi 47 | fi 48 | [[ -n "${text}" ]] && text_output="${text}" 49 | 50 | # style should be one of 'good', 'attention', 'warning', 'default' 51 | # good (green) if the build succeeded 52 | # attention (red) if the build failed 53 | # warning (yellow) if the build errored 54 | # default (gray) by default 55 | # look for pass/fail/error in the title 56 | style=$(evaluate "$(jq -r '.params.style // "default"' < "${payload}")") 57 | [[ "${title,,}" =~ (pass|succeed|success) ]] && style="good" 58 | [[ "${title,,}" =~ (fail) ]] && style="attention" 59 | [[ "${title,,}" =~ (error|warning) ]] && style="warning" 60 | # or in the text_output if we didn't find it in the title 61 | [[ "${style}" == "default" ]] && [[ "${text_output,,}" =~ (pass|succeed|success) ]] && style="good" 62 | [[ "${style}" == "default" ]] && [[ "${text_output,,}" =~ (fail) ]] && style="attention" 63 | [[ "${style}" == "default" ]] && [[ "${text_output,,}" =~ (error|warning) ]] && style="warning" 64 | 65 | DIR="${BASH_SOURCE%/*}" 66 | if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi 67 | 68 | body=$(jq -n --arg format "${format}" \ 69 | --arg actionName "${actionName}" \ 70 | --arg actionTarget "${actionTarget}" \ 71 | --arg title "${title}" \ 72 | --arg text "${text_output}" \ 73 | --arg style "${style}" \ 74 | -f ${DIR}/card.jq) 75 | 76 | webhook_url="$(jq -r '.source.url // empty' < "${payload}")" 77 | 78 | proxy_url="$(jq -r '.source.proxy_url // empty' < "${payload}")" 79 | proxy_port="$(jq -r '.source.proxy_port // empty' < "${payload}")" 80 | proxy_username="$(jq -r '.source.proxy_username // empty' < "${payload}")" 81 | proxy_password="$(jq -r '.source.proxy_password // empty' < "${payload}")" 82 | skip_cert_verification="$(jq -r '.source.skip_cert_verification // empty' < "${payload}")" 83 | verbose="$(jq -r '.source.verbose // empty' < "${payload}")" 84 | silent="$(jq -r '.source.silent // empty' < "${payload}")" 85 | 86 | if [ -z ${proxy_url} ] || [ -z ${proxy_port} ]; then 87 | PROXY_OPTIONS="" 88 | else 89 | PROXY_OPTIONS="--proxy ${proxy_url}:${proxy_port}" 90 | fi 91 | 92 | if [ -z ${proxy_url} ] || [ -z ${proxy_port} ] || [ -z ${proxy_username} ] || [ -z ${proxy_password} ]; then 93 | PROXY_OPTIONS="${PROXY_OPTIONS}" 94 | else 95 | PROXY_OPTIONS="--proxy-user ${proxy_username}:${proxy_password} ${PROXY_OPTIONS}" 96 | fi 97 | 98 | if [ -z ${skip_cert_verification} ]; then 99 | CURL_OPTION="${CURL_OPTION}" 100 | else 101 | CURL_OPTION="-k ${CURL_OPTION}" 102 | fi 103 | 104 | if [ -z ${verbose} ]; then 105 | CURL_OPTION="${CURL_OPTION}" 106 | else 107 | CURL_OPTION="-v ${CURL_OPTION}" 108 | fi 109 | 110 | if [ -z ${silent} ]; then 111 | CURL_OPTION="${CURL_OPTION}" 112 | else 113 | CURL_OPTION="-s ${CURL_OPTION}" 114 | fi 115 | 116 | redacted_webhook_url=$(echo "${webhook_url}" | sed -e 's#/\([^/\.]\{2\}\)[^/.]\{5,\}\([^/.]\{2\}\)#/\1…\2#g' | jq -R .) 117 | 118 | url_path="$(echo ${webhook_url} | sed -e "s/https\{0,1\}:\/\/[^\/]*\(\/[^?&#]*\).*/\1/")" 119 | 120 | curl ${CURL_OPTION} ${PROXY_OPTIONS} -H 'Content-Type: application/json' -d "${body}" "${webhook_url}" 2>&1 | sed -e "s#${url_path}#***WEBHOOK URL REDACTED***#g" 121 | 122 | timestamp=$(date +%s) 123 | metadata="$(cat <&3 135 | 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ---------- 2 | 3 | ## NOTICE - help wanted - Aug 2024 4 | MSFT is ending support of the connector protocol and this module will need to be migrated to the new Teams Workflow method https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/ 5 | 6 | PRs welcome, as well as advice of where to direct users if a new project that has the same features but isn't broken by MSFT emerges. 7 | 8 | ----------- 9 | 10 | # Concourse CI Teams Resource 11 | 12 | Sends messages to [Microsoft Teams](https://teams.microsoft.com) from 13 | within [Concourse CI](https://concourse-ci.org/) pipelines. 14 | 15 | Implements the Microsoft Teams 16 | [Connector](https://dev.outlook.com/Connectors/Reference) protocols and 17 | the Concourse CI [resource](https://concourse-ci.org/implementing-resource-types.html) 18 | protocols. 19 | 20 | ![teams](images/teams2.png) 21 | 22 | Resolves at runtime Concourse CI environment variables referenced in your Teams 23 | connector messages such as: 24 | 25 | ``` 26 | $BUILD_ID 27 | $BUILD_NAME 28 | $BUILD_JOB_NAME 29 | $BUILD_PIPELINE_NAME 30 | $BUILD_TEAM_NAME 31 | $ATC_EXTERNAL_URL 32 | ``` 33 | 34 | ## STATUS 35 | 36 | * Actively used and supported (as of Oct '21) 37 | * Works with all Concourse CI releases from 11/2016 thru at least 10/2021 38 | 39 | 40 | ## SETUP 41 | 42 | 1. Open the Microsoft Teams UI. 43 | 2. Identify the channel you wish to post notifications to - ie: #devops.... 44 | 3. Open the "more options" menu of that channel and select "Connectors". 45 | ![connector](images/connector.png) 46 | 4. Select "Incoming Webhook" and respond to the prompts for details like the 47 | icon and connector name. 48 | ![webhook](images/webhook.png) 49 | 5. Use the webhook url from above in your pipeline `source` definition. The 50 | example below creates an `alert` resource. Each point in the pipeline labeled 51 | `alert` is a Microsoft Teams Connector message. 52 | 53 | 54 | ## PIPELINE EXAMPLE 55 | 56 | ![pipeline](images/pipeline.png) 57 | 58 | ### Source Configuration 59 | 60 | ``` 61 | resources: 62 | 63 | - name: alert 64 | type: teams-notification 65 | source: 66 | url: https://outlook.office365.com/webhook/blah-blah-blah [required] 67 | proxy_url: https://my.corp.net [optional] 68 | proxy_port: 1234 [optional] 69 | proxy_username: myusername [optional] 70 | proxy_password: mysecret [optional] 71 | skip_cert_verification [optional] 72 | verbose [optional] 73 | silent [optional] 74 | 75 | ``` 76 | * `url`: *Required.* The webhook URL as provided by Teams when you add a 77 | connection for "Incomming Webhook". Usually in the 78 | form: `https://outlook.office365.com/webhook/XXX` 79 | * `proxy_url`: *Optional.* Basic auth for forwarding proxies - prefix with protocol. ie: https:// 80 | * `proxy_port`: *Optional.* Basic auth for forwarding proxies 81 | * `proxy_username`: *Optional.* Basic auth for forwarding proxies 82 | * `proxy_password`: *Optional.* Basic auth for forwarding proxies 83 | * `skip_cert_verification`: *Optional.* cURL `-k` option for debugging when behind TLS rewrite proxies when the internal cert is self-signed or not properly distributed 84 | * `verbose`: *Optional.* cURL `-v` option for debugging 85 | * `silent`: *Optional.* cURL `-s` option for no logging 86 | 87 | Next, define the non-built-in type: 88 | 89 | ``` 90 | resource_types: 91 | 92 | - name: teams-notification 93 | type: docker-image 94 | source: 95 | repository: navicore/teams-notification-resource 96 | tag: v0.9.12 97 | ``` 98 | 99 | ## Param Configuration 100 | 101 | Example of an alert in a pull-request job: 102 | ``` 103 | - name: Test-Pull-Request 104 | plan: 105 | - get: {{mypipeline}}-pull-request 106 | trigger: true 107 | - task: test-pr 108 | file: {{mypipeline}}-pull-request/pipeline/test-pr.yml 109 | on_success: 110 | put: alert 111 | params: 112 | text: | 113 | pull request tested: success 114 | title: {{mypipeline}} Pull Request Tested 115 | actionName: {{mypipeline}} Pipeline 116 | actionTarget: $ATC_EXTERNAL_URL/teams/$BUILD_TEAM_NAME/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME 117 | on_failure: 118 | put: alert 119 | params: 120 | color: EA4300 121 | text: | 122 | pull request tested: **WITH ERRORS** 123 | title: {{mypipeline}} Pull Request Tested 124 | actionName: {{mypipeline}} Pipeline 125 | actionTarget: $ATC_EXTERNAL_URL/teams/$BUILD_TEAM_NAME/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME 126 | ``` 127 | * One of the following: 128 | * `text`: *Required.* Text of the message to send - markdown supported (higher precedence over `text_file`) 129 | * `text_file`: *Required.* A location of text file of the message to send, usually an output from a previous task - markdown supported 130 | * `title`: *Optional.* 131 | * `actionName`: *Optional.* Text on the button/link (shows up as a link though the Teams docs show a button) 132 | * `actionTarget`: *Optional.* URL to connect to the button/link 133 | * `style`: *Optional.* Adaptive Card header style. Can be one of 'good', 'attention', 'warning'. If not provided, `title` and `text` are scanned for keywords to determine the style. If no keywords are found, 'default' is used. 134 | * ~~`color`~~: *Deprecated* 135 | 136 | # MORE EXAMPLES 137 | 138 | 139 | See `*.yml` files in the ![testing](testing) directory. 140 | 141 | # DEVELOPMENT ENVIRONMENT 142 | 143 | Testing with docker-compose. 144 | 145 | See working standalone docker-compose example in ![testing](testing) dir. 146 | 147 | # PROXY SUPPORT 148 | 149 | Proxy support testing is limited to squid4. Dev env was created with default squid4 setup. 150 | 151 | ``` 152 | sudo service squid start 153 | sudo vim /etc/squid/squid.conf 154 | sudo service squid restart 155 | sudo service squid stop 156 | ``` 157 | 158 | We tested proxy support with squid4. We don't have access to a wide range of proxies. 159 | 160 | The ntlm flag on cURL is not set in our implementation but squid4 was happy to call our session ntlm. We're open to PRs for more support of proxies. 161 | --------------------------------------------------------------------------------