-
-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: initial infra for slack intergration (#1131)
Co-authored-by: Sergio Moya <[email protected]>%0ACo-authored-by: Quetzalli <[email protected]>%0ACo-authored-by: asyncapi-bot <[email protected]>
- Loading branch information
1 parent
806e31a
commit 494354c
Showing
14 changed files
with
1,777 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
name: Automatic Slack Management | ||
|
||
on: | ||
push: | ||
paths: | ||
- '**/slack/**/*' | ||
- 'MAINTAINERS.yaml' | ||
- 'WORKING_GROUPS.yaml' | ||
|
||
jobs: | ||
deploy-changes-to-slack: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
- name: Deploy changes to Slack | ||
run: | | ||
cd .github/workflows/slack | ||
terraform init | ||
terraform apply \ | ||
-var "slack_token=${{ secrets.SLACK_TOKEN }}" \ | ||
-auto-approve | ||
- name: Check if there are any uncommitted changes | ||
id: git-check | ||
run: | | ||
# Set the output should_push to true if there are uncommitted changes | ||
if [ -n "$(git status --porcelain)" ]; then | ||
echo "Changes detected" | ||
echo "should_push=true" >> $GITHUB_OUTPUT | ||
else | ||
echo "No changes detected" | ||
echo "should_push=false" >> $GITHUB_OUTPUT | ||
fi | ||
- name: Push changes to GitHub | ||
if: steps.git-check.outputs.should_push == 'true' | ||
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 # use 4.2.4 https://github.com/peter-evans/create-pull-request/releases/tag/v4.2.4 | ||
with: | ||
token: ${{ secrets.GH_TOKEN }} | ||
commit-message: 'chore(slack): update slack configuration' | ||
committer: asyncapi-bot <[email protected]> | ||
author: asyncapi-bot <[email protected]> | ||
title: 'ci(slack): update slack configuration' | ||
body: 'This PR was automatically created by the Automatic Slack Management GitHub Action.' | ||
branch: 'chore/slack-update-${{ github.run_number }}' | ||
base: 'main' |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
## Infrastructure for slack integration | ||
|
||
This directory contains the infrastructure for Slack integration. It is used to create/manage Slack channels and groups and invite users to Slack channels. The Slack integration is implemented using the [slack-terraform-provider](https://github.com/pablovarela/terraform-provider-slack). | ||
|
||
### Prerequisites | ||
|
||
- [A slack App](https://api.slack.com/apps) with the following scopes under `User Token Scopes` in `OAuth & Permissions`: | ||
|
||
Write Permissions: | ||
- `channels:write` | ||
- `groups:write` | ||
- `usergroups:write` | ||
|
||
Read Permissions: | ||
- `channels:read` | ||
- `groups:read` | ||
- `usergroups:read` | ||
|
||
> [!CAUTION] | ||
> Try to use a bot to log into Slack to prevent any changes from being attributed to the workspace owner. This is due to using a `user token` for authentication, which does the changes on behalf of the user who created the token. | ||
- [API Token](https://api.slack.com/apps) after installing the app in your workspace. ( `xoxp-<your-slack-token>` ) | ||
|
||
- [Terraform](https://www.terraform.io/downloads.html) installed on your local machine. | ||
|
||
### Usage | ||
|
||
- Create a `terraform.tfvars` file in the `slack` directory with the following content: | ||
|
||
```hcl | ||
slack_token = "xoxp-<your-slack-token>" | ||
``` | ||
|
||
- Run the following commands to create the Slack resources: | ||
|
||
```bash | ||
terraform init | ||
terraform apply | ||
``` | ||
|
||
> [!TIP] | ||
> The `terraform apply` command will create the resources better to use `terraform plan` to see the changes before applying. | ||
### How it works | ||
|
||
Three main resources are created using the slack integration: | ||
|
||
- `slack_channel`: This resource creates a slack channel. The channels are defined in the [channels.yaml](./channels/channels.yaml) file. with the structure explained there. | ||
|
||
- `slack_usergroup`: This resource creates a Slack user group. The usergroups are defined in the [usergroups.yaml](./groups/groups.yaml) file, and their structure is explained there. | ||
|
||
> [!CAUTION] | ||
> The user groups should be unique across the workspace (i.e., no channel, user, or user group should have the same handle). Also, in case of user groups mentioned in the yaml existing in the workspace, you have to run the following command to import it to terraform state: | ||
> ```bash | ||
> terraform import module.groups.slack_usergroup.<resource name>[\"<usergroup name>\"] <usergroup id> | ||
> | ||
> # Example | ||
> terraform import module.groups.slack_usergroup.wg_groups[\"Developer Experience\"] <actual_group_id> | ||
> ``` | ||
- `slack_user`: This resource invites users to the Slack workspace. The users are defined in the [users.tf](./users/users.tf) file, and their structure is explained there. | ||
### Pitfalls | ||
- Use of bot token of the format `xoxo-<your-slack-token>` is not supported for creating user groups. | ||
- The user group should be unique across the workspace (i.e., no channel, user, or user group should have the same handle). | ||
- Please [import](#importing-existing-resources) the user groups to terraform state if they already exist in the workspace, as they **cannot be deleted** in Slack 😢. | ||
> [!IMPORTANT] | ||
> The terraform state will overwrite any description, name, or topic change. It is better to manage the changes in the YAML files and then apply them. However, the terraform state will not affect bookmarks, pinned items, etc. | ||
### Importing existing resources | ||
In case you have existing resources such as channels, user groups in the workspace, you can import them to the terraform state by transforming the `json` response from the slack API. An example script can be seen below: | ||
```javascript | ||
const fs = require('fs'); | ||
const fetch = require('node-fetch'); | ||
const token = 'xoxp-<your-slack-token>'; | ||
const fetchResource = async (resource, url_params) => { | ||
// convert the url_params to query string | ||
const url = new URL(`https://slack.com/api/${resource}`); | ||
Object.keys(url_params).forEach(key => url.searchParams.append(key, url_params[key])); | ||
const response = await fetch(url, { | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
'Authorization': `Bearer ${token}` | ||
} | ||
}); | ||
const data = await response.json(); | ||
return data; | ||
} | ||
async function main() { | ||
const channels = await fetchResource('conversations.list', { exclude_archived: true }); | ||
const usergroups = await fetchResource('usergroups.list', { include_users: true }); | ||
channels.channels.forEach(channel => { | ||
console.log(`terraform import module.channels.slack_conversation.channels[\\"${channel.name}\\"] ${channel.id}`); | ||
}); | ||
usergroups.usergroups.forEach(usergroup => { | ||
console.log(`terraform import module.groups.slack_usergroup.wg_groups[\\"${usergroup.name}\\"] ${usergroup.id}`); | ||
}); | ||
} | ||
main(); | ||
``` | ||
### What all can be done? | ||
#### Groups | ||
The groups can be mentioned in the slack messages using the `@<group-name>` syntax. Addition of groups can be done by adding the group to the [groups.yaml](./groups/groups.yaml) file. | ||
The following groups are being created currently: | ||
- `tsc` | ||
This group is for the Technical Steering Committee members mentioned in the [TSC_MEMBERS](../../../TSC_MEMBERS.json) file. Can be used to mention all the TSC members at once. | ||
- `maintainers` | ||
This group is for the all maintainers of the repository mentioned in the [MAINTAINERS](../../../MAINTAINERS.yaml) file. Can be used to mention all the maintainers at once. | ||
- `studio` | ||
This group consists of members actively working on the studio project. | ||
- `coc_commitee` | ||
This group consists of members of the Code of Conduct committee. | ||
In addition to these groups are also being created for each working group mentioned in the [WORKING_GROUPS](../../../WORKING_GROUPS.yaml) file. Example: `@dx_wg`. | ||
We are also having groups for maintainers of each repository mentioned in the [MAINTAINERS](../../../MAINTAINERS.yaml) file. You can mention the maintainers of a repository using `@maintainers_<repo-name>`. Example: `@maintainers_studio`. | ||
#### Channels | ||
Two types of channels are being created currently: | ||
- General channels: The channels are defined in the [channels.yaml](./channels/channels.yaml) file with the structure explained there. | ||
- Working group channels: The working group channels are created for each working group mentioned in the [WORKING_GROUPS](../../../WORKING_GROUPS.yaml) file. The channels are created with the name `wg_<working-group-name>` or custom nameas configured in the [WORKING_GROUPS](../../../WORKING_GROUPS.yaml) file. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
terraform { | ||
required_providers { | ||
slack = { | ||
source = "pablovarela/slack" | ||
version = "~> 1.0" | ||
} | ||
} | ||
} | ||
|
||
variable "data_sources" { | ||
default = { | ||
tsc_members_user_ids = [] | ||
maintainers_user_ids = [] | ||
repo_maintainers = {} | ||
} | ||
description = "Data sources for the slack channels from the users module" | ||
} | ||
|
||
locals { | ||
channel_data = yamldecode(file("${path.module}/channels.yaml")) | ||
channels = { | ||
for channel in local.channel_data : channel.name => { | ||
name = channel.name | ||
topic = channel.topic | ||
purpose = channel.purpose | ||
|
||
# if permanent_members is not provided, then it wil be taken from local with the name in data sources | ||
permanent_members = lookup(channel, "permanent_members", lookup(var.data_sources, lookup(channel, "data_source", channel.name), [])) | ||
is_private = channel.is_private | ||
action_on_destroy = channel.action_on_destroy | ||
|
||
# if private channel, then kick all users on update else none | ||
action_on_update_permanent_members = channel.is_private ? "kick" : "none" | ||
adopt_existing_channel = true | ||
} | ||
} | ||
} | ||
|
||
resource "slack_conversation" "channels" { | ||
for_each = local.channels | ||
name = each.value.name | ||
topic = each.value.topic | ||
purpose = each.value.purpose | ||
permanent_members = each.value.permanent_members | ||
|
||
is_private = each.value.is_private | ||
action_on_destroy = each.value.action_on_destroy | ||
action_on_update_permanent_members = each.value.action_on_update_permanent_members | ||
adopt_existing_channel = each.value.adopt_existing_channel | ||
} | ||
|
||
locals { | ||
working_groups_data = yamldecode(file("${path.module}/../../../../WORKING_GROUPS.yaml")).working_groups | ||
wg_channels = { | ||
for wg_data in local.working_groups_data : wg_data.name => { | ||
name = lookup(lookup(lookup(wg_data, "slack", {}), "channel", {}), "handle", "wg-${replace(lower(wg_data.name), " ", "-")}") | ||
purpose = lookup(lookup(lookup(wg_data, "slack", {}), "channel", {}), "description", lookup(wg_data, "description", "")) | ||
topic = lookup(lookup(lookup(wg_data, "slack", {}), "channel", {}), "topic", "") | ||
|
||
permanent_members = concat([for member in wg_data.chairpersons : member.slack], [for member in wg_data.members : member.slack]) | ||
is_private = false | ||
|
||
action_on_destroy = "archive" | ||
action_on_update_permanent_members = "none" | ||
adopt_existing_channel = true | ||
} | ||
} | ||
} | ||
|
||
resource "slack_conversation" "wg_channels" { | ||
for_each = local.wg_channels | ||
name = each.value.name | ||
topic = each.value.topic | ||
purpose = each.value.purpose | ||
permanent_members = each.value.permanent_members | ||
|
||
is_private = each.value.is_private | ||
action_on_destroy = each.value.action_on_destroy | ||
action_on_update_permanent_members = each.value.action_on_update_permanent_members | ||
adopt_existing_channel = each.value.adopt_existing_channel | ||
} | ||
|
||
output "wg_channels" { | ||
value = slack_conversation.wg_channels | ||
} |
Oops, something went wrong.