Deploy External services of mosip using Helmsman #363
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy External services of mosip using Helmsman | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| profile: | |
| description: "Deployment profile to use" | |
| required: true | |
| default: "mosip-platform-java11" | |
| type: choice | |
| options: | |
| - mosip-platform-java11 | |
| - mosip-platform-java21 | |
| - esignet | |
| mode: | |
| description: "Choose Helmsman mode: dry-run or apply" | |
| required: true | |
| default: "dry-run" | |
| type: choice | |
| options: | |
| - dry-run | |
| - apply | |
| domain_name: | |
| description: "Domain name for this environment (e.g. example.xyz.net)" | |
| required: false | |
| type: string | |
| env_name: | |
| description: "Environment name (e.g. sandbox, dev, staging)" | |
| required: false | |
| type: string | |
| slack_channel_name: | |
| description: "Slack channel name for alerting (e.g. #mosip-alerts)" | |
| required: false | |
| type: string | |
| slack_webhook_url: | |
| description: "Slack webhook URL for alerting (configure as secrets.SLACK_WEBHOOK_URL in the environment)" | |
| required: false | |
| type: string | |
| clusterid: | |
| description: "Rancher cluster ID for rancher-monitoring (e.g. c-xxxxx)" | |
| required: false | |
| type: string | |
| push: | |
| paths: | |
| - Helmsman/dsf/**/prereq-dsf.yaml | |
| - Helmsman/dsf/**/external-dsf.yaml | |
| permissions: | |
| actions: write | |
| contents: read | |
| jobs: | |
| validate-inputs: | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: ${{ github.ref_name }} | |
| steps: | |
| - name: Validate required variables | |
| run: | | |
| errors=() | |
| DOMAIN="${{ github.event.inputs.domain_name || vars.DOMAIN_NAME }}" | |
| ENV="${{ github.event.inputs.env_name || vars.ENV_NAME }}" | |
| CLUSTER="${{ github.event.inputs.clusterid || vars.CLUSTER_ID }}" | |
| SLACK_CH="${{ github.event.inputs.slack_channel_name || vars.SLACK_CHANNEL_NAME }}" | |
| [ -z "$DOMAIN" ] && errors+=("domain_name is empty — set vars.DOMAIN_NAME under Environment '${{ github.ref_name }}'") | |
| [ -z "$ENV" ] && errors+=("env_name is empty — set vars.ENV_NAME under Environment '${{ github.ref_name }}'") | |
| [ -z "$CLUSTER" ] && errors+=("clusterid is empty — set vars.CLUSTER_ID under Environment '${{ github.ref_name }}'") | |
| [ -z "$SLACK_CH" ] && errors+=("slack_channel_name is empty — set vars.SLACK_CHANNEL_NAME under Environment '${{ github.ref_name }}'") | |
| if [ ${#errors[@]} -gt 0 ]; then | |
| echo "❌ Required variables missing:" | |
| printf ' - %s\n' "${errors[@]}" | |
| echo "Go to: Settings → Environments → ${{ github.ref_name }} → Variables" | |
| exit 1 | |
| fi | |
| echo "✓ domain_name = $DOMAIN" | |
| echo "✓ env_name = $ENV" | |
| echo "✓ clusterid = $CLUSTER" | |
| echo "✓ slack_channel_name = $SLACK_CH" | |
| - name: Persist workflow inputs as environment variables | |
| if: github.event_name == 'workflow_dispatch' | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_INFRA_PAT }} | |
| run: | | |
| REPO="${{ github.repository }}" | |
| ENVIRONMENT="${{ github.ref_name }}" | |
| save_var() { | |
| local name=$1 value=$2 | |
| if [ -z "$value" ]; then echo "⏭ Skipping $name — no input provided, existing value preserved"; return; fi | |
| STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X PATCH \ | |
| -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/$REPO/environments/$ENVIRONMENT/variables/$name" \ | |
| -d "{\"name\":\"$name\",\"value\":\"$value\"}") | |
| if [ "$STATUS" = "404" ]; then | |
| curl -f -s -X POST -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/$REPO/environments/$ENVIRONMENT/variables" \ | |
| -d "{\"name\":\"$name\",\"value\":\"$value\"}" && echo "✓ Created $name" | |
| elif [ "$STATUS" = "200" ] || [ "$STATUS" = "204" ]; then | |
| echo "✓ Updated $name" | |
| else | |
| echo "✗ Failed to save $name (HTTP $STATUS)"; exit 1 | |
| fi | |
| } | |
| save_var "DOMAIN_NAME" "${{ github.event.inputs.domain_name }}" | |
| save_var "ENV_NAME" "${{ github.event.inputs.env_name }}" | |
| save_var "CLUSTER_ID" "${{ github.event.inputs.clusterid }}" | |
| save_var "SLACK_CHANNEL_NAME" "${{ github.event.inputs.slack_channel_name }}" | |
| set-matrix: | |
| runs-on: ubuntu-latest | |
| needs: validate-inputs | |
| outputs: | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| profile: ${{ steps.set-matrix.outputs.PROFILE }} | |
| steps: | |
| - name: Checkout repository with full history | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Get full commit history | |
| - name: Generate workflow matrix | |
| id: set-matrix | |
| run: | | |
| matrix_json='{"include":[]}' | |
| if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then | |
| PROFILE="${{ github.event.inputs.profile }}" | |
| matrix_json="{\"include\":[{\"dsf_files\":\"${PROFILE}/prereq-dsf.yaml\",\"wg_conf\":\"wg0\"},{\"dsf_files\":\"${PROFILE}/external-dsf.yaml\",\"wg_conf\":\"wg1\"}]}" | |
| else | |
| # Handle different event types properly | |
| if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then | |
| base_sha="${{ github.event.before }}" | |
| head_sha="${{ github.sha }}" | |
| elif [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then | |
| base_sha="${{ github.event.pull_request.base.sha }}" | |
| head_sha="${{ github.event.pull_request.head.sha }}" | |
| fi | |
| # Get changed files safely; fall back to HEAD~1 if base_sha is unreachable | |
| changed_files=$(git diff --name-only "$base_sha" "$head_sha" -- 'Helmsman/dsf/' 2>/dev/null || \ | |
| git diff --name-only HEAD~1 HEAD -- 'Helmsman/dsf/' 2>/dev/null || echo "") | |
| entries=() | |
| # Dynamically detect changed profiles from file paths | |
| changed_profiles=$(echo "$changed_files" | grep 'Helmsman/dsf/' | sed 's|Helmsman/dsf/\([^/]*\)/.*|\1|' | sort -u) | |
| # Derive PROFILE from the changed files; take the first detected profile for downstream dispatch | |
| PROFILE=$(echo "$changed_profiles" | head -n 1) | |
| if [[ -z "$PROFILE" ]]; then | |
| echo "Error: could not detect profile from changed DSF files." | |
| exit 1 | |
| fi | |
| for profile_dir in $changed_profiles; do | |
| if echo "$changed_files" | grep -q "Helmsman/dsf/${profile_dir}/prereq-dsf.yaml"; then | |
| entries+=("{\"dsf_files\":\"${profile_dir}/prereq-dsf.yaml\",\"wg_conf\":\"wg0\"}") | |
| fi | |
| if echo "$changed_files" | grep -q "Helmsman/dsf/${profile_dir}/external-dsf.yaml"; then | |
| entries+=("{\"dsf_files\":\"${profile_dir}/external-dsf.yaml\",\"wg_conf\":\"wg1\"}") | |
| fi | |
| done | |
| if [ ${#entries[@]} -gt 0 ]; then | |
| matrix_json="{\"include\":[$(IFS=,; echo "${entries[*]}")]}" | |
| fi | |
| fi | |
| echo "Using profile: $PROFILE" | |
| echo "matrix=$matrix_json" >> $GITHUB_OUTPUT | |
| echo "PROFILE=$PROFILE" >> $GITHUB_OUTPUT | |
| deploy: | |
| runs-on: ubuntu-latest | |
| needs: set-matrix | |
| strategy: | |
| matrix: ${{ fromJson(needs.set-matrix.outputs.matrix) }} | |
| environment: | |
| name: ${{ github.ref_name }} # Dynamic environment based on branch name | |
| env: | |
| KUBECONFIG: ${{ github.workspace }}/.kube/config | |
| PATH: ${{ github.workspace }}/istio-1.22.0/bin:${{ github.workspace }}/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin | |
| ISTIOCTL_PATH: ${{ github.workspace }}/istio-1.22.0/bin/istioctl | |
| KUBECTL_PATH: ${{ github.workspace }}/.local/bin/kubectl | |
| domain_name: ${{ github.event.inputs.domain_name || vars.DOMAIN_NAME }} | |
| env_name: ${{ github.event.inputs.env_name || vars.ENV_NAME }} | |
| slack_channel_name: ${{ github.event.inputs.slack_channel_name || vars.SLACK_CHANNEL_NAME }} | |
| SLACK_WEBHOOK_URL: ${{ github.event.inputs.slack_webhook_url || secrets.SLACK_WEBHOOK_URL }} | |
| CLUSTER_ID: ${{ github.event.inputs.clusterid || vars.CLUSTER_ID }} | |
| PREREG_CAPTCHA_SITE_KEY: ${{ secrets.PREREG_CAPTCHA_SITE_KEY }} | |
| ESIGNET_CAPTCHA_SITE_KEY: ${{ secrets.ESIGNET_CAPTCHA_SITE_KEY }} | |
| ESIGNET_CAPTCHA_SECRET_KEY: ${{ secrets.ESIGNET_CAPTCHA_SECRET_KEY }} | |
| PREREG_CAPTCHA_SECRET_KEY: ${{ secrets.PREREG_CAPTCHA_SECRET_KEY }} | |
| ADMIN_CAPTCHA_SITE_KEY: ${{ secrets.ADMIN_CAPTCHA_SITE_KEY }} | |
| ADMIN_CAPTCHA_SECRET_KEY: ${{ secrets.ADMIN_CAPTCHA_SECRET_KEY }} | |
| RESIDENT_CAPTCHA_SITE_KEY: ${{ secrets.RESIDENT_CAPTCHA_SITE_KEY }} | |
| RESIDENT_CAPTCHA_SECRET_KEY: ${{ secrets.RESIDENT_CAPTCHA_SECRET_KEY }} | |
| steps: | |
| - name: Show matrix values | |
| run: | | |
| echo "Processing ${{ matrix.dsf_files }}" | |
| echo "Using WireGuard config: ${{ matrix.wg_conf }}" | |
| pwd | |
| - name: Checkout repository | |
| uses: actions/checkout@v2 | |
| - name: Mask sensitive secrets | |
| run: | | |
| MINIO_ROOT_PASSWORD=$(kubectl -n minio get secret minio -o jsonpath='{.data.root-password}' 2>/dev/null | base64 -d || true) | |
| if [ -n "$MINIO_ROOT_PASSWORD" ]; then echo "::add-mask::$MINIO_ROOT_PASSWORD"; fi | |
| echo "::add-mask::${{ secrets.ESIGNET_CAPTCHA_SITE_KEY }}" | |
| echo "::add-mask::${{ secrets.ESIGNET_CAPTCHA_SECRET_KEY }}" | |
| echo "::add-mask::${{ secrets.PREREG_CAPTCHA_SITE_KEY }}" | |
| echo "::add-mask::${{ secrets.PREREG_CAPTCHA_SECRET_KEY }}" | |
| echo "::add-mask::${{ secrets.ADMIN_CAPTCHA_SITE_KEY }}" | |
| echo "::add-mask::${{ secrets.ADMIN_CAPTCHA_SECRET_KEY }}" | |
| echo "::add-mask::${{ secrets.RESIDENT_CAPTCHA_SITE_KEY }}" | |
| echo "::add-mask::${{ secrets.RESIDENT_CAPTCHA_SECRET_KEY }}" | |
| echo "::add-mask::${{ secrets.ESIGNET_CAPTCHA_SITE_KEY }}" | |
| echo "::add-mask::${{ secrets.ESIGNET_CAPTCHA_SECRET_KEY }}" | |
| - name: Setup kubectl, istioctl and kubeconfig | |
| env: | |
| KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }} | |
| run: | | |
| # Create directories | |
| mkdir -p ${{ github.workspace }}/.local/bin | |
| mkdir -p ${{ github.workspace }}/.kube | |
| # Install kubectl | |
| curl -LO https://dl.k8s.io/release/v1.31.3/bin/linux/amd64/kubectl | |
| chmod +x kubectl | |
| mv ./kubectl ${{ github.workspace }}/.local/bin/kubectl | |
| # Install istioctl | |
| curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.22.0 TARGET_ARCH=x86_64 sh - | |
| # Setup kubeconfig | |
| echo "$KUBECONFIG_CONTENT" > ${{ github.workspace }}/.kube/config | |
| chmod 400 ${{ github.workspace }}/.kube/config | |
| # Add tools to GitHub PATH for subsequent steps | |
| echo "${{ github.workspace }}/.local/bin" >> $GITHUB_PATH | |
| echo "${{ github.workspace }}/istio-1.22.0/bin" >> $GITHUB_PATH | |
| # Export environment variables for immediate use | |
| echo "KUBECTL_PATH=${{ github.workspace }}/.local/bin/kubectl" >> $GITHUB_ENV | |
| echo "ISTIOCTL_PATH=${{ github.workspace }}/istio-1.22.0/bin/istioctl" >> $GITHUB_ENV | |
| # Verify installations | |
| ${{ github.workspace }}/.local/bin/kubectl version --client | |
| ${{ github.workspace }}/istio-1.22.0/bin/istioctl version --remote=false | |
| ${{ github.workspace }}/.local/bin/kubectl config view | |
| - name: Set Default Mode | |
| run: | | |
| if [ -z "${{ github.event.inputs.mode }}" ]; then | |
| echo "HELMSMAN_MODE=apply" >> $GITHUB_ENV | |
| else | |
| echo "HELMSMAN_MODE=${{ github.event.inputs.mode }}" >> $GITHUB_ENV | |
| fi | |
| - name: Setup ufw firewall | |
| run: | | |
| sudo ufw enable | |
| sudo ufw allow ssh | |
| sudo ufw allow 51820/udp | |
| sudo ufw status | |
| - name: Install WireGuard | |
| run: sudo apt-get install -y wireguard | |
| - name: Configure WireGuard | |
| run: | | |
| echo "${{ secrets.CLUSTER_WIREGUARD_WG0 }}" | sudo tee /etc/wireguard/wg0.conf | |
| echo "${{ secrets.CLUSTER_WIREGUARD_WG1 }}" | sudo tee /etc/wireguard/wg1.conf | |
| - name: Start WireGuard | |
| run: | | |
| sudo chmod 600 /etc/wireguard/${{ matrix.wg_conf }}.conf | |
| sudo chmod 700 /etc/wireguard/ | |
| sudo chmod 644 /lib/systemd/system/wg-quick@.service | |
| sudo systemctl daemon-reload | |
| sudo wg-quick up ${{ matrix.wg_conf }} | |
| sudo wg show ${{ matrix.wg_conf }} | |
| - name: Setup Helm | |
| run: | | |
| curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | |
| sudo chmod 700 get_helm.sh | |
| sudo ./get_helm.sh | |
| helm version --client | |
| - name: Install Helmsman | |
| run: | | |
| curl -L https://github.com/Praqma/helmsman/releases/download/v3.17.1/helmsman_3.17.1_linux_amd64.tar.gz -o helmsman.tar.gz | |
| tar xzf helmsman.tar.gz | |
| sudo mv helmsman /usr/local/bin | |
| # Verify all tools are available | |
| echo "All tools verification:" | |
| echo "kubectl: $(which kubectl)" | |
| echo "istioctl: $(which istioctl)" | |
| echo "helmsman: $(which helmsman)" | |
| kubectl version --client | |
| istioctl version --remote=false | |
| helmsman -v | |
| - name: Verify cluster access | |
| run: | | |
| # Verify tools are accessible | |
| echo "Verifying tool availability:" | |
| echo "kubectl path: $(which kubectl)" | |
| echo "istioctl path: $(which istioctl)" | |
| echo "KUBECONFIG: $KUBECONFIG" | |
| # Test kubectl functionality | |
| kubectl version --client | |
| kubectl get nodes | |
| kubectl cluster-info | |
| # Test istioctl functionality | |
| istioctl version --remote=false | |
| - name: Check if external-dsf label is completed only for push | |
| if: github.event_name == 'push' | |
| run: | | |
| STATUS=$(kubectl get namespace default -o jsonpath='{.metadata.labels.external-dsf}') | |
| if [[ "$STATUS" != "completed" ]]; then | |
| echo "❌ External DSF not completed." | |
| exit 1 | |
| fi | |
| - name: Show deployment variables | |
| run: | | |
| echo "domain_name = $domain_name" | |
| echo "env_name = $env_name" | |
| echo "slack_channel_name = $slack_channel_name" | |
| echo "CLUSTER_ID = $CLUSTER_ID" | |
| - name: Initiate helmsman to apply the DSF configurations. | |
| run: | | |
| export HOME="${{ github.workspace }}" | |
| export WORKDIR="$HOME/Helmsman" | |
| # Verify tools are available | |
| echo "Using kubectl: $(which kubectl)" | |
| echo "Using istioctl: $(which istioctl)" | |
| echo "Using kubeconfig: $KUBECONFIG" | |
| # Run helmsman with the determined mode | |
| helmsman --${HELMSMAN_MODE} -f $WORKDIR/dsf/${{ matrix.dsf_files }} | |
| - name: Health Check External Pods | |
| run: | | |
| set -e | |
| NAMESPACES=( cattle-logging-system istio-system istio-operator httpbin keycloak kafka minio softhsm clamav activemq landing-page) | |
| for NS in "${NAMESPACES[@]}"; do | |
| echo "Checking health for namespace: $NS" | |
| for i in {1..30}; do | |
| UNREADY=$(kubectl get pods -n "$NS" --no-headers | grep -vE 'Running|Completed' | wc -l) | |
| if [ "$UNREADY" -eq 0 ]; then | |
| break | |
| fi | |
| sleep 10 | |
| done | |
| UNREADY_FINAL=$(kubectl get pods -n "$NS" --no-headers | grep -vE 'Running|Completed' | wc -l) | |
| if [ "$UNREADY_FINAL" -ne 0 ]; then | |
| echo "Pods in $NS not healthy" | |
| exit 1 | |
| fi | |
| done | |
| - name: Mark External DSF Completed | |
| run: | | |
| kubectl label namespace default external-dsf=completed --overwrite | |
| workflow-caller: | |
| runs-on: ubuntu-latest | |
| needs: [deploy, set-matrix] | |
| # Only trigger MOSIP workflow for mosip-platform profiles, not for standalone esignet | |
| if: ${{ startsWith(needs.set-matrix.outputs.profile, 'mosip-platform-') }} | |
| steps: | |
| - name: Trigger helmsman mosip workflow via API | |
| env: | |
| GITHUB_REPO: ${{ github.repository }} | |
| GITHUB_TOKEN: ${{ secrets.GH_INFRA_PAT }} | |
| BRANCH: ${{ github.ref_name }} | |
| MODE: ${{ github.event.inputs.mode }} | |
| DOMAIN_NAME: ${{ github.event.inputs.domain_name || vars.DOMAIN_NAME }} | |
| ENV_NAME: ${{ github.event.inputs.env_name || vars.ENV_NAME }} | |
| run: | | |
| curl -X POST \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| https://api.github.com/repos/$GITHUB_REPO/actions/workflows/helmsman_mosip.yml/dispatches \ | |
| -d '{"ref":"'"$BRANCH"'","inputs":{"mode":"'"${MODE:-apply}"'","profile":"'"${{ needs.set-matrix.outputs.profile }}"'","domain_name":"'"$DOMAIN_NAME"'","env_name":"'"$ENV_NAME"'"}}' |