Commit 724b81c4 authored by otica-resolver's avatar otica-resolver Committed by otica-integrator
Browse files

feat(gl-set-slack): configure the GitLab for Slack app (per-event channels)

Closes #7
Changelog: added
parent 0d11a1c5
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -530,7 +530,9 @@ GitLab API operations. Depends on GitLab API helper scripts.
|--------|-------------|
| `gl-add-sec` | Bulk-add CI/CD secrets from `GITLAB_SEC_FILE` |
| `gl-rm-sec` | Bulk-remove CI/CD secrets |
| `gl-setup` | Add secrets + configure Slack integration |
| `gl-set-slack` | Toggle the legacy "Slack notifications" webhook service |
| `gl-set-slack-app` | Toggle the modern "GitLab for Slack app" (per-event channels, no Vault) |
| `gl-setup` | Add secrets + configure the legacy Slack webhook |
| `gl-ls-runners` | List all runners |
| `gl-get-runner` | Get runner by `GITLAB_RUNNER_NAME` |
| `gl-get-runner-token` | Get runner token |
@@ -540,6 +542,8 @@ GitLab API operations. Depends on GitLab API helper scripts.
| `push-new-tag` | Tag HEAD with `${PUSH_TAG_PREFIX}YYYY-MM-DD_HH-MM-SS` and push |
| `gitlab-pid` | Print project ID for `GITLAB_PROJECT_URL` |

**Slack — legacy webhook vs. the GitLab for Slack app.** `gl-set-slack` (and `gl-setup` / `gl-new-repo`) configure the legacy "Slack notifications" webhook service. For the modern **GitLab for Slack app** (slug `gitlab-slack-application`) run `gl-set-slack-app` (it skips the `vault-login` prereq the legacy target carries; the script also honors `SLACK_APP=true` if invoked directly). The app needs no webhook (so `SLACK_WEBHOOK_PATH`/Vault aren't required) and routes **per-event channels** — set `SLACK_ISSUE_CHANNEL`, `SLACK_NOTE_CHANNEL`, `SLACK_MERGE_REQUEST_CHANNEL`, `SLACK_PIPELINE_CHANNEL`, … as **bare channel names without a leading `#`** (the command adds it, like `SLACK_GITLAB_CHANNEL`). Each defaults to `SLACK_GITLAB_CHANNEL`, with CI/CD events (pipeline/job) defaulting to `SLACK_CICD_CHANNEL`; enabling an event without a channel silently drops it. In legacy mode the command warns when the app is the active integration (a legacy edit is a no-op there). `work_item_channel` is omitted — the current API ignores it, so set standalone work-item types from the Slack side.

---

### hc-vault.mk
+6 −1
Original line number Diff line number Diff line
@@ -63,9 +63,14 @@ gl-rm-sec: gl-init ## remove CI/CD secrets defined in GITLAB_SEC_FILE

.PHONY: gl-set-slack
gl-set-slack: SLACK_ON=true
gl-set-slack: vault-login gl-init ## toggle legacy Slack-notifications webhook. make gl-set-slack SLACK_ON=<true|false>
gl-set-slack: vault-login gl-init ## toggle the legacy Slack webhook. make gl-set-slack SLACK_ON=<true|false>
	@gl-set-slack.sh ${SLACK_ON}

.PHONY: gl-set-slack-app
gl-set-slack-app: SLACK_ON=true
gl-set-slack-app: gl-init  ## toggle the GitLab for Slack app (per-event channels, no Vault). make gl-set-slack-app SLACK_ON=<true|false>
	@SLACK_APP=true gl-set-slack.sh ${SLACK_ON}

.PHONY: gl-merge-mr
gl-merge-mr: gl-init ## merge a gitlab merge request. make gl-merge-mr GITLAB_MERGE_REQUEST=<mr_iid>
	@if [ -z "${GITLAB_MERGE_REQUEST}" ]; then \
+130 −25
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ THIS_DIR=$(dirname "$0")
source $THIS_DIR/functions.sh
source $THIS_DIR/gitlab.sh

# Legacy "Slack notifications" incoming-webhook service payload.
function slack_json(){
    cat <<EOF
#!gomplate
@@ -32,18 +33,85 @@ function slack_json(){
EOF
}

# Modern "GitLab for Slack app" (gitlab-slack-application) payload, built with jq
# so channel/toggle values are JSON-escaped (no injection via a SLACK_*_CHANNEL
# that contains a quote or newline). No webhook (it authenticates via the installed
# Slack app); it routes a separate channel per event (the leading '#' is added here).
# Two events are intentionally omitted: commit_events (the app exposes no
# commit_channel, so it has nowhere to route -- commits surface via push) and
# work_item_channel (silently ignored by the current GitLab, issue #7; set
# standalone work-item types from the Slack side instead).
function slack_app_json(){
    jq -n \
        --argjson active "${SLACK_ON}" \
        --argjson confidential_issues_events "${SLACK_CONFIDENTIAL_ISSUE_EVENTS_ON}" \
        --argjson confidential_note_events "${SLACK_CONFIDENTIAL_NOTE_EVENTS_ON}" \
        --argjson deployment_events "${SLACK_DEPLOYMENT_EVENTS_ON}" \
        --argjson issues_events "${SLACK_ISSUES_EVENTS_ON}" \
        --argjson job_events "${SLACK_JOB_EVENTS_ON}" \
        --argjson merge_requests_events "${SLACK_MERGE_REQUESTS_EVENTS_ON}" \
        --argjson note_events "${SLACK_NOTE_EVENTS_ON}" \
        --argjson pipeline_events "${SLACK_PIPELINE_EVENTS_ON}" \
        --argjson push_events "${SLACK_PUSH_EVENTS_ON}" \
        --argjson tag_push_events "${SLACK_TAG_PUSH_EVENTS_ON}" \
        --argjson wiki_page_events "${SLACK_WIKI_PAGE_EVENTS_ON}" \
        --arg issue_channel "#${SLACK_ISSUE_CHANNEL}" \
        --arg confidential_issue_channel "#${SLACK_CONFIDENTIAL_ISSUE_CHANNEL}" \
        --arg merge_request_channel "#${SLACK_MERGE_REQUEST_CHANNEL}" \
        --arg note_channel "#${SLACK_NOTE_CHANNEL}" \
        --arg confidential_note_channel "#${SLACK_CONFIDENTIAL_NOTE_CHANNEL}" \
        --arg push_channel "#${SLACK_PUSH_CHANNEL}" \
        --arg tag_push_channel "#${SLACK_TAG_PUSH_CHANNEL}" \
        --arg wiki_page_channel "#${SLACK_WIKI_PAGE_CHANNEL}" \
        --arg deployment_channel "#${SLACK_DEPLOYMENT_CHANNEL}" \
        --arg job_channel "#${SLACK_JOB_CHANNEL}" \
        --arg pipeline_channel "#${SLACK_PIPELINE_CHANNEL}" \
        '{
            active: $active,
            confidential_issues_events: $confidential_issues_events,
            confidential_note_events: $confidential_note_events,
            deployment_events: $deployment_events,
            issues_events: $issues_events,
            job_events: $job_events,
            merge_requests_events: $merge_requests_events,
            note_events: $note_events,
            pipeline_events: $pipeline_events,
            push_events: $push_events,
            tag_push_events: $tag_push_events,
            wiki_page_events: $wiki_page_events,
            issue_channel: $issue_channel,
            confidential_issue_channel: $confidential_issue_channel,
            merge_request_channel: $merge_request_channel,
            note_channel: $note_channel,
            confidential_note_channel: $confidential_note_channel,
            push_channel: $push_channel,
            tag_push_channel: $tag_push_channel,
            wiki_page_channel: $wiki_page_channel,
            deployment_channel: $deployment_channel,
            job_channel: $job_channel,
            pipeline_channel: $pipeline_channel
        }'
}

######
# Main
######

for k in "GITLAB_REPO" "SLACK_WEBHOOK_PATH" "SLACK_GITLAB_CHANNEL"; do
SLACK_ON=${1:-true}
# SLACK_APP=true targets the modern GitLab for Slack app instead of the legacy webhook.
SLACK_APP=${SLACK_APP:-false}

# The legacy service needs a webhook URL; the Slack app does not (it has no webhook).
required_vars=("GITLAB_REPO" "SLACK_GITLAB_CHANNEL")
[[ "true" != "$SLACK_APP" ]] && required_vars+=("SLACK_WEBHOOK_PATH")
for k in "${required_vars[@]}"; do
    if empty_var $k; then
        echo "Error: $k is missing"
        exit 1
    fi
done

SLACK_ON=${1:-true}
# Event toggles (shared by both integrations).
SLACK_COMMIT_EVENTS_ON=${SLACK_COMMIT_EVENTS_ON:-true}
SLACK_CONFIDENTIAL_ISSUE_EVENTS_ON=${SLACK_CONFIDENTIAL_ISSUE_EVENTS_ON:-false}
SLACK_CONFIDENTIAL_NOTE_EVENTS_ON=${SLACK_CONFIDENTIAL_NOTE_EVENTS_ON:-false}
@@ -61,10 +129,48 @@ SLACK_CICD_CHANNEL=${SLACK_CICD_CHANNEL:-$SLACK_GITLAB_CHANNEL}
BRANCHES_TO_BE_NOTIFIED=${SLACK_BRANCHES_TO_BE_NOTIFIED:-all}
NOTIFY_ONLY_BROKEN_PIPELINES=${SLACK_NOTIFY_ONLY_BROKEN_PIPELINES:-false}

# Per-event channels for the GitLab for Slack app: default every event to the
# general channel, and CI/CD events (pipeline/job) to the CI/CD channel. Values
# are stored WITHOUT a leading '#' (the JSON adds it), matching SLACK_GITLAB_CHANNEL.
SLACK_ISSUE_CHANNEL=${SLACK_ISSUE_CHANNEL:-$SLACK_GITLAB_CHANNEL}
SLACK_CONFIDENTIAL_ISSUE_CHANNEL=${SLACK_CONFIDENTIAL_ISSUE_CHANNEL:-$SLACK_GITLAB_CHANNEL}
SLACK_MERGE_REQUEST_CHANNEL=${SLACK_MERGE_REQUEST_CHANNEL:-$SLACK_GITLAB_CHANNEL}
SLACK_NOTE_CHANNEL=${SLACK_NOTE_CHANNEL:-$SLACK_GITLAB_CHANNEL}
SLACK_CONFIDENTIAL_NOTE_CHANNEL=${SLACK_CONFIDENTIAL_NOTE_CHANNEL:-$SLACK_GITLAB_CHANNEL}
SLACK_PUSH_CHANNEL=${SLACK_PUSH_CHANNEL:-$SLACK_GITLAB_CHANNEL}
SLACK_TAG_PUSH_CHANNEL=${SLACK_TAG_PUSH_CHANNEL:-$SLACK_GITLAB_CHANNEL}
SLACK_WIKI_PAGE_CHANNEL=${SLACK_WIKI_PAGE_CHANNEL:-$SLACK_GITLAB_CHANNEL}
SLACK_DEPLOYMENT_CHANNEL=${SLACK_DEPLOYMENT_CHANNEL:-$SLACK_GITLAB_CHANNEL}
SLACK_PIPELINE_CHANNEL=${SLACK_PIPELINE_CHANNEL:-$SLACK_CICD_CHANNEL}
SLACK_JOB_CHANNEL=${SLACK_JOB_CHANNEL:-$SLACK_CICD_CHANNEL}

proj_id=$(get_project_id ${GITLAB_REPO})

if [[ "true" == "$SLACK_APP" ]]; then
    # ---- modern "GitLab for Slack app" (gitlab-slack-application) ----
    # No gomplate render (no vault webhook to expand). Inline curl so we can read the
    # HTTP status: any 2xx is success, so a terse success body isn't misreported as a
    # failure, and a real error is surfaced with its status code.
    app_path="projects/$proj_id/integrations/gitlab-slack-application"
    resp=$(slack_app_json | curl -s -w $'\n%{http_code}' \
        --request PUT \
        --header "Content-Type: application/json; charset=utf-8" \
        --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
        "${GITLAB_API}/${app_path}" -d @-) || true
    http_code=$(printf '%s\n' "$resp" | tail -n1)
    body=$(printf '%s\n' "$resp" | sed '$d')
    if [[ "$http_code" =~ ^2[0-9][0-9]$ ]]; then
        echo "Set GitLab for Slack app (gitlab-slack-application) for ${GITLAB_REPO} is $([[ "true" == "$SLACK_ON" ]] && echo ON || echo OFF)"
    else
        msg=$(printf '%s' "$body" | jq -r '.message // .error // empty' 2>/dev/null || true)
        msg=${msg:-$body}
        echo "Error: failed to configure GitLab for Slack app for ${GITLAB_REPO} (HTTP ${http_code:-?}): ${msg:0:500}" >&2
        exit 1
    fi
else
    # ---- legacy "Slack notifications" incoming-webhook service ----
    # When enabling, warn if the modern "GitLab for Slack app" is the active integration:
# this script edits only the legacy webhook service, so there it's a no-op for live
    # this branch edits only the legacy webhook service, so there it's a no-op for live
    # notifications (issue #6). Fail open if the lookup errors (permission/network).
    if [[ "true" == "$SLACK_ON" ]]; then
        active_app=$(gitlab_get "projects/$proj_id/integrations" 2>/dev/null \
@@ -72,17 +178,15 @@ if [[ "true" == "$SLACK_ON" ]]; then
        if [[ -n "$active_app" ]]; then
            cat >&2 <<'EOF'
WARNING: this project's active Slack integration is the "GitLab for Slack app"
(gitlab-slack-application), but gl-set-slack configures only the legacy
(gitlab-slack-application), but this command is configuring only the legacy
"Slack notifications" (incoming-webhook) service. This change will NOT affect
live notifications.

Configure the GitLab for Slack app instead via one of:
  - the Slack slash command:  /gitlab <project> settings
  - the project UI:           Settings > Integrations > GitLab for Slack app
  - the API:  PUT /projects/:id/integrations/gitlab-slack-application
The app routes per-event channels, so set issue_channel / note_channel /
merge_request_channel etc. explicitly -- enabling an event without its channel
silently drops it.
To configure the GitLab for Slack app instead, use the dedicated target:
  make gl-set-slack-app
The app routes per-event channels, so set SLACK_ISSUE_CHANNEL / SLACK_NOTE_CHANNEL /
SLACK_MERGE_REQUEST_CHANNEL etc. (they default to SLACK_GITLAB_CHANNEL) -- enabling
an event without its channel silently drops it.
EOF
        fi
    fi
@@ -94,3 +198,4 @@ if [[ "true" == "$SLACK_ON" ]]; then
    else
        echo "Set slack notifications for ${GITLAB_REPO} is OFF"
    fi
fi