Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Default ownership
Comment thread
rguichard marked this conversation as resolved.
* @morpho-org/platform-engineers @0x666c6f

# Helm charts migrated from morpho-infra-helm (2026-06-17)
/helm/ @morpho-org/platform-engineers @morpho-org/api-engineers @0x666c6f
/validate-helm-charts.sh @morpho-org/platform-engineers @0x666c6f
/setup-hooks.sh @morpho-org/platform-engineers @0x666c6f
154 changes: 154 additions & 0 deletions .github/workflows/helm-charts-validation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
name: Helm Chart Validation
Comment thread
rguichard marked this conversation as resolved.

on:
push:
branches: [morpho-main]
paths:
- 'helm/**'
pull_request:
branches: [morpho-main]
paths:
- 'helm/**'

jobs:
# =============================================================================
# Job 1: Discover all charts to validate
# =============================================================================
discover-charts:
name: Discover Charts
runs-on: ubuntu-latest
outputs:
charts: ${{ steps.find.outputs.charts }}
chart-count: ${{ steps.find.outputs.count }}
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
Comment thread
rguichard marked this conversation as resolved.
with:
persist-credentials: false

- name: Find all charts
id: find
run: |
# Find all Chart.yaml files and get their directories
charts=$(find helm/charts helm/environments -name Chart.yaml -type f -exec dirname {} \; | sort | jq -R -s -c 'split("\n") | map(select(length > 0))')
count=$(echo "$charts" | jq 'length')
echo "Found $count charts to validate"
echo "charts=$charts" >> $GITHUB_OUTPUT
echo "count=$count" >> $GITHUB_OUTPUT

# =============================================================================
# Job 2: Validate each chart in parallel
# =============================================================================
validate:
name: Validate
needs: discover-charts
runs-on: ubuntu-latest
strategy:
fail-fast: false # Continue validating other charts if one fails
matrix:
chart: ${{ fromJson(needs.discover-charts.outputs.charts) }}
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Set up Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
with:
version: v3.16.4

- name: Install kubeconform
run: |
KUBECONFORM_VERSION=v0.6.1
KUBECONFORM_SHA256=7f84c1e4109fb72f151da41bdffea05e1e37591f179de51adc048c83533ad215
curl -fsSLo /tmp/kubeconform.tar.gz "https://github.com/yannh/kubeconform/releases/download/${KUBECONFORM_VERSION}/kubeconform-linux-amd64.tar.gz"
echo "${KUBECONFORM_SHA256} /tmp/kubeconform.tar.gz" | sha256sum -c -
tar xzf /tmp/kubeconform.tar.gz
sudo mv kubeconform /usr/local/bin/

- name: Add Helm repositories
run: |
helm repo add bitnami https://charts.bitnami.com/bitnami 2>/dev/null || true
helm repo add cnpg https://cloudnative-pg.github.io/charts 2>/dev/null || true
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts 2>/dev/null || true
helm repo add haproxytech https://haproxytech.github.io/helm-charts 2>/dev/null || true
helm repo add dandydeveloper https://dandydeveloper.github.io/charts 2>/dev/null || true
helm repo update

- name: Get chart info
id: chart-info
run: |
chart_path="${{ matrix.chart }}"
chart_name=$(basename "$chart_path")
echo "name=$chart_name" >> $GITHUB_OUTPUT
echo "path=$chart_path" >> $GITHUB_OUTPUT

# Check if chart should skip kubeconform validation
skip_validation="false"
case "$chart_name" in
*-db|*database*|*postgres*)
skip_validation="true"
;;
esac
echo "skip-kubeconform=$skip_validation" >> $GITHUB_OUTPUT

- name: Helm lint
run: |
echo "::group::Helm lint ${{ steps.chart-info.outputs.name }}"
helm lint "${{ matrix.chart }}"
echo "::endgroup::"

- name: Update dependencies
run: |
cd "${{ matrix.chart }}"
if grep -q "dependencies:" Chart.yaml 2>/dev/null; then
echo "::group::Updating dependencies for ${{ steps.chart-info.outputs.name }}"
helm dependency update
echo "::endgroup::"
fi

- name: Kubeconform validation
if: steps.chart-info.outputs.skip-kubeconform != 'true'
run: |
CUSTOM_RESOURCES="PodMonitor,ServiceMonitor,AlertManager,Prometheus,PrometheusRule,Cluster,ScheduledBackup,ImageCatalog,IngressRoute,Certificate,CronJob,EventSource,EventBus,Sensor"

echo "::group::Kubeconform validation for ${{ steps.chart-info.outputs.name }}"
cd "${{ matrix.chart }}"
helm template . | kubeconform --ignore-missing-schemas --summary --skip "$CUSTOM_RESOURCES"
Comment thread
rguichard marked this conversation as resolved.
echo "::endgroup::"

- name: Skip notice
if: steps.chart-info.outputs.skip-kubeconform == 'true'
run: |
echo "::notice::Skipping kubeconform validation for ${{ steps.chart-info.outputs.name }} (database chart)"

# =============================================================================
# Job 3: Summary (ensures all validations completed)
# =============================================================================
validation-summary:
name: Validation Summary
needs: [discover-charts, validate]
runs-on: ubuntu-latest
if: always()
steps:
- name: Check validation results
run: |
if [ "${{ needs.discover-charts.result }}" == "failure" ]; then
echo "::error::Chart discovery failed"
exit 1
elif [ "${{ needs.discover-charts.result }}" == "cancelled" ]; then
echo "::warning::Chart discovery was cancelled"
exit 1
elif [ "${{ needs.validate.result }}" == "failure" ]; then
echo "::error::Some chart validations failed"
exit 1
elif [ "${{ needs.validate.result }}" == "cancelled" ]; then
echo "::warning::Validation was cancelled"
exit 1
elif [ "${{ needs.validate.result }}" == "skipped" ]; then
echo "::warning::Validation was skipped unexpectedly"
exit 1
else
echo "::notice::All ${{ needs.discover-charts.outputs.chart-count }} charts validated successfully"
fi
63 changes: 63 additions & 0 deletions .github/workflows/only-values-yaml-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Required status check that scopes the morpho-infra-deployer bot's review
# bypass to values.yaml-only changes: it is skipped unless the deployer bot
# authored the PR or triggered the run, and fails only when such a PR touches
# anything other than values.yaml.
name: only-values-yaml-guard

on:
pull_request:
branches: [morpho-main]
merge_group:

permissions:
contents: read
pull-requests: read

jobs:
guard:
name: values-yaml-guard
runs-on: ubuntu-latest
# Skipped for non-bot PRs: a skipped job SATISFIES required status checks,
# so human PRs merge normally without spinning up a runner. Covers the bot
# both as PR author and as pusher on someone else's PR, matching on the
# immutable user id (not the login) so an app rename cannot silently turn
# the guard off while the bot keeps its review-bypass.
if: github.event.pull_request.user.id == 187494245 || github.actor_id == 187494245 # morpho-infra-deployer[bot]
steps:
- name: Enforce values.yaml-only changes for the deployer bot
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
MERGE_GROUP_HEAD_REF: ${{ github.event.merge_group.head_ref }}
REPO: ${{ github.repository }}
run: |
# merge_group events carry no pull_request.number; extract it from
# the head ref (refs/heads/gh-readonly-queue/<base>/pr-<N>-<sha>).
if [[ -z "$PR_NUMBER" && -n "$MERGE_GROUP_HEAD_REF" ]]; then
PR_NUMBER=$(echo "$MERGE_GROUP_HEAD_REF" | grep -oE '/pr-[0-9]+' | grep -oE '[0-9]+')
fi

# Include previous_filename so a rename cannot smuggle a non-values
# path out of sight (the files API only reports the new path).
if ! mapfile -t CHANGED < <(gh api --paginate "repos/${REPO}/pulls/${PR_NUMBER}/files" --jq '.[] | .filename, (.previous_filename // empty)'); then
echo "❌ Failed to fetch changed files from the GitHub API."
exit 1
fi
echo "Files changed by the deployer bot:"
printf ' %s\n' "${CHANGED[@]}"

OFFENDING=()
for file in "${CHANGED[@]}"; do
if [[ "$file" != "values.yaml" && "$file" != *"/values.yaml" ]]; then
OFFENDING+=("$file")
fi
done

if (( ${#OFFENDING[@]} > 0 )); then
echo "❌ Deployer bot PR modifies files other than values.yaml:"
printf ' %s\n' "${OFFENDING[@]}"
echo "The bot may only bypass review for values.yaml-only changes."
exit 1
fi

echo "✅ Deployer bot PR only modifies values.yaml files."
49 changes: 49 additions & 0 deletions .github/workflows/slack-pr-notification.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Slack PR Notification

on:
pull_request:
types: [closed]
branches: [morpho-main]

jobs:
notify-slack:
if: github.event.pull_request.merged == true && github.event.pull_request.user.login != 'github-actions[bot]' && github.event.pull_request.user.login != 'morpho-infra-deployer[bot]'
runs-on: ubuntu-latest

steps:
- name: Send Slack notification
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"text": ":rocket: PR merged to Production",
"attachments": [
{
"color": "#ff6b6b",
"fallback": "PR #${{ github.event.pull_request.number }} merged: ${{ github.event.pull_request.title }}",
"title": ":white_check_mark: PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}",
"title_link": "${{ github.event.pull_request.html_url }}",
"fields": [
{
"title": ":busts_in_silhouette: Author",
"value": "${{ github.event.pull_request.user.login }}",
"short": true
},
{
"title": ":earth_americas: Environment",
"value": ":red_circle: Production (morpho-main)",
"short": true
},
{
"title": ":arrow_right: Branch",
"value": "`${{ github.event.pull_request.head.ref }}` → `${{ github.event.pull_request.base.ref }}`",
"short": false
}
],
"footer": ":octopus: ${{ github.repository }}",
"footer_icon": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
}
]
}
Loading
Loading