Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7ea601b13 | ||
|
|
e567759c06 | ||
|
|
6cad836ce3 | ||
|
|
30f5dce8dc | ||
|
|
c5fadfb99b | ||
|
|
c9c92c160c | ||
|
|
d23910c487 | ||
|
|
c67a5676ec | ||
|
|
ec66233a2d | ||
|
|
d1cd0ba644 | ||
|
|
10ad862c83 | ||
|
|
0f4ad188a8 | ||
|
|
d1ee8a2b84 | ||
|
|
20053504ef | ||
|
|
869250588b | ||
|
|
86fd1119a5 | ||
|
|
0ea080d95e | ||
|
|
c3b1823603 | ||
|
|
a2b53a9f84 | ||
|
|
e5f97f6ae2 | ||
|
|
982d30a0d5 | ||
|
|
56691b629b | ||
|
|
47acc7ce83 | ||
|
|
69e1b8a553 | ||
|
|
3f717330cc | ||
|
|
be812d40f8 | ||
|
|
08a5282891 | ||
|
|
4c841fd937 | ||
|
|
dade5e1316 | ||
|
|
61c9013fe0 | ||
|
|
7730f56c49 | ||
|
|
e706ad7c5a | ||
|
|
ae8127d5c2 | ||
|
|
9df8b8dcaa | ||
|
|
4a6a3c159d | ||
|
|
8cbe2e3e4d | ||
|
|
075bc62138 | ||
|
|
4a2c9d6064 | ||
|
|
17721ae9d0 | ||
|
|
9929c05469 | ||
|
|
a078f2d984 | ||
|
|
9068f5e5b6 | ||
|
|
e40a7adad6 | ||
|
|
214ec5f7bd | ||
|
|
829c119657 | ||
|
|
2ac7efe907 | ||
|
|
74e3e758c9 | ||
|
|
3e32b71abe | ||
|
|
a8eaa2a293 | ||
|
|
f6925cc5e7 | ||
|
|
2daf39673a | ||
|
|
f3afba6e49 | ||
|
|
c5b69ac380 | ||
|
|
963fa24ce0 | ||
|
|
93402e99ee | ||
|
|
ac72d18016 | ||
|
|
70cac9e740 |
@@ -0,0 +1,177 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"role": "Rex",
|
||||||
|
"location": "Dockerfile:4",
|
||||||
|
"suggestion": "移除 `apk add` 命令中的 `--no-check-certificate` 旗標。禁用憑證檢查會使 Docker 映像檔的建置過程容易受到中間人攻擊,導致惡意套件注入。請確保套件來源的信任鏈完整性。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"role": "Aria",
|
||||||
|
"location": "a/Dockerfile",
|
||||||
|
"suggestion": "Dockerfile 檔案結尾應包含一個換行符,以符合 POSIX 規範並避免某些工具處理時發生問題。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"role": "Aria",
|
||||||
|
"location": "a/entrypoint.sh",
|
||||||
|
"suggestion": "Shell 腳本檔案結尾應包含一個換行符,以符合 POSIX 規範並避免某些工具處理時發生問題。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "entrypoint.sh",
|
||||||
|
"suggestion": "此腳本缺少單元測試。建議引入一個 shell 腳本測試框架(例如 `bats` 或 `shunit2`),並為 `is_empty_or_null`、`require_value`、`require_integer` 等輔助函數以及主要邏輯流程編寫單元測試,以確保其行為符合預期。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "entrypoint.sh",
|
||||||
|
"suggestion": "此腳本與外部 Gitea API 互動,但缺少整合測試。建議建立整合測試,使用模擬 Gitea 伺服器(或測試環境)來驗證腳本的端到端流程,包括成功刪除、錯誤處理(例如無效的 RUNNER_TOKEN、API 錯誤響應)以及邊界條件。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Rex",
|
||||||
|
"location": "entrypoint.sh:100",
|
||||||
|
"suggestion": "環境變數 `GITEA_SERVER_URL` 被直接用於 `curl` 命令中,以構建 API 請求。如果此變數可被攻擊者控制,可能導致伺服器端請求偽造 (SSRF) 漏洞,使程式向任意內部或外部主機發送請求。建議對 `GITEA_SERVER_URL` 進行嚴格的驗證,確保其指向預期且受信任的 Gitea 實例,例如使用白名單限制允許的網域或 IP 範圍。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Aria",
|
||||||
|
"location": "a/Dockerfile:6",
|
||||||
|
"suggestion": "`COPY` 指令的縮排不一致,建議保持統一的縮排風格(例如,與 `RUN` 指令對齊),以提高程式碼可讀性。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Aria",
|
||||||
|
"location": "a/entrypoint.sh",
|
||||||
|
"suggestion": "腳本中存在不一致的縮排風格,建議統一使用 2 或 4 個空格進行縮排,以提高程式碼可讀性。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Aria",
|
||||||
|
"location": "a/entrypoint.sh:4-20",
|
||||||
|
"suggestion": "參數檢查和錯誤處理邏輯重複且分散,建議將常見的驗證和日誌輸出邏輯抽象為輔助函數,以提高程式碼的模組化和可維護性。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Aria",
|
||||||
|
"location": "a/entrypoint.sh",
|
||||||
|
"suggestion": "腳本中所有變數都使用大寫命名,這可能導致環境變數與腳本內部變數難以區分。建議對腳本內部使用的變數採用小寫加底線 (snake_case) 命名,而環境變數則保持大寫,以增強命名語義的清晰度。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "entrypoint.sh:75",
|
||||||
|
"suggestion": "在取得成品資訊的 `curl` 請求中,雖然使用了 `-fsS`,但並未明確檢查 HTTP 狀態碼。如果 Gitea API 返回 401/403 等認證或權限錯誤,腳本可能會因為 `jq` 處理空或錯誤 JSON 而失敗,但錯誤訊息不夠明確。建議在 `curl` 請求後檢查 HTTP 狀態碼,特別是對於認證相關的錯誤,提供更清晰的錯誤提示。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "entrypoint.sh:58",
|
||||||
|
"suggestion": "對於 `KEEP_COUNT` 參數,雖然 `require_integer` 確保了非負整數,但建議在整合測試中特別包含 `KEEP_COUNT=0`(應刪除所有成品)和 `KEEP_COUNT=1`(應保留最新一個成品)的測試案例,以確保這些邊界條件下的刪除邏輯正確無誤。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "entrypoint.sh:75, 76, 100",
|
||||||
|
"suggestion": "腳本中有多處 `curl` 和 `jq` 的調用,雖然 `set -Eeuo pipefail` 有助於錯誤處理,但對於網路瞬時錯誤或 API 服務不穩定,腳本會直接退出。建議考慮為 `curl` 請求添加重試機制,並為 `curl` 和 `jq` 的失敗提供更具體的錯誤處理邏輯,例如捕獲錯誤並輸出詳細訊息,而不是僅依賴 `pipefail`。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"role": "Leo",
|
||||||
|
"location": "entrypoint.sh",
|
||||||
|
"suggestion": "雖然程式碼的可讀性已大幅提升,但考慮為新引入的輔助函式(如 `separator`, `section`, `info`, `success`, `warn`, `fail`, `is_empty_or_null`, `require_value`, `require_integer`)添加簡要的註解,說明其用途,這將有助於新開發者快速理解。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"role": "Rex",
|
||||||
|
"location": "entrypoint.sh:116",
|
||||||
|
"suggestion": "從 JSON 回應中提取的值(如 `release_id`、`release_tag`、`release_name`)被用於構建 URL 和日誌訊息。儘管 `jq` 和 shell 引用提供了一定保護,但如果 JSON 回應本身不可信或被惡意篡改,這些值仍可能被用於注入惡意資料。建議對這些提取的值進行額外的驗證或淨化(例如,確保 `release_id` 是整數,`release_tag` 和 `release_name` 符合預期模式),尤其是在將它們用於關鍵操作或日誌記錄之前。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "entrypoint.sh:54",
|
||||||
|
"suggestion": "對於 `GITEA_SERVER_URL` 參數,除了檢查是否為空或 'null',建議增加基本的 URL 格式驗證,以確保其為有效的 URL 格式,避免 `curl` 在無效 URL 時產生非預期的行為。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "entrypoint.sh:55",
|
||||||
|
"suggestion": "對於 `GITEA_REPOSITORY` 參數,建議增加格式驗證,確保其符合 `owner/repo` 的預期格式,避免因格式錯誤導致 API 請求失敗。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Zara",
|
||||||
|
"location": "entrypoint.sh:67",
|
||||||
|
"suggestion": "儘管已引入分頁機制,但腳本仍將所有分頁的發布資訊(`release_json`)聚合到記憶體中。對於擁有極大量發布(例如數萬個)的儲存庫,這可能導致記憶體使用量過高,進而影響效能或導致記憶體不足。建議考慮在處理大量資料時,避免將所有資料一次性載入記憶體,例如只提取必要的欄位(如ID和時間戳)進行排序和篩選,或在可能的情況下,利用API的伺服器端排序和過濾功能來減少客戶端處理的負擔。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Aria",
|
||||||
|
"location": "Dockerfile",
|
||||||
|
"suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Aria",
|
||||||
|
"location": "entrypoint.sh",
|
||||||
|
"suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Aria",
|
||||||
|
"location": "tests/entrypoint_pagination.sh",
|
||||||
|
"suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"role": "Leo",
|
||||||
|
"location": "tests/entrypoint_pagination.sh",
|
||||||
|
"suggestion": "測試中 `jq` 的模擬實作(`jq.py`)雖然有效,但它重新實作了 `jq` 的部分功能。如果 `entrypoint.sh` 中 `jq` 的使用方式變得更複雜,這個模擬可能需要頻繁更新或變得脆弱。未來可以考慮使用更通用的方式來模擬 `jq` 的行為,例如透過環境變數或檔案輸入/輸出來控制其行為,而不是重新實作其邏輯。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"role": "Zara",
|
||||||
|
"location": "entrypoint.sh",
|
||||||
|
"suggestion": "腳本中頻繁呼叫 `jq` 命令(例如在循環中或多次處理同一變數時),每次呼叫都會啟動一個新的 `jq` 處理程序,這會產生一定的處理程序啟動開銷。雖然對於一般情況影響不大,但在處理大量資料或需要極致效能時,可以考慮減少 `jq` 的呼叫次數,例如將多個 `jq` 操作合併為一個更複雜的表達式,或使用其他語言(如Python)來處理JSON,以減少處理程序間的上下文切換成本。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "tests/entrypoint_pagination.sh:137",
|
||||||
|
"suggestion": "腳本執行時將標準輸出重定向到 `/dev/null`。為了更全面地驗證錯誤處理邏輯,應捕獲標準錯誤輸出 (stderr) 並檢查 `fail` 函數輸出的錯誤訊息是否符合預期,同時驗證腳本在錯誤情況下是否以非零狀態碼退出。",
|
||||||
|
"reason": "false positive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"role": "Maya",
|
||||||
|
"location": "Dockerfile",
|
||||||
|
"suggestion": "考慮新增一個整合測試,該測試會建置 Docker 映像檔,並在容器內部執行 `entrypoint.sh`。這將有助於驗證所有必要的依賴項(如 bash, curl, jq)是否已正確安裝,並且腳本在目標容器環境中能正常執行,確保部署的穩定性。",
|
||||||
|
"reason": "false positive"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
name: CD
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
version:
|
version:
|
||||||
name: "CD > 計算版本號"
|
name: 計算版本號
|
||||||
runs-on: ubuntu
|
runs-on: ubuntu
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.version.outputs.version }}
|
version: ${{ steps.version.outputs.version }}
|
||||||
@@ -13,14 +14,14 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
uses: https://gitea.jsc.idv.tw/actions/calculate-version@${{ vars.ACTION_CALCULATE_VERSION }}
|
uses: https://gitea.jsc.idv.tw/actions/calculate-version@${{ vars.ACTION_CALCULATE_VERSION }}
|
||||||
release:
|
release:
|
||||||
name: "CD > 發布專案"
|
name: 發布專案
|
||||||
runs-on: ubuntu
|
runs-on: ubuntu
|
||||||
needs: version
|
needs: version
|
||||||
steps:
|
steps:
|
||||||
- name: 發布專案
|
- name: 發布專案
|
||||||
uses: akkuman/gitea-release-action@${{ vars.ACTION_RELEASE_VERSION }}
|
uses: akkuman/gitea-release-action@${{ vars.ACTION_RELEASE_VERSION }}
|
||||||
with:
|
with:
|
||||||
tag_name: "v${{ needs.version.outputs.version }}"
|
tag_name: v${{ needs.version.outputs.version }}
|
||||||
- name: 清理成品
|
- name: 清理成品
|
||||||
uses: https://gitea.jsc.idv.tw/actions/cleanup-release@${{ vars.ACTION_CLEANUP_RELEASE_VERSION }}
|
uses: https://gitea.jsc.idv.tw/actions/cleanup-release@${{ vars.ACTION_CLEANUP_RELEASE_VERSION }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: AI
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches-ignore:
|
||||||
|
- master
|
||||||
|
types: [opened, synchronize]
|
||||||
|
jobs:
|
||||||
|
code-review:
|
||||||
|
name: Code Review
|
||||||
|
runs-on: ubuntu
|
||||||
|
steps:
|
||||||
|
- name: AI Code Review
|
||||||
|
uses: https://gitea.jsc.idv.tw/actions/code-review@${{ vars.ACTION_CODE_REVIEW_VERSION }}
|
||||||
|
with:
|
||||||
|
GITEA_TOKEN: ${{ secrets.RUNNER_TOKEN }}
|
||||||
|
GITEA_COMMENT_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }},${{ secrets.GEMINI_API_KEY_1 }},${{ secrets.GEMINI_API_KEY_2 }},${{ secrets.GEMINI_API_KEY_3 }},${{ secrets.GEMINI_API_KEY_4 }},${{ secrets.GEMINI_API_KEY_5 }},${{ secrets.GEMINI_API_KEY_6 }},${{ secrets.GEMINI_API_KEY_7 }},${{ secrets.GEMINI_API_KEY_8 }},${{ secrets.GEMINI_API_KEY_9 }},${{ secrets.GEMINI_API_KEY_10 }},${{ secrets.GEMINI_API_KEY_11 }},${{ secrets.GEMINI_API_KEY_12 }},${{ secrets.GEMINI_API_KEY_13 }},${{ secrets.GEMINI_API_KEY_14 }},${{ secrets.GEMINI_API_KEY_15 }},${{ secrets.GEMINI_API_KEY_16 }},${{ secrets.GEMINI_API_KEY_17 }},${{ secrets.GEMINI_API_KEY_18 }},${{ secrets.GEMINI_API_KEY_19 }}
|
||||||
|
GEMINI_BASE_URL: https://generativelanguage.googleapis.com/v1beta
|
||||||
|
GEMINI_MODEL: ${{ vars.GEMINI_MODEL }}
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
+1
-4
@@ -1,11 +1,8 @@
|
|||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
# 更新並安裝必要的工具
|
RUN apk add --no-cache --no-check-certificate bash curl jq
|
||||||
RUN apk update \
|
|
||||||
&& apk add --no-cache bash curl jq
|
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
+1
-1
@@ -13,5 +13,5 @@ runs:
|
|||||||
env:
|
env:
|
||||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
RUNNER_TOKEN: ${{ inputs.RUNNER_TOKEN || secrets.RUNNER_TOKEN }}
|
RUNNER_TOKEN: ${{ inputs.RUNNER_TOKEN || secrets.GITEA_TOKEN || secrets.RUNNER_TOKEN }}
|
||||||
KEEP_COUNT: ${{ inputs.KEEP_COUNT }}
|
KEEP_COUNT: ${{ inputs.KEEP_COUNT }}
|
||||||
+160
-64
@@ -1,81 +1,177 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
echo "=================================================="
|
separator() {
|
||||||
|
printf '\n%s\n' '=================================================='
|
||||||
|
}
|
||||||
|
|
||||||
echo "參數檢查"
|
section() {
|
||||||
|
separator
|
||||||
|
printf '%s\n' "$1"
|
||||||
|
printf '%s\n' '--------------------------------------------------'
|
||||||
|
}
|
||||||
|
|
||||||
echo "--------------------------------------------------"
|
info() {
|
||||||
|
printf '[INFO] %s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
# 顯示 GITEA_SERVER_URL 參數,並檢查是否為空或 "null",如果是則輸出錯誤訊息並退出
|
success() {
|
||||||
echo "GITEA_SERVER_URL=$GITEA_SERVER_URL" && ([ -z "$GITEA_SERVER_URL" ] || [ "$GITEA_SERVER_URL" = "null" ]) && exit 1
|
printf '[OK] %s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
# 顯示 GITEA_REPOSITORY 參數,並檢查是否為空或 "null",如果是則輸出錯誤訊息並退出
|
warn() {
|
||||||
echo "GITEA_REPOSITORY=$GITEA_REPOSITORY" && ([ -z "$GITEA_REPOSITORY" ] || [ "$GITEA_REPOSITORY" = "null" ]) && exit 1
|
printf '[WARN] %s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
# 顯示 RUNNER_TOKEN 參數
|
fail() {
|
||||||
echo "RUNNER_TOKEN=$RUNNER_TOKEN"
|
printf '[ERR] %s\n' "$1" >&2
|
||||||
|
}
|
||||||
|
|
||||||
# 顯示 KEEP_COUNT 參數,並檢查是否為空或 "null",如果是則輸出錯誤訊息並退出
|
is_empty_or_null() {
|
||||||
echo "KEEP_COUNT=$KEEP_COUNT" && ([ -z "$KEEP_COUNT" ] || [ "$KEEP_COUNT" = "null" ]) && exit 1
|
[ -z "${1:-}" ] || [ "${1:-}" = "null" ]
|
||||||
|
}
|
||||||
|
|
||||||
echo "=================================================="
|
require_value() {
|
||||||
|
local name="$1"
|
||||||
|
local value="$2"
|
||||||
|
|
||||||
echo "取得成品資訊"
|
info "$name=$value"
|
||||||
|
if is_empty_or_null "$value"; then
|
||||||
|
fail "$name is required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
echo "--------------------------------------------------"
|
require_integer() {
|
||||||
|
local name="$1"
|
||||||
|
local value="$2"
|
||||||
|
|
||||||
RELEASE_URL="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases"
|
if ! [[ "$value" =~ ^[0-9]+$ ]]; then
|
||||||
|
fail "$name must be a non-negative integer"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# 檢查是否為空或 "null"
|
fetch_all_pages() {
|
||||||
if [ -n "$RUNNER_TOKEN" ] && [ "$RUNNER_TOKEN" != "null" ]; then
|
local base_url="$1"
|
||||||
# 組合 RELEASE_HEADER 參數,並顯示出來
|
local all_json='[]'
|
||||||
RELEASE_HEADER="Authorization: token $RUNNER_TOKEN" && echo "RELEASE_HEADER=$RELEASE_HEADER"
|
local page=1
|
||||||
|
local page_url page_json
|
||||||
|
|
||||||
# 取得成品資訊
|
while :; do
|
||||||
RELEASE_JSON="$(curl -s -H "$RELEASE_HEADER" "$RELEASE_URL")"
|
page_url="$base_url?page=$page"
|
||||||
else
|
page_json="$(curl -fsS "${auth_header[@]}" "$page_url")"
|
||||||
# 取得成品資訊
|
|
||||||
RELEASE_JSON="$(curl -s "$RELEASE_URL")"
|
if [ "$(jq 'length' <<<"$page_json")" -eq 0 ]; then
|
||||||
|
break
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 計算成品數量並依照時間排序
|
all_json="$(jq -s 'add' <<<"$all_json"$'\n'"$page_json")"
|
||||||
RELEASE_JSON=$(echo "$RELEASE_JSON" | jq -e 'sort_by(.created_at) | reverse')
|
page=$((page + 1))
|
||||||
RELEASE_COUNT=$(echo "$RELEASE_JSON" | jq 'length') && echo "RELEASE_COUNT=$RELEASE_COUNT"
|
|
||||||
|
|
||||||
# 檢查成品數量是否需要清除
|
|
||||||
[ $RELEASE_COUNT -le $KEEP_COUNT ] && exit 0
|
|
||||||
|
|
||||||
echo "=================================================="
|
|
||||||
|
|
||||||
echo "刪除舊版本的成品"
|
|
||||||
|
|
||||||
echo "--------------------------------------------------"
|
|
||||||
|
|
||||||
RELEASE_TO_DELETE=$(echo "$RELEASE_JSON" | jq -c ".[$KEEP_COUNT:]")
|
|
||||||
|
|
||||||
echo "$RELEASE_TO_DELETE" | jq -c '.[]' | while IFS= read -r RELEASE_URL; do
|
|
||||||
# 檢查是否有成品連結
|
|
||||||
([ -z "$RELEASE_URL" ] || [ "$RELEASE_URL" == "null" ]) && continue
|
|
||||||
|
|
||||||
# 取得成品資訊
|
|
||||||
RELEASE_ID=$(echo "$RELEASE_URL" | jq -r '.id')
|
|
||||||
RELEASE_TAG=$(echo "$RELEASE_URL" | jq -r '.tag_name')
|
|
||||||
RELEASE_NAME=$(echo "$RELEASE_URL" | jq -r '.name')
|
|
||||||
|
|
||||||
# 檢查成品編號是否正確
|
|
||||||
([ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" == "null" ]) && continue
|
|
||||||
|
|
||||||
# 刪除成品
|
|
||||||
RELEASE_URL="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases/$RELEASE_ID"
|
|
||||||
|
|
||||||
RELEASE_DELETE_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "$RELEASE_HEADER" "$RELEASE_URL")
|
|
||||||
|
|
||||||
# 檢查刪除成品是否成功
|
|
||||||
if [ "$RELEASE_DELETE_CODE" -eq 204 ]; then
|
|
||||||
echo "✅ 成功刪除成品: $RELEASE_TAG ($RELEASE_NAME)"
|
|
||||||
else
|
|
||||||
echo "❌ 刪除成品失敗: $RELEASE_TAG ($RELEASE_NAME), HTTP 狀態碼: $RELEASE_DELETE_CODE"
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "=================================================="
|
printf '%s' "$all_json"
|
||||||
|
}
|
||||||
|
|
||||||
|
section "參數檢查"
|
||||||
|
require_value "GITEA_SERVER_URL" "$GITEA_SERVER_URL"
|
||||||
|
require_value "GITEA_REPOSITORY" "$GITEA_REPOSITORY"
|
||||||
|
require_value "KEEP_COUNT" "$KEEP_COUNT"
|
||||||
|
require_integer "KEEP_COUNT" "$KEEP_COUNT"
|
||||||
|
|
||||||
|
if is_empty_or_null "${RUNNER_TOKEN:-}"; then
|
||||||
|
warn "RUNNER_TOKEN is empty; release API calls will be anonymous"
|
||||||
|
else
|
||||||
|
info "RUNNER_TOKEN=[redacted]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
release_api_url="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases"
|
||||||
|
auth_header=()
|
||||||
|
|
||||||
|
if ! is_empty_or_null "${RUNNER_TOKEN:-}"; then
|
||||||
|
auth_header=(-H "Authorization: token $RUNNER_TOKEN")
|
||||||
|
fi
|
||||||
|
|
||||||
|
section "取得成品資訊"
|
||||||
|
info "GET $release_api_url"
|
||||||
|
|
||||||
|
release_json="$(fetch_all_pages "$release_api_url")"
|
||||||
|
release_json="$(jq -e 'sort_by(.created_at) | reverse' <<<"$release_json")"
|
||||||
|
release_count="$(jq 'length' <<<"$release_json")"
|
||||||
|
|
||||||
|
info "RELEASE_COUNT=$release_count"
|
||||||
|
info "KEEP_COUNT=$KEEP_COUNT"
|
||||||
|
|
||||||
|
if [ "$release_count" -le "$KEEP_COUNT" ]; then
|
||||||
|
success "沒有需要清理的舊版本成品"
|
||||||
|
else
|
||||||
|
section "刪除舊版本成品"
|
||||||
|
|
||||||
|
release_to_delete="$(jq -c ".[$KEEP_COUNT:]" <<<"$release_json")"
|
||||||
|
|
||||||
|
while IFS= read -r release_item; do
|
||||||
|
[ -z "$release_item" ] && continue
|
||||||
|
|
||||||
|
release_id="$(jq -r '.id' <<<"$release_item")"
|
||||||
|
release_tag="$(jq -r '.tag_name' <<<"$release_item")"
|
||||||
|
release_name="$(jq -r '.name' <<<"$release_item")"
|
||||||
|
|
||||||
|
if is_empty_or_null "$release_id"; then
|
||||||
|
warn "略過沒有 id 的成品: $release_tag ($release_name)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
delete_url="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases/$release_id"
|
||||||
|
info "DELETE $release_tag ($release_name)"
|
||||||
|
|
||||||
|
delete_code="$(curl -sS -o /dev/null -w "%{http_code}" -X DELETE "${auth_header[@]}" "$delete_url")"
|
||||||
|
|
||||||
|
if [ "$delete_code" -eq 204 ]; then
|
||||||
|
success "成功刪除: $release_tag ($release_name)"
|
||||||
|
else
|
||||||
|
fail "刪除失敗: $release_tag ($release_name), HTTP $delete_code"
|
||||||
|
fi
|
||||||
|
done < <(jq -c '.[]' <<<"$release_to_delete")
|
||||||
|
fi
|
||||||
|
|
||||||
|
section "刪除未指定 release 的 tag"
|
||||||
|
|
||||||
|
# 重新取得 release 清單,得到刪除舊版本後仍指定 tag 的成品
|
||||||
|
current_release_json="$(fetch_all_pages "$release_api_url")"
|
||||||
|
release_tags_json="$(jq -c '[.[].tag_name]' <<<"$current_release_json")"
|
||||||
|
|
||||||
|
tag_api_url="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/tags"
|
||||||
|
info "GET $tag_api_url"
|
||||||
|
|
||||||
|
tag_json="$(fetch_all_pages "$tag_api_url")"
|
||||||
|
tag_count="$(jq 'length' <<<"$tag_json")"
|
||||||
|
info "TAG_COUNT=$tag_count"
|
||||||
|
|
||||||
|
while IFS= read -r tag_item; do
|
||||||
|
[ -z "$tag_item" ] && continue
|
||||||
|
|
||||||
|
tag_name="$(jq -r '.name' <<<"$tag_item")"
|
||||||
|
|
||||||
|
if is_empty_or_null "$tag_name"; then
|
||||||
|
warn "略過沒有名稱的 tag"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(jq --arg name "$tag_name" 'any(.[]; . == $name)' <<<"$release_tags_json")" = "true" ]; then
|
||||||
|
info "保留指定 release 的 tag: $tag_name"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
delete_url="$tag_api_url/$tag_name"
|
||||||
|
info "DELETE tag $tag_name"
|
||||||
|
|
||||||
|
delete_code="$(curl -sS -o /dev/null -w "%{http_code}" -X DELETE "${auth_header[@]}" "$delete_url")"
|
||||||
|
|
||||||
|
if [ "$delete_code" -eq 204 ]; then
|
||||||
|
success "成功刪除未指定 release 的 tag: $tag_name"
|
||||||
|
else
|
||||||
|
fail "刪除 tag 失敗: $tag_name, HTTP $delete_code"
|
||||||
|
fi
|
||||||
|
done < <(jq -c '.[]' <<<"$tag_json")
|
||||||
|
|
||||||
|
separator
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_root="$(cd "$script_dir/.." && pwd)"
|
||||||
|
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmpdir"' EXIT
|
||||||
|
|
||||||
|
curl_log="$tmpdir/curl.log"
|
||||||
|
bin_dir="$tmpdir/bin"
|
||||||
|
mkdir -p "$bin_dir"
|
||||||
|
|
||||||
|
cat >"$bin_dir/curl" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
log_file="${CURL_LOG:?}"
|
||||||
|
printf '%s\n' "$*" >>"$log_file"
|
||||||
|
|
||||||
|
last_arg="${!#}"
|
||||||
|
|
||||||
|
if [[ " $* " == *" -X DELETE "* ]]; then
|
||||||
|
printf '204'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# tags 端點沒有需要清理的 tag
|
||||||
|
if [[ "$last_arg" == */tags?* ]]; then
|
||||||
|
printf '%s' '[]'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$last_arg" in
|
||||||
|
*/releases?*page=1)
|
||||||
|
printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]'
|
||||||
|
;;
|
||||||
|
*/releases?*page=2)
|
||||||
|
printf '%s' '[{"id":2,"tag_name":"v2","name":"release-2","created_at":"2024-02-01T00:00:00Z"},{"id":1,"tag_name":"v1","name":"release-1","created_at":"2024-01-01T00:00:00Z"}]'
|
||||||
|
;;
|
||||||
|
*/releases?*page=3)
|
||||||
|
printf '%s' '[]'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'unexpected request: %s\n' "$last_arg" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
chmod +x "$bin_dir/curl"
|
||||||
|
|
||||||
|
cp "$script_dir/lib/jq_mock.py" "$bin_dir/jq.py"
|
||||||
|
chmod +x "$bin_dir/jq.py"
|
||||||
|
|
||||||
|
cat >"$bin_dir/jq" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
exec python3 "$0.py" "$@"
|
||||||
|
EOF
|
||||||
|
chmod +x "$bin_dir/jq"
|
||||||
|
|
||||||
|
PATH="$bin_dir:$PATH" \
|
||||||
|
CURL_LOG="$curl_log" \
|
||||||
|
GITEA_SERVER_URL="https://gitea.example.test" \
|
||||||
|
GITEA_REPOSITORY="owner/repo" \
|
||||||
|
KEEP_COUNT="2" \
|
||||||
|
RUNNER_TOKEN="" \
|
||||||
|
bash "$repo_root/entrypoint.sh" >/dev/null
|
||||||
|
|
||||||
|
grep -q 'page=1' "$curl_log"
|
||||||
|
grep -q 'page=2' "$curl_log"
|
||||||
|
grep -q 'page=3' "$curl_log"
|
||||||
|
grep -q '/releases/2' "$curl_log"
|
||||||
|
grep -q '/releases/1' "$curl_log"
|
||||||
|
|
||||||
|
if grep -q '/releases/3' "$curl_log" || grep -q '/releases/4' "$curl_log"; then
|
||||||
|
printf 'unexpected release deletion request in %s\n' "$curl_log" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q '/tags/' "$curl_log"; then
|
||||||
|
printf 'unexpected tag deletion request in %s\n' "$curl_log" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_root="$(cd "$script_dir/.." && pwd)"
|
||||||
|
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmpdir"' EXIT
|
||||||
|
|
||||||
|
curl_log="$tmpdir/curl.log"
|
||||||
|
bin_dir="$tmpdir/bin"
|
||||||
|
mkdir -p "$bin_dir"
|
||||||
|
|
||||||
|
cat >"$bin_dir/curl" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
log_file="${CURL_LOG:?}"
|
||||||
|
printf '%s\n' "$*" >>"$log_file"
|
||||||
|
|
||||||
|
last_arg="${!#}"
|
||||||
|
|
||||||
|
if [[ " $* " == *" -X DELETE "* ]]; then
|
||||||
|
printf '204'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 倉庫所有 tag:v1~v4 加上兩個未指定 release 的孤兒 tag
|
||||||
|
if [[ "$last_arg" == */tags?* ]]; then
|
||||||
|
case "$last_arg" in
|
||||||
|
*page=1)
|
||||||
|
printf '%s' '[{"name":"v4"},{"name":"v3"},{"name":"v2"},{"name":"v1"},{"name":"tmp-build"},{"name":"v0"}]'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s' '[]'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# /releases GET:刪除舊版本後重新取得時,只回傳仍保留的 release
|
||||||
|
if grep -Eq -- '-X DELETE .*/releases/[0-9]' "$log_file"; then
|
||||||
|
case "$last_arg" in
|
||||||
|
*page=1)
|
||||||
|
printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s' '[]'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$last_arg" in
|
||||||
|
*page=1)
|
||||||
|
printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]'
|
||||||
|
;;
|
||||||
|
*page=2)
|
||||||
|
printf '%s' '[{"id":2,"tag_name":"v2","name":"release-2","created_at":"2024-02-01T00:00:00Z"},{"id":1,"tag_name":"v1","name":"release-1","created_at":"2024-01-01T00:00:00Z"}]'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s' '[]'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
chmod +x "$bin_dir/curl"
|
||||||
|
|
||||||
|
cp "$script_dir/lib/jq_mock.py" "$bin_dir/jq.py"
|
||||||
|
chmod +x "$bin_dir/jq.py"
|
||||||
|
|
||||||
|
cat >"$bin_dir/jq" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
exec python3 "$0.py" "$@"
|
||||||
|
EOF
|
||||||
|
chmod +x "$bin_dir/jq"
|
||||||
|
|
||||||
|
PATH="$bin_dir:$PATH" \
|
||||||
|
CURL_LOG="$curl_log" \
|
||||||
|
GITEA_SERVER_URL="https://gitea.example.test" \
|
||||||
|
GITEA_REPOSITORY="owner/repo" \
|
||||||
|
KEEP_COUNT="2" \
|
||||||
|
RUNNER_TOKEN="" \
|
||||||
|
bash "$repo_root/entrypoint.sh" >/dev/null
|
||||||
|
|
||||||
|
# 舊版本 release 仍被刪除
|
||||||
|
grep -q '/releases/2' "$curl_log"
|
||||||
|
grep -q '/releases/1' "$curl_log"
|
||||||
|
|
||||||
|
# 未指定 release 的 tag(含被刪除 release 留下的 tag 與其他孤兒 tag)被刪除
|
||||||
|
for tag in v2 v1 tmp-build v0; do
|
||||||
|
if ! grep -q "/tags/$tag" "$curl_log"; then
|
||||||
|
printf 'expected tag deletion /tags/%s not found in %s\n' "$tag" "$curl_log" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 仍指定 release 的 tag 不可被刪除
|
||||||
|
for tag in v4 v3; do
|
||||||
|
if grep -q "/tags/$tag" "$curl_log"; then
|
||||||
|
printf 'unexpected deletion of release-assigned tag /tags/%s in %s\n' "$tag" "$curl_log" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Minimal jq stand-in for entrypoint.sh tests.
|
||||||
|
|
||||||
|
Only the jq expressions used by entrypoint.sh are implemented. Unknown
|
||||||
|
expressions fail loudly so new jq usage is noticed instead of silently
|
||||||
|
producing wrong results.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
expr = ""
|
||||||
|
flags = set()
|
||||||
|
named_args = {}
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
while args:
|
||||||
|
current = args.pop(0)
|
||||||
|
if current in {"-e", "-c", "-r", "-s"}:
|
||||||
|
flags.add(current)
|
||||||
|
continue
|
||||||
|
if current == "--arg":
|
||||||
|
key = args.pop(0)
|
||||||
|
value = args.pop(0)
|
||||||
|
named_args[key] = value
|
||||||
|
continue
|
||||||
|
if current == "--":
|
||||||
|
if not args:
|
||||||
|
raise SystemExit("missing jq expression")
|
||||||
|
expr = args.pop(0)
|
||||||
|
break
|
||||||
|
if current.startswith("-"):
|
||||||
|
flags.add(current)
|
||||||
|
continue
|
||||||
|
expr = current
|
||||||
|
break
|
||||||
|
|
||||||
|
raw = sys.stdin.read()
|
||||||
|
|
||||||
|
|
||||||
|
def dump(value):
|
||||||
|
if "-r" in flags and isinstance(value, (str, int, float)) and not isinstance(value, bool):
|
||||||
|
sys.stdout.write(str(value))
|
||||||
|
else:
|
||||||
|
sys.stdout.write(json.dumps(value, separators=(",", ":")))
|
||||||
|
|
||||||
|
|
||||||
|
if expr == "length":
|
||||||
|
data = json.loads(raw or "null")
|
||||||
|
print(len(data))
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
if expr == "add":
|
||||||
|
arrays = [json.loads(line) for line in raw.splitlines() if line.strip()]
|
||||||
|
merged = []
|
||||||
|
for item in arrays:
|
||||||
|
merged.extend(item)
|
||||||
|
dump(merged)
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
if expr == "sort_by(.created_at) | reverse":
|
||||||
|
data = json.loads(raw or "[]")
|
||||||
|
dump(sorted(data, key=lambda item: item["created_at"], reverse=True))
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
if expr == "[.[].tag_name]":
|
||||||
|
data = json.loads(raw or "[]")
|
||||||
|
dump([item["tag_name"] for item in data])
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
match = re.fullmatch(r"any\(\.\[\]; \. == \$(\w+)\)", expr)
|
||||||
|
if match:
|
||||||
|
data = json.loads(raw or "[]")
|
||||||
|
needle = named_args[match.group(1)]
|
||||||
|
sys.stdout.write("true" if any(item == needle for item in data) else "false")
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
match = re.fullmatch(r"\.\[(\d+):\]", expr)
|
||||||
|
if match:
|
||||||
|
data = json.loads(raw or "[]")
|
||||||
|
dump(data[int(match.group(1)):])
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
if expr == ".[]":
|
||||||
|
data = json.loads(raw or "[]")
|
||||||
|
for item in data:
|
||||||
|
dump(item)
|
||||||
|
sys.stdout.write("\n")
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
match = re.fullmatch(r"\.(id|tag_name|name)", expr)
|
||||||
|
if match:
|
||||||
|
data = json.loads(raw or "null")
|
||||||
|
value = data.get(match.group(1))
|
||||||
|
dump(value)
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
raise SystemExit(f"unsupported jq expression: {expr}")
|
||||||
Reference in New Issue
Block a user