Create Slack channel for Incident Management
Overview
Streamline incident response with this self-service action. It automatically creates dedicated Slack channels for your services, optionally adding key team members during setup.
This guide includes one or more steps that require integration with GitHub.
Port supports two GitHub integrations:
- GitHub (Legacy) - uses a GitHub app, which is soon to be deprecated.
- GitHub (Ocean) - uses the Ocean framework, recommended for new integrations.
Both integration options are present in this guide via tabs, choose the one that fits your needs.
Key Benefits
- Faster Incident Response: Instantly create communication channels when issues arise.
- Consistent Process: Ensure standard channel naming and member inclusion.
- Developer Self-Service: Empower teams to create incident channels without needing extra Slack permissions.
- Reduced Manual Steps: Automate a routine task, freeing up time for your team.
Prerequisites
-
Install the GitHub integration relevant to you:
- GitHub (Legacy)
- GitHub (Ocean)
Port's GitHub app needs to be installed.
Install GitHub ocean.
-
Setup Slack App:
- Create a slack app and install it on a workspace.
- Add the following permissions to the slack app:
- Create channel (Required) :
channels:managegroups:writeim:writempim:write - Find a user with an email address (Optional) :
users:read.email - Invite users to channel (Optional) :
channels:write.invitesgroups:write.invitesmpim:write.invites
- Create channel (Required) :
warningWithout scopes for
Find a user with an email addressandInvite users to channel, the channel will be created but users will not be added to it.- Then install the app in your slack workspace.
- Navigate back to the OAuth & Permissions page. You'll see an access token under OAuth Tokens for Your Workspace that you will use in the
BOT_USER_OAUTH_TOKENGitHub secret.
-
In your GitHub repository, go to Settings > Secrets and add the following secrets:
BOT_USER_OAUTH_TOKEN- Slack Bot User Oauth Token generated for the slack app.PORT_CLIENT_ID- Your port client id.PORT_CLIENT_SECRET- Your port client secret.
-
Create a service blueprint with the following JSON definition:
Service Blueprint (Click to expand)
{
"identifier": "service",
"title": "Service",
"icon": "Github",
"teamInheritance": {
"path": "team"
},
"schema": {
"properties": {
"readme": {
"title": "README",
"type": "string",
"format": "markdown",
"icon": "Book"
},
"language": {
"icon": "Git",
"type": "string",
"title": "Language",
"enum": ["GO", "Python", "Node", "React"],
"enumColors": {
"GO": "red",
"Python": "green",
"Node": "blue",
"React": "yellow"
}
},
"tier": {
"icon": "DefaultProperty",
"title": "Tier",
"description": "Service Tiers indicate the significance or importance of a Service for business operations",
"type": "string",
"enum": [
"Mission Critical",
"Customer Facing",
"Internal Service",
"Other"
],
"enumColors": {
"Mission Critical": "turquoise",
"Customer Facing": "green",
"Internal Service": "darkGray",
"Other": "yellow"
}
},
"code_owners": {
"title": "Code owners",
"description": "This service's code owners",
"type": "array",
"icon": "TwoUsers"
},
"resource_definitions": {
"title": "Resource definitions",
"description": "A link to the service's resource definitions",
"icon": "Terraform",
"type": "string",
"format": "url"
},
"type": {
"title": "Type",
"description": "This service's type",
"type": "string",
"enum": ["Backend", "Frontend", "Library"],
"enumColors": {
"Backend": "purple",
"Frontend": "pink",
"Library": "green"
},
"icon": "DefaultProperty"
},
"lifecycle": {
"title": "Lifecycle",
"type": "string",
"enum": ["Production", "Experimental", "Deprecated"],
"enumColors": {
"Production": "green",
"Experimental": "yellow",
"Deprecated": "red"
},
"icon": "DefaultProperty"
},
"require_approval_count": {
"title": "Require approvals",
"type": "number",
"icon": "DefaultProperty"
},
"is_protected": {
"title": "Is branch protected",
"type": "boolean",
"icon": "DefaultProperty"
},
"require_code_owner_review": {
"title": "Require code owner review",
"type": "boolean",
"icon": "DefaultProperty"
},
"locked_in_prod": {
"icon": "DefaultProperty",
"title": "Locked in Prod",
"type": "boolean",
"default": false
},
"last_push": {
"icon": "GitPullRequest",
"title": "Last push",
"description": "Last commit to the main branch",
"type": "string",
"format": "date-time"
},
"required_approvals": {
"icon": "Lock",
"title": "Required approvals",
"type": "number",
"description": "Number of required approvals for a PR to be merged to main branch"
},
"locked_reason_prod": {
"icon": "DefaultProperty",
"title": "Locked Reason Prod",
"type": "string"
},
"locked_reason_test": {
"title": "Locked Reason Test",
"type": "string",
"icon": "DefaultProperty"
},
"locked_in_test": {
"title": "Locked in Test",
"type": "boolean",
"default": false,
"icon": "DefaultProperty"
},
"runbooks": {
"title": "Runbooks",
"type": "array",
"icon": "Misconfiguration",
"items": {
"type": "string",
"format": "url"
}
},
"monitor_dashboards": {
"title": "Monitor Dashboards",
"type": "array",
"icon": "Grafana",
"items": {
"type": "string",
"format": "url"
}
},
"spec": {
"title": "Spec",
"type": "string",
"description": "Spec in Prod",
"icon": "Swagger",
"format": "yaml",
"spec": "open-api"
},
"last_contributer": {
"icon": "TwoUsers",
"title": "Last contributor",
"type": "string",
"format": "user"
},
"url": {
"title": "URL",
"format": "url",
"type": "string",
"icon": "Link"
}
},
"required": []
},
"mirrorProperties": {
"on_call": {
"path": "pager_duty_service.oncall"
},
"coverage": {
"path": "sonar_qube_project.coverage"
},
"sonar_qube_vulnerabilities": {
"path": "sonar_qube_project.numberOfVulnerabilities"
},
"security_hotspots": {
"path": "sonar_qube_project.numberOfHotSpots"
},
"prod_health": {
"path": "prod_runtime.healthStatus"
},
"synced_in_prod": {
"path": "prod_runtime.syncStatus"
},
"test_health": {
"path": "test_runtime.healthStatus"
},
"synced_in_test": {
"path": "test_runtime.syncStatus"
},
"health_status": {
"path": "dev_runtime.healthStatus"
},
"sync_status": {
"path": "dev_runtime.syncStatus"
},
"status": {
"path": "pager_duty_service.status"
}
},
"calculationProperties": {
"freshness": {
"title": "Freshness",
"icon": "Clock",
"calculation": "((now - ((.properties.last_push) | fromdate)) / 86400) | floor",
"type": "number",
"colorized": false,
"colors": {}
},
"slack": {
"title": "Slack",
"icon": "Slack",
"calculation": "\"https://slack.com/\" + .identifier",
"type": "string"
}
},
"aggregationProperties": {},
"relations": {
"sonar_qube_project": {
"title": "SonarQube Project",
"target": "sonarQubeProject",
"required": false,
"many": false
},
"prod_runtime": {
"title": "Prod runtime",
"description": "The service's prod runtime",
"target": "running_service",
"required": false,
"many": false
},
"snyk_project": {
"title": "Snyk Project",
"target": "snykProject",
"required": false,
"many": false
},
"ecr_repo": {
"title": "ECR Repo",
"target": "ecrRepository",
"required": false,
"many": false
},
"githubTeams": {
"title": "GitHub teams",
"target": "githubTeam",
"required": false,
"many": true
},
"test_runtime": {
"title": "Test runtime",
"description": "The service's test runtime",
"target": "running_service",
"required": false,
"many": false
},
"pager_duty_service": {
"title": "PagerDuty Service",
"description": "Corresponding Pagerduty Service",
"target": "pagerdutyService",
"required": false,
"many": false
},
"consumes_api": {
"title": "Consumes API",
"target": "api",
"required": false,
"many": true
},
"dora_last_7_days": {
"title": "DORA last 7 days",
"target": "doraMetrics",
"required": false,
"many": false
},
"team": {
"title": "Team",
"target": "idp_team",
"required": false,
"many": false
},
"domain": {
"title": "Domain",
"description": "The domain this service belongs to",
"target": "domain",
"required": false,
"many": false
},
"provides_api": {
"title": "Provides API",
"target": "api",
"required": false,
"many": false
},
"dev_runtime": {
"title": "Dev Runtime",
"target": "running_service",
"required": false,
"many": false
},
"dora_last_30_days": {
"title": "DORA last 30 days",
"target": "doraMetrics",
"required": false,
"many": false
}
}
}
GitHub Workflow
Create the file .github/workflows/open-slack-channel.yaml in the .github/workflows folder of your repository.
We recommend creating a dedicated repository for the workflows that are used by Port actions.
Github Workflow: Open Slack Channel (Click to expand)
name: Open Slack Channel
on:
workflow_dispatch:
inputs:
channel_name:
description: Name of the public or private channel to create.
required: true
type: string
is_private:
description: Create a private channel instead of a public one.
required: false
type: boolean
members:
description: Add members manually to the channel.
type: array
required: false
port_context:
description: Details of the action and general port_context (blueprint, run ID, entity identifier from Port, etc...).
required: true
jobs:
open-slack-channel:
runs-on: ubuntu-latest
steps:
- name: Log Executing Request to Open Channel
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{ fromJson(github.event.inputs.port_context).runId }}
logMessage: "About to create a conversation channel in slack..."
- name: Create Slack Channel
id: create_channel
env:
SLACK_TOKEN: ${{ secrets.BOT_USER_OAUTH_TOKEN }}
run: |
response=$(curl -s -X POST "https://slack.com/api/conversations.create" \
-H "Authorization: Bearer $SLACK_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"name\": \"${{ github.event.inputs.channel_name }}\",\"is_private\": ${{ github.event.inputs.is_private }} }")
echo "API Response: $response"
if [[ "$(echo $response | jq -r '.ok')" == "true" ]]; then
channel_id=$(echo $response | jq -r '.channel.id')
echo "Channel ID: $channel_id"
echo "CHANNEL_ID=$channel_id" >> $GITHUB_ENV
else
echo "Failed to create Slack channel. Channel ID is null."
error=$(echo $response | jq -r '.error')
error_message="${error//_/ }"
echo "Error: $error_message"
echo "CREATE_CHANNEL_ERROR=$error_message" >> $GITHUB_ENV
exit 1
fi
- name: Log If Create Channel Request Fails
if: failure()
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{ fromJson(github.event.inputs.port_context).runId }}
logMessage: "Failed to create slack channel: ${{env.CREATE_CHANNEL_ERROR}} ❌"
- name: Log If Create Channel Request is Successful
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{ fromJson(github.event.inputs.port_context).runId }}
logMessage: "Channel created successfully, channel Id: ${{env.CHANNEL_ID}} ✅"
- name: Checkout code
uses: actions/checkout@v4
- name: Add Members to Slack Channel
id: add_members
if: success()
env:
SLACK_TOKEN: ${{ secrets.BOT_USER_OAUTH_TOKEN }}
CHANNEL_ID: ${{env.CHANNEL_ID}}
CLIENT_ID: ${{ secrets.PORT_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.PORT_CLIENT_SECRET }}
RUN_ID: ${{ fromJson(github.event.inputs.port_context).runId }}
MEMBER_EMAILS: ${{ toJSON(github.event.inputs.members) }}
run: |
cd slack
chmod +x add-members-to-channel.sh
bash add-members-to-channel.sh "$SLACK_TOKEN" "$CHANNEL_ID" "$CLIENT_ID" "$CLIENT_SECRET" "$RUN_ID" "$MEMBER_EMAILS"
- name: Log Successful Action
if: steps.add_members.outcome == 'failure'
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
status: "FAILURE"
runId: ${{ fromJson(github.event.inputs.port_context).runId }}
logMessage: "Failed to add members to channel ❌"
- Create a bash script (
add-members-to-channel.sh) in a folder namedslackat the root of your GitHub repository with this content:
Bash Script: Add Members to Channel (Click to expand)
#!/bin/bash
# Assign arguments to variables
SLACK_TOKEN=$1
CHANNEL_ID=$2
clientId=$3
clientSecret=$4
run_id=$5
MEMBER_EMAILS_JSON=$6
# Get the Port access token
PORT_TOKEN_RESPONSE=$(curl -s -X 'POST' \
'https://api.getport.io/v1/auth/access_token' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d "{
\"clientId\": \"$clientId\",
\"clientSecret\": \"$clientSecret\"
}"
)
echo $PORT_TOKEN_RESPONSE
PORT_ACCESS_TOKEN=$(echo $PORT_TOKEN_RESPONSE | jq -r '.accessToken')
# Ensure the access token was obtained successfully
if [ -z "$PORT_ACCESS_TOKEN" ] || [ "$PORT_ACCESS_TOKEN" == "null" ]; then
error_message="Failed to obtain Port access token ❌"
echo $error_message
report_error "$error_message"
exit 1
fi
# Function to report error
report_error() {
local message=$1
echo $message
echo "ADD_MEMBER_TO_CHANNEL_ERROR=$message" >> $GITHUB_ENV
curl -s -X POST "https://api.getport.io/v1/actions/runs/$run_id/logs" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PORT_ACCESS_TOKEN" \
-d "{\"message\": \"$message\"}"
}
user_ids=""
# Convert MEMBER_EMAILS_JSON to an array
readarray -t MEMBER_EMAILS < <(echo $MEMBER_EMAILS_JSON | jq -r 'fromjson | .[]')
for email in "${MEMBER_EMAILS[@]}"; do
user_response=$(curl -s -X GET "https://slack.com/api/users.lookupByEmail?email=$email" \
-H "Authorization: Bearer $SLACK_TOKEN")
if [[ "$(echo $user_response | jq -r '.ok')" == "true" ]]; then
user_id=$(echo $user_response | jq -r '.user.id')
user_ids+="${user_id},"
else
error_message="Failed to retrieve user id for $email: $(echo $user_response | jq -r '.error' | tr '_' ' ') ⚠️"
report_error "$error_message"
fi
done
user_ids=${user_ids%,}
if [[ -n "$user_ids" ]]; then
invite_response=$(curl -s -X POST "https://slack.com/api/conversations.invite" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $SLACK_TOKEN" \
--data "{\"channel\":\"$CHANNEL_ID\",\"users\":\"$user_ids\"}")
if [[ "$(echo $invite_response | jq -r '.ok')" == "false" ]]; then
error_message="Failed to invite users to channel: $(echo $invite_response | jq -r '.error' | tr '_' ' ') ⚠️"
report_error "$error_message"
fi
else
report_error "No user IDs found to invite."
fi
Port Configuration
Create a new self service action using the following JSON configuration.
Open Slack Channel (Click to expand)
Make sure to replace <GITHUB_ORG> and <GITHUB_REPO> with your GitHub organization and repository names respectively.
{
"identifier": "open_slack_channel",
"title": "Open Slack Channel",
"icon": "Slack",
"description": "Create and slack channel and optionally add members to it",
"trigger": {
"type": "self-service",
"operation": "DAY-2",
"userInputs": {
"properties": {
"channel_name": {
"icon": "Slack",
"title": "Channel Name",
"type": "string",
"default": {
"jqQuery": "\"incident-\"+.entity.identifier"
}
},
"is_private": {
"description": "Create a private channel instead of a public one",
"title": "Is Private",
"type": "boolean",
"default": false,
"icon": "Slack"
},
"members": {
"items": {
"type": "string",
"format": "user"
},
"title": "Members",
"icon": "Slack",
"type": "array",
"description": "Add members manually to the channel.",
"default": {
"jqQuery": ".entity.properties.code_owners"
}
}
},
"required": [
"channel_name"
],
"order": [
"channel_name",
"members",
"is_private"
]
},
"blueprintIdentifier": "service"
},
"invocationMethod": {
"type": "GITHUB",
"org": "<GITHUB_ORG>",
"repo": "<GITHUB_REPO>",
"workflow": "open-slack-channel.yaml",
"workflowInputs": {
"channel_name": "{{.inputs.\"channel_name\"}}",
"is_private": "{{.inputs.\"is_private\"}}",
"members": "{{.inputs.\"members\"}}",
"port_context": {
"entity": "{{.entity}}",
"blueprint": "{{.action.blueprint}}",
"runId": "{{.run.id}}",
"trigger": "{{ .trigger }}"
}
},
"reportWorkflowStatus": true
},
"requiredApproval": false
}
Now you should see the Open Slack Channel action in the self-service page. 🎉
Let's test it!
- Head to the Self Service hub
- Click on the
Open Slack Channelaction - Enter your preferred details for
channel_nameand optionally addmembers. You can toggle theis_privateflag to make the channel private. - Click on
Execute - Done! wait for the channel to be created in slack.