Workflow Automation Skill
Overview
Design and implement automated workflows for development teams, including CI/CD pipelines, release automation, code quality gates, deployment processes, and team collaboration workflows. This skill encompasses GitHub Actions, Harness pipelines, automated testing workflows, release management, and process optimization strategies.
Core Competencies
GitHub Actions Workflows
Design Comprehensive CI Workflows:
Create multi-stage CI pipelines with proper job dependencies:
.github/workflows/ci.yml
name: Continuous Integration
on: push: branches: [main, develop] pull_request: branches: [main, develop] workflow_dispatch:
env: NODE_VERSION: '20' REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
jobs: setup: name: Setup and Cache runs-on: ubuntu-latest outputs: cache-key: ${{ steps.cache-key.outputs.key }} steps: - uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Generate cache key
id: cache-key
run: echo "key=${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}" >> $GITHUB_OUTPUT
- name: Install dependencies
run: npm ci
lint: name: Lint Code needs: setup runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint -- --format json --output-file eslint-report.json
continue-on-error: true
- name: Annotate code
uses: ataylorme/eslint-annotate-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
report-json: eslint-report.json
- name: Upload ESLint results
uses: actions/upload-artifact@v4
with:
name: eslint-report
path: eslint-report.json
type-check: name: Type Check needs: setup runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run TypeScript compiler
run: npm run type-check
unit-tests: name: Unit Tests needs: setup runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit -- --coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage-final.json
flags: unit
name: unit-tests
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: unit-test-results
path: |
coverage/
test-results/
integration-tests: name: Integration Tests needs: setup runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_DB: test_db POSTGRES_USER: test_user POSTGRES_PASSWORD: test_pass options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run database migrations
run: npm run migrate
env:
DATABASE_URL: postgresql://test_user:test_pass@localhost:5432/test_db
- name: Run integration tests
run: npm run test:integration -- --coverage
env:
DATABASE_URL: postgresql://test_user:test_pass@localhost:5432/test_db
REDIS_URL: redis://localhost:6379
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage-final.json
flags: integration
name: integration-tests
e2e-tests: name: E2E Tests needs: [unit-tests, integration-tests] runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npm run test:e2e
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
security-scan: name: Security Scan needs: setup runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Run npm audit
run: npm audit --audit-level=moderate
continue-on-error: true
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
build: name: Build Application needs: [lint, type-check, unit-tests, integration-tests] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
docker-build: name: Build Docker Image needs: [build, security-scan] runs-on: ubuntu-latest if: github.event_name == 'push' permissions: contents: read packages: write steps: - uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
NODE_VERSION=${{ env.NODE_VERSION }}
BUILD_DATE=${{ github.event.head_commit.timestamp }}
VCS_REF=${{ github.sha }}
quality-gate: name: Quality Gate needs: [lint, type-check, unit-tests, integration-tests, e2e-tests, security-scan] runs-on: ubuntu-latest steps: - name: Download all artifacts uses: actions/download-artifact@v4
- name: Check quality metrics
run: |
echo "All quality checks passed"
echo "Ready for deployment"
- name: Post summary
run: |
echo "## CI Pipeline Summary" >> $GITHUB_STEP_SUMMARY
echo "✅ Linting passed" >> $GITHUB_STEP_SUMMARY
echo "✅ Type checking passed" >> $GITHUB_STEP_SUMMARY
echo "✅ Unit tests passed" >> $GITHUB_STEP_SUMMARY
echo "✅ Integration tests passed" >> $GITHUB_STEP_SUMMARY
echo "✅ E2E tests passed" >> $GITHUB_STEP_SUMMARY
echo "✅ Security scan passed" >> $GITHUB_STEP_SUMMARY
Implement Automated Release Workflow:
Create release automation with changelog generation:
.github/workflows/release.yml
name: Release
on: push: tags: - 'v*'
permissions: contents: write packages: write
jobs: create-release: name: Create Release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0
- name: Generate changelog
id: changelog
uses: mikepenz/release-changelog-builder-action@v4
with:
configuration: '.github/changelog-config.json'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-npm: name: Publish to NPM needs: create-release runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish to NPM
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
deploy-production: name: Deploy to Production needs: create-release runs-on: ubuntu-latest environment: name: production url: https://app.example.com steps: - uses: actions/checkout@v4
- name: Setup kubectl
uses: azure/setup-kubectl@v3
- name: Setup Helm
uses: azure/setup-helm@v3
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Get AKS credentials
run: |
az aks get-credentials \
--resource-group ${{ secrets.RESOURCE_GROUP }} \
--name ${{ secrets.CLUSTER_NAME }}
- name: Deploy with Helm
run: |
helm upgrade --install app \
./deployment/helm/app \
--namespace production \
--create-namespace \
--values ./deployment/helm/app/values-prod.yaml \
--set image.tag=${{ github.ref_name }} \
--wait \
--timeout 10m
- name: Run smoke tests
run: |
kubectl run smoke-test \
--image=curlimages/curl:latest \
--restart=Never \
--rm \
-i \
-- curl -f https://app.example.com/health
Harness Pipeline Configuration
Design Enterprise CI/CD Pipeline:
Configure Harness pipelines for complex deployment scenarios:
harness/pipelines/production-deploy.yml
pipeline: name: Production Deployment Pipeline identifier: prod_deployment projectIdentifier: platform orgIdentifier: engineering tags: env: production team: platform stages: - stage: name: Build and Test identifier: build_test type: CI spec: cloneCodebase: true infrastructure: type: KubernetesDirect spec: connectorRef: k8s_delegate namespace: harness-builds automountServiceAccountToken: true execution: steps: - step: type: Run name: Install Dependencies identifier: install_deps spec: connectorRef: docker_hub image: node:20-alpine shell: Bash command: npm ci
- step:
type: Run
name: Run Tests
identifier: run_tests
spec:
connectorRef: docker_hub
image: node:20-alpine
shell: Bash
command: |
npm run lint
npm run test:unit
npm run test:integration
failureStrategies:
- onFailure:
errors:
- AllErrors
action:
type: Abort
- step:
type: Run
name: Security Scan
identifier: security_scan
spec:
connectorRef: docker_hub
image: aquasec/trivy:latest
shell: Bash
command: trivy fs --security-checks vuln,config .
- step:
type: BuildAndPushDockerRegistry
name: Build and Push Image
identifier: build_push
spec:
connectorRef: docker_registry
repo: platform/app
tags:
- <+pipeline.sequenceId>
- <+pipeline.executionId>
- latest
optimize: true
caching: true
- stage:
name: Deploy to Staging
identifier: deploy_staging
type: Deployment
spec:
deploymentType: Kubernetes
service:
serviceRef: app_service
serviceInputs:
serviceDefinition:
type: Kubernetes
spec:
artifacts:
primary:
primaryArtifactRef: <+input>
sources:
- identifier: docker_image
type: DockerRegistry
spec:
tag: <+pipeline.sequenceId>
environment:
environmentRef: staging
deployToAll: false
infrastructureDefinitions:
- identifier: staging_k8s
execution:
steps:
- step:
type: K8sRollingDeploy
name: Rolling Deployment
identifier: rolling_deploy
spec:
skipDryRun: false
pruningEnabled: true
- step:
type: ShellScript
name: Run Smoke Tests
identifier: smoke_tests
spec:
shell: Bash
source:
type: Inline
spec:
script: |
#!/bin/bash
set -e
echo "Running smoke tests..."
curl -f https://staging.example.com/health
echo "Smoke tests passed"
timeout: 5m
rollbackSteps:
- step:
type: K8sRollingRollback
name: Rollback Deployment
identifier: rollback
- stage:
name: Approval
identifier: approval
type: Approval
spec:
execution:
steps:
- step:
type: HarnessApproval
name: Manual Approval
identifier: manual_approval
spec:
approvalMessage: Please review and approve deployment to production
includePipelineExecutionHistory: true
approvers:
userGroups:
- account.ProductionApprovers
minimumCount: 2
disallowPipelineExecutor: false
timeout: 1d
- stage:
name: Deploy to Production
identifier: deploy_production
type: Deployment
spec:
deploymentType: Kubernetes
service:
serviceRef: app_service
serviceInputs:
serviceDefinition:
type: Kubernetes
spec:
artifacts:
primary:
primaryArtifactRef: <+input>
sources:
- identifier: docker_image
type: DockerRegistry
spec:
tag: <+pipeline.sequenceId>
environment:
environmentRef: production
deployToAll: false
infrastructureDefinitions:
- identifier: prod_k8s_us_east
- identifier: prod_k8s_eu_west
execution:
steps:
- step:
type: K8sBlueGreenDeploy
name: Blue Green Deployment
identifier: bg_deploy
spec:
skipDryRun: false
pruningEnabled: false
- step:
type: ShellScript
name: Health Check
identifier: health_check
spec:
shell: Bash
source:
type: Inline
spec:
script: |
#!/bin/bash
set -e
for i in {1..10}; do
if curl -f https://app.example.com/health; then
echo "Health check passed"
exit 0
fi
echo "Attempt $i failed, retrying..."
sleep 10
done
echo "Health check failed"
exit 1
timeout: 5m
- step:
type: K8sBGSwapServices
name: Swap Traffic
identifier: swap_traffic
spec:
skipDryRun: false
- step:
type: ShellScript
name: Monitor Metrics
identifier: monitor_metrics
spec:
shell: Bash
source:
type: Inline
spec:
script: |
#!/bin/bash
# Monitor error rates and latency for 5 minutes
for i in {1..30}; do
ERROR_RATE=$(curl -s "https://monitoring.example.com/api/v1/query?query=rate(http_requests_total{status=~\"5..\"}[5m])" | jq '.data.result[0].value[1]' -r)
if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
echo "Error rate too high: $ERROR_RATE"
exit 1
fi
sleep 10
done
timeout: 10m
rollbackSteps:
- step:
type: K8sBGSwapServices
name: Rollback Traffic
identifier: rollback_traffic
notificationRules: - name: Pipeline Failed pipelineEvents: - type: PipelineFailed notificationMethod: type: Slack spec: userGroups: - account.DevOps webhookUrl: <+secrets.getValue("slack_webhook")>
- name: Production Deployed
pipelineEvents:
- type: StageSuccess
notificationMethod:
type: Email
spec:
userGroups:
- account.Engineering
recipients:
- engineering@example.com
Automation Scripts
Create Reusable Workflow Templates:
Build modular automation scripts for common tasks:
#!/bin/bash
scripts/automation/deploy.sh
set -euo pipefail
Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
Colors for output
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color
Functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1" }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" }
log_error() { echo -e "${RED}[ERROR]${NC} $1" }
check_prerequisites() { log_info "Checking prerequisites..."
command -v kubectl >/dev/null 2>&1 || {
log_error "kubectl is required but not installed."
exit 1
}
command -v helm >/dev/null 2>&1 || {
log_error "helm is required but not installed."
exit 1
}
command -v docker >/dev/null 2>&1 || {
log_error "docker is required but not installed."
exit 1
}
}
build_image() { local tag=$1 log_info "Building Docker image with tag: $tag"
docker build \
-f deployment/docker/Dockerfile \
-t "$DOCKER_REGISTRY/$IMAGE_NAME:$tag" \
--build-arg NODE_VERSION=20 \
--build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--build-arg VCS_REF="$(git rev-parse --short HEAD)" \
.
}
push_image() { local tag=$1 log_info "Pushing Docker image with tag: $tag"
docker push "$DOCKER_REGISTRY/$IMAGE_NAME:$tag"
}
deploy_helm() { local environment=$1 local tag=$2
log_info "Deploying to $environment with tag: $tag"
helm upgrade --install "$RELEASE_NAME" \
./deployment/helm/app \
--namespace "$NAMESPACE" \
--create-namespace \
--values "./deployment/helm/app/values-$environment.yaml" \
--set "image.tag=$tag" \
--wait \
--timeout 10m
}
run_smoke_tests() { local url=$1 log_info "Running smoke tests against $url"
for i in {1..10}; do
if curl -f -s "$url/health" > /dev/null; then
log_info "Smoke tests passed"
return 0
fi
log_warn "Attempt $i failed, retrying..."
sleep 5
done
log_error "Smoke tests failed"
return 1
}
Main deployment function
deploy() { local environment=$1 local version=${2:-$(git rev-parse --short HEAD)}
check_prerequisites
log_info "Starting deployment to $environment"
log_info "Version: $version"
# Build and push image
build_image "$version"
push_image "$version"
# Deploy with Helm
deploy_helm "$environment" "$version"
# Run smoke tests
case $environment in
production)
run_smoke_tests "https://app.example.com"
;;
staging)
run_smoke_tests "https://staging.example.com"
;;
*)
log_warn "Skipping smoke tests for $environment"
;;
esac
log_info "Deployment completed successfully"
}
Parse command line arguments
case ${1:-} in production|staging|development) deploy "$@" ;; *) echo "Usage: $0 {production|staging|development} [version]" exit 1 ;; esac
Create Database Migration Workflow:
Automate database schema changes:
#!/bin/bash
scripts/automation/migrate.sh
set -euo pipefail
Database migration automation
run_migrations() { local environment=$1 local direction=${2:-up}
log_info "Running $direction migrations for $environment"
# Load environment-specific configuration
case $environment in
production)
DB_URL="$PRODUCTION_DB_URL"
;;
staging)
DB_URL="$STAGING_DB_URL"
;;
development)
DB_URL="$DEVELOPMENT_DB_URL"
;;
esac
# Create backup before migration
if [ "$environment" = "production" ]; then
log_info "Creating database backup..."
backup_database "$environment"
fi
# Run migrations
if [ "$direction" = "up" ]; then
npm run migrate:up
else
npm run migrate:down
fi
log_info "Migrations completed"
}
backup_database() { local environment=$1 local timestamp=$(date +%Y%m%d_%H%M%S) local backup_file="backup_${environment}_${timestamp}.sql"
pg_dump "$DB_URL" > "$backup_file"
# Upload to cloud storage
aws s3 cp "$backup_file" "s3://backups/$backup_file"
log_info "Backup created: $backup_file"
}
run_migrations "$@"
Process Optimization
Implement Branch Protection Rules:
Configure automated branch protection:
.github/branch-protection.yml
rules:
-
pattern: main required_status_checks: strict: true contexts: - ci/lint - ci/type-check - ci/unit-tests - ci/integration-tests - ci/e2e-tests - ci/security-scan required_pull_request_reviews: required_approving_review_count: 2 dismiss_stale_reviews: true require_code_owner_reviews: true restrictions: users: [] teams: - core-team - platform-team enforce_admins: true required_signatures: true allow_force_pushes: false allow_deletions: false
-
pattern: develop required_status_checks: strict: true contexts: - ci/lint - ci/type-check - ci/unit-tests required_pull_request_reviews: required_approving_review_count: 1 dismiss_stale_reviews: true enforce_admins: false allow_force_pushes: false
Create Automated Dependency Updates:
Implement automated dependency management:
.github/workflows/dependency-update.yml
name: Dependency Updates
on: schedule: - cron: '0 0 * * 1' # Weekly on Monday workflow_dispatch:
jobs: update-dependencies: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Update dependencies
run: |
npm update
npm outdated || true
- name: Run tests
run: |
npm ci
npm run test
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update dependencies'
title: 'chore: automated dependency updates'
body: |
This PR contains automated dependency updates.
Please review the changes and ensure all tests pass.
branch: chore/dependency-updates
labels: dependencies, automated
Related Resources
-
DevOps Practices Skill - For deployment and infrastructure automation
-
Code Quality Skill - For quality gate implementation in pipelines
-
Integration Patterns Skill - For API and service integration in workflows
-
GitHub Actions Documentation - https://docs.github.com/actions
-
Harness Documentation - https://developer.harness.io