shell của GitHub Actions là một thiết lập quy định cách thực thi khối run:, nhưng trên thực tế mục tiêu của nó không phải là một danh sách shell cố định mà còn có thể là các tệp thực thi trong $PATH
- Trong workflow thì đây là tùy chọn, còn trong định nghĩa action thì là bắt buộc, và giá trị mặc định thay đổi theo runner, như
bash trên Linux/macOS và pwsh trên Windows
- Khi chỉ định rõ như
shell: bash, GitHub sẽ gắn thêm các cờ bổ sung như bash --noprofile --norc -eo pipefail, nhưng bản thân mục tiêu thực thi vẫn có thể là một chương trình tùy ý
- Nếu tệp thực thi không nhận trực tiếp một đầu vào là tệp đơn, cần thêm đối số
{0} vào giá trị shell; GitHub sẽ thay nó bằng đường dẫn tới tệp tạm chứa khối run
- Ngay cả những tên quen thuộc như
bash cũng được phân giải từ $PATH, nên việc thay đổi $GITHUB_PATH hoặc có một tệp thực thi giả có thể ảnh hưởng đến cách các bước sau đó được chạy
Cách hoạt động mặc định của từ khóa shell
- Từ khóa
shell trong GitHub Actions dùng để chỉ định shell nào sẽ chạy một khối run: cụ thể
- Trong workflow thì đây là tùy chọn, nhưng trong định nghĩa action thì bắt buộc
- Shell mặc định được quyết định theo môi trường runner
- Trên Linux và macOS thường là
bash
- Trên Windows thường là
pwsh
Chỉ định shell tường minh và các cờ bổ sung
- GitHub trong tài liệu
defaults.run.shell cho biết rằng khi chỉ định shell tường minh, GitHub cũng áp dụng các cờ do GitHub chọn
- Ví dụ, khi chỉ định
shell: bash, dạng thực thi sẽ trở thành
bash --noprofile --norc -eo pipefail
- Chỉ nhìn vào hành vi này thì rất dễ nghĩ rằng GitHub quản lý một danh sách giá trị shell hợp lệ bị giới hạn, và chỉ gắn các cờ đặc biệt cho những giá trị đó
Tệp thực thi tùy ý trong $PATH cũng có thể trở thành shell
- Trên thực tế, có thể chỉ định bất kỳ tệp thực thi nào có trong
$PATH cho shell
- GitHub sẽ dùng tệp thực thi đã chỉ định đó để chạy khối
run
- Nếu lệnh đó không trực tiếp nhận một đầu vào là tệp đơn, cần truyền
{0} trong giá trị shell
- GitHub sẽ đổi
{0} thành đường dẫn tệp tạm
- Tệp tạm này chứa nội dung của khối
run sau khi mở rộng template
Ví dụ dùng C làm trình chạy step
- Những công cụ hoạt động như trình biên dịch/trình thông dịch C cũng có thể được dùng để thực thi step trong GitHub Actions
- Ví dụ chỉ định
tcc -run {0} làm shell hoạt động bình thường
- run: sudo apt install -y tcc
- shell: tcc -run {0}
run: |
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
Ví dụ thay đổi cách phân giải shell bằng $GITHUB_PATH
- Nếu sửa đổi
$PATH động thông qua $GITHUB_PATH, thì shell: bash ở các bước sau có thể trỏ tới một tệp thực thi khác với kỳ vọng
- Ví dụ tạo một tệp thực thi tên
bash trong thư mục hiện tại rồi thêm thư mục hiện tại vào $GITHUB_PATH
- Sau đó khi dùng
shell: bash, GitHub có thể chạy bash giả được tìm thấy trong $PATH
- run: |
touch ./bash
chmod +x ./bash
echo '#!/bin/sh' > ./bash
echo 'echo hello from fake bash' >> ./bash
echo "${PWD}" >> "${GITHUB_PATH}"
- run: |
echo "this doesn't do what you expect"
shell: bash
Điểm bất ngờ về mặt bảo mật
- Khó có thể khẳng định dứt khoát liệu hành vi này có quan trọng về mặt bảo mật hay không
- Trong GitHub Actions đã có sẵn nhiều con đường mà việc ghi tệp có thể dẫn tới thực thi, và
GITHUB_ENV cũng nằm trong số đó
- Dù vậy, việc GitHub vẫn tra cứu qua
$PATH ngay cả với những giá trị shell quen thuộc như bash có thể là điều ngoài dự đoán
- Đặc biệt, nếu GitHub chèn các cờ dòng lệnh tùy theo những giá trị shell quen thuộc, người ta có thể kỳ vọng
bash sẽ được cố định vào một đường dẫn cụ thể như /bin/bash
- Tổng quát hơn, cũng có thể nghĩ rằng GitHub chỉ cho phép các công cụ đã được đăng ký sẵn trong tool cache, nhưng hành vi quan sát được là sử dụng các tệp thực thi trong
$PATH
2 bình luận
Chỉ nhìn vào repo github/runner-image thôi cũng thấy có khá nhiều gói đã được cài sẵn để có thể dùng ngay...
Tạo image là dung lượng đội lên 1GB ngay...
Ý kiến trên Hacker News
-xđể khiến bash in ra mọi lệnh mà nó chạy trong workflow Actions, và nó khá hữu ích cho việc gỡ lỗihttps://github.com/jstrieb/just.sh/blob/2da1e2a3bfb51d583be0...
pipefail cũng không ngăn được các trạng thái lỗi phức tạp hơn. Ví dụ, trong bước
curl ... | sudo tar ...của cấu hình, các trường hợp như giải néntarthất bại,sudothất bại, hay lỗi shell sẽ lộ ra, nhưng nếucurlbị lỗi mạng và fail giữa chừng,tarcó thể chỉ giải nén một nửa binaryjustrồi shell thoát ngay. Khi đó không có thông báo lỗi, file thực thi bị hỏng vẫn nằm trên đĩa, và nếu bật bỏ qua khi lỗi thì thậm chí có thể nó còn được thử chạyrepository_dispatchđể matchCó thể viết kiểu
on: repository_dispatch: - security_scan - security_scan::*. Nếu tập trung hóa release pipeline, bạn có thể buộc từng repository đi qua workflow tái sử dụng đã định nghĩa, và nếu gửi sự kiện nhưsecurity_scan::$product_name::$versionthì trong tab Actions của repository release trung tâm sẽ dễ hơn nhiều để biết workflow của sản phẩm và phiên bản nào đang chạyTôi vừa gia nhập một tổ chức đang cố làm điều tương tự, nhưng thực tế trông gần như vô dụng. Template thường xuyên bị hỏng, và nhiều cái được viết với giả định về một cách build cụ thể mà không có tài liệu về việc code cần được build theo cách nào
Thường tôi thích đặt logic vào hệ thống build như Make rồi chỉ gọi từ GitHub Actions, hoặc viết một chương trình dòng lệnh nhỏ để gọi. Những thứ như vậy dễ gỡ lỗi cục bộ hơn CI rất nhiều. Đây là một mẹo thú vị, nhưng tôi không chắc nó hữu dụng ở đâu
make buildvàmake testSau khi bị mua lại, tôi xem file workflow của công ty mua lại thì thấy nó dài hàng trăm dòng và có nhiều đoạn lặp. Có thể gọi tôi là cổ lỗ, nhưng tôi muốn thoát khỏi làng YAML càng nhanh càng tốt
Ngay cả với một tính năng bạn không định dùng thật, biết hệ thống có thể làm được gì vẫn hữu ích cho bảo mật và gỡ lỗi
Đặc biệt khi “khám phá” runner tự host
Thế hệ tiếp theo sẽ run lên khi được yêu cầu thiết lập kỷ luật cho triển khai làm bằng GitHub Actions
Nếu có thể mô tả được, có lẽ chúng tôi có thể hiện thực hóa nó trong tổ chức của mình
bashđể nó chạy chương trình tùy ýTôi đã nhiều lần thấy shell script khởi đầu gần như chỉ là vài dòng lệnh gõ tay rồi biến thành con quái vật hơn trăm dòng, và mỗi lần như vậy tôi đều ước có mảng và kiểu dữ liệu thật sự, cùng các tính năng batteries included của thư viện chuẩn Python. Nhưng tôi sẽ không triển khai Action build của công ty bằng elisp đâu
Nó được expose qua phương thức
ScriptHandlerHelpers.GetScriptArgumentsFormat. TrongScriptHandler.cscó code chuẩn bị môi trường process và đối số, còn code thực sự khởi chạy process nằm ở đây: https://github.com/actions/runner/blob/main/src/Runner.Worke...Nhìn chung tôi ngạc nhiên theo hướng tích cực về sự đơn giản của phần code này. Nó rất thủ tục và xử lý rất nhiều trường hợp ngoại lệ, nhưng có vẻ dễ hiểu và dễ gỡ lỗi
Có lẽ assembly cũng được
Tuy nhiên goeval hiện chưa hỗ trợ nhập file trực tiếp mà chỉ hỗ trợ standard input, nên cần mẹo shell. Hiện tại là kiểu
go run github.com/dolmen-go/goeval@v1 - <<'EOF' ... EOF, cần một ít boilerplate. Nhân tiện, tôi là tác giả của goeval[1] https://github.com/dolmen-go/goeval
go run github.com/dolmen-go/goeval@v1 - < file.gokhông được sao?Trong bối cảnh này, nếu muốn quảng bá một dự án đồ chơi thì giá mà đưa ví dụ liên quan hơn “in hello” thì đã đỡ mất một cú click
run: pipeline.sh?Có thể chạy đồng thời công việc trên nhiều hệ điều hành và kiến trúc CPU, đồng thời lấy được thông tin ngữ cảnh về sự kiện kích hoạt công việc và trạng thái repository. Nó tiện cho các job theo từng PR hoặc tự động hóa release, và cũng tích hợp được với GitHub web UI, chẳng hạn linter hiển thị vấn đề trên từng dòng của PR hoặc render lỗi test lên trang web. Cũng có thể dùng cache nhỏ để tránh tải lại hoặc build lại các file không thay đổi. Lý tưởng nhất là đưa càng nhiều càng tốt vào các công cụ thông thường chạy được cục bộ, còn cấu hình GitHub CI chỉ đảm nhiệm phần keo dán cần cho trigger, caching và tích hợp GitHub
Có thể dễ dàng tận dụng GitHub Actions do người khác tạo trong pipeline, module hóa workflow và kiểm soát phụ thuộc cùng thực thi song song. Chắc còn nhiều nữa, nhưng lợi ích cốt lõi là bạn không phải tự triển khai những thứ đó