Skip to content
CodeSook
CodeSook

17. Job Execution and Control Flow


GitHub Actions Job Execution Control Flow

Default Behavior โดยปกติแล้ว Github actions จะทำตามกฏ 3 ข้อดังนี้

  1. Jobs จะทำงานขนานกันเสมอถ้าไม่มี dependencies (needs)
  2. Job จะไม่ทำงานถ้ามี dependencies (needs) ที่ไม่สำเร็จ
  3. Workflow จะหยุดทำงานไปเลยถ้ามี job ใด job หนึ่งทำงานไม่สำเร็จ
flowchart TD
    subgraph Main ["Deploy workflow"]
    direction TB
      Start([Push to main]) --> Trigger{GitHub Actions}

      subgraph parallel ["Parallel Jobs"]
          subgraph test ["🧪 Test Job"]
              T1[Install deps]
              T2[Run tests]
              T3[Upload coverage]
              T1 --> T2 --> T3
          end

          subgraph lint ["🔍 Lint Job"]
              L1[Install deps]
              L2[ESLint check]
              L3[Type check]
              L1 --> L2 --> L3
          end
      end

      subgraph deploy ["🚀 Deploy Job"]
          D1[Install deps]
          D2[Build Vite]
          D3[Deploy to Vercel]
          D1 --> D2 --> D3
      end

      Trigger --> T1
      Trigger --> L1

      T3 --> D1
      L3 --> D1

      D3 --> Success([✅ Live on Server])

      %% Failure paths
      T2 -.->|❌| Failed([Build Failed])
      L2 -.->|❌| Failed
      D2 -.->|❌| Failed
    end

    %% Catppuccin Mocha Colors
    classDef start fill:#a6e3a1,stroke:#40a02b,color:#11111b
    classDef trigger fill:#f9e2af,stroke:#df8e1d,color:#11111b
    classDef testJob fill:#89b4fa,stroke:#1e66f5,color:#11111b
    classDef lintJob fill:#cba6f7,stroke:#8839ef,color:#11111b
    classDef deployJob fill:#fab387,stroke:#fe640b,color:#11111b
    classDef failed fill:#f38ba8,stroke:#d20f39,color:#11111b

    class Start,Success start
    class Trigger trigger
    class T1,T2,T3 testJob
    class L1,L2,L3 lintJob
    class D1,D2,D3 deployJob
    class Failed failed

    style parallel fill:#a6e3a1,stroke:#6c7086,color:#cdd6f4
    style test fill:#f9e2af,stroke:#89b4fa,color:#cdd6f4
    style lint fill:#f5c2e7,stroke:#cba6f7,color:#cdd6f4
    style deploy fill:#cdd6f4,stroke:#fab387,color:#cdd6f4

Basic Sequential Jobs with Dependencies

ตัวอย่าง Jobs ที่อยากให้ทำงานต่อๆกัน ไม่ทำงานขนานกัน

เราจะใช้ needs: [] เพื่อระบุงานที่ต้องทำงานก่อน

name: Sequential Job Execution
on: [push]
jobs:
setup:
runs-on: ubuntu-latest
steps:
- name: Setup environment
run: echo "Setting up environment"
- name: Install dependencies
run: echo "Installing dependencies"
test:
needs: setup # Waits for setup to complete successfully
runs-on: ubuntu-latest
steps:
- name: Run tests
run: echo "Running tests"
build:
needs: test # Waits for test to complete successfully
runs-on: ubuntu-latest
steps:
- name: Build application
run: echo "Building application"
deploy:
needs: [setup, test, build] # Waits for ALL specified jobs
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: echo "Deploying to production"

Jobs Condition helper functions

ใน Github actions มี function พิเศษที่ช่วยให้เราจจัดการ Jobs ที่ Failed หรือ Success ได้

  • success() จะทำงานก็ต่อเมื่อ Jobs ใน needs ทั้งหมดสำเร็จ เป็น Default behavior อยู่แล้วนะ
  • failure() จะทำงานก็ต่อเมื่อ Jobs ใน needs ตัวใดตัวหนึ่ง Failed ขอแค่ failed สักตัวนึงก็เข้าเงื่อนไขละ
  • always() จะทำงานเสมอ ไม่สนว่า failed หรือ success
  • cancelled() จะทำงานตอนที่ Workflow ถูก Cancelled ก็คือมีคนกด Cancel ที่หน้าเว็ป
name: Advanced Conditional Execution
on: [push]
jobs:
test:
runs-on: ubuntu-latest
outputs:
test-result: ${{ steps.test-step.outcome }}
steps:
- name: Run tests
id: test-step
run: |
# Simulate random test failure
if [ $((RANDOM % 2)) -eq 0 ]; then
echo "Tests passed"
exit 0
else
echo "Tests failed"
exit 1
fi
continue-on-error: true
# Runs only if tests succeed
deploy-production:
needs: test
if: success() # Default behavior, explicit here
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: echo "Deploying to production"
# Runs only if tests fail
notify-failure:
needs: test
if: failure()
runs-on: ubuntu-latest
steps:
- name: Notify team of failure
run: echo "Notifying team about test failure"
# Runs regardless of test outcome
generate-report:
needs: test
if: always()
runs-on: ubuntu-latest
steps:
- name: Generate test report
run: echo "Generating test report"
# Runs only if tests were cancelled
handle-cancellation:
needs: test
if: cancelled()
runs-on: ubuntu-latest
steps:
- name: Handle cancellation
run: echo "Handling workflow cancellation"
# Complex conditional logic
conditional-deploy:
needs: test
if: success() && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Deploy to staging
run: echo "Deploying to staging from main branch"

สรุป function มาเป็นแบบในตารางนี้

ExpressionDescriptionWhen it runs
success()All previous jobs succeededDefault behavior
failure()Any previous job failedOnly on failure
always()Regardless of statusAlways runs
cancelled()Workflow was cancelledOnly on cancellation
success() && conditionSuccess AND custom conditionBoth must be true
failure() || cancelled()Failed OR cancelledEither condition

ตัวอย่างการใช้งาน

  • ใช้ always() ในเคสที่เราต้องการ cleanup jobs
  • ใช้ failure() ในเคสที่เราต้องการส่ง Notification

Job Execution Control Settings

Continue on Error

เราใส่ continue-on-error: true ใน step ที่อาจจะ fail ทำให้ step ถัดไปยังคงทำงานต่อไปได้

jobs:
resilient-job:
runs-on: ubuntu-latest
steps:
- name: Step that might fail
run: exit 1
continue-on-error: true # Job continues even if this step fails
- name: This step will still run
run: echo "This runs even after the previous step failed"

Outcome vs Conclusion

เราสามารถ control flow ได้ด้วยการใช้งาน outcome และ conclusion พอเราใช้ continue-on-error: true ทำให้ step ถัดไปยังคงทำงานต่อไปได้ ทำให้ Github actions ไปสร้าง ตัวแปร outcome และ conclusion ที่สามารถใช้ในการ control flow ได้ละเอียดมากขึ้น
การใช้งานอาจจะงงๆ หน่อย ดูตัวอย่างประกอบเพื่อความเข้าใจละกันนะ แล้วดูผลลัพธ์น่าจะเข้าใจได้มากขึ้น

jobs:
test-cakes:
runs-on: ubuntu-latest
steps:
- name: ทดสอบเค้กชิ้นที่ 1
id: cake1
run: echo "เค้กชิ้นที่ 1 อร่อย" && exit 0
- name: ทดสอบเค้กชิ้นที่ 2
id: cake2
run: echo "เค้กชิ้นที่ 2 ไหม้" && exit 1
continue-on-error: true
- name: ทดสอบเค้กชิ้นที่ 3
id: cake3
run: echo "เค้กชิ้นที่ 3 อร่อย" && exit 0
- name: สรุปผลการทดสอบเค้ก
run: |
echo "เค้กชิ้นที่ 1 - outcome: ${{ steps.cake1.outcome }}, conclusion: ${{ steps.cake1.conclusion }}"
echo "เค้กชิ้นที่ 2 - outcome: ${{ steps.cake2.outcome }}, conclusion: ${{ steps.cake2.conclusion }}"
echo "เค้กชิ้นที่ 3 - outcome: ${{ steps.cake3.outcome }}, conclusion: ${{ steps.cake3.conclusion }}"

ผลลัพธ์:

  • เค้กชิ้นที่ 1: outcome = success, conclusion = success (ทำสำเร็จ)
  • เค้กชิ้นที่ 2: outcome = failure, conclusion = success (ทำไม่สำเร็จ แต่เราใช้ continue-on-error: true ดังนั้น conclusion จึงเป็น success)
  • เค้กชิ้นที่ 3: outcome = success, conclusion = success (ทำสำเร็จ)

Time Control

สามารถใส่ timeout ให้กับ Job ได้ด้วย
พอ Job ใช้เวลาทำงานนานเกินที่กำหนด Github actions ก็จะยกเลิก Job

jobs:
timeout-job:
runs-on: ubuntu-latest
timeout-minutes: 10 # Job will be cancelled after 10 minutes
steps:
- name: Long running task
run: sleep 600 # This will be cancelled due to timeout
timeout-minutes: 5 # Step-level timeout (5 minutes)