Skip to content
59 changes: 50 additions & 9 deletions .github/workflows/gitops-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ on:
type: string
default: ''
kustomize_image_name:
description: 'Required when gitops_layout=kustomize. Image reference matched by `kustomize edit set image` (e.g. ghcr.io/lerianstudio/ungoliant-controller).'
description: 'Required when gitops_layout=kustomize, unless every image is covered by kustomize_image_mappings. Image reference matched by `kustomize edit set image` (e.g. ghcr.io/lerianstudio/ungoliant-controller). Also the fallback for artifacts absent from kustomize_image_mappings.'
type: string
default: ''
kustomize_image_mappings:
description: 'JSON object mapping artifact names to kustomize image references (e.g. {"ungoliant-api":"ghcr.io/lerianstudio/ungoliant-api"}). Use when one release publishes more than one image into the same kustomization.yaml — each downloaded artifact is named after the app that built it. Artifacts not listed fall back to kustomize_image_name. Kustomize layout only.'
type: string
default: ''
kustomize_environments:
Expand Down Expand Up @@ -267,14 +271,29 @@ jobs:
env:
KUSTOMIZE_BASE_PATH: ${{ inputs.kustomize_base_path }}
KUSTOMIZE_IMAGE_NAME: ${{ inputs.kustomize_image_name }}
KUSTOMIZE_IMAGE_MAPPINGS: ${{ inputs.kustomize_image_mappings }}
run: |
set -euo pipefail
if [[ -z "$KUSTOMIZE_BASE_PATH" ]]; then
echo "::error::gitops_layout=kustomize requires kustomize_base_path."
exit 1
fi
if [[ -z "$KUSTOMIZE_IMAGE_NAME" ]]; then
echo "::error::gitops_layout=kustomize requires kustomize_image_name."
# An empty or null mapping value would resolve to nothing and silently
# fall back to kustomize_image_name, writing the extra artifact's tag
# onto the primary image — the exact failure the mapping exists to
# prevent. Reject it instead of resolving it.
MAPPED_IMAGES=0
if [[ -n "$KUSTOMIZE_IMAGE_MAPPINGS" ]]; then
if ! jq -e 'type == "object" and all(.[]; type == "string" and length > 0)' >/dev/null 2>&1 <<< "$KUSTOMIZE_IMAGE_MAPPINGS"; then
echo "::error::kustomize_image_mappings must be a JSON object whose every value is a non-empty image reference string."
exit 1
fi
MAPPED_IMAGES=$(jq -r 'length' <<< "$KUSTOMIZE_IMAGE_MAPPINGS")
echo "kustomize_image_mappings:"
jq -r 'to_entries[] | " \(.key) -> \(.value)"' <<< "$KUSTOMIZE_IMAGE_MAPPINGS"
fi
if [[ -z "$KUSTOMIZE_IMAGE_NAME" && "$MAPPED_IMAGES" -eq 0 ]]; then
echo "::error::gitops_layout=kustomize requires kustomize_image_name or a non-empty kustomize_image_mappings."
exit 1
fi

Expand Down Expand Up @@ -469,6 +488,7 @@ jobs:
GITOPS_LAYOUT: ${{ inputs.gitops_layout }}
KUSTOMIZE_BASE_PATH: ${{ inputs.kustomize_base_path }}
KUSTOMIZE_IMAGE_NAME: ${{ inputs.kustomize_image_name }}
KUSTOMIZE_IMAGE_MAPPINGS: ${{ inputs.kustomize_image_mappings }}
KUSTOMIZE_ENVIRONMENTS: ${{ inputs.kustomize_environments }}
MANIFEST_FILE: shared-workflows/${{ inputs.deployment_matrix_file }}
UPDATE_SANDBOX: ${{ inputs.update_sandbox }}
Expand Down Expand Up @@ -677,16 +697,37 @@ jobs:
FILE_BEFORE_HASH=$(sha256sum "$KUSTOMIZATION_FILE" | cut -d' ' -f1)

# Apply each available artifact tag via `kustomize edit set image`.
# The artifact value (TAG) is the only thing that varies; we set the
# same image_name for every artifact since kustomize layouts typically
# have a single image per kustomization.yaml. If an app has multiple
# images, callers can extend kustomize_image_name later (out of scope here).
# Each artifact file is named after the app that built it
# (build.yml writes gitops-tags/<app>.tag), so that basename is the
# key used to resolve which image the tag belongs to:
# kustomize_image_mappings[<app>], falling back to
# kustomize_image_name when the app is not listed.
# A release publishing more than one image into the same
# kustomization.yaml maps every extra app explicitly; single-image
# callers leave the mapping empty and every artifact resolves to
# kustomize_image_name, exactly as before.
for artifact_file in .gitops-tags/*; do
[[ -f "$artifact_file" ]] || continue
TAG="$(cut -d= -f2 < "$artifact_file" | tr -d '[:space:]')"
[[ -n "$TAG" ]] || continue
echo " kustomize edit set image ${KUSTOMIZE_IMAGE_NAME}=${KUSTOMIZE_IMAGE_NAME}:${TAG}"
( cd "gitops/${TARGET_DIR}" && kustomize edit set image "${KUSTOMIZE_IMAGE_NAME}=${KUSTOMIZE_IMAGE_NAME}:${TAG}" )

ARTIFACT_APP="$(basename "$artifact_file")"
ARTIFACT_APP="${ARTIFACT_APP%.tag}"

IMAGE_NAME=""
if [[ -n "$KUSTOMIZE_IMAGE_MAPPINGS" ]]; then
IMAGE_NAME=$(jq -r --arg app "$ARTIFACT_APP" '.[$app] // empty' <<< "$KUSTOMIZE_IMAGE_MAPPINGS")
fi
if [[ -z "$IMAGE_NAME" ]]; then
IMAGE_NAME="$KUSTOMIZE_IMAGE_NAME"
fi
if [[ -z "$IMAGE_NAME" ]]; then
echo "::warning::No kustomize image resolved for artifact '${ARTIFACT_APP}' — not in kustomize_image_mappings and kustomize_image_name is empty. Skipping."
continue
fi

echo " ${ARTIFACT_APP}: kustomize edit set image ${IMAGE_NAME}=${IMAGE_NAME}:${TAG}"
( cd "gitops/${TARGET_DIR}" && kustomize edit set image "${IMAGE_NAME}=${IMAGE_NAME}:${TAG}" )
done

FILE_AFTER_HASH=$(sha256sum "$KUSTOMIZATION_FILE" | cut -d' ' -f1)
Expand Down
22 changes: 21 additions & 1 deletion .github/workflows/go-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ on:
description: 'Merge main into a prerelease branch (develop/release-candidate) before calculating its next version. Defaults to false (opt-in) — set to true to enable this pre-version-calculation sync, which can skip/block a release on those branches when the merge cannot complete directly. The post-release backmerge on main after a stable release is unaffected by this input either way.'
type: boolean
default: false
enable_release_announcement:
description: 'Announce the published release to the repository Slack channel after a successful release'
type: boolean
default: true
announcement_product_name:
description: 'Product name displayed in the announcement. Defaults to the repository name.'
type: string
default: ''
announcement_slack_channel:
description: 'Slack channel that receives the announcement. Defaults to the RELEASE_SLACK_CHANNEL repository variable; the announcement is skipped when both are empty.'
type: string
default: ''

# ----------------- Build (build.yml) -----------------
enable_dockerhub:
Expand Down Expand Up @@ -209,8 +221,12 @@ on:
description: 'Required when gitops_layout=kustomize. Path within the gitops repo to the kustomization folder (e.g. environments/anacleto/kustomize/ungoliant-controller). Supports ${SERVER} and ${ENV} placeholders.'
type: string
default: ''
kustomize_image_mappings:
description: 'JSON object mapping artifact names to kustomize image references (e.g. {"ungoliant-api":"ghcr.io/lerianstudio/ungoliant-api"}). Use when extra_builds groups publish additional images into the same kustomization.yaml — artifacts not listed fall back to kustomize_image_name. Requires gitops_artifact_pattern to also match the extra groups (the default only matches the repository name).'
type: string
default: ''
kustomize_image_name:
description: 'Required when gitops_layout=kustomize. Image reference matched by `kustomize edit set image` (e.g. ghcr.io/lerianstudio/ungoliant-controller).'
description: 'Required when gitops_layout=kustomize, unless every image is covered by kustomize_image_mappings. Image reference matched by `kustomize edit set image` (e.g. ghcr.io/lerianstudio/ungoliant-controller).'
type: string
default: ''
kustomize_environments:
Expand Down Expand Up @@ -383,6 +399,9 @@ jobs:
stable_releases_only: ${{ inputs.stable_releases_only }}
changelog_bot_ignore_list: ${{ inputs.changelog_bot_ignore_list }}
prerelease_backmerge_sync_enabled: ${{ inputs.prerelease_backmerge_sync_enabled }}
enable_release_announcement: ${{ inputs.enable_release_announcement }}
announcement_product_name: ${{ inputs.announcement_product_name }}
announcement_slack_channel: ${{ inputs.announcement_slack_channel }}
shared_paths: ${{ inputs.shared_paths }}
filter_paths: ${{ !inputs.release_single_app && inputs.filter_paths || '' }}
secrets: inherit
Expand Down Expand Up @@ -686,6 +705,7 @@ jobs:
gitops_layout: ${{ inputs.gitops_layout }}
kustomize_base_path: ${{ inputs.kustomize_base_path }}
kustomize_image_name: ${{ inputs.kustomize_image_name }}
kustomize_image_mappings: ${{ inputs.kustomize_image_mappings }}
kustomize_environments: ${{ inputs.kustomize_environments }}
kustomize_version: ${{ inputs.kustomize_version }}
argocd_app_name_template: ${{ inputs.argocd_app_name_template }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/go-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ jobs:
fetch-depth: 0

- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@27b0417c16317ca9a472a9a8092acce143b49c55 # v3.95.9
uses: trufflesecurity/trufflehog@6f3c981e7b77f235fd2702dd74af25fc4b72bf11 # v3.96.0
with:
path: ./
base: ${{ github.event.repository.default_branch }}
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/js-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ on:
description: 'Directory depth level to extract app name (e.g., 2 -> "apps/agent")'
type: string
default: '2'
enable_release_announcement:
description: 'Announce the published release to the repository Slack channel after a successful release'
type: boolean
default: true
announcement_product_name:
description: 'Product name displayed in the announcement. Defaults to the repository name.'
type: string
default: ''
announcement_slack_channel:
description: 'Slack channel that receives the announcement. Defaults to the RELEASE_SLACK_CHANNEL repository variable; the announcement is skipped when both are empty.'
type: string
default: ''

# ----------------- Build (typescript-build.yml) -----------------
enable_dockerhub:
Expand Down Expand Up @@ -298,6 +310,9 @@ jobs:
backmerge_mode: ${{ inputs.backmerge_mode }}
shared_paths: ${{ inputs.shared_paths }}
path_level: ${{ inputs.path_level }}
enable_release_announcement: ${{ inputs.enable_release_announcement }}
announcement_product_name: ${{ inputs.announcement_product_name }}
announcement_slack_channel: ${{ inputs.announcement_slack_channel }}
filter_paths: ${{ !inputs.release_single_app && inputs.filter_paths || '' }}
secrets: inherit

Expand Down
72 changes: 54 additions & 18 deletions .github/workflows/release-notification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ on:
required: false
type: string
default: ""
release_tag:
description: Release tag to announce. When empty, resolves from the release event or the latest release.
required: false
type: string
default: ""
discord_color:
description: Discord embed color (decimal)
required: false
Expand Down Expand Up @@ -130,8 +135,16 @@ jobs:
id: release
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
INPUT_TAG: ${{ inputs.release_tag }}
EVENT_TAG: ${{ github.event.release.tag_name }}
run: |
TAG='${{ github.event.release.tag_name }}'
TAG="$INPUT_TAG"
if [[ -n "$TAG" ]]; then
echo "Using release tag provided by the caller: $TAG"
fi
if [[ -z "$TAG" ]]; then
TAG="$EVENT_TAG"
fi
if [[ -z "$TAG" ]]; then
echo "No release event tag — falling back to gh release list"
TAG=$(gh release list --repo "$GITHUB_REPOSITORY" --limit 1 --json tagName --jq '.[0].tagName')
Expand All @@ -147,28 +160,51 @@ jobs:
if: ${{ inputs.dry_run }}
env:
RESOLVED_RUNNER: ${{ vars.GENERAL_RUNNERS || 'blacksmith-4vcpu-ubuntu-2404' }}
PRODUCT_NAME: ${{ inputs.product_name }}
RELEASE_TAG: ${{ steps.release.outputs.tag }}
DISCORD_COLOR: ${{ inputs.discord_color }}
DISCORD_USERNAME: ${{ inputs.discord_username }}
DISCORD_CONTENT: ${{ inputs.discord_content }}
SKIP_BETA_DISCORD: ${{ inputs.skip_beta_discord }}
SLACK_CHANNEL: ${{ inputs.slack_channel }}
SLACK_COLOR: ${{ inputs.slack_color }}
SLACK_ICON_EMOJI: ${{ inputs.slack_icon_emoji }}
run: |
ENABLE_DISCORD="${{ env.DISCORD_WEBHOOK_URL != '' && 'true' || 'false' }}"
ENABLE_SLACK="${{ env.SLACK_WEBHOOK_URL != '' && inputs.slack_channel != '' && 'true' || 'false' }}"
DISCORD_STATE="not set"
ENABLE_DISCORD=false
if [[ -n "$DISCORD_WEBHOOK_URL" ]]; then
DISCORD_STATE="configured"
ENABLE_DISCORD=true
fi

SLACK_STATE="not set"
ENABLE_SLACK=false
if [[ -n "$SLACK_WEBHOOK_URL" ]]; then
SLACK_STATE="configured"
if [[ -n "$SLACK_CHANNEL" ]]; then
ENABLE_SLACK=true
fi
fi

echo "::notice::DRY RUN — no notifications will be sent"
echo " runner : $RESOLVED_RUNNER"
echo " product_name : ${{ inputs.product_name }}"
echo " release_tag : ${{ steps.release.outputs.tag }}"
echo " discord_webhook : ${{ env.DISCORD_WEBHOOK_URL != '' && 'configured' || 'not set' }}"
echo " discord_color : ${{ inputs.discord_color }}"
echo " discord_username : ${{ inputs.discord_username }}"
echo " discord_content : ${{ inputs.discord_content }}"
echo " skip_beta_discord: ${{ inputs.skip_beta_discord }}"
echo " enable_discord : ${ENABLE_DISCORD}"
echo " slack_webhook : ${{ env.SLACK_WEBHOOK_URL != '' && 'configured' || 'not set' }}"
echo " slack_channel : ${{ inputs.slack_channel }}"
echo " slack_color : ${{ inputs.slack_color }}"
echo " slack_icon_emoji : ${{ inputs.slack_icon_emoji }}"
echo " enable_slack : ${ENABLE_SLACK}"
echo " product_name : $PRODUCT_NAME"
echo " release_tag : $RELEASE_TAG"
echo " discord_webhook : $DISCORD_STATE"
echo " discord_color : $DISCORD_COLOR"
echo " discord_username : $DISCORD_USERNAME"
echo " discord_content : $DISCORD_CONTENT"
echo " skip_beta_discord: $SKIP_BETA_DISCORD"
echo " enable_discord : $ENABLE_DISCORD"
echo " slack_webhook : $SLACK_STATE"
echo " slack_channel : $SLACK_CHANNEL"
echo " slack_color : $SLACK_COLOR"
echo " slack_icon_emoji : $SLACK_ICON_EMOJI"
echo " enable_slack : $ENABLE_SLACK"

- name: Discord notification
if: ${{ env.DISCORD_WEBHOOK_URL != '' }}
uses: LerianStudio/github-actions-shared-workflows/src/notify/discord-release@v1.18.0
uses: LerianStudio/github-actions-shared-workflows/src/notify/discord-release@v1
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
release-tag: ${{ steps.release.outputs.tag }}
Expand All @@ -180,7 +216,7 @@ jobs:

- name: Slack notification
if: ${{ env.SLACK_WEBHOOK_URL != '' && inputs.slack_channel != '' }}
uses: LerianStudio/github-actions-shared-workflows/src/notify/slack-release@v1.18.0
uses: LerianStudio/github-actions-shared-workflows/src/notify/slack-release@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
channel: ${{ inputs.slack_channel }}
Expand Down
45 changes: 45 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ on:
type: string
default: ''

# ----------------- Release Announcement -----------------
enable_release_announcement:
description: 'Announce the published release to the repository Slack channel after a successful release'
required: false
type: boolean
default: true
announcement_product_name:
description: 'Product name displayed in the announcement. Defaults to the repository name.'
required: false
type: string
default: ''
announcement_slack_channel:
description: 'Slack channel that receives the announcement. Defaults to the RELEASE_SLACK_CHANNEL repository variable; the announcement is skipped when both are empty.'
required: false
type: string
default: ''

# ----------------- Backmerge -----------------
backmerge_enabled:
description: 'Backmerge the release branch into the target branch after a successful release'
Expand Down Expand Up @@ -606,6 +623,34 @@ jobs:
echo "ℹ️ No stable vX.Y.Z release found — leaving Latest untouched"
fi

# ----------------- Release Announcement -----------------
# Announces the published release to the channel owned by the calling
# repository. Routing is per-repo: the channel comes from
# announcement_slack_channel or the RELEASE_SLACK_CHANNEL variable, and the
# webhook from the RELEASE_WEBHOOK_URL (or legacy
# RELEASE_WEBHOOK_NOTIFICATION_URL) repository secret. Discord is not wired
# here because SethCohen/github-releases-to-discord reads the `release` event
# payload, which is absent on the push event that drives this workflow —
# Discord announcements stay on release-notification.yml with on: release.
announce_release:
name: Announce Release
needs: [publish_release, publish_release_status, generate_changelog, backmerge, update_major_tag, enforce_latest]
if: >-
always() &&
inputs.enable_release_announcement &&
needs.publish_release_status.outputs.release_published == 'true' &&
(inputs.announcement_slack_channel != '' || vars.RELEASE_SLACK_CHANNEL != '')
uses: ./.github/workflows/release-notification.yml
with:
product_name: ${{ inputs.announcement_product_name != '' && inputs.announcement_product_name || github.event.repository.name }}
slack_channel: ${{ inputs.announcement_slack_channel != '' && inputs.announcement_slack_channel || vars.RELEASE_SLACK_CHANNEL }}
release_tag: ${{ needs.publish_release_status.outputs.release_git_tag }}
dry_run: ${{ inputs.dry_run }}
secrets:
APP_ID: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID }}
APP_PRIVATE_KEY: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY }}
SLACK_WEBHOOK_URL: ${{ secrets.RELEASE_WEBHOOK_URL || secrets.RELEASE_WEBHOOK_NOTIFICATION_URL }}

# Slack notification
notify:
name: Notify
Expand Down
Loading
Loading