-
Notifications
You must be signed in to change notification settings - Fork 0
278 lines (242 loc) · 10.1 KB
/
Copy pathtag-git.yml
File metadata and controls
278 lines (242 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# ==============================================================================
# GitHub Workflow: Tag Repository
#
# Description:
# Creates a Git tag (annotated or lightweight) for a specified commit or ref.
# Supports optional deletion of existing tags (locally and remotely) and
# conditional authentication using a Personal Access Token (GH_PAT).
#
# Typical Use Cases:
# - CI/CD pipelines creating tags for versioning, test results, or promotions
# - Conditional tagging based on input parameters
#
# Features:
# - Supports both lightweight and annotated tags
# - Optional dry-run mode that avoids any side effects
# - Optional forced tag overwrite (locally and remotely)
# - Conditional use of GH_PAT for write access to protected branches/repos
#
# Inputs:
# - action: Optional. Action to perform, either `create` or `delete` (default: `create`)
# - tag_name: Required. Name of the tag to create (e.g. `1.2.3/CI/check/PASS`)
# - ref: Optional. Commit SHA or reference to tag (defaults to current HEAD)
# - message: Optional. Message for annotated tag (if provided)
# - token: Optional. Token to use for pushing tag (defaults to GITHUB_TOKEN)
# - force: Optional. Whether to delete existing tags before tagging
# - dry_run: Optional. If true, simulate actions without changing repo state
#
# Requirements:
# - Caller must pass this workflow using `workflow_call`
#
# Behavior:
# - Resolves the ref to a commit SHA
# - Logs normalized inputs and decisions
# - Handles dry-run, force deletion, annotated/lightweight tag creation and tag deletion
# - Pushes the tag to the remote repository using appropriate credentials
#
# ==============================================================================
name: Tag Repository
on:
workflow_call:
inputs:
action:
description: "Action to perform: 'create' or (silently) 'delete' tag"
required: false
type: string
default: "create"
tag_name:
description: "Tag to create or delete (e.g. 1.2.3/CI/preflight-check/PASS) - delete if 'delete' is true"
required: true
type: string
ref:
description: "Commit SHA or ref to create or delete tag for (default: current commit)"
required: false
type: string
message:
description: "Message for annotated tag (optional, triggers annotated tag if set, ignored for delete)"
required: false
type: string
token:
description: "Token to use for pushing tag - defaults to GITHUB_TOKEN, but can be set to a Personal Access Token (PAT) if needed"
required: false
type: string
force:
description: "If true, force tag by deleting any existing tag with the same name (local & remote) - ignored for delete"
required: false
type: boolean
dry_run:
description: "If true, run in dry-run mode (no actual changes made)"
required: false
type: boolean
default: false
jobs:
# -----------------------------------------------
# Tagging Job
# -----------------------------------------------
tag:
runs-on: ubuntu-latest
env:
ACTION: ${{ inputs.action }}
TAG_NAME: ${{ inputs.tag_name }}
REF: ${{ inputs.ref }}
TAG_MESSAGE: ${{ inputs.message }}
GH_TOKEN: ${{ inputs.token || secrets.GITHUB_TOKEN }}
FORCE: ${{ inputs.force }}
DRY_RUN: ${{ inputs.dry_run }}
steps:
- name: Log params
run: |
echo "ACTION: $ACTION"
echo "TAG_NAME: $TAG_NAME"
echo "REF: $REF"
echo "TAG_MESSAGE: $TAG_MESSAGE"
echo "FORCE: $FORCE"
echo "DRY_RUN: $DRY_RUN"
- name: Normalize inputs
run: |
echo "🔧 Normalizing inputs..."
# Validate tag name
if [ -z "$TAG_NAME" ]; then
echo "❌ Error: 'tag_name' input cannot be empty."
exit 1
fi
# Default ref to HEAD if not provided
echo "REF=${REF:-HEAD}" >> "$GITHUB_ENV"
# Normalize ACTION to 'create' if invalid
if [[ "$ACTION" != "create" && "$ACTION" != "delete" ]]; then
echo "ACTION=create" >> "$GITHUB_ENV"
fi
# Check if TOKEN is empty and set to GITHUB_TOKEN if not provided
if [ -z "$GH_TOKEN" ]; then
echo "GH_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> "$GITHUB_ENV"
else
echo "GH_TOKEN=$GH_TOKEN" >> "$GITHUB_ENV"
fi
# Normalize boolean flags
for var in FORCE DRY_RUN; do
val="${!var}"
case "$val" in
true|false) echo "$var=$val" >> "$GITHUB_ENV" ;;
*) echo "$var=false" >> "$GITHUB_ENV" ;;
esac
done
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Update permissions for scripts
run: find ./devops/scripts -type f -name '*.sh' -exec chmod +x {} +
- name: Set up git identity
run: |
source ./devops/scripts/bootstrap.sh
log INFO "Setting up git identity for tagging."
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Set up authenticated remote
run: |
source ./devops/scripts/bootstrap.sh
log INFO "Configuring remote origin with provided token"
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
- name: Determine commit to create or delete tag for
id: commit
env:
REF: ${{ env.REF }}
run: |
source ./devops/scripts/bootstrap.sh
# Function to resolve a commit SHA from a ref, branch, or tag
resolve_sha() {
local ref="$1"
if [[ "$ref" == refs/tags/* ]]; then
git fetch --tags --force
local tag="${ref##refs/tags/}"
git rev-parse "$tag"
else
git rev-parse "$ref"
fi
}
if [ -n "$REF" ]; then
sha=$(resolve_sha "$REF")
log INFO "Using provided ref $REF (resolved as $sha)."
else
sha=$(git rev-parse HEAD)
log INFO "No ref provided, defaulting to HEAD ($sha)."
fi
if [ -z "$sha" ]; then
log ERROR "Could not resolve a valid commit SHA. Exiting."
exit 1
fi
echo "sha=$sha" >> $GITHUB_OUTPUT
- name: Check for dry-run mode
if: ${{ env.DRY_RUN == 'true' }}
run: |
source ./devops/scripts/bootstrap.sh
log INFO "Dry-run enabled. Resolved SHA is '${{ steps.commit.outputs.sha }}', but no tag operations will be performed."
- name: Force delete existing tag if needed
if: ${{ env.ACTION == 'create' && env.FORCE == 'true' && env.DRY_RUN != 'true' }}
run: |
source ./devops/scripts/bootstrap.sh
if git tag | grep -q "^$TAG_NAME$"; then
log WARN "Tag '$TAG_NAME' already exists locally. Deleting local tag."
git tag -d "$TAG_NAME" || log WARN "Local tag delete failed or tag already gone"
fi
log INFO "Checking if tag '$TAG_NAME' exists on remote."
if git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME$"; then
log WARN "Tag '$TAG_NAME' exists on remote. Deleting remote tag."
git push --delete origin "$TAG_NAME" || log WARN "Remote tag delete failed or tag already gone"
git fetch --tags --prune
fi
- name: Create tag
if: ${{ env.DRY_RUN != 'true' && env.ACTION == 'create' }}
run: |
source ./devops/scripts/bootstrap.sh
SHA="${{ steps.commit.outputs.sha }}"
if git rev-parse --verify "refs/tags/$TAG_NAME" >/dev/null 2>&1; then
log WARN "Tag '$TAG_NAME' already exists. Skipping tag creation."
else
if [ -n "$TAG_MESSAGE" ]; then
log INFO "Creating annotated tag '$TAG_NAME' at $SHA with message: $TAG_MESSAGE"
git tag -a "$TAG_NAME" "$SHA" -m "$TAG_MESSAGE"
else
log INFO "Creating lightweight tag '$TAG_NAME' at $SHA."
git tag "$TAG_NAME" "$SHA"
fi
fi
- name: Push tag to remote
if: ${{ env.DRY_RUN != 'true' && env.ACTION == 'create' }}
run: |
source ./devops/scripts/bootstrap.sh
if git tag | grep -q "^$TAG_NAME$"; then
log INFO "Preparing to push tag '$TAG_NAME' to origin."
if [ "$FORCE" = "true" ]; then
log INFO "Pushing tag '$TAG_NAME' with --force"
git push --force origin "$TAG_NAME"
else
log INFO "Pushing tag '$TAG_NAME'"
git push origin "$TAG_NAME"
fi
else
log ERROR "Tag '$TAG_NAME' does not exist, nothing to push."
exit 1
fi
- name: Delete tag
if: ${{ env.DRY_RUN != 'true' && env.ACTION == 'delete' }}
run: |
source ./devops/scripts/bootstrap.sh
log INFO "Deleting tag '$TAG_NAME' at SHA ${{ steps.commit.outputs.sha }}"
# Delete local tag if it exists
if git tag | grep -q "^$TAG_NAME$"; then
log INFO "Deleting local tag '$TAG_NAME'"
git tag -d "$TAG_NAME" || log WARN "Failed to delete local tag"
else
log INFO "Local tag '$TAG_NAME' does not exist"
fi
# Delete remote tag if it exists
if git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME$"; then
log INFO "Remote tag '$TAG_NAME' exists. Deleting..."
git push --delete origin "$TAG_NAME" || log WARN "Failed to delete remote tag"
# Delete all local tags before fetch to avoid clobber errors
git tag -l | xargs -r git tag -d
git fetch --tags --prune
else
log INFO "Remote tag '$TAG_NAME' does not exist"
fi