Service Refresh #3
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: Service Refresh | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| environment: | |
| description: "Environment to refresh (staging, prod)" | |
| required: true | |
| type: choice | |
| options: | |
| - staging | |
| - prod | |
| graph_refresh_enabled: | |
| description: "Refresh graph containers" | |
| required: false | |
| type: boolean | |
| default: true | |
| api_refresh_enabled: | |
| description: "Refresh API ECS service" | |
| required: false | |
| type: boolean | |
| default: true | |
| dagster_refresh_enabled: | |
| description: "Refresh Dagster ECS services" | |
| required: false | |
| type: boolean | |
| default: true | |
| graph_node_types: | |
| description: "Graph node types to refresh" | |
| required: false | |
| type: choice | |
| options: | |
| - writer | |
| - all | |
| - shared | |
| default: "writer" | |
| workflow_call: | |
| inputs: | |
| environment: | |
| description: "Environment to refresh (staging, prod)" | |
| required: true | |
| type: string | |
| runner_config: | |
| description: "GitHub Actions runner configuration (JSON array)" | |
| required: false | |
| type: string | |
| default: '["ubuntu-latest"]' | |
| aws_region: | |
| description: "AWS region" | |
| required: false | |
| type: string | |
| default: "us-east-1" | |
| # Graph refresh configuration | |
| graph_refresh_enabled: | |
| description: "Refresh graph containers" | |
| required: false | |
| type: string | |
| default: "true" | |
| graph_node_types: | |
| description: "Graph node types to refresh (writer, all, shared)" | |
| required: false | |
| type: string | |
| default: "writer" | |
| graph_health_check_timeout: | |
| description: "Seconds to wait for graph health check" | |
| required: false | |
| type: string | |
| default: "30" | |
| # API refresh configuration | |
| api_refresh_enabled: | |
| description: "Refresh API ECS service" | |
| required: false | |
| type: string | |
| default: "true" | |
| api_stack_name: | |
| description: "API CloudFormation stack name" | |
| required: false | |
| type: string | |
| default: "" | |
| # Dagster refresh configuration | |
| dagster_refresh_enabled: | |
| description: "Refresh Dagster ECS services" | |
| required: false | |
| type: string | |
| default: "true" | |
| secrets: | |
| ACTIONS_TOKEN: | |
| description: "GitHub token for checkout (optional - falls back to github.token)" | |
| required: false | |
| # Minimal top-level permissions - jobs define their own | |
| permissions: {} | |
| jobs: | |
| # ============================================ | |
| # Graph Container Refresh (EC2 instances via SSM) | |
| # ============================================ | |
| collect-graph-instances: | |
| if: ${{ inputs.graph_refresh_enabled == true || inputs.graph_refresh_enabled == 'true' }} | |
| runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-latest' || fromJSON(inputs.runner_config) }} | |
| permissions: | |
| id-token: write | |
| contents: read | |
| outputs: | |
| matrix: ${{ steps.collect.outputs.matrix }} | |
| has_instances: ${{ steps.collect.outputs.has_instances }} | |
| steps: | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ vars.AWS_ROLE_ARN }} | |
| aws-region: ${{ github.event_name == 'workflow_dispatch' && (vars.AWS_REGION || 'us-east-1') || inputs.aws_region }} | |
| - name: Collect instances to update | |
| id: collect | |
| run: | | |
| echo "🔍 Collecting graph database instances for environment: ${{ inputs.environment }}" | |
| NODE_TYPES="${{ inputs.graph_node_types }}" | |
| INSTANCES_JSON="[]" | |
| # Check what to update based on node_types input | |
| if [ "$NODE_TYPES" == "all" ] || [ "$NODE_TYPES" == "writer" ] || [ "$NODE_TYPES" == "shared" ]; then | |
| echo "📦 Collecting writer instances..." | |
| # Get all graph writers (LadybugDB backend - using LadybugRole tag) | |
| LBUG_WRITERS=$(aws ec2 describe-instances \ | |
| --filters \ | |
| "Name=tag:Environment,Values=${{ inputs.environment }}" \ | |
| "Name=tag:LadybugRole,Values=writer" \ | |
| "Name=instance-state-name,Values=running" \ | |
| --query "Reservations[].Instances[].InstanceId" \ | |
| --output text \ | |
| --region "${{ github.event_name == 'workflow_dispatch' && (vars.AWS_REGION || 'us-east-1') || inputs.aws_region }}" 2>/dev/null || echo "") | |
| # Get all Neo4j writers (using GraphRole tag) | |
| NEO4J_WRITERS=$(aws ec2 describe-instances \ | |
| --filters \ | |
| "Name=tag:Environment,Values=${{ inputs.environment }}" \ | |
| "Name=tag:GraphRole,Values=writer" \ | |
| "Name=tag:GraphBackend,Values=neo4j" \ | |
| "Name=instance-state-name,Values=running" \ | |
| --query "Reservations[].Instances[].InstanceId" \ | |
| --output text \ | |
| --region "${{ github.event_name == 'workflow_dispatch' && (vars.AWS_REGION || 'us-east-1') || inputs.aws_region }}" 2>/dev/null || echo "") | |
| # Process graph writers (LadybugDB backend) | |
| for INSTANCE in $LBUG_WRITERS; do | |
| TIER=$(aws ec2 describe-tags \ | |
| --filters \ | |
| "Name=resource-id,Values=$INSTANCE" \ | |
| "Name=key,Values=WriterTier" \ | |
| --query "Tags[0].Value" \ | |
| --output text \ | |
| --region "${{ github.event_name == 'workflow_dispatch' && (vars.AWS_REGION || 'us-east-1') || inputs.aws_region }}" 2>/dev/null || echo "") | |
| # Filter instances based on node_types parameter | |
| if [ "$NODE_TYPES" == "shared" ]; then | |
| if [ "$TIER" != "shared" ]; then | |
| continue | |
| fi | |
| elif [ "$NODE_TYPES" == "writer" ] || [ "$NODE_TYPES" == "all" ]; then | |
| true | |
| fi | |
| # Determine node type based on tier | |
| if [ "$TIER" == "shared" ]; then | |
| NODE_TYPE="shared-writer" | |
| echo " Found graph shared writer (LadybugDB): $INSTANCE" | |
| else | |
| NODE_TYPE="writer" | |
| echo " Found graph writer (LadybugDB): $INSTANCE (tier: ${TIER:-standard})" | |
| fi | |
| INSTANCE_OBJ="{\"instance_id\":\"$INSTANCE\",\"node_type\":\"$NODE_TYPE\",\"backend\":\"ladybug\"}" | |
| if [ "$INSTANCES_JSON" == "[]" ]; then | |
| INSTANCES_JSON="[$INSTANCE_OBJ]" | |
| else | |
| INSTANCES_JSON="${INSTANCES_JSON%]},${INSTANCE_OBJ}]" | |
| fi | |
| done | |
| # Process Neo4j writers | |
| for INSTANCE in $NEO4J_WRITERS; do | |
| TIER=$(aws ec2 describe-tags \ | |
| --filters \ | |
| "Name=resource-id,Values=$INSTANCE" \ | |
| "Name=key,Values=WriterTier" \ | |
| --query "Tags[0].Value" \ | |
| --output text \ | |
| --region "${{ github.event_name == 'workflow_dispatch' && (vars.AWS_REGION || 'us-east-1') || inputs.aws_region }}" 2>/dev/null || echo "") | |
| # Filter instances based on node_types parameter | |
| if [ "$NODE_TYPES" == "shared" ]; then | |
| if [ "$TIER" != "shared" ]; then | |
| continue | |
| fi | |
| elif [ "$NODE_TYPES" == "writer" ] || [ "$NODE_TYPES" == "all" ]; then | |
| true | |
| fi | |
| # Determine node type | |
| if [ "$TIER" == "shared" ]; then | |
| NODE_TYPE="shared-writer" | |
| echo " Found Neo4j shared writer: $INSTANCE" | |
| else | |
| NODE_TYPE="writer" | |
| echo " Found Neo4j writer: $INSTANCE (tier: ${TIER:-standard})" | |
| fi | |
| INSTANCE_OBJ="{\"instance_id\":\"$INSTANCE\",\"node_type\":\"$NODE_TYPE\",\"backend\":\"neo4j\"}" | |
| if [ "$INSTANCES_JSON" == "[]" ]; then | |
| INSTANCES_JSON="[$INSTANCE_OBJ]" | |
| else | |
| INSTANCES_JSON="${INSTANCES_JSON%]},${INSTANCE_OBJ}]" | |
| fi | |
| done | |
| fi | |
| echo "instances_json=$INSTANCES_JSON" | |
| if [ "$INSTANCES_JSON" == "[]" ]; then | |
| echo "ℹ️ No graph instances found to update" | |
| echo "matrix={}" >> $GITHUB_OUTPUT | |
| echo "has_instances=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "✅ Found graph instances to update" | |
| echo "matrix={\"include\":$INSTANCES_JSON}" >> $GITHUB_OUTPUT | |
| echo "has_instances=true" >> $GITHUB_OUTPUT | |
| # Display what will be updated | |
| echo "📋 Graph instances to update:" | |
| echo "$INSTANCES_JSON" | jq -r '.[] | " - \(.instance_id) (\(.backend)/\(.node_type))"' | |
| fi | |
| refresh-graph: | |
| needs: [collect-graph-instances] | |
| if: needs.collect-graph-instances.outputs.has_instances == 'true' | |
| runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-latest' || fromJSON(inputs.runner_config) }} | |
| timeout-minutes: 15 | |
| permissions: | |
| id-token: write | |
| contents: read | |
| strategy: | |
| matrix: ${{ fromJSON(needs.collect-graph-instances.outputs.matrix) }} | |
| max-parallel: 10 | |
| fail-fast: false | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ github.repository }} | |
| ref: ${{ github.ref }} | |
| token: ${{ secrets.ACTIONS_TOKEN || github.token }} | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ vars.AWS_ROLE_ARN }} | |
| aws-region: ${{ github.event_name == 'workflow_dispatch' && (vars.AWS_REGION || 'us-east-1') || inputs.aws_region }} | |
| - name: Get AWS Account ID | |
| id: aws-account | |
| run: | | |
| # Fetch from STS - validates OIDC connection is working | |
| ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) | |
| echo "account_id=${ACCOUNT_ID}" >> $GITHUB_OUTPUT | |
| - name: Update Graph Container | |
| uses: ./.github/actions/refresh-graph-containers | |
| with: | |
| environment: ${{ inputs.environment }} | |
| instance-id: ${{ matrix.instance_id }} | |
| node-type: ${{ matrix.node_type }} | |
| backend: ${{ matrix.backend }} | |
| aws-region: ${{ github.event_name == 'workflow_dispatch' && (vars.AWS_REGION || 'us-east-1') || inputs.aws_region }} | |
| aws-account-id: ${{ steps.aws-account.outputs.account_id }} | |
| health-check-timeout: ${{ inputs.graph_health_check_timeout || '30' }} | |
| # ============================================ | |
| # API ECS Service Refresh | |
| # ============================================ | |
| refresh-api: | |
| if: ${{ inputs.api_refresh_enabled == true || inputs.api_refresh_enabled == 'true' }} | |
| runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-latest' || fromJSON(inputs.runner_config) }} | |
| timeout-minutes: 10 | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ github.repository }} | |
| ref: ${{ github.ref }} | |
| token: ${{ secrets.ACTIONS_TOKEN || github.token }} | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ vars.AWS_ROLE_ARN }} | |
| aws-region: ${{ github.event_name == 'workflow_dispatch' && (vars.AWS_REGION || 'us-east-1') || inputs.aws_region }} | |
| - name: Determine API Stack Name | |
| id: stack-name | |
| run: | | |
| # Use provided stack name or construct default | |
| if [ -n "${{ inputs.api_stack_name }}" ]; then | |
| STACK_NAME="${{ inputs.api_stack_name }}" | |
| else | |
| # Default naming convention | |
| ENV="${{ inputs.environment }}" | |
| if [ "$ENV" == "prod" ]; then | |
| STACK_NAME="RoboSystemsAPIProd" | |
| else | |
| STACK_NAME="RoboSystemsAPIStaging" | |
| fi | |
| fi | |
| echo "stack_name=$STACK_NAME" >> $GITHUB_OUTPUT | |
| echo "📦 Using API stack: $STACK_NAME" | |
| - name: Refresh API ECS Service | |
| uses: ./.github/actions/refresh-ecs | |
| with: | |
| stack-name: ${{ steps.stack-name.outputs.stack_name }} | |
| service-type: "api" | |
| max-wait-time: "180" | |
| service-stability-delay: "15" | |
| # ============================================ | |
| # Dagster Daemon ECS Service Refresh | |
| # ============================================ | |
| refresh-dagster-daemon: | |
| if: ${{ inputs.dagster_refresh_enabled == true || inputs.dagster_refresh_enabled == 'true' }} | |
| runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-latest' || fromJSON(inputs.runner_config) }} | |
| timeout-minutes: 15 | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ github.repository }} | |
| ref: ${{ github.ref }} | |
| token: ${{ secrets.ACTIONS_TOKEN || github.token }} | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ vars.AWS_ROLE_ARN }} | |
| aws-region: ${{ github.event_name == 'workflow_dispatch' && (vars.AWS_REGION || 'us-east-1') || inputs.aws_region }} | |
| - name: Refresh Dagster Daemon Service | |
| uses: ./.github/actions/refresh-ecs | |
| with: | |
| cluster-name: robosystems-dagster-${{ inputs.environment }}-cluster | |
| service-name: robosystems-dagster-daemon-${{ inputs.environment }} | |
| service-type: daemon | |
| max-wait-time: "600" | |
| service-stability-delay: "30" | |
| # ============================================ | |
| # Dagster Webserver ECS Service Refresh | |
| # ============================================ | |
| refresh-dagster-webserver: | |
| if: ${{ inputs.dagster_refresh_enabled == true || inputs.dagster_refresh_enabled == 'true' }} | |
| runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-latest' || fromJSON(inputs.runner_config) }} | |
| timeout-minutes: 10 | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ github.repository }} | |
| ref: ${{ github.ref }} | |
| token: ${{ secrets.ACTIONS_TOKEN || github.token }} | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ vars.AWS_ROLE_ARN }} | |
| aws-region: ${{ github.event_name == 'workflow_dispatch' && (vars.AWS_REGION || 'us-east-1') || inputs.aws_region }} | |
| - name: Refresh Dagster Webserver Service | |
| uses: ./.github/actions/refresh-ecs | |
| with: | |
| cluster-name: robosystems-dagster-${{ inputs.environment }}-cluster | |
| service-name: robosystems-dagster-webserver-${{ inputs.environment }} | |
| service-type: api | |
| max-wait-time: "180" | |
| service-stability-delay: "15" |