Repository navigation
Expand file tree
/
Copy pathfunctions-jira.sh
More file actions
executable file
·421 lines (345 loc) · 12.7 KB
/
Copy pathfunctions-jira.sh
File metadata and controls
executable file
·421 lines (345 loc) · 12.7 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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
#!/usr/bin/env bash
# Reusable Jira-specific functions for shell scripts
# --- Interactive Template Support ---
# Check if a value is an interactive placeholder
# Args: $1 - value to check
# Returns: 0 if it's a placeholder, 1 otherwise
is_interactive_placeholder() {
local value="$1"
# Match patterns like {{PROMPT: text}}, {{INPUT: text}}, {{PROMPT_MULTI: text}}, or {{INPUT_MULTI: text}}
[[ "${value}" =~ ^\{\{(PROMPT|INPUT|PROMPT_MULTI|INPUT_MULTI):[[:space:]]*(.+)\}\}$ ]]
}
# Extract prompt text from placeholder
# Args: $1 - placeholder value (e.g., "{{PROMPT: Enter summary}}")
# Returns: The prompt text
extract_prompt_text() {
local value="$1"
if [[ "${value}" =~ ^\{\{(PROMPT|INPUT|PROMPT_MULTI|INPUT_MULTI):[[:space:]]*(.+)\}\}$ ]]; then
echo "${BASH_REMATCH[2]}"
fi
}
# Check if placeholder is multi-line type
# Args: $1 - placeholder value
# Returns: 0 if multi-line, 1 otherwise
is_multiline_placeholder() {
local value="$1"
[[ "${value}" =~ ^\{\{(PROMPT_MULTI|INPUT_MULTI):[[:space:]]*.+\}\}$ ]]
}
# Prompt user for input interactively
# Args: $1 - field name, $2 - prompt text, $3 - is_multiline (true/false)
# Returns: User input
prompt_user_input() {
local field_name="$1"
local prompt_text="$2"
local is_multiline="${3:-false}"
# Display the prompt text as the main label (it's the human-readable field name/question)
log_info "${prompt_text}"
log_verbose "(Field ID: ${field_name})"
local user_input
if [ "${is_multiline}" = "true" ]; then
# Multi-line input mode
echo " [Enter multi-line text. Type 'END' on a new line when finished]" >&2
echo "" >&2
local lines=()
local line
while IFS= read -r line < /dev/tty; do
# Check if user entered END to finish
if [ "${line}" = "END" ]; then
break
fi
lines+=("${line}")
done
# Join lines with actual newlines
user_input=$(printf "%s\n" "${lines[@]}" | sed '$d' || printf "%s\n" "${lines[@]}")
# Remove trailing newline if lines array is not empty
if [ "${#lines[@]}" -gt 0 ]; then
# Join all lines with newline, but remove the last extra newline
user_input=$(IFS=$'\n'; echo "${lines[*]}")
fi
else
# Single-line input mode
echo -n " > " >&2
read -r user_input < /dev/tty
fi
echo "${user_input}"
}
# Process JSON and replace interactive placeholders with user input
# Args: $1 - JSON string, $2 - enable interactive mode (true/false)
# Returns: Modified JSON with placeholders replaced
process_interactive_template() {
local json="$1"
local interactive="${2:-true}"
if [ "${interactive}" != "true" ]; then
# Check for any placeholders and error if found
if echo "${json}" | jq -e '.. | select(type == "string" and test("^\\{\\{(PROMPT|INPUT|PROMPT_MULTI|INPUT_MULTI):"))' > /dev/null 2>&1; then
log_error "Template contains interactive placeholders but interactive mode is disabled."
log_error "Use --interactive flag or remove placeholders from template."
return 1
fi
echo "${json}"
return 0
fi
log_info "Processing interactive template..."
echo "" >&2
# Extract all string values with their paths
local paths_and_values
paths_and_values=$(echo "${json}" | jq -r 'paths(type == "string") as $p | "\($p | join("."))\t\(getpath($p))"')
local modified_json="${json}"
while IFS=$'\t' read -r path value; do
if is_interactive_placeholder "${value}"; then
local prompt_text
prompt_text=$(extract_prompt_text "${value}")
# Extract field name from path (last component)
local field_name
field_name=$(echo "${path}" | awk -F'.' '{print $NF}')
# Check if this is a multi-line placeholder
local is_multiline="false"
if is_multiline_placeholder "${value}"; then
is_multiline="true"
fi
# Get user input
local user_input
user_input=$(prompt_user_input "${field_name}" "${prompt_text}" "${is_multiline}")
# Validate path contains only safe characters to prevent injection
if [[ ! "${path}" =~ ^[a-zA-Z0-9._-]+$ ]]; then
log_error "Invalid field path detected: ${path}"
log_error "Paths must contain only alphanumeric characters, dots, hyphens, and underscores."
return 1
fi
# Escape the input for JSON
local escaped_input
escaped_input=$(echo -n "${user_input}" | jq -R -s '.')
# Replace in JSON using jq with safe parameter passing
# Convert dot-separated path like "fields.summary" to array ["fields", "summary"]
local path_array
path_array=$(echo "${path}" | jq -R 'split(".")')
# Update the JSON using setpath() with safely passed arguments
local updated_json
if ! updated_json=$(echo "${modified_json}" | jq \
--argjson path "${path_array}" \
--argjson value "${escaped_input}" \
'setpath($path; $value)'); then
log_error "Failed to update field '${field_name}' in template."
return 1
fi
modified_json="${updated_json}"
fi
done <<< "${paths_and_values}"
echo "" >&2
log_info "Interactive input complete."
echo "${modified_json}"
}
# --- Configuration Validation ---
# Validate that required Jira environment variables are set
validate_jira_env() {
: "${jira_base_url:?Environment variable jira_base_url not set.}"
: "${jira_user:?Environment variable jira_user not set.}"
: "${jira_password:?Environment variable jira_password not set.}"
}
# --- Authentication ---
# --- HTTP Response Handling ---
# Parse HTTP status from curl response with -w "\nHTTP_STATUS:%{http_code}"
# Args: $1 - full response string
# Returns: HTTP status code
parse_http_status() {
local response="$1"
echo "${response}" | grep "HTTP_STATUS:" | cut -d: -f2
}
# Parse HTTP body from curl response with -w "\nHTTP_STATUS:%{http_code}"
# Args: $1 - full response string
# Returns: response body without status line
parse_http_body() {
local response="$1"
echo "${response}" | sed '$d' # Remove last line (the status)
}
# Check if HTTP status indicates success for GET requests
# Args: $1 - HTTP status code
# Returns: 0 if success (200), 1 otherwise
is_http_success_get() {
local status="$1"
[ "$status" -eq 200 ]
}
# Check if HTTP status indicates success for POST requests
# Args: $1 - HTTP status code
# Returns: 0 if success (201), 1 otherwise
is_http_success_post() {
local status="$1"
[ "$status" -eq 201 ]
}
# Check if HTTP status indicates success for PUT requests
# Args: $1 - HTTP status code
# Returns: 0 if success (204 or 200), 1 otherwise
is_http_success_put() {
local status="$1"
[ "$status" -eq 204 ] || [ "$status" -eq 200 ]
}
# --- YAML/JSON Handling ---
# Validate that a YAML file exists and is valid YAML/JSON
# Args: $1 - path to YAML file
# Returns: 0 if valid, 1 otherwise
validate_yaml_file() {
local yaml_file="$1"
if [ ! -f "${yaml_file}" ]; then
log_error "YAML file not found at: ${yaml_file}"
return 1
fi
# Try to convert to JSON and validate
local json_content
if ! json_content=$(yq -o=json '.' "${yaml_file}" 2>/dev/null); then
log_error "Failed to read YAML file: ${yaml_file}"
return 1
fi
if ! echo "${json_content}" | jq . > /dev/null 2>&1; then
log_error "The file '${yaml_file}' does not contain valid YAML/JSON."
return 1
fi
return 0
}
# --- Field Default Values ---
# Get a sensible default value for a Jira field based on its schema type
# Args: $1 - schema type, $2 - field name, $3 - allowed values (comma-separated, optional), $4 - issue type (for issuetype fields)
# Returns: default value string suitable for YAML
get_field_default_value() {
local schema_type="$1"
local field_name="$2"
local allowed_values="${3:-}"
local issue_type="${4:-Task}"
local default_value
case "${schema_type}" in
string)
default_value="'TODO: Enter ${field_name}'"
;;
number)
default_value="0"
;;
array)
default_value="[]"
;;
option)
if [ -n "${allowed_values}" ]; then
default_value="{name: 'TODO: Choose from: ${allowed_values}'}"
else
default_value="{name: 'TODO'}"
fi
;;
user)
default_value="{name: 'username'}"
;;
priority)
default_value="{name: 'Medium'}"
;;
project)
# Skip project field - it's set by the script
return 1
;;
issuetype)
default_value="{name: '${issue_type}'}"
;;
date|datetime)
default_value="'TODO: YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss.sssZ'"
;;
*)
default_value="'TODO'"
;;
esac
echo "${default_value}"
}
# --- Field Comments ---
# Build a comment string for a field with metadata
# Args: $1 - field name, $2 - is required (true/false), $3 - schema type, $4 - allowed values (comma-separated, optional)
# Returns: comment string
build_field_comment() {
local field_name="$1"
local is_required="$2"
local schema_type="$3"
local allowed_values="${4:-}"
local comment="${field_name}"
if [ "${is_required}" = "true" ]; then
comment="${comment} [REQUIRED]"
fi
comment="${comment} - Type: ${schema_type}"
if [ -n "${allowed_values}" ]; then
comment="${comment} - Allowed: ${allowed_values}"
fi
echo "${comment}"
}
# --- Jira API Calls ---
# Make a Jira API GET request
# Args: $1 - API endpoint path (e.g., "/rest/api/2/field")
# Sets global variables: jira_response_status, jira_response_body
jira_api_get() {
local endpoint="$1"
local url="${jira_base_url}${endpoint}"
log_debug "GET ${url}"
local response
response=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \
-u "${jira_user}:${jira_password}" \
-H "Accept: application/json" \
"${url}")
jira_response_status=$(parse_http_status "${response}")
jira_response_body=$(parse_http_body "${response}")
log_debug "HTTP status: ${jira_response_status}"
if ! is_http_success_get "${jira_response_status}"; then
log_error "API request failed. HTTP Status: ${jira_response_status}"
log_error "Response Body:"
echo "${jira_response_body}" | jq . >&2
return 1
fi
return 0
}
# Make a Jira API POST request
# Args: $1 - API endpoint path, $2 - JSON payload
# Sets global variables: jira_response_status, jira_response_body
jira_api_post() {
local endpoint="$1"
local payload="$2"
local url="${jira_base_url}${endpoint}"
log_debug "POST ${url}"
log_debug "Payload: ${payload}"
local response
response=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \
-X POST \
-u "${jira_user}:${jira_password}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
--data "${payload}" \
"${url}")
jira_response_status=$(parse_http_status "${response}")
jira_response_body=$(parse_http_body "${response}")
log_debug "HTTP status: ${jira_response_status}"
if ! is_http_success_post "${jira_response_status}"; then
log_error "API request failed. HTTP Status: ${jira_response_status}"
log_error "Response Body:"
echo "${jira_response_body}" | jq . >&2
return 1
fi
return 0
}
# Make a Jira API PUT request
# Args: $1 - API endpoint path, $2 - JSON payload
# Sets global variables: jira_response_status, jira_response_body
jira_api_put() {
local endpoint="$1"
local payload="$2"
local url="${jira_base_url}${endpoint}"
log_debug "PUT ${url}"
log_debug "Payload: ${payload}"
local response
response=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \
-X PUT \
-u "${jira_user}:${jira_password}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
--data "${payload}" \
"${url}")
jira_response_status=$(parse_http_status "${response}")
jira_response_body=$(parse_http_body "${response}")
log_debug "HTTP status: ${jira_response_status}"
if ! is_http_success_put "${jira_response_status}"; then
log_error "API request failed. HTTP Status: ${jira_response_status}"
log_error "Response Body:"
echo "${jira_response_body}" | jq . >&2
return 1
fi
return 0
}