17. Job Execution and Control Flow
GitHub Actions Job Execution Control Flow
Default Behavior โดยปกติแล้ว Github actions จะทำตามกฏ 3 ข้อดังนี้
- Jobs จะทำงานขนานกันเสมอถ้าไม่มี dependencies (
needs
) - Job จะไม่ทำงานถ้ามี dependencies (
needs
) ที่ไม่สำเร็จ - 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 Executionon: [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 หรือ successcancelled()
จะทำงานตอนที่ Workflow ถูก Cancelled ก็คือมีคนกด Cancel ที่หน้าเว็ป
name: Advanced Conditional Executionon: [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 มาเป็นแบบในตารางนี้
Expression | Description | When it runs |
---|---|---|
success() | All previous jobs succeeded | Default behavior |
failure() | Any previous job failed | Only on failure |
always() | Regardless of status | Always runs |
cancelled() | Workflow was cancelled | Only on cancellation |
success() && condition | Success AND custom condition | Both must be true |
failure() || cancelled() | Failed OR cancelled | Either 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)