diff --git a/.github/resources/.env.example b/.github/resources/.env.example index c638f894..f423d514 100644 --- a/.github/resources/.env.example +++ b/.github/resources/.env.example @@ -4,4 +4,5 @@ export SLACK_BOT_TOKEN=xoxb-01010101-example export SLACK_CHANNEL_ID=C0123456789 export SLACK_INCOMING_WEBHOOK=https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz +export SLACK_SERVICE_TOKEN=xoxp-01010101-example export SLACK_WEBHOOK_TRIGGER=https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz diff --git a/.github/resources/.slack/config.json b/.github/resources/.slack/config.json index 6de199bc..2aee8db3 100644 --- a/.github/resources/.slack/config.json +++ b/.github/resources/.slack/config.json @@ -1,5 +1,7 @@ { - "experiments": ["bolt"], + "manifest": { + "source": "local" + }, "project_id": "c4805b41-d1ce-4ea0-b297-ed2f8c64c267" } diff --git a/.github/resources/slack.json b/.github/resources/.slack/hooks.json similarity index 78% rename from .github/resources/slack.json rename to .github/resources/.slack/hooks.json index 89e6f776..512438d5 100644 --- a/.github/resources/slack.json +++ b/.github/resources/.slack/hooks.json @@ -1,7 +1,7 @@ { "runtime": "actions", "hooks": { - "deploy": "open https://api.slack.com/apps || true", + "deploy": "echo https://api.slack.com/apps", "get-manifest": "cat ./.slack/manifest.json #" }, "config": { diff --git a/.github/resources/README.md b/.github/resources/README.md index a936d39f..b9731f3b 100644 --- a/.github/resources/README.md +++ b/.github/resources/README.md @@ -6,7 +6,7 @@ experiments with the [Slack CLI][cli]. ## Overview -This app showcases all three techniques of sending data into Slack and follows +This app showcases all four techniques of sending data into Slack and follows patterns found in the integration tests. - **Technique 1** Slack Workflow Builder: Use a Slack webhook trigger to start a @@ -15,6 +15,8 @@ patterns found in the integration tests. data provided through the GitHub workflow. - **Technique 3** Incoming webhook: Post a message to a Slack channel using an incoming webhook. +- **Technique 4** Slack CLI Command: Install and run Slack CLI commands such as + `deploy` or `manifest` using a service token. Configurations for the Slack app and workflow, and the GitHub Actions workflow are found in the following files: @@ -22,6 +24,9 @@ are found in the following files: - Slack app setup: [`.github/resources/.slack/manifest.json`][slacktion] - GitHub Actions steps: [`.github/workflows/develop.yml`][develop] +> **Note:** During CLI integration tests, `.github/resources/.slack` is moved to +> `.slack` at the project root so the Slack CLI can discover the app manifest. + Either the techniques or app setup and workflow steps can be adjusted during testing and development. For experimenting with new changes, we recommend using the [steps for development](#experimenting-for-development) while the @@ -76,6 +81,8 @@ tested. Required values include: https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz - `SLACK_WEBHOOK_TRIGGER`: https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz +- `SLACK_SERVICE_TOKEN`: xoxp-service-token-example (secret — for CLI commands) +- `SLACK_APP_ID`: A0123456789 (variable — used with `--app` flag in CLI deploy) ### Experimenting for development @@ -98,6 +105,7 @@ export SLACK_BOT_TOKEN=xoxb-01010101-example export SLACK_CHANNEL_ID=C0123456789 export SLACK_INCOMING_WEBHOOK=https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz export SLACK_WEBHOOK_TRIGGER=https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz +export SLACK_SERVICE_TOKEN=xoxp-service-token-example ``` Environment variables and credentials should be set in the created `.env` file diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 6df1377c..effa3ff9 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -12,7 +12,7 @@ on: jobs: integration: - name: Run integration tests + name: Run API tests runs-on: ubuntu-latest environment: staging permissions: @@ -68,7 +68,7 @@ jobs: env: INPUT_REF: ${{ inputs.ref }} - - name: "integration(wfb): send a payload to workflow builder via webhook trigger" + - name: "test(wfb): send a payload to workflow builder via webhook trigger" id: wfb uses: ./ with: @@ -82,12 +82,12 @@ jobs: repo_name: ${{ github.event.repository.full_name }} status: ${{ job.status }} - - name: "integration(wfb): confirm a payload was sent" + - name: "test(wfb): confirm a payload was sent" run: test -n "$WFB_OUTPUT_TIME" env: WFB_OUTPUT_TIME: ${{ steps.wfb.outputs.time }} - - name: "integration(botToken): post a message to channel" + - name: "test(api): post a message to channel" id: message uses: ./ with: @@ -98,12 +98,12 @@ jobs: channel: ${{ secrets.SLACK_CHANNEL_ID }} text: ":checkered_flag: Action happens at " - - name: "integration(method): confirm a message was posted" + - name: "test(api): confirm a message was posted" run: test -n "$MESSAGE_OUTPUT_TS" env: MESSAGE_OUTPUT_TS: ${{ steps.message.outputs.ts }} - - name: "integration(method): post a message with blocks" + - name: "test(api): post a message with blocks" id: blocks uses: ./ with: @@ -120,12 +120,12 @@ jobs: short: true value: "Processing" - - name: "integration(method): confirm the blocks were posted" + - name: "test(api): confirm the blocks were posted" run: test -n "$BLOCKS_OUTPUT_TS" env: BLOCKS_OUTPUT_TS: ${{ steps.blocks.outputs.ts }} - - name: "integration(method): post a threaded message" + - name: "test(api): post a threaded message" id: timer uses: ./ with: @@ -137,15 +137,15 @@ jobs: text: "Started at `${{ steps.blocks.outputs.time }}`" thread_ts: "${{ steps.blocks.outputs.ts }}" - - name: "integration(incoming): confirm the thread started" + - name: "test(api): confirm the thread started" run: test -n "$TIMER_OUTPUT_TIME" env: TIMER_OUTPUT_TIME: ${{ steps.timer.outputs.time }} - - name: "integration(method): wait to mock event processing" + - name: "test(api): wait to mock event processing" run: sleep 3 - - name: "integration(method): update the original message" + - name: "test(api): update the original message" id: finished uses: ./ with: @@ -163,7 +163,7 @@ jobs: short: true value: "Completed" - - name: "integration(method): post another threaded message" + - name: "test(api): post another threaded message" id: done uses: ./ with: @@ -175,7 +175,7 @@ jobs: text: "Finished at `${{ steps.finished.outputs.time }}`" thread_ts: "${{ steps.timer.outputs.thread_ts }}" - - name: "integration(method): post a file into a channel" + - name: "test(api): post a file into a channel" id: file uses: ./ with: @@ -188,7 +188,7 @@ jobs: file: .github/workflows/integration.yml filename: integration.yml - - name: "integration(method): react to the completed update message" + - name: "test(api): react to the completed update message" uses: ./ with: errors: true @@ -199,12 +199,12 @@ jobs: timestamp: ${{ steps.blocks.outputs.ts }} name: "tada" - - name: "integration(method): confirm the thread ended" + - name: "test(api): confirm the thread ended" run: test -n "$DONE_OUTPUT_TIME" env: DONE_OUTPUT_TIME: ${{ steps.done.outputs.time }} - - name: "integration(incoming): post a message via incoming webhook" + - name: "test(incoming): post a message via incoming webhook" id: incoming uses: ./ with: @@ -220,17 +220,17 @@ jobs: text: ":link: A message was received via incoming webhook" emoji: true - - name: "integration(incoming): confirm a webhook was posted" + - name: "test(incoming): confirm a webhook was posted" run: test -n "$INCOMING_WEBHOOK_OUTPUT_TIME" env: INCOMING_WEBHOOK_OUTPUT_TIME: ${{ steps.incoming.outputs.time }} - - name: "integration(incoming): reveal contents of the github payload" + - name: "test(incoming): reveal contents of the github payload" run: echo "$JSON" env: JSON: ${{ toJSON(github) }} - - name: "integration(incoming): post a message via payload file" + - name: "test(incoming): post a message via payload file" id: payload_file uses: ./ with: @@ -243,15 +243,81 @@ jobs: JOB_STATUS: ${{ job.status }} ATTACHMENT_COLOR: ${{ (job.status == 'success' && 'good') || (job.status == 'failure' && 'danger') || 'warning' }} - - name: "integration(incoming): confirm a payload file was posted" + - name: "test(incoming): confirm a payload file was posted" run: test -n "$PAYLOAD_FILE_OUTPUT_TIME" env: PAYLOAD_FILE_OUTPUT_TIME: ${{ steps.payload_file.outputs.time }} - - name: "chore(health): check up on recent changes to the health score" - uses: slackapi/slack-health-score@d58a419f15cdaff97e9aa7f09f95772830ab66f7 # v0.1.1 + cli: + name: Run CLI tests + runs-on: ${{ matrix.os }} + environment: staging + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + permissions: + contents: read + steps: + - name: "build: checkout the latest changes" + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + ref: ${{ inputs.ref || github.event.pull_request.head.sha || github.sha }} + + - name: "test(cli): run a slack cli version check" + id: version + uses: ./cli + with: + command: "version" + version: "3.14.0" + + - name: "test(cli): confirm the version check outputs" + shell: bash + run: | + set -ex + [ "$CLI_OK" = "true" ] + echo "$CLI_TIME" | grep -qE '^[0-9]+$' + [ -n "$CLI_RESPONSE" ] + env: + CLI_OK: ${{ steps.version.outputs.ok }} + CLI_RESPONSE: ${{ steps.version.outputs.response }} + CLI_TIME: ${{ steps.version.outputs.time }} + + - name: "test(cli): run an unknown command" + id: unknown + continue-on-error: true + uses: ./cli + with: + command: "off" + + - name: "test(cli): confirm the unknown command outputs" + shell: bash + run: | + set -ex + [ "$CLI_OK" = "false" ] + echo "$CLI_TIME" | grep -qE '^[0-9]+$' + [ -n "$CLI_RESPONSE" ] + env: + CLI_OK: ${{ steps.unknown.outputs.ok }} + CLI_RESPONSE: ${{ steps.unknown.outputs.response }} + CLI_TIME: ${{ steps.unknown.outputs.time }} + + - name: "chore: configure the actioneering application" + shell: bash + run: mv .github/resources/.slack .slack + + - name: "test(cli): validate the app manifest" + uses: ./cli + with: + command: "manifest" + token: ${{ secrets.SLACK_SERVICE_TOKEN }} + + - name: "test(cli): deploy the app" + uses: ./cli with: - codecov_token: ${{ secrets.CODECOV_API_TOKEN }} - github_token: ${{ secrets.GITHUB_TOKEN }} - extension: js - include: src + command: "deploy --app ${{ vars.SLACK_APP_ID }} --hide-triggers --force" + token: ${{ secrets.SLACK_SERVICE_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 63532b20..54d5ec10 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -38,6 +38,9 @@ jobs: uses: teunmooij/github-versioned-release@3edf649c6e5e5e976d43f2584b15bdc8b4c8f0df # v1.2.1 with: template: javascript-action + include: | + dist/**/* + cli/**/* env: GITHUB_TOKEN: ${{ github.token }} diff --git a/cli/action.yml b/cli/action.yml new file mode 100644 index 00000000..a7042e5b --- /dev/null +++ b/cli/action.yml @@ -0,0 +1,150 @@ +name: "Slack: Run CLI Command" +author: "slackapi" +description: "Install the Slack CLI and run a command in a GitHub Actions workflow" +inputs: + command: + description: "A Slack CLI command to run without the 'slack' prefix." + required: true + token: + description: "A service token passed as the '--token' flag to the CLI command." + required: false + version: + description: "The version of the Slack CLI to install. Defaults to the latest." + required: false +outputs: + ok: + description: "If the command completed with a '0' exit code." + value: ${{ steps.slack-cli-unix.outputs.ok || steps.slack-cli-windows.outputs.ok }} + response: + description: "The standard output from the CLI command." + value: ${{ steps.slack-cli-unix.outputs.response || steps.slack-cli-windows.outputs.response }} + time: + description: "The Unix epoch time that the step completed." + value: ${{ steps.slack-cli-unix.outputs.time || steps.slack-cli-windows.outputs.time }} +runs: + using: composite + steps: + - name: Check if Slack CLI already exists + id: slack-cli-check + shell: bash + run: | + if command -v slack &> /dev/null; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Cache Slack CLI + if: steps.slack-cli-check.outputs.exists != 'true' + id: cache-cli + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: | + ${{ runner.os == 'Windows' && format('{0}\AppData\Local\slack-cli', env.USERPROFILE) || format('{0}/.slack/bin', env.HOME) }} + key: slack-cli-${{ runner.os }}-${{ runner.arch }}-${{ inputs.version }} + + - name: Bash Add Slack CLI to PATH + if: steps.slack-cli-check.outputs.exists != 'true' && runner.os != 'Windows' + shell: bash + run: echo "$HOME/.slack/bin" >> "$GITHUB_PATH" # zizmor: ignore[github-env] + + - name: Pwsh Add Slack CLI to PATH + if: steps.slack-cli-check.outputs.exists != 'true' && runner.os == 'Windows' + shell: pwsh + run: Add-Content -Path $env:GITHUB_PATH -Value "$env:USERPROFILE\AppData\Local\slack-cli\bin" # zizmor: ignore[github-env] + + - name: Bash Install Slack CLI + if: >- + steps.slack-cli-check.outputs.exists != 'true' && + steps.cache-cli.outputs.cache-hit != 'true' && + runner.os != 'Windows' + shell: bash + run: | + if [ -n "$SLACK_CLI_VERSION" ]; then + curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash -s -- -v "$SLACK_CLI_VERSION" + else + curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash -s + fi + env: + SLACK_CLI_VERSION: ${{ inputs.version }} + + - name: Pwsh Install Slack CLI + if: >- + steps.slack-cli-check.outputs.exists != 'true' && + steps.cache-cli.outputs.cache-hit != 'true' && + runner.os == 'Windows' + shell: pwsh + run: | + if ($env:SLACK_CLI_VERSION) { + $installer = Join-Path $env:TEMP "install-slack-cli.ps1" + Invoke-WebRequest -Uri "https://downloads.slack-edge.com/slack-cli/install-windows.ps1" -OutFile $installer + & $installer -v $env:SLACK_CLI_VERSION + } else { + irm https://downloads.slack-edge.com/slack-cli/install-windows.ps1 | iex + } + env: + SLACK_CLI_VERSION: ${{ inputs.version }} + + - name: Bash Run Slack CLI command + id: slack-cli-unix + if: runner.os != 'Windows' + shell: bash + env: + SLACK_COMMAND: ${{ inputs.command }} + SLACK_TOKEN: ${{ inputs.token }} + run: | + args="$SLACK_COMMAND --skip-update" + if [ "$RUNNER_DEBUG" = "1" ]; then + args="$args --verbose" + fi + if [ -n "$SLACK_TOKEN" ]; then + args="$args --token $SLACK_TOKEN" + fi + + set +e + output=$(slack $args 2>&1 | tee /dev/stderr) + exit_code=$? + set -e + + echo "ok=$([ $exit_code -eq 0 ] && echo 'true' || echo 'false')" >> "$GITHUB_OUTPUT" + echo "time=$(date +%s)" >> "$GITHUB_OUTPUT" + + echo "response<> "$GITHUB_OUTPUT" + echo "$output" >> "$GITHUB_OUTPUT" + echo "SLACKCLIEOF" >> "$GITHUB_OUTPUT" + + if [ $exit_code -ne 0 ]; then + exit $exit_code + fi + + - name: Pwsh Run Slack CLI command + id: slack-cli-windows + if: runner.os == 'Windows' + shell: pwsh + env: + SLACK_COMMAND: ${{ inputs.command }} + SLACK_TOKEN: ${{ inputs.token }} + run: | + $cliArgs = "$env:SLACK_COMMAND --skip-update" + if ($env:RUNNER_DEBUG -eq "1") { + $cliArgs = "$cliArgs --verbose" + } + if ($env:SLACK_TOKEN) { + $cliArgs = "$cliArgs --token $env:SLACK_TOKEN" + } + + $outputFile = New-TemporaryFile + slack $cliArgs.Split(' ') 2>&1 | Tee-Object -FilePath $outputFile.FullName + $exit_code = $LASTEXITCODE + $output = Get-Content $outputFile.FullName -Raw + + "ok=$( if ($exit_code -eq 0) { 'true' } else { 'false' } )" >> $env:GITHUB_OUTPUT + "time=$([DateTimeOffset]::UtcNow.ToUnixTimeSeconds())" >> $env:GITHUB_OUTPUT + + "response<> $env:GITHUB_OUTPUT + $output >> $env:GITHUB_OUTPUT + "SLACKCLIEOF" >> $env:GITHUB_OUTPUT + + if ($exit_code -ne 0) { + exit $exit_code + } diff --git a/docs/_sidebar.json b/docs/_sidebar.json index 4e597b86..1772c310 100644 --- a/docs/_sidebar.json +++ b/docs/_sidebar.json @@ -57,6 +57,20 @@ "tools/slack-github-action/sending-techniques/sending-data-slack-incoming-webhook/post-inline-block-message", "tools/slack-github-action/sending-techniques/sending-data-slack-incoming-webhook/post-blocks-found-in-file" ] + }, + { + "type": "category", + "label": "Running Slack CLI commands", + "link": { + "type": "doc", + "id": "tools/slack-github-action/sending-techniques/running-slack-cli-commands/running-slack-cli-commands" + }, + "items": [ + "tools/slack-github-action/sending-techniques/running-slack-cli-commands/running-slack-cli-commands", + "tools/slack-github-action/sending-techniques/running-slack-cli-commands/deploy-an-app", + "tools/slack-github-action/sending-techniques/running-slack-cli-commands/validate-a-manifest", + "tools/slack-github-action/sending-techniques/running-slack-cli-commands/manage-collaborators" + ] } ] }, diff --git a/docs/sending-techniques/running-slack-cli-commands/deploy-an-app.md b/docs/sending-techniques/running-slack-cli-commands/deploy-an-app.md new file mode 100644 index 00000000..fc225d72 --- /dev/null +++ b/docs/sending-techniques/running-slack-cli-commands/deploy-an-app.md @@ -0,0 +1,13 @@ +# Example workflow: deploy an app + +This workflow deploys a Slack app when changes are pushed to the main branch. + +This example uses a service token to authenticate the deploy command. + +## Files + +### GitHub Actions workflow + +```js reference +https://github.com/slackapi/slack-github-action/blob/main/example-workflows/Technique_4_Slack_CLI_Command/deploy.yml +``` diff --git a/docs/sending-techniques/running-slack-cli-commands/manage-collaborators.md b/docs/sending-techniques/running-slack-cli-commands/manage-collaborators.md new file mode 100644 index 00000000..462339a6 --- /dev/null +++ b/docs/sending-techniques/running-slack-cli-commands/manage-collaborators.md @@ -0,0 +1,13 @@ +# Example workflow: manage collaborators + +This workflow adds or removes an app collaborator using a manually triggered workflow. + +This example combines the Slack API technique (`users.lookupByEmail`, `chat.postMessage`) with the CLI technique (`collaborators add/remove`) to look up a user by email, update collaborators, and post a confirmation message. + +## Files + +### GitHub Actions workflow + +```js reference +https://github.com/slackapi/slack-github-action/blob/main/example-workflows/Technique_4_Slack_CLI_Command/collaborators.yml +``` diff --git a/docs/sending-techniques/running-slack-cli-commands/running-slack-cli-commands.md b/docs/sending-techniques/running-slack-cli-commands/running-slack-cli-commands.md new file mode 100644 index 00000000..8a82e6b8 --- /dev/null +++ b/docs/sending-techniques/running-slack-cli-commands/running-slack-cli-commands.md @@ -0,0 +1,103 @@ +--- +sidebar_label: Overview +--- + +# Running Slack CLI commands + +The Slack CLI technique installs and runs [Slack CLI](/tools/slack-cli/) commands directly from a GitHub Actions workflow. + +This is useful for automating tasks such as deploying apps, managing triggers, or interacting with Slack platform features that are accessible through the CLI. + +## Setup + +### Authentication + +Authentication can be provided in one of two ways: + +- **Token input**: Pass a [service token](/authentication/tokens/) via the `token` input. This is appended as `--token ` to the CLI command. +- **Environment variable**: Set `SLACK_SERVICE_TOKEN` as an environment variable in your workflow. The Slack CLI reads this automatically. + +### CLI version + +By default, the latest version of the Slack CLI is installed. To pin a specific version, use the `version` input: + +```yaml +- uses: slackapi/slack-github-action/cli@v2 + with: + command: "version" + version: "3.14.0" +``` + +The CLI binary is cached across workflow runs using `actions/cache`, keyed by OS, architecture, and version. If the `slack` command already exists on `PATH`, installation is skipped entirely. + +## Usage + +Provide a `command` input with the Slack CLI command to run, omitting the `slack` prefix. The `--skip-update` flag is appended automatically. + +```yaml +- uses: slackapi/slack-github-action/cli@v2 + with: + command: "version" +``` + +## Debug logging + +When a workflow is re-run with **Enable debug logging**, the action automatically appends `--verbose` to the CLI command. You can also include `--verbose` in your `command` input manually at any time. + +```yaml +- uses: slackapi/slack-github-action/cli@v2 + with: + command: "deploy --app A0123456789 --verbose" + token: ${{ secrets.SLACK_SERVICE_TOKEN }} +``` + +## Outputs + +The following outputs are available after a CLI command runs: + +| Output | Type | Description | +| ---------- | --------- | --------------------------------------------------------------------------------------- | +| `ok` | `boolean` | If the command completed with a `0` exit code. | +| `response` | `string` | The standard output from the CLI command. | +| `time` | `number` | The Unix [epoch time](https://en.wikipedia.org/wiki/Unix_time) that the step completed. | + +## Examples + +### Check the installed CLI version + +```yaml +steps: + - uses: slackapi/slack-github-action/cli@v2 + id: slack + with: + command: "version" + - run: echo "${{ steps.slack.outputs.response }}" +``` + +### Validate the app manifest + +```yaml +steps: + - uses: actions/checkout@v4 + - uses: slackapi/slack-github-action/cli@v2 + with: + command: "manifest validate" + token: ${{ secrets.SLACK_SERVICE_TOKEN }} +``` + +### Deploy an app with a service token + +```yaml +steps: + - uses: actions/checkout@v4 + - uses: slackapi/slack-github-action/cli@v2 + with: + command: "deploy --app A0123456789 --hide-triggers --force" + token: ${{ secrets.SLACK_SERVICE_TOKEN }} +``` + +## Example workflows + +* [**Deploy an app**](/tools/slack-github-action/sending-techniques/running-slack-cli-commands/deploy-an-app): Deploy to Slack on push to the main branch. +* [**Validate a manifest**](/tools/slack-github-action/sending-techniques/running-slack-cli-commands/validate-a-manifest): Check the app manifest on pull requests. +* [**Manage collaborators**](/tools/slack-github-action/sending-techniques/running-slack-cli-commands/manage-collaborators): Add or remove an app collaborator using CLI and API techniques together. diff --git a/docs/sending-techniques/running-slack-cli-commands/validate-a-manifest.md b/docs/sending-techniques/running-slack-cli-commands/validate-a-manifest.md new file mode 100644 index 00000000..bf69b53e --- /dev/null +++ b/docs/sending-techniques/running-slack-cli-commands/validate-a-manifest.md @@ -0,0 +1,13 @@ +# Example workflow: validate a manifest + +This workflow validates the app manifest on pull requests to catch configuration issues early. + +This example checks the app manifest file in the repository. + +## Files + +### GitHub Actions workflow + +```js reference +https://github.com/slackapi/slack-github-action/blob/main/example-workflows/Technique_4_Slack_CLI_Command/manifest.yml +``` diff --git a/docs/sending-techniques/sending-techniques.md b/docs/sending-techniques/sending-techniques.md index 84b4a1e5..fd942ab0 100644 --- a/docs/sending-techniques/sending-techniques.md +++ b/docs/sending-techniques/sending-techniques.md @@ -4,11 +4,12 @@ sidebar_label: Overview # Sending techniques -This GitHub Action offers three different techniques to send data to Slack: +This GitHub Action offers four different techniques to send data to Slack: * [Send data with a webhook to start a workflow in Workflow Builder](/tools/slack-github-action/sending-techniques/sending-data-webhook-slack-workflow). * [Send data using a Slack API method and a secret token with required scopes](/tools/slack-github-action/sending-techniques/sending-data-slack-api-method/). -* [Send data as a message with a Slack incoming webhook URL](/tools/slack-github-action/sending-techniques/sending-data-slack-incoming-webhook/). +* [Send data as a message with a Slack incoming webhook URL](/tools/slack-github-action/sending-techniques/sending-data-slack-incoming-webhook/). +* [Run Slack CLI commands with a service token](/tools/slack-github-action/sending-techniques/running-slack-cli-commands/running-slack-cli-commands). ## Expected outputs diff --git a/example-workflows/Technique_4_Slack_CLI_Command/README.md b/example-workflows/Technique_4_Slack_CLI_Command/README.md new file mode 100644 index 00000000..f4e00091 --- /dev/null +++ b/example-workflows/Technique_4_Slack_CLI_Command/README.md @@ -0,0 +1,37 @@ +# Technique 4: Slack CLI Command + +A [service token](/authentication/tokens/#service) is used to install and run [Slack CLI](/tools/slack-cli/) commands directly from a GitHub Actions workflow with this technique. + +## Setup + +For details on how to setup this technique in GitHub Actions, read the [setup](/tools/slack-github-action/sending-techniques/running-slack-cli-commands/) section of the docs. + +## Example workflows + +1. [**Deploy an app**](#deploy-an-app): Deploy to Slack on push to the main branch. +2. [**Validate the app manifest**](#validate-the-app-manifest): Check the app manifest on pull requests. +3. [**Manage a collaborator**](#manage-a-collaborator): Add or remove an app collaborator using CLI and API techniques together. + +### Deploy an app + +Deploy a Slack app when changes are pushed to the main branch. This example uses a service token to authenticate the deploy command. + +**Related files**: + +- [`deploy.yml`](./deploy.yml): GitHub Actions workflow. + +### Validate the app manifest + +Run manifest validation on pull requests to catch configuration issues early. This example checks the app manifest file in the repository. + +**Related files**: + +- [`manifest.yml`](./manifest.yml): GitHub Actions workflow. + +### Manage a collaborator + +Add or remove an app collaborator using a manually triggered workflow. This example combines the Slack API technique (`users.lookupByEmail`, `chat.postMessage`) with the CLI technique (`collaborators add/remove`) to look up a user by email, update collaborators, and post a confirmation message. + +**Related files**: + +- [`collaborators.yml`](./collaborators.yml): GitHub Actions workflow. diff --git a/example-workflows/Technique_4_Slack_CLI_Command/collaborators.yml b/example-workflows/Technique_4_Slack_CLI_Command/collaborators.yml new file mode 100644 index 00000000..fcff4da7 --- /dev/null +++ b/example-workflows/Technique_4_Slack_CLI_Command/collaborators.yml @@ -0,0 +1,58 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Manage a collaborator +on: + workflow_dispatch: + inputs: + email: + description: "The collaborator's email address" + required: true + type: string + add: + description: "Checked to add, unchecked to remove" + required: false + default: true + type: boolean +jobs: + collaborator: + name: Add or remove a collaborator + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Look up the Slack user by email + id: email + uses: slackapi/slack-github-action@v2 + with: + method: users.lookupByEmail # https://docs.slack.dev/reference/methods/users.lookupByEmail/ + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + email: ${{ inputs.email }} + + - name: Gather the display name + if: ${{ steps.email.outputs.ok }} + run: | + SLACK_USER_ID=$(echo '${{ steps.email.outputs.response }}' | jq -r '.user.id') + SLACK_DISPLAY_NAME=$(echo '${{ steps.email.outputs.response }}' | jq -r '.user.profile.display_name // .user.real_name') + echo "SLACK_USER_ID=$SLACK_USER_ID" >> "$GITHUB_ENV" + echo "SLACK_DISPLAY_NAME=$SLACK_DISPLAY_NAME" >> "$GITHUB_ENV" + + - name: Add or remove the collaborator + if: ${{ steps.email.outputs.ok }} + uses: slackapi/slack-github-action/cli@v2 + with: + command: "collaborators ${{ inputs.add && 'add' || 'remove' }} ${{ inputs.email }}" + token: ${{ secrets.SLACK_SERVICE_TOKEN }} + + - name: Post a confirmation message + if: ${{ steps.email.outputs.ok }} + uses: slackapi/slack-github-action@v2 + with: + method: chat.postMessage # https://docs.slack.dev/reference/methods/chat.postMessage/ + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "<@${{ env.SLACK_USER_ID }}> (${{ env.SLACK_DISPLAY_NAME }}) was ${{ inputs.add && 'added to' || 'removed from' }} the app collaborators." diff --git a/example-workflows/Technique_4_Slack_CLI_Command/deploy.yml b/example-workflows/Technique_4_Slack_CLI_Command/deploy.yml new file mode 100644 index 00000000..a8e1cc6d --- /dev/null +++ b/example-workflows/Technique_4_Slack_CLI_Command/deploy.yml @@ -0,0 +1,22 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Deploy an app +on: + push: + branches: + - main +jobs: + deploy: + name: Deploy to Slack + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Deploy the app + uses: slackapi/slack-github-action/cli@v2 + with: + command: "deploy --app ${{ vars.SLACK_APP_ID }} --hide-triggers --force" + token: ${{ secrets.SLACK_SERVICE_TOKEN }} diff --git a/example-workflows/Technique_4_Slack_CLI_Command/manifest.yml b/example-workflows/Technique_4_Slack_CLI_Command/manifest.yml new file mode 100644 index 00000000..4ec5b832 --- /dev/null +++ b/example-workflows/Technique_4_Slack_CLI_Command/manifest.yml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Validate the app manifest +on: + pull_request: +jobs: + validate: + name: Check the app manifest + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Validate the manifest + uses: slackapi/slack-github-action/cli@v2 + with: + command: "manifest validate" + token: ${{ secrets.SLACK_SERVICE_TOKEN }}