Skip to content

Service Refresh

Service Refresh #3

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"