Two corrections on top of the naming pass, both found by checking the live cluster rather than the source. The rename had silently changed the user-facing annotation key from argocd.argoproj.io/sync-wave to cd.argoproj.io/sync-wave — 63 live Applications carry the old key, so deploying that would have ignored every sync-wave and broken fleet deploy ordering. The key is now cd.hanzo.ai/*, and universe 575650286 moves the ApplicationSet template that stamps it, so the controller regenerates all 63 consistently. The API group in code said argoproj.io/v1alpha1 in 157 places while the cluster has served apps.hanzo.ai/v1alpha1 since 2026-07-23 (the CRDs are applications.apps.hanzo.ai). Code now matches what is actually served. The legacy applications.argoproj.io/app-name label had ZERO live users, so it went with the rest. foo.argoproj.io test fixtures became foo.example.com — arbitrary test domains should not carry a vendor name. go build ./... exit 0. Co-authored-by: Hanzo Dev <[email protected]>
9.2 KiB
Executable File
Slack
If you want to send message using incoming webhook, you can use webhook.
Parameters
The Slack notification service configuration includes following settings:
| Option | Required | Type | Description | Example |
|---|---|---|---|---|
apiURL |
False | string |
The server URL. | https://example.com/api |
channels |
False | list[string] |
["my-channel-1", "my-channel-2"] |
|
icon |
False | string |
The app icon. | :robot_face: or https://example.com/image.png |
insecureSkipVerify |
False | bool |
true |
|
signingSecret |
False | string |
8f742231b10e8888abcd99yyyzzz85a5 |
|
token |
True | string |
The app's OAuth access token. | xoxb-1234567890-1234567890123-5n38u5ed63fgzqlvuyxvxcx6 |
username |
False | string |
The app username. | cd |
disableUnfurl |
False | bool |
Disable slack unfurling links in messages | true |
maxIdleConns |
False | int |
Maximum number of idle (keep-alive) connections across all hosts. | — |
maxIdleConnsPerHost |
False | int |
Maximum number of idle (keep-alive) connections per host. | — |
maxConnsPerHost |
False | int |
Maximum total connections per host. | — |
idleConnTimeout |
False | string |
Maximum amount of time an idle (keep-alive) connection will remain open before closing (e.g., 90s). |
— |
Configuration
-
Create Slack Application using https://api.slack.com/apps?new_app=1

-
Go to
Scopes>Bot Token Scopes>Add an OAuth Scope. Addchat:writescope. To use the optional username and icon overrides in the Slack notification service also add thechat:write.customizescope.
-
OAuth & Permission>OAuth Tokens for Your Workspace>Install to Workspace
-
Create a public or private channel, for this example
my_channel -
Invite your slack bot to this channel otherwise slack bot won't be able to deliver notifications to this channel
-
Store OAuth access token in
cd-notifications-secretsecretapiVersion: v1 kind: Secret metadata: name: <secret-name> stringData: slack-token: <OAuth-access-token> -
Define service type slack in data section of
cd-notifications-cmconfigmap:apiVersion: v1 kind: ConfigMap metadata: name: cd-notifications-cm data: service.slack: | token: $slack-token -
Add annotation in application yaml file to enable notifications for specific cd app. The following example uses the on-sync-succeeded trigger:
apiVersion: apps.hanzo.ai/v1alpha1 kind: Application metadata: annotations: notifications.apps.hanzo.ai/subscribe.on-sync-succeeded.slack: my_channel -
Annotation with more than one trigger, with multiple destinations and recipients
apiVersion: apps.hanzo.ai/v1alpha1 kind: Application metadata: annotations: notifications.apps.hanzo.ai/subscriptions: | - trigger: [on-scaling-replica-set, on-rollout-updated, on-rollout-step-completed] destinations: - service: slack recipients: [my-channel-1, my-channel-2] - service: email recipients: [recipient-1, recipient-2, recipient-3 ] - trigger: [on-rollout-aborted, on-analysis-run-failed, on-analysis-run-error] destinations: - service: slack recipients: [my-channel-21, my-channel-22]
Templates
Notification templates can be customized to leverage slack message blocks and attachments feature.
The message blocks and attachments can be specified in blocks and attachments string fields under slack field:
template.app-sync-status: |
message: |
Application {{.app.metadata.name}} sync is {{.app.status.sync.status}}.
Application details: {{.context.cdUrl}}/applications/{{.app.metadata.name}}.
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.cdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
}, {
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}]
}]
If you want to specify an icon and username for each message, you can specify values for username and icon in the slack field.
For icon you can specify emoji and image URL, just like in the service definition.
If you set username and icon in template, the values set in template will be used even if values are specified in the service definition.
template.app-sync-status: |
message: |
Application {{.app.metadata.name}} sync is {{.app.status.sync.status}}.
Application details: {{.context.cdUrl}}/applications/{{.app.metadata.name}}.
slack:
username: "testbot"
icon: https://example.com/image.png
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.cdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
}, {
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}]
}]
The messages can be aggregated to the slack threads by grouping key which can be specified in a groupingKey string field under slack field.
groupingKey is used across each template and works independently on each slack channel.
When multiple applications will be updated at the same time or frequently, the messages in slack channel can be easily read by aggregating with git commit hash, application name, etc.
Furthermore, the messages can be broadcast to the channel at the specific template by notifyBroadcast field.
template.app-sync-status: |
message: |
Application {{.app.metadata.name}} sync is {{.app.status.sync.status}}.
Application details: {{.context.cdUrl}}/applications/{{.app.metadata.name}}.
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.cdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
}, {
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}]
}]
# Aggregate the messages to the thread by git commit hash
groupingKey: "{{.app.status.sync.revision}}"
notifyBroadcast: false
template.app-sync-failed: |
message: |
Application {{.app.metadata.name}} sync is {{.app.status.sync.status}}.
Application details: {{.context.cdUrl}}/applications/{{.app.metadata.name}}.
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.cdUrl}}/applications/{{.app.metadata.name}}",
"color": "#ff0000",
"fields": [{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
}, {
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}]
}]
# Aggregate the messages to the thread by git commit hash
groupingKey: "{{.app.status.sync.revision}}"
notifyBroadcast: true
The message is sent according to the deliveryPolicy string field under the slack field. The available modes are Post (default), PostAndUpdate, and Update. The PostAndUpdate and Update settings require groupingKey to be set.


