├── .gitignore ├── assets ├── copy.png ├── sync.png ├── new-secret.png └── settings-actions-secrets.png ├── copy.sh ├── sync.sh ├── LICENSE ├── .github └── workflows │ ├── sync.yml │ └── copy.yml ├── status.sh ├── README.md └── exec.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | run_logs/ -------------------------------------------------------------------------------- /assets/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikrong/sync-docker-image/HEAD/assets/copy.png -------------------------------------------------------------------------------- /assets/sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikrong/sync-docker-image/HEAD/assets/sync.png -------------------------------------------------------------------------------- /copy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=`dirname $0` 4 | cd $SCRIPT_DIR 5 | 6 | ./exec.sh copy $@ -------------------------------------------------------------------------------- /sync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=`dirname $0` 4 | cd $SCRIPT_DIR 5 | 6 | ./exec.sh sync $@ -------------------------------------------------------------------------------- /assets/new-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikrong/sync-docker-image/HEAD/assets/new-secret.png -------------------------------------------------------------------------------- /assets/settings-actions-secrets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikrong/sync-docker-image/HEAD/assets/settings-actions-secrets.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 krong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: Sync Docker Images 2 | run-name: Sync ${{ inputs.source_repo }} to ${{ inputs.destination_scope }} 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | source: 7 | description: '镜像源 (Registry)' 8 | required: true 9 | default: 'docker.io' 10 | destination: 11 | description: '目标源 (Registry)' 12 | required: true 13 | default: 'registry.cn-beijing.aliyuncs.com' 14 | source_repo: 15 | description: '仓库 (格式 repo)' 16 | required: true 17 | default: '' 18 | destination_scope: 19 | description: '目标Scope (格式 scope)' 20 | required: true 21 | default: '' 22 | 23 | jobs: 24 | sync: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Sync Docker Images 28 | uses: ikrong/docker-sync-action@main 29 | with: 30 | source: ${{ github.event.inputs.source }} 31 | source-credential: ${{ secrets.SOURCE_CREDENTIAL }} 32 | destination: ${{ github.event.inputs.destination }} 33 | destination-credential: ${{ secrets.DESTINATION_CREDENTIAL }} 34 | sync: "${{ github.event.inputs.source_repo }} ${{ github.event.inputs.destination_scope }}" -------------------------------------------------------------------------------- /.github/workflows/copy.yml: -------------------------------------------------------------------------------- 1 | name: Copy Docker Image 2 | run-name: Copy ${{ inputs.source_repo }} to ${{ inputs.destination_repo }} 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | source: 7 | description: '镜像源 (Registry)' 8 | required: true 9 | default: 'docker.io' 10 | destination: 11 | description: '目标源 (Registry)' 12 | required: true 13 | default: 'registry.cn-beijing.aliyuncs.com' 14 | source_repo: 15 | description: '仓库及标签 (格式 repo:tag)' 16 | required: true 17 | default: '' 18 | destination_repo: 19 | description: '目标仓库及标签 (格式 repo:tag)' 20 | required: true 21 | default: '' 22 | 23 | jobs: 24 | copy: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Copy Docker Images 28 | uses: ikrong/docker-sync-action@main 29 | with: 30 | source: ${{ github.event.inputs.source }} 31 | source-credential: ${{ secrets.SOURCE_CREDENTIAL }} 32 | destination: ${{ github.event.inputs.destination }} 33 | destination-credential: ${{ secrets.DESTINATION_CREDENTIAL }} 34 | copy: "${{ github.event.inputs.source_repo }} ${{ github.event.inputs.destination_repo }}" -------------------------------------------------------------------------------- /status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=`dirname $0` 4 | cd $SCRIPT_DIR 5 | 6 | function select_option() { 7 | OLDIFS="$IFS" 8 | IFS="|" 9 | local description=$1 10 | local options=($2) 11 | local selected=0 12 | local option_length=${#options[@]} 13 | 14 | function show_select() { 15 | clear 16 | echo "$description" 17 | local i=0 18 | for option in "${options[@]}"; do 19 | if [ $i -eq $selected ]; then 20 | echo -e "\033[32m● $option\033[0m" 21 | else 22 | echo -e "○ $option" 23 | fi 24 | i=$((i + 1)) 25 | done 26 | } 27 | 28 | while true;do 29 | show_select 30 | read -rs -n 1 31 | case "$REPLY" in 32 | A) selected=$((selected-1)) ;; 33 | B) selected=$((selected+1)) ;; 34 | "") break ;; 35 | esac 36 | if [ $selected -lt 0 ]; then 37 | selected=$((option_length - 1)) 38 | elif [ $selected -ge ${#options[@]} ]; then 39 | selected=0 40 | fi 41 | done 42 | 43 | SELECTED_INDEX=$selected 44 | } 45 | 46 | select_option "Select a workflow to view running state:" "Show copy.yml running state|Show sync.yml running state" 47 | 48 | clear 49 | 50 | echo "Waiting..." 51 | 52 | if [ $SELECTED_INDEX -eq 0 ]; then 53 | ./exec.sh status -w copy.yml 54 | elif [ $SELECTED_INDEX -eq 1 ]; then 55 | ./exec.sh status -w sync.yml 56 | fi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 同步DockerHub上的镜像仓库到阿里云容器镜像仓库 2 | 3 | Docker 的一些服务所在域名被封杀,无法直接访问和拉取镜像。国内的镜像源又宣布停止服务,所以需要一个工具将DockerHub上的镜像同步到阿里云容器镜像仓库。 4 | 5 | 阿里云容器镜像仓库提供了个人实例服务,支持最多创建300个仓库,而且免费。个人使用完全够满足需求。 6 | 7 | 阿里云容器镜像仓库地址: [https://cr.console.aliyun.com/](https://cr.console.aliyun.com/) 8 | 9 | 支持用命令行触发workflow运行,[点此查看方法](#使用命令行直接同步镜像) 10 | 11 | ## Copy.yml 运行介绍 12 | 13 | 这个工具主要是将 DockerHub 上某个仓库下的某个标签同步到阿里云容器镜像仓库。 14 | 15 | 1. 使用阿里云开通个人实例服务,并获取 [登录用户名和固定密码](https://cr.console.aliyun.com/cn-hangzhou/instance/credentials) 16 | 17 | 2. 克隆本仓库,在仓库设置中配置阿里云密码,注意 *Name* 必须为 `DESTINATION_CREDENTIAL` 且内容格式必须为 `:` 即用户名和密码之间用冒号分隔。 18 | 19 | ![配置密码页面](assets/settings-actions-secrets.png) 20 | 21 | ![配置内容](assets/new-secret.png) 22 | 23 | 3. 在 *Actions* 页面上选择 *copy.yml* 点击 *Run workflow* 填写内容即可运行。 24 | 25 | ![Run Copy workflow](assets/copy.png) 26 | 27 | > 填写说明: 28 | > 29 | > 如同步 DockerHub 上的 nginx:1.13 到 阿里云容器镜像仓库 registry.cn-beijing.aliyuncs.com/ikrong/nginx:1.13,则填写如下: 30 | > 31 | > ```yaml 32 | > # 镜像源 (Registry) 33 | > source: docker.io 34 | > # 目标源 (Registry) 35 | > destination: registry.cn-beijing.aliyuncs.com 36 | > # 仓库及标签 (格式 repo:tag) 37 | > source_repo: nginx:1.13 38 | > # 目标仓库及标签 (格式 repo:tag) 39 | > destination_repo: ikrong/nginx:1.13 40 | > ``` 41 | > 必须要填写仓库及标签 42 | 43 | ## Sync.yml 运行介绍 44 | 45 | 这个工具主要是将 DockerHub 上某个仓库下的所有标签全部同步到阿里云容器镜像仓库。 46 | 47 | 1. 配置密码同上 48 | 49 | 2. 在 *Actions* 页面上选择 *sync.yml* 点击 *Run workflow* 填写内容即可运行。 50 | 51 | ![RUN Sync workflow](assets/sync.png) 52 | 53 | > 填写说明: 54 | > 55 | > 如同步 DockerHub 上的 nginx 的所有标签到阿里云容器镜像仓库 registry.cn-beijing.aliyuncs.com/ikrong/nginx,则填写如下: 56 | > 57 | > ```yaml 58 | > # 镜像源 (Registry) 59 | > source: docker.io 60 | > # 目标源 (Registry) 61 | > destination: registry.cn-beijing.aliyuncs.com 62 | > # 仓库 (格式 repo) 63 | > source_repo: nginx 64 | > # 目标Scope (格式 scope) 65 | > destination_scope: ikrong 66 | > ``` 67 | > 只需要填写需要同步的仓库和目标仓库所在的scope 68 | 69 | 70 | ## 使用命令行直接同步镜像 71 | 72 | 现在提供脚本 ```exec.sh``` 可以在linux或者macos上运行,下面介绍运行方法: 73 | 74 | 1. 命令行上基于 [github-cli](https://github.com/cli/cli) 实现的,所以需要先安装 github-cli 工具 75 | 76 | ```shell 77 | # 快速安装方法 78 | curl -sS https://webi.sh/gh | sh 79 | # 或者可以查看 github-cli 文档自己下载安装 80 | # https://github.com/cli/cli?#installation 81 | ``` 82 | 83 | 2. 安装 github-cli 后需要登陆 84 | 85 | ```shell 86 | # 登陆命令 87 | gh auth login 88 | ``` 89 | 90 | 3. fork本仓库,并且按照 [上面copy.yml中密码相关配置](#copyyml-运行介绍) 进行配置 91 | 92 | 4. 使用git clone你fork后的仓库,然后开始执行根目录下的 exec.sh 文件,注意文件的执行权限 93 | 94 | 5. 命令行运行 copy.yml workflow 95 | 96 | 以将 nginx:1.13 复制到 registry.cn-beijing.aliyuncs.com/ikrong/nginx:1.13 仓库为例 97 | 98 | ```shell 99 | # 命令行如下: 100 | ./exec.sh trigger -w copy.yml destination=registry.cn-beijing.aliyuncs.com source_repo=nginx:1.13 destination_repo=ikrong/nginx:1.13 101 | # 可以省略等号前面的,但是顺序不能变 102 | ./exec.sh trigger -w copy.yml registry.cn-beijing.aliyuncs.com nginx:1.13 ikrong/nginx:1.13 103 | # 由于脚本默认 registry.cn-beijing.aliyuncs.com ,所以这个也可以省略 104 | ./exec.sh trigger -w copy.yml nginx:1.13 ikrong/nginx:1.13 105 | # 另外 trigger -w copy.yml 可以简写为 copy,所以命令可以改为 106 | ./exec.sh copy nginx:1.13 ikrong/nginx:1.13 107 | 108 | # 查看运行状态,不过上面的 trigger 命令执行时会自动输出 status,下面的命令一般不需要执行 109 | ./exec.sh status -w copy.yml 110 | ``` 111 | 112 | 6. 命令行运行 sync.yml workflow 113 | 114 | 以将 nginx 同步到 registry.cn-beijing.aliyuncs.com/ikrong/nginx 仓库为例 115 | 116 | ```shell 117 | # 命令行如下 118 | ./exec.sh trigger -w sync.yml destination=registry.cn-beijing.aliyuncs.com source_repo=nginx destination_scope=ikrong 119 | # 仍然可以省略等号前面的 120 | ./exec.sh trigger -w sync.yml nginx ikrong 121 | # 另外 trigger -w sync.yml 可以简写为 sync,所以命令可以改为 122 | ./exec.sh sync nginx ikrong 123 | ``` 124 | 125 | 7. 推荐使用命令 126 | 127 | ```shell 128 | # 如果想要复制1个标签,如 nginx:1.13 到 registry.cn-beijing.aliyuncs.com/ikrong/nginx:1.13 129 | # 则可以使用命令 130 | ./exec.sh copy nginx:1.13 ikrong/nginx:1.13 131 | 132 | # 如果想要同步某个仓库,如 nginx 到 registry.cn-beijing.aliyuncs.com/ikrong/nginx 仓库 133 | # 则可以使用命令 134 | ./exec.sh sync nginx ikrong 135 | ``` 136 | 137 | 8. 为了减少记忆负担,再次简化 copy 和 sync 命令 138 | 139 | 执行 copy 和 sync 命令时,可以将 registry/scope/repo:tag 写在一起,更符合常见的用法 140 | 141 | 不过,由于 sync 命令特殊,源仓库的 tag 和目标仓库的 repo:tag 将会被忽略掉 142 | 143 | 同时,增加 ./copy.sh 和 ./sync.sh 两个脚本,内部调用 ./exec.sh 144 | 145 | ```shell 146 | # 想要复制某个镜像标签,可以直接这样执行命令 147 | ./exec.sh copy ghcr.io/nginx:1.13 registry.cn-hangzhou.aliyuncs.com/ikrong/nginx:1.13 148 | ./exec.sh copy nginx:1.13 registry.cn-hangzhou.aliyuncs.com/ikrong/nginx:1.13 149 | # 想要同步某个仓库,可以直接这样执行命令 150 | ./exec.sh sync ghcr.io/nginx registry.cn-hangzhou.aliyuncs.com/ikrong 151 | ./exec.sh sync ghcr.io/nginx:1.13 registry.cn-hangzhou.aliyuncs.com/ikrong/nginx:1.13 152 | # 指定标签和上面不指定标签无任何区别,脚本会忽略掉后面的标签 153 | 154 | # 使用 ./copy.sh 和 ./sync.sh 命令 155 | ./copy.sh ghcr.io/nginx:1.13 registry.cn-hangzhou.aliyuncs.com/ikrong/nginx:1.13 156 | ./sync.sh nginx registry.cn-hangzhou.aliyuncs.com/ikrong 157 | ``` 158 | 159 | 9. 当使用copy时,可以指定参数 --pull 就可以在 workflow 执行完毕后,自动拉取镜像 160 | 161 | ```shell 162 | ./copy.sh nginx:1.14 ikrong/nginx:1.14 --pull 163 | ``` 164 | 165 | 10. 脚本默认会有确认提示,使用参数 -y 可以跳过确认执行 166 | 167 | ```shell 168 | ./copy.sh nginx:1.14 ikrong/nginx:1.14 -y 169 | ./sync.sh nginx ikrong -y 170 | ``` 171 | 172 | ## 镜像同步之后如何使用 173 | 174 | 当使用上面办法将镜像同步到阿里云容器镜像仓库后,就可以直接使用阿里云容器镜像仓库的镜像了。 175 | 176 | 以 `nginx:1.13` 为例: 177 | 178 | 1. 使用命令拉取 179 | 180 | ```sh 181 | docker pull registry.cn-beijing.aliyuncs.com/ikrong/nginx:1.13 182 | ``` 183 | 184 | 2. 在 `Dockerfile` 中使用: 185 | 186 | ```dockerfile 187 | FROM registry.cn-beijing.aliyuncs.com/ikrong/nginx:1.13 188 | 189 | # 其他内容 190 | ``` 191 | -------------------------------------------------------------------------------- /exec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=`dirname $0` 4 | cd $SCRIPT_DIR 5 | 6 | CMD= 7 | REPO= 8 | WORKFLOW= 9 | BRANCH="main" 10 | INPUTS= 11 | PULL= 12 | CONFIRM= 13 | 14 | # 你可以在这里修改执行的默认值,请保证顺序一致 15 | INPUT_CONFIGS=(docker.io registry.cn-beijing.aliyuncs.com) 16 | 17 | INPUT_SHORT=() 18 | 19 | RUN_ID= 20 | RUN_NUMBER= 21 | 22 | OLDIFS="$IFS" 23 | 24 | function usage() { 25 | echo 26 | echo "Usage: $0 [options]" 27 | echo 28 | echo "COMMAND:" 29 | echo 30 | echo " trigger --repo,-r --branch,-b --workflow,-w [input]=[value]" 31 | echo 32 | echo " copy shortcut for 'trigger -w copy.yml'" 33 | echo " copy --repo,-r --branch,-b [input]=[value]" 34 | echo 35 | echo " sync shortcut for 'trigger -w sync.yml'" 36 | echo " sync --repo,-r --branch,-b [input]=[value]" 37 | echo 38 | echo " status --repo,-r --workflow,-w " 39 | echo 40 | } 41 | 42 | function r() { 43 | echo -e "\033[31m$1\033[0m" 44 | } 45 | 46 | function g() { 47 | echo -e "\033[32m$1\033[0m" 48 | } 49 | 50 | function b() { 51 | echo -e "\033[34m$1\033[0m" 52 | } 53 | 54 | function y() { 55 | echo -e "\033[33m$1\033[0m" 56 | } 57 | 58 | function get_repo() { 59 | command -v git >/dev/null 2>&1 60 | if [ $? -eq 0 ] && [ -z "$REPO" ]; then 61 | REPO=$(git remote get-url origin | awk -F ':' '{print $2}') 62 | REPO=${REPO%.git} 63 | REPO=${REPO/https:\/\//} 64 | fi 65 | } 66 | 67 | function read_copy_config() { 68 | case $1 in 69 | source) 70 | INPUT_CONFIGS[0]=$2 71 | ;; 72 | destination) 73 | INPUT_CONFIGS[1]=$2 74 | ;; 75 | source_repo) 76 | INPUT_CONFIGS[2]=$2 77 | ;; 78 | destination_repo) 79 | INPUT_CONFIGS[3]=$2 80 | ;; 81 | esac 82 | } 83 | 84 | function read_sync_config() { 85 | case $1 in 86 | source) 87 | INPUT_CONFIGS[0]=$2 88 | ;; 89 | destination) 90 | INPUT_CONFIGS[1]=$2 91 | ;; 92 | source_repo) 93 | INPUT_CONFIGS[2]=$2 94 | ;; 95 | destination_scope) 96 | INPUT_CONFIGS[3]=$2 97 | ;; 98 | esac 99 | } 100 | 101 | function check_inputs() { 102 | if [ "$INPUTS" = "" ]; then 103 | r "Error: inputs is not correct" 104 | usage 105 | exit 1 106 | fi 107 | if [ "${INPUT_CONFIGS[0]}" = "" ]; then 108 | r "ERROR: source is required" 109 | exit 1 110 | fi 111 | if [ "${INPUT_CONFIGS[1]}" = "" ]; then 112 | r "ERROR: destination is required" 113 | exit 1 114 | fi 115 | if [ "${INPUT_CONFIGS[2]}" = "" ]; then 116 | r "ERROR: source_repo is required" 117 | exit 1 118 | fi 119 | if [ "${INPUT_CONFIGS[3]}" = "" ] && [ "$WORKFLOW" = "sync.yml" ]; then 120 | r "ERROR: destination_scope is required" 121 | exit 1 122 | fi 123 | if [ "${INPUT_CONFIGS[3]}" = "" ] && [ "$WORKFLOW" = "sync.yml" ]; then 124 | r "ERROR: destination_repo is required" 125 | exit 1 126 | fi 127 | } 128 | 129 | function format_config() { 130 | timestamp=$(date "+%Y-%m-%d %H:%M:%S") 131 | case $WORKFLOW in 132 | copy.yml) 133 | echo "[$timestamp] COPY ${INPUT_CONFIGS[0]}/${INPUT_CONFIGS[2]} ${INPUT_CONFIGS[1]}/${INPUT_CONFIGS[3]} <$1>" 134 | ;; 135 | sync.yml) 136 | echo "[$timestamp] SYNC ${INPUT_CONFIGS[0]}/${INPUT_CONFIGS[2]} ${INPUT_CONFIGS[1]}/${INPUT_CONFIGS[3]} <$1>" 137 | ;; 138 | esac 139 | } 140 | 141 | function copy() { 142 | WORKFLOW="copy.yml" 143 | trigger 144 | } 145 | 146 | function sync() { 147 | WORKFLOW="sync.yml" 148 | trigger 149 | } 150 | 151 | function check_repo() { 152 | local url 153 | local repoSeg 154 | local remain 155 | local seg 156 | local segLength 157 | url=$1 158 | repoSeg=() 159 | repoSeg[0]="" # registry 160 | repoSeg[1]="" # scope 161 | repoSeg[2]="" # repo 162 | repoSeg[3]="" # tag 163 | remain="$url" 164 | if [[ "$url" = *:* ]]; then 165 | IFS=":" 166 | seg=($url) 167 | repoSeg[3]=${seg[1]} 168 | remain=${seg[0]} 169 | fi 170 | if [[ "$remain" = *.*/* ]]; then 171 | IFS="/" 172 | seg=($remain) 173 | repoSeg[0]=${seg[0]} 174 | IFS="/" 175 | remain="${seg[*]:1}" 176 | fi 177 | if [[ "$remain" = */* ]]; then 178 | IFS="/" 179 | seg=($remain) 180 | segLength=${#seg[@]} 181 | repoSeg[2]="${seg[$((segLength-1))]}" 182 | repoSeg[1]="${seg[$((segLength-2))]}" 183 | if [ $segLength -gt 2 ]; then 184 | for ((i=0; i<${segLength}-2; i++)); do 185 | repoSeg[0]="${repoSeg[0]}/${seg[$i]}" 186 | done 187 | fi 188 | else 189 | repoSeg[2]=$remain 190 | fi 191 | IFS="|" 192 | echo "${repoSeg[*]}" 193 | IFS="$OLDIFS" 194 | } 195 | 196 | function get_image_repo() { 197 | IFS="|" 198 | local source_seg=($(check_repo $1)) 199 | local ignore_tag=$2 200 | local start=1 201 | local repo_seg=() 202 | if [ "${source_seg[1]}" = "" ]; then 203 | start=2 204 | fi 205 | for ((i=$start; i<3; i++)); do 206 | if [ "${source_seg[$i]}" != "" ]; then 207 | local index=${#repo_seg[@]} 208 | repo_seg[$index]=${source_seg[$i]} 209 | fi 210 | done 211 | if [ "${source_seg[3]}" = "" ] || [ "$ignore_tag" = "1" ]; then 212 | IFS="/" 213 | echo "${repo_seg[*]}" 214 | else 215 | IFS="/" 216 | echo "${repo_seg[*]}:${source_seg[3]}" 217 | fi 218 | } 219 | 220 | function trigger() { 221 | if [ "$INPUTS" = "" ]; then 222 | if [ ${#INPUT_SHORT[@]} = 2 ]; then 223 | if [ "$WORKFLOW" = "copy.yml" ]; then 224 | local source_seg_str=$(check_repo ${INPUT_SHORT[0]}) 225 | local dest_seg_str=$(check_repo ${INPUT_SHORT[1]}) 226 | IFS="|" 227 | local source_seg=($source_seg_str) 228 | if [ "${source_seg[0]}" != "" ];then 229 | INPUTS="$INPUTS source=${source_seg[0]} source_repo=$(get_image_repo ${INPUT_SHORT[0]})" 230 | else 231 | INPUTS="$INPUTS source_repo=$(get_image_repo ${INPUT_SHORT[0]})" 232 | fi 233 | IFS="|" 234 | local dest_seg=($dest_seg_str) 235 | if [ "${dest_seg[0]}" != "" ];then 236 | INPUTS="$INPUTS destination=${dest_seg[0]} destination_repo=$(get_image_repo ${INPUT_SHORT[1]})" 237 | else 238 | INPUTS="$INPUTS destination_repo=$(get_image_repo ${INPUT_SHORT[1]})" 239 | fi 240 | else 241 | local source_seg_str=$(check_repo ${INPUT_SHORT[0]}) 242 | local dest_seg_str=$(check_repo ${INPUT_SHORT[1]}) 243 | IFS="|" 244 | local source_seg=($source_seg_str) 245 | if [ "${source_seg[0]}" != "" ];then 246 | INPUTS="$INPUTS source=${source_seg[0]} source_repo=$(get_image_repo ${INPUT_SHORT[0]} 1)" 247 | else 248 | INPUTS="$INPUTS source_repo=$(get_image_repo ${INPUT_SHORT[0]} 1)" 249 | fi 250 | IFS="|" 251 | local dest_seg=($dest_seg_str) 252 | if [ "${dest_seg[0]}" != "" ];then 253 | INPUTS="$INPUTS destination=${dest_seg[0]} destination_scope=$( [ "${dest_seg[1]}" != "" ] && echo "${dest_seg[1]}" || echo "${dest_seg[2]}" )" 254 | else 255 | INPUTS="$INPUTS destination_scope=$( [ "${dest_seg[1]}" != "" ] && echo "${dest_seg[1]}" || echo "${dest_seg[2]}" )" 256 | fi 257 | if [ "${source_seg[3]}" != "" ] || [ "${dest_seg[3]}" != "" ]; then 258 | echo "$(y "Repository tag will be ignored in sync workflow")" 259 | fi 260 | fi 261 | fi 262 | if [ ${#INPUT_SHORT[@]} = 3 ]; then 263 | if [ "$WORKFLOW" = "copy.yml" ]; then 264 | INPUTS="destination=${INPUT_SHORT[0]} source_repo=${INPUT_SHORT[1]} destination_repo=${INPUT_SHORT[2]}" 265 | else 266 | INPUTS="destination=${INPUT_SHORT[0]} source_repo=${INPUT_SHORT[1]} destination_scope=${INPUT_SHORT[2]}" 267 | fi 268 | fi 269 | if [ ${#INPUT_SHORT[@]} = 4 ]; then 270 | if [ "$WORKFLOW" = "copy.yml" ]; then 271 | INPUTS="source=${INPUT_SHORT[0]} destination=${INPUT_SHORT[1]} source_repo=${INPUT_SHORT[2]} destination_repo=${INPUT_SHORT[3]}" 272 | else 273 | INPUTS="source=${INPUT_SHORT[0]} destination=${INPUT_SHORT[1]} source_repo=${INPUT_SHORT[2]} destination_scope=${INPUT_SHORT[3]}" 274 | fi 275 | fi 276 | IFS="$OLDIFS" 277 | local inputs=($INPUTS) 278 | INPUTS="${inputs[*]}" 279 | fi 280 | 281 | params= 282 | inputs=($INPUTS) 283 | for INPUT in "${inputs[@]}"; do 284 | KEY=$(echo $INPUT | cut -d '=' -f 1) 285 | VALUE=$(echo $INPUT | cut -d '=' -f 2) 286 | case $WORKFLOW in 287 | sync.yml) 288 | read_sync_config $KEY $VALUE 289 | ;; 290 | copy.yml) 291 | read_copy_config $KEY $VALUE 292 | ;; 293 | esac 294 | done 295 | 296 | if [ "$INPUTS" != "" ]; then 297 | case $WORKFLOW in 298 | sync.yml) 299 | params="-f 'inputs[source]=${INPUT_CONFIGS[0]}' \ 300 | -f 'inputs[destination]=${INPUT_CONFIGS[1]}' \ 301 | -f 'inputs[source_repo]=${INPUT_CONFIGS[2]}' \ 302 | -f 'inputs[destination_scope]=${INPUT_CONFIGS[3]}'" 303 | ;; 304 | copy.yml) 305 | params="-f 'inputs[source]=${INPUT_CONFIGS[0]}' \ 306 | -f 'inputs[destination]=${INPUT_CONFIGS[1]}' \ 307 | -f 'inputs[source_repo]=${INPUT_CONFIGS[2]}' \ 308 | -f 'inputs[destination_repo]=${INPUT_CONFIGS[3]}'" 309 | ;; 310 | esac 311 | fi 312 | 313 | if [ "$REPO" == "" ]; then 314 | echo "$(r "No repository specified, use -repo or -r")" 315 | exit 1 316 | fi 317 | 318 | if [ "$WORKFLOW" == "" ]; then 319 | echo "$(r "No workflow specified, use -workflow or -w")" 320 | exit 1 321 | fi 322 | 323 | check_inputs 324 | 325 | if [ "$INPUTS" != "" ]; then 326 | echo "Triggering workflow $(r $WORKFLOW) on repository $(r $REPO) with branch $(r $BRANCH) and providing the following inputs:" 327 | for ((i=0;i<${#inputs[@]};i++)) { 328 | echo "$(g ${inputs[$i]})" 329 | } 330 | fi 331 | local REPLY 332 | if [ "$CONFIRM" = "true" ]; then 333 | REPLY="y" 334 | else 335 | read -p "Confirm? [Y/n] " 336 | fi 337 | if [[ $REPLY =~ ^[Yy]$ ]] || [ -z $REPLY ]; then 338 | echo "gh api \ 339 | --method POST \ 340 | -H \"Accept: application/vnd.github.v3+json\" \ 341 | -H \"X-GitHub-Api-Version: 2022-11-28\" \ 342 | /repos/$REPO/actions/workflows/$WORKFLOW/dispatches \ 343 | -f \"ref=$BRANCH\" \ 344 | $params 345 | " | sh 346 | 347 | if [ $? -ne 0 ]; then 348 | echo $(r "Failed to trigger workflow") 349 | exit 1 350 | else 351 | echo $(g "Workflow Triggered") 352 | echo $(b "Wait to check workflow running state...") 353 | sleep 15 354 | status 355 | fi 356 | fi 357 | IFS="$OLDIFS" 358 | } 359 | 360 | function get_workflow_runid() { 361 | get_run_id_try=0 362 | get_run_id_cmd="gh api \ 363 | --method GET \ 364 | -H 'Accept: application/vnd.github+json' \ 365 | -H 'X-GitHub-Api-Version: 2022-11-28' \ 366 | /repos/$REPO/actions/workflows/$WORKFLOW/runs \ 367 | -f 'per_page=20'\ 368 | --jq '.workflow_runs | map(select(.conclusion == null)) | .[0] | @text \"\(.id) | \(.run_number)\"' \ 369 | " 370 | result=$(echo "$get_run_id_cmd" | sh) 371 | RUN_ID=($(echo "$result" | cut -d '|' -f1)) 372 | RUN_NUMBER=($(echo "$result" | cut -d '|' -f2)) 373 | if [ "$RUN_ID" = "null" ]; then 374 | RUN_ID="" 375 | fi 376 | if [ "$RUN_NUMBER" = "null" ]; then 377 | RUN_NUMBER="" 378 | fi 379 | while [ "$RUN_ID" == "" ] && [ $get_run_id_try -lt 6 ]; do 380 | clear 381 | echo "Get running id, retring $get_run_id_try/5 times..." 382 | get_run_id_try=$((get_run_id_try+1)) 383 | sleep 5 384 | result=$(echo "$get_run_id_cmd" | sh) 385 | RUN_ID=($(echo "$result" | cut -d '|' -f1)) 386 | RUN_NUMBER=($(echo "$result" | cut -d '|' -f2)) 387 | if [ "$RUN_ID" = "null" ]; then 388 | RUN_ID="" 389 | fi 390 | if [ "$RUN_NUMBER" = "null" ]; then 391 | RUN_NUMBER="" 392 | fi 393 | done 394 | } 395 | 396 | function status() { 397 | if [ "$REPO" == "" ]; then 398 | echo "$(r "No repository specified, use -repo or -r")" 399 | exit 1 400 | fi 401 | 402 | if [ "$WORKFLOW" == "" ]; then 403 | echo "$(r "No workflow specified, use -workflow or -w")" 404 | exit 1 405 | fi 406 | get_workflow_runid 407 | if [ "$RUN_ID" != "" ]; then 408 | status_cmd="gh api \ 409 | --method GET \ 410 | -H 'Accept: application/vnd.github+json' \ 411 | -H 'X-GitHub-Api-Version: 2022-11-28' \ 412 | /repos/$REPO/actions/runs/$RUN_ID \ 413 | --template '{{.status}} | {{.conclusion | truncate 20}} | {{.run_started_at}}' 414 | " 415 | result=$(echo $status_cmd | sh) 416 | status=($(echo "$result" | cut -d '|' -f1)) 417 | conclusion=($(echo "$result"| cut -d '|' -f2)) 418 | time=($(echo "$result" | cut -d '|' -f3)) 419 | while [ "$status" = "in_progress" ] || [ "$status" = "" ] || [ "$conclusion" = "" ]; do 420 | clear 421 | duration=$( [ "$(uname)" = "Linux" ] && echo "$(date -d "$time" "+%s")" || echo "$(date -u -jf "%Y-%m-%dT%H:%M:%SZ" "$time" "+%s")" ) 422 | duration=$(( $(date "+%s") - $duration )) 423 | if [ $duration -ge 120 ]; then 424 | local m=$((duration/60)) 425 | local s=$((duration%60)) 426 | if [ $s -eq 0 ]; then 427 | duration="${m}min" 428 | else 429 | duration="${m}min${s}s" 430 | fi 431 | else 432 | duration="${duration}s" 433 | fi 434 | show_status=$( [ "$conclusion" = "" ] && echo "$status" || echo "$conclusion" ) 435 | echo "Workflow $(g $WORKFLOW) RunID: $(b $RUN_ID) $(g $show_status) $duration" 436 | echo "Open https://github.com/$REPO/actions/runs/$RUN_ID view more detail" 437 | sleep 1 438 | result=$(echo $status_cmd | sh) 439 | status=($(echo "$result" | cut -d '|' -f1)) 440 | conclusion=($(echo "$result"| cut -d '|' -f2)) 441 | done 442 | clear 443 | echo "Workflow $(g $WORKFLOW) has finished with result: $( [ "$conclusion" = "success" ] && echo $(g $conclusion) || echo $(r $conclusion) )" 444 | if [[ "$CMD" = "trigger" || "$CMD" = "copy" || "$CMD" = "sync" ]]; then 445 | format_config $conclusion >> run.log 446 | download_log 447 | fi 448 | echo "Open https://github.com/$REPO/actions/runs/$RUN_ID view more detail" 449 | if [ "$conclusion" = "success" ] && [ "$WORKFLOW" = "copy.yml" ] && [ "$PULL" = "true" ]; then 450 | pull_image 451 | fi 452 | else 453 | echo "No running workflow" 454 | fi 455 | } 456 | 457 | function download_log() { 458 | if [ "$RUN_ID" = "" ]; then 459 | exit 0 460 | fi 461 | gh api \ 462 | -H "Accept: application/vnd.github+json" \ 463 | -H "X-GitHub-Api-Version: 2022-11-28" \ 464 | /repos/$REPO/actions/runs/$RUN_ID/logs > ./$RUN_ID.zip 465 | mkdir -p ./run_logs/ 466 | command -v unzip > /dev/null 2>&1 467 | if [ $? -ne 0 ]; then 468 | mv ./$RUN_ID.zip ./run_logs/$RUN_ID.zip 469 | echo "Log file downloaded to $SCRIPT_DIR/run_logs/$RUN_ID.zip" 470 | exit 0 471 | fi 472 | if [ "$WORKFLOW" = "copy.yml" ]; then 473 | logfile="$SCRIPT_DIR/run_logs/copy-$RUN_NUMBER-$RUN_ID.log" 474 | unzip -p ./$RUN_ID.zip '0_copy.txt' > "$logfile" 475 | echo "Log file downloaded to $logfile" 476 | fi 477 | if [ "$WORKFLOW" = "sync.yml" ]; then 478 | logfile="$SCRIPT_DIR/run_logs/sync-$RUN_NUMBER-$RUN_ID.log" 479 | unzip -p ./$RUN_ID.zip '0_sync.txt' > "$logfile" 480 | echo "Log file downloaded to $logfile" 481 | fi 482 | rm -f ./$RUN_ID.zip 483 | } 484 | 485 | function pull_image() { 486 | command -v docker > /dev/null 2>&1 487 | if [ $? -ne 0 ]; then 488 | echo "You need to install docker first" 489 | else 490 | echo "Pulling image ${INPUT_CONFIGS[1]}/${INPUT_CONFIGS[3]}" 491 | docker pull ${INPUT_CONFIGS[1]}/${INPUT_CONFIGS[3]} 492 | docker tag ${INPUT_CONFIGS[1]}/${INPUT_CONFIGS[3]} ${INPUT_CONFIGS[0]}/${INPUT_CONFIGS[2]} 493 | fi 494 | } 495 | 496 | function main() { 497 | gh auth status -h github.com >> /dev/null 2>&1 498 | if [ $? -ne 0 ]; then 499 | echo 500 | echo $(r "Need login first, you can run with following command:") 501 | echo 502 | echo $(b "gh auth login") 503 | echo 504 | exit 1 505 | fi 506 | get_repo 507 | $CMD 508 | } 509 | 510 | command -v gh > /dev/null 2>&1 511 | if [ $? -ne 0 ]; then 512 | echo 513 | echo $(r "You need to install github cli tool.") 514 | echo $(r "Run with following command to install: ") 515 | echo 516 | echo $(g "curl -sS https://webi.sh/gh | sh") 517 | echo 518 | echo $(g "Visit https://github.com/cli/cli?#installation for more install instructions.") 519 | echo 520 | exit 1 521 | fi 522 | 523 | while [ $# -gt 0 ]; do 524 | case $1 in 525 | --repo | -r) 526 | shift 527 | REPO=$1 528 | ;; 529 | --branch | -b) 530 | shift 531 | BRANCH=$1 532 | ;; 533 | --workflow | -w) 534 | shift 535 | WORKFLOW=$1 536 | ;; 537 | --pull) 538 | PULL="true" 539 | ;; 540 | -y | --y | --yes) 541 | CONFIRM="true" 542 | ;; 543 | trigger) 544 | CMD="trigger" 545 | ;; 546 | status) 547 | CMD="status" 548 | ;; 549 | copy) 550 | CMD="copy" 551 | ;; 552 | sync) 553 | CMD="sync" 554 | ;; 555 | *=*) 556 | INPUTS="$INPUTS $1 " 557 | ;; 558 | *) 559 | INPUT_SHORT+=($1) 560 | ;; 561 | esac 562 | shift 563 | done 564 | 565 | if [ "$CMD" = "" ] || [[ "$CMD" != "trigger" && "$CMD" != "status" && "$CMD" != "copy" && "$CMD" != "sync" ]]; then 566 | usage 567 | exit 1 568 | fi 569 | 570 | main --------------------------------------------------------------------------------