Tôi thích làm việc với GitHub Actions. Chúng rất dễ sử dụng nhưng rất mạnh mẽ. Tôi đặc biệt hào hứng khi thấy mọi người sáng tạo như thế nào khi sử dụng chúng để tự động hóa các nhiệm vụ khác nhau.
Tôi muốn bạn có sức mạnh tương tự. Đó là lý do tại sao tôi sẽ chỉ cho bạn cách tạo hành động JavaScript tùy chỉnh đầu tiên chỉ trong vài bước.
Nào cùng đào vào bên trong.
Hành động GitHub là gì?
Đầu tiên, chúng ta cần thiết lập sự khác biệt giữa “Hành động GitHub” và “Hành động”. Cái trước là tên của sản phẩm và cái sau là mã tùy chỉnh mà bạn có thể đưa vào công việc dòng công việc như một bước để hoàn thành nhiệm vụ.
Ví dụ: một hành động có thể xuất bản mã của bạn lên trình quản lý gói như npm hoặc yarn. Nó cũng có thể tích hợp với nhà cung cấp dịch vụ SMS để thông báo cho bạn khi có vấn đề khẩn cấp được tạo trong repo của bạn. Hoặc nó có thể bật máy pha cà phê của bạn khi bạn tạo một yêu cầu kéo mới.
Khả năng là vô tận cho những gì bạn có thể làm!
Các thành phần của GitHub Actions là gì?
Trước khi bắt đầu viết mã, điều quan trọng là chúng ta phải hiểu các khối xây dựng của GitHub Actions.
Hãy chia nhỏ sơ đồ này, bắt đầu từ bên trái và bên phải:
- Biến cố: Đây là sự kiện kích hoạt hành động. Nó đại diện cho một hoạt động trong kho lưu trữ sẽ kích hoạt chạy quy trình làm việc.
- quy trình làm việc: Đây là quy trình công việc được chạy khi sự kiện xảy ra.
- Việc làm: Một tập hợp các bước được chạy theo trình tự để hoàn thành một nhiệm vụ. Mỗi công việc chạy trên Á hậu riêng của mình.
- Bước chân: Một bước là tập lệnh trình bao hoặc một hành động sẽ được chạy trên trình chạy được chỉ định cho công việc mà bước đó là một phần của.
- người chạy: Người chạy là một máy ảo (hoặc bất kỳ máy tính nào có hệ điều hành được hỗ trợ) chạy các bước trong một công việc.
Điều này được giải thích rất rõ ràng trong các tài liệu mở rộng của GitHub và bạn có thể đọc thêm về các thành phần tại đây.
Khi nào tôi cần tạo một Hành động?
Vì mỗi bước có thể là tập lệnh trình bao hoặc hành động, làm cách nào để chúng tôi quyết định nên chọn tùy chọn nào?
Nếu bạn trả lời “có” cho bất kỳ câu hỏi nào dưới đây, thì bạn nên tạo một Hành động:
- Những người khác sẽ được hưởng lợi từ hành động bạn đang tạo và thực sự sử dụng lại hành động đó chứ?
- Bạn có cần xây dựng logic phức tạp không thể viết bằng shell script không?
- Bạn có định sử dụng bất kỳ thư viện của bên thứ ba nào không?
- Bạn có cần thực hiện lệnh gọi API tới dịch vụ của bên thứ ba không?
- Bạn có khả năng duy trì mã này và phát hành các bản sửa lỗi hoặc cập nhật không?
- Bạn có cần chạy hành động này trên các hệ điều hành khác nhau không?
- Bạn có thành thạo JavaScript nhưng không thành thạo Bash hay PowerShell không?
- Bạn có muốn tìm hiểu làm thế nào để làm cho một?
Hãy tạo hành động của chúng tôi
Chúng tôi sẽ tạo một Hành động sẽ tạo nhận xét bất cứ khi nào yêu cầu kéo được mở trên kho lưu trữ của chúng tôi và thêm nhãn tùy thuộc vào loại tệp đã thay đổi. Nhận xét sẽ chứa một bản tóm tắt các thay đổi được giới thiệu trong yêu cầu kéo.
1. Tạo một kho lưu trữ công cộng trống
Hãy bắt đầu bằng cách tạo một kho lưu trữ GitHub trống có tên: PR-metadata-action
. Đây sẽ là kho lưu trữ mà chúng tôi sẽ sử dụng để lưu trữ Hành động của mình.
Nó phải được công khai, nếu không chúng tôi sẽ không thể sử dụng nó trong quy trình làm việc của mình.
2. Sao chép kho lưu trữ cục bộ và khởi tạo dự án Node
Chuyển đến thư mục mà bạn muốn lưu trữ kho lưu trữ của Hành động. Sau đó, hãy sao chép kho lưu trữ trên máy của chúng tôi:
$ git clone [email protected]:Link-/PR-metadata-action.git
Cloning into 'PR-metadata-action'...
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (4/4), done.
Receiving objects: 100% (4/4), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Bên trong thư mục kho lưu trữ mới được tạo của chúng tôi, hãy khởi tạo một dự án Node.js mới:
$ cd PR-metadata-action/
$ npm init -y
Wrote to /Users/link-/PR-metadata-action/package.json:
{
"name": "pr-metadata-action",
"version": "1.0.0",
"description": "Adds pull request file changes as a comment to a newly opened PR",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Link-/PR-metadata-action.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/Link-/PR-metadata-action/issues"
},
"homepage": "https://github.com/Link-/PR-metadata-action#readme"
}
3. Tạo tệp siêu dữ liệu Hành động
Hãy tạo ra action.yml
. Tệp này rất quan trọng, vì nó sẽ xác định interface
hành động của chúng tôi:
- đầu vào: các tham số chứa dữ liệu mà hành động muốn sử dụng trong thời gian chạy
- đầu ra: dữ liệu mà một hành động thiết lập sau khi thực hiện xong. Lần này chúng ta sẽ không có đầu ra cho hành động của mình.
- chạy: chỉ định thời gian chạy thực thi của hành động, trong trường hợp này sẽ là nút16
Đọc thêm về cú pháp tệp siêu dữ liệu.
name: 'PR Metadata Action'
description: 'Adds pull request file changes as a comment to a newly opened PR'
inputs:
owner:
description: 'The owner of the repository'
required: true
repo:
description: 'The name of the repository'
required: true
pr_number:
description: 'The number of the pull request'
required: true
token:
description: 'The token to use to access the GitHub API'
required: true
runs:
using: 'node16'
main: 'index.js'
4. Thêm các gói bộ công cụ Actions
GitHub đã tạo một bộ công cụ phát triển phần mềm nguồn mở (SDK) sẽ giúp cuộc sống của bạn dễ dàng hơn nhiều khi tạo các hành động.
2 gói chính chúng ta sẽ sử dụng hôm nay là:
-
@actions/core: gói này chứa chức năng cốt lõi của Hành động, chẳng hạn như
context
đối tượng chứa thông tin về lần chạy hiện tại,inputs
đối tượng chứa các tham số của hành động vàoutputs
đối tượng sẽ chứa dữ liệu mà hành động thiết lập sau khi thực hiện xong. -
@actions/github: gói này chứa ứng dụng khách GitHub API REST mà chúng tôi sẽ sử dụng để tương tác với API GitHub.
$ npm install @actions/core
added 3 packages, and audited 4 packages in 1s
found 0 vulnerabilities
$ npm install @actions/github
added 21 packages, and audited 25 packages in 1s
found 0 vulnerabilities
Cấu trúc thư mục của chúng ta bây giờ sẽ trông như thế này:
/Users/link-/PR-metadata-action
├── LICENSE
├── README.md
├── action.yml
├── node_modules
├── package-lock.json
└── package.json
1 directory, 6 files
5. Viết hành động
Tạo ra một .gitignore
tệp rất quan trọng ở giai đoạn này để tránh đẩy các tệp không cần thiết vào kho lưu trữ.
Một công cụ tuyệt vời mà tôi thường xuyên sử dụng là: https://www.toptal.com/developers/gitignore
Của tôi .gitignore
tập tin là:
https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node
Tạo một cái dành riêng cho môi trường và dự án của bạn.
Chúng tôi cuối cùng đã sẵn sàng để tạo ra index.js
tập tin. Đây là nơi tất cả logic của hành động của chúng tôi sẽ được. Chúng tôi chắc chắn có thể có một cấu trúc phức tạp hơn, nhưng bây giờ một tệp sẽ làm được.
Tôi đã nhận xét tất cả mã bên dưới để bạn biết điều gì đang xảy ra từng bước.
const core = require('@actions/core');
const github = require('@actions/github');
const main = async () => {
try {
/**
* We need to fetch all the inputs that were provided to our action
* and store them in variables for us to use.
**/
const owner = core.getInput('owner', { required: true });
const repo = core.getInput('repo', { required: true });
const pr_number = core.getInput('pr_number', { required: true });
const token = core.getInput('token', { required: true });
/**
* Now we need to create an instance of Octokit which will use to call
* GitHub's REST API endpoints.
* We will pass the token as an argument to the constructor. This token
* will be used to authenticate our requests.
* You can find all the information about how to use Octokit here:
* https://octokit.github.io/rest.js/v18
**/
const octokit = new github.getOctokit(token);
/**
* We need to fetch the list of files that were changes in the Pull Request
* and store them in a variable.
* We use octokit.paginate() to automatically loop over all the pages of the
* results.
* Reference: https://octokit.github.io/rest.js/v18#pulls-list-files
*/
const { data: changedFiles } = await octokit.rest.pulls.listFiles({
owner,
repo,
pull_number: pr_number,
});
/**
* Contains the sum of all the additions, deletions, and changes
* in all the files in the Pull Request.
**/
let diffData = {
additions: 0,
deletions: 0,
changes: 0
};
// Reference for how to use Array.reduce():
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
diffData = changedFiles.reduce((acc, file) => {
acc.additions += file.additions;
acc.deletions += file.deletions;
acc.changes += file.changes;
return acc;
}, diffData);
/**
* Loop over all the files changed in the PR and add labels according
* to files types.
**/
for (const file of changedFiles) {
/**
* Add labels according to file types.
*/
const fileExtension = file.filename.split('.').pop();
switch(fileExtension) {
case 'md':
await octokit.rest.issues.addLabels({
owner,
repo,
issue_number: pr_number,
labels: ['markdown'],
});
case 'js':
await octokit.rest.issues.addLabels({
owner,
repo,
issue_number: pr_number,
labels: ['javascript'],
});
case 'yml':
await octokit.rest.issues.addLabels({
owner,
repo,
issue_number: pr_number,
labels: ['yaml'],
});
case 'yaml':
await octokit.rest.issues.addLabels({
owner,
repo,
issue_number: pr_number,
labels: ['yaml'],
});
}
}
/**
* Create a comment on the PR with the information we compiled from the
* list of changed files.
*/
await octokit.rest.issues.createComment({
owner,
repo,
issue_number: pr_number,
body: `
Pull Request #${pr_number} has been updated with: \n
- ${diffData.changes} changes \n
- ${diffData.additions} additions \n
- ${diffData.deletions} deletions \n
`
});
} catch (error) {
core.setFailed(error.message);
}
}
// Call the main function to run the action
main();
6. Đẩy các tệp Hành động của chúng tôi lên GitHub
Hãy tạo giai đoạn, cam kết và đẩy các tệp của chúng tôi lên nhánh chính ngược dòng:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
action.yml
index.js
package-lock.json
package.json
nothing added to commit but untracked files present (use "git add" to track)
Hãy thêm tất cả các tệp sẽ được dàn dựng:
$ git add .
Bây giờ chúng ta có thể cam kết các thay đổi của mình:
$ git commit -m "Add main action structure"
[main 1fc5d18] Add main action structure
5 files changed, 686 insertions(+)
create mode 100644 .gitignore
create mode 100644 action.yml
create mode 100644 index.js
create mode 100644 package-lock.json
create mode 100644 package.json
Và đẩy các thay đổi của chúng tôi:
$ git push origin main
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 16 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 5.82 KiB | 5.82 MiB/s, done.
Total 7 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:Link-/PR-metadata-action.git
457fee2..1fc5d18 main -> main
7. Cách kiểm tra Hành động của chúng tôi
Để chúng tôi có thể kiểm tra hành động của mình, chúng tôi cần tạo một gói. Nếu bạn nhận thấy ở bước trước, chúng tôi đã không đẩy node_modules
thư mục chứa các gói chúng tôi đã sử dụng để xây dựng index.js
tập tin.
Hành động của chúng tôi sẽ không chạy nếu không có các gói đó! Để khắc phục điều này, chúng ta có thể sử dụng một công cụ hay có tên là ncc. Nó sẽ giúp chúng tôi tạo một tệp bao gồm mã của chúng tôi và tất cả các gói chúng tôi cần để chạy hành động của mình.
Hãy bắt đầu bằng cách cài đặt ncc
:
$ npm install @vercel/ncc
added 1 package, and audited 26 packages in 5s
found 0 vulnerabilities
Biên dịch JavaScript của chúng tôi đơn giản như chạy:
$ ncc build index.js -o dist
ncc: Version 0.22.1
ncc: Compiling file index.js
530kB dist/index.js
530kB [845ms] - ncc 0.22.1
Điều này sẽ tạo ra một thư mục mới gọi là dist
và tạo một tệp có tên index.js
chứa mã của chúng tôi và tất cả các gói chúng tôi cần để chạy hành động của mình.
Bây giờ chúng ta cần đảm bảo rằng chúng ta action.yml
tập tin chứa chính xác runs
tiết diện. Bạn cần thay thế:
runs:
using: 'node16'
main: 'index.js'
với:
runs:
using: 'node16'
main: 'dist/index.js'
Hãy đẩy các thay đổi của chúng tôi ngược dòng một lần nữa (đến kho lưu trữ GitHub của chúng tôi). Hãy chắc chắn rằng chúng tôi dist/
thư mục không có trong .gitignore
tập tin:
$ git status
$ git add .
$ git commit -m "Add compiled action"
[main adfc4f0] Add compiled action
4 files changed, 8505 insertions(+), 3 deletions(-)
create mode 100644 dist/index.js
$ git push origin main
Cuối cùng thì chúng ta cũng đã sẵn sàng để tạo quy trình làm việc của mình! Tạo một quy trình công việc mới trong cùng kho lưu trữ hoặc trong bất kỳ kho lưu trữ nào khác (công khai hay riêng tư không quan trọng) như sau:
mkdir -p .github/workflows
touch .github/workflows/pr-metadata.yaml
Sao chép quy trình công việc sau vào của chúng tôi pr-metadata.yaml
tập tin:
name: PR metadata annotation
on:
pull_request:
types: [opened, reopened, synchronize]
jobs:
annotate-pr:
runs-on: ubuntu-latest
name: Annotates pull request with metadata
steps:
- name: Annotate PR
uses: link-/[email protected]
with:
owner: ${{ github.repository_owner }}
repo: ${{ github.event.repository.name }}
pr_number: ${{ github.event.number }}
token: ${{ secrets.GITHUB_TOKEN }}
Khi bạn hoàn thành tất cả các bước này, kho lưu trữ của chúng ta sẽ trông như thế này:
Để chúng tôi kiểm tra quy trình công việc này, chúng tôi cần thực hiện thay đổi trong kho lưu trữ của mình và tạo Yêu cầu kéo (PR). Chúng ta có thể làm điều này bằng cách chỉnh sửa README.md
tệp trực tiếp trên GitHub:
Các phương pháp hay nhất về hành động GitHub
Cuối cùng, tôi muốn chia sẻ với bạn một số phương pháp hay nhất khi tạo Hành động tùy chỉnh:
-
Áp dụng nguyên tắc trách nhiệm duy nhất. Đảm bảo hành động của bạn làm một việc duy nhất. Nó sẽ làm cho mã của bạn dễ bảo trì hơn và dễ kiểm tra hơn.
-
Hãy suy nghĩ kỹ về giao diện hành động của bạn (đầu vào và đầu ra). Giữ cho giao diện của bạn đơn giản và rõ ràng bằng cách giảm số lượng đầu vào tùy chọn.
-
Chúng tôi đã không làm điều đó trong hướng dẫn này, nhưng bạn cần phải xác thực đầu vào của hành động của bạn! Phần lớn các dự án bảo mật có thể bị loại bỏ bằng cách xác thực đầu vào.
-
Hãy chắc chắn rằng bạn hành động là idempotent, nghĩa là nếu bạn chạy hành động nhiều lần theo trình tự thì kết quả sẽ luôn giống nhau. Trong trường hợp của chúng tôi, hành động sẽ thực thi và đăng nhận xét và thêm nhãn hoặc hành động đó sẽ thoát ra một cách duyên dáng.
-
Đọc và tuân theo các thực tiễn tốt nhất về tăng cường bảo mật được ghi lại trong các Tài liệu GitHub này.
-
Không tạo hành động mới nếu bạn không thể duy trì hành động đó. Tìm kiếm các hành động tương tự trên thị trường và sử dụng chúng để thay thế.
Phần kết luận
Đối với hướng dẫn này, chúng tôi đã tạo một hành động tùy chỉnh nhận xét tóm tắt các thay đổi trong Yêu cầu kéo và thêm nhãn cho các loại tệp đã được sửa đổi.
Bạn sẽ có thể sử dụng lại các bước này để tạo các hành động phức tạp hơn có thể làm được nhiều hơn thế!
Tôi đang trong quá trình tạo một khóa học DevOps toàn diện bằng GitHub Actions. Nếu bạn đang tìm kiếm thông tin chuyên sâu hơn về cách bạn có thể sử dụng Hành động để tích hợp liên tục, Phân phối liên tục hoặc gitOps (trong số nhiều chủ đề khác), hãy theo dõi các video sau:
Mã hóa vui vẻ!