Skip to content

Commit 6323e67

Browse files
authored
Merge pull request #2095 from jrobertboos/lcore-2978
LCORE-2978: Added Release Jiras
2 parents 52f2d12 + 3df04a7 commit 6323e67

2 files changed

Lines changed: 259 additions & 16 deletions

File tree

dev-tools/file-jiras.sh

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
#!/usr/bin/env bash
2-
# File JIRA sub-tickets from a spike doc.
2+
# File JIRA sub-tickets from a spike doc or template with placeholders.
33
#
44
# Usage:
55
# file-jiras.sh --spike-doc <path> --feature-ticket <key>
66
# file-jiras.sh --spike-doc spike.md --feature-ticket LCORE-1311
77
# file-jiras.sh --spike-doc spike.md --feature-ticket 1311
8+
# file-jiras.sh --spike-doc release-jiras.md --feature-ticket LCORE-2000 \
9+
# --var X=0 --var Y=7 --var Z=0 --var PRE=rc1
810
#
911
# Bare numbers default to LCORE- prefix.
1012
#
1113
# The script:
12-
# 1. Parses JIRA sections from the spike doc (### LCORE-???? headings)
13-
# 2. Auto-generates an Epic stub (ticket #0)
14-
# 3. Reads <!-- type: Task/Story/Epic --> metadata from each ticket
15-
# 4. Opens an interactive menu: view, edit, drop, file
16-
# 5. Files Epic first, then children under it
17-
# 6. Links spike ticket to Epic with "Informs" relationship
14+
# 1. Replaces {KEY} placeholders with --var values (if any)
15+
# 2. Parses JIRA sections from the doc (### LCORE-???? headings)
16+
# 3. Auto-generates an Epic stub (ticket #0)
17+
# 4. Reads <!-- type: Task/Story/Epic --> metadata from each ticket
18+
# 5. Opens an interactive menu: view, edit, drop, file
19+
# 6. Files Epic first, then children under it
20+
# 7. Links spike ticket to Epic with "Informs" relationship
1821

1922
set -euo pipefail
2023

@@ -28,25 +31,32 @@ SPIKE_TICKET_KEY=""
2831

2932
show_help() {
3033
echo "Usage: file-jiras.sh --spike-doc <path> --feature-ticket <key> [--output-dir <path>] [--parse-only]"
34+
echo " [--var KEY=VALUE ...]"
3135
echo ""
3236
echo "Options:"
3337
echo " --spike-doc Path to the spike doc containing proposed JIRAs"
3438
echo " --feature-ticket Parent feature ticket (e.g., LCORE-1311 or 1311)"
3539
echo " --output-dir Directory for parsed ticket files (default: <spike-doc-dir>/jiras/)"
40+
echo " --var KEY=VALUE Replace {KEY} placeholders in the doc before parsing."
41+
echo " Repeatable. Useful for template docs like release-jiras.md."
42+
echo " Example: --var X=0 --var Y=7 --var Z=0 --var PRE=rc1"
3643
echo " --parse-only Parse the spike doc into ticket files and exit;"
3744
echo " skip the interactive filing loop and credentials check."
3845
echo " Useful for inspecting parsed output, pre-commit hooks,"
3946
echo " and CI validation."
4047
echo " --help Show this help"
4148
echo ""
42-
echo "Example:"
49+
echo "Examples:"
4350
echo " file-jiras.sh --spike-doc docs/design/.../spike.md --feature-ticket 1311"
51+
echo " file-jiras.sh --spike-doc dev-tools/release-jiras.md --feature-ticket 2000 \\"
52+
echo " --var X=0 --var Y=7 --var Z=0 --var PRE=rc1"
4453
}
4554

4655
SPIKE_DOC=""
4756
FEATURE_TICKET=""
4857
JIRA_DIR=""
4958
PARSE_ONLY=0
59+
PLACEHOLDER_VARS=()
5060

5161
while [ $# -gt 0 ]; do
5262
case "$1" in
@@ -59,6 +69,13 @@ while [ $# -gt 0 ]; do
5969
--output-dir)
6070
[ $# -ge 2 ] || { echo "Error: --output-dir requires a value"; exit 1; }
6171
JIRA_DIR="$2"; shift 2 ;;
72+
--var)
73+
[ $# -ge 2 ] || { echo "Error: --var requires KEY=VALUE"; exit 1; }
74+
case "$2" in
75+
*=*) PLACEHOLDER_VARS+=("$2") ;;
76+
*) echo "Error: --var value must be KEY=VALUE (got '$2')"; exit 1 ;;
77+
esac
78+
shift 2 ;;
6279
--parse-only|--dry-run)
6380
PARSE_ONLY=1; shift ;;
6481
--help|-h) show_help; exit 0 ;;
@@ -159,7 +176,7 @@ if [ "${SKIP_PARSE:-}" != "1" ]; then
159176
rm -rf "$JIRA_DIR"
160177
mkdir -p "$JIRA_DIR"
161178

162-
python3 - "$SPIKE_DOC" "$JIRA_DIR" "$FEATURE_TICKET" << 'PYEOF'
179+
python3 - "$SPIKE_DOC" "$JIRA_DIR" "$FEATURE_TICKET" ${PLACEHOLDER_VARS[@]+"${PLACEHOLDER_VARS[@]}"} << 'PYEOF'
163180
import json
164181
import re
165182
import sys
@@ -169,6 +186,15 @@ spike_doc = Path(sys.argv[1]).read_text()
169186
out_dir = Path(sys.argv[2])
170187
feature_ticket = sys.argv[3]
171188
189+
# Apply {KEY} placeholder substitutions from --var arguments
190+
for kv in sys.argv[4:]:
191+
key, value = kv.split('=', 1)
192+
spike_doc = spike_doc.replace('{' + key + '}', value)
193+
194+
unreplaced = set(re.findall(r'\{([A-Z][A-Z0-9_]*)\}', spike_doc))
195+
if unreplaced:
196+
print(f" Warning: unreplaced placeholders: {', '.join(sorted(unreplaced))}", file=sys.stderr)
197+
172198
173199
def strip_multiline_comments(text):
174200
"""Strip HTML comment blocks that span multiple lines.
@@ -287,14 +313,14 @@ def parse_proposed_section(section_text):
287313
children.append((child_heading, child_body, ticket_type))
288314
289315
epic_blocks.append((epic_name, epic_prose, children))
290-
return epic_blocks, "epic_grouped"
316+
return epic_blocks, "epic_grouped", []
291317
292318
# Backward compat: flat ### LCORE-... children, no Epic boundaries.
293319
# Match both LCORE-???? (placeholder) and LCORE-NNNN (real key).
294320
legacy_pattern = re.compile(r'^###\s+(LCORE-[\d?]+.*?)$', re.MULTILINE)
295321
legacy_matches = list(legacy_pattern.finditer(section_text))
296322
if not legacy_matches:
297-
return [], "empty"
323+
return [], "empty", []
298324
299325
children = []
300326
for i, m in enumerate(legacy_matches):
@@ -308,6 +334,10 @@ def parse_proposed_section(section_text):
308334
body = strip_leaked_metadata(body)
309335
children.append((heading, body, ticket_type))
310336
337+
# <!-- no-epic --> directive: skip auto-Epic generation.
338+
if '<!-- no-epic -->' in spike_doc:
339+
return [], "no_epic", children
340+
311341
# Auto-generate Epic name from spike-doc parent dir
312342
spike_path = Path(sys.argv[1])
313343
feature_dir = spike_path.parent.name
@@ -316,10 +346,10 @@ def parse_proposed_section(section_text):
316346
else:
317347
epic_name = "TODO: Epic title"
318348
319-
return [(epic_name, "", children)], "legacy_flat"
349+
return [(epic_name, "", children)], "legacy_flat", []
320350
321351
322-
epic_blocks, parse_mode = parse_proposed_section(proposed_section)
352+
epic_blocks, parse_mode, no_epic_tickets = parse_proposed_section(proposed_section)
323353
324354
325355
# --- Structure linter ---
@@ -372,9 +402,9 @@ def lint_proposed_section(section_text, epic_blocks, parse_mode):
372402
else:
373403
seen[title] = epic_name
374404
375-
# No JIRAs at all
405+
# No JIRAs at all (no_epic mode has its own tickets, so skip this check)
376406
total_children = sum(len(c) for _, _, c in epic_blocks)
377-
if total_children == 0:
407+
if total_children == 0 and parse_mode != "no_epic":
378408
issues.append((
379409
"ERROR",
380410
"Proposed JIRAs section parsed zero JIRAs. Expected at least one "
@@ -425,6 +455,9 @@ def parse_incidental_section(section_text):
425455
426456
incidental_tickets = parse_incidental_section(incidental_section)
427457
458+
# <!-- no-epic --> tickets are treated as incidental (filed under feature-ticket)
459+
incidental_tickets = no_epic_tickets + incidental_tickets
460+
428461
429462
# --- Write parsed files ---
430463
file_count = 0
@@ -737,7 +770,7 @@ except Exception as e:
737770

738771
# Extract description body (everything after the heading, skip metadata comments)
739772
local body
740-
body=$(grep -v '^<!-- \(type\|key\):' "$ticket_file" | tail -n +2)
773+
body=$(grep -v '^<!-- \(type\|key\|incidental\|parent_epic_file\):' "$ticket_file" | grep -v '^### ' | tail -n +1)
741774

742775
# Build ADF description
743776
local adf_desc

dev-tools/release-jiras.md

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# Release JIRAs
2+
3+
Tickets to file for each major/minor release.
4+
5+
Placeholders:
6+
7+
- **{X}** — major version
8+
- **{Y}** — minor version
9+
- **{Z}** — patch version
10+
- **{PRE}** — pre-release suffix (e.g. `rc1`, `rc2`), empty for GA
11+
12+
<!-- no-epic -->
13+
14+
## Proposed JIRAs
15+
16+
<!-- type: Task -->
17+
### LCORE-???? Create release branch for lightspeed-stack
18+
19+
**Description**: Create the `release/{X}.{Y}` branch for lightspeed-stack to establish the new release stream.
20+
21+
**Scope**:
22+
23+
- Create `release/{X}.{Y}` branch from `main`
24+
- Confirm `src/version.py` on `main` reflects the previous shipped version before branching
25+
- Retarget the previous application's Konflux components to the new release branch
26+
27+
**Acceptance criteria**:
28+
29+
- Branch `release/{X}.{Y}` exists on `lightspeed-core/lightspeed-stack`
30+
- CI pipelines pass on the new branch
31+
- Konflux components for the previous minor are retargeted from `main` to the release branch
32+
33+
<!-- type: Task -->
34+
### LCORE-???? Create release branch for rag-content
35+
36+
**Description**: Create the `release/{X}.{Y}` branch for rag-content to establish the new release stream.
37+
38+
**Scope**:
39+
40+
- Create `release/{X}.{Y}` branch from `main`
41+
- Retarget Konflux `rag-content-*` components for the previous minor to the new release branch
42+
43+
**Acceptance criteria**:
44+
45+
- Branch `release/{X}.{Y}` exists on `lightspeed-core/rag-content`
46+
- CI pipelines pass on the new branch
47+
- Konflux rag-content components for the previous minor are retargeted from `main` to the release branch
48+
49+
<!-- type: Task -->
50+
### LCORE-???? Update version string in src/version.py
51+
52+
**Description**: Bump `__version__` in `src/version.py` on `main` to the new release version and update all files that reference the version.
53+
54+
**Scope**:
55+
56+
- Set `__version__` to `{X}.{Y}.{Z}{PRE}` (or `{X}.{Y}.{Z}`) on `main`
57+
- Update version references in tests and docs per the list in `docs/releasing.md`
58+
- Confirm `pdm show --version` matches the new version string
59+
60+
**Acceptance criteria**:
61+
62+
- `src/version.py` contains the correct `__version__` value
63+
- All version references across the codebase are consistent
64+
- CI version validation passes
65+
66+
<!-- type: Task -->
67+
### LCORE-???? Regenerate OpenAPI specification
68+
69+
**Description**: Regenerate `docs/openapi.json` after the version bump so the published spec reflects the new release version.
70+
71+
**Scope**:
72+
73+
- Run `make schema` to regenerate the OpenAPI spec
74+
- Verify the version field in the generated spec matches `src/version.py`
75+
- Commit the updated `docs/openapi.json`
76+
77+
**Acceptance criteria**:
78+
79+
- `docs/openapi.json` reflects the new version
80+
- No unintended schema changes beyond the version bump
81+
82+
<!-- type: Task -->
83+
### LCORE-???? Create release tag for lightspeed-stack
84+
85+
**Description**: Create a git tag on lightspeed-stack to trigger the release build pipeline and mark the release point.
86+
87+
**Scope**:
88+
89+
- Create tag `{X}.{Y}.{Z}{PRE}` (PEP 440 format, no separator) on the appropriate branch
90+
- Draft a new GitHub release from the tag
91+
- Verify the tag triggers `build_and_push_release.yaml`
92+
93+
**Acceptance criteria**:
94+
95+
- Git tag exists and points to the correct commit
96+
- GitHub Release is published with auto-generated release notes
97+
98+
<!-- type: Task -->
99+
### LCORE-???? Create release tag for rag-content
100+
101+
**Description**: Create a git tag on rag-content to trigger the release build pipeline and mark the release point.
102+
103+
**Scope**:
104+
105+
- Create tag `{X}.{Y}.{Z}{PRE}` (PEP 440 format, no separator) on the appropriate branch
106+
- Draft a new GitHub release from the tag
107+
108+
**Acceptance criteria**:
109+
110+
- Git tag exists and points to the correct commit
111+
- GitHub Release is published with auto-generated release notes
112+
113+
<!-- type: Task -->
114+
### LCORE-???? Generate release notes for lightspeed-stack
115+
116+
**Description**: Generate and publish release notes for the lightspeed-stack release.
117+
118+
**Scope**:
119+
120+
- Use GitHub Releases "Generate release notes" to produce a changelog
121+
- Review and edit the auto-generated notes for clarity
122+
- Publish the release on GitHub
123+
124+
**Acceptance criteria**:
125+
126+
- Release notes are published on the GitHub Releases page
127+
- Notes accurately summarize changes since the previous release
128+
129+
<!-- type: Task -->
130+
### LCORE-???? Generate release notes for rag-content
131+
132+
**Description**: Generate and publish release notes for the rag-content release.
133+
134+
**Scope**:
135+
136+
- Use GitHub Releases "Generate release notes" to produce a changelog
137+
- Review and edit the auto-generated notes for clarity
138+
- Publish the release on GitHub
139+
140+
**Acceptance criteria**:
141+
142+
- Release notes are published on the GitHub Releases page
143+
- Notes accurately summarize changes since the previous release
144+
145+
<!-- type: Task -->
146+
### LCORE-???? Publish release images to Quay.io for lightspeed-stack
147+
148+
**Description**: Verify that release container images for lightspeed-stack are built and pushed to Quay.io.
149+
150+
**Scope**:
151+
152+
- Confirm the git tag push triggered `build_and_push_release.yaml`
153+
- Verify images are available at `quay.io/lightspeed-core/lightspeed-stack` with the tag name and `latest`
154+
- Smoke-test the published image
155+
156+
**Acceptance criteria**:
157+
158+
- Image is available on Quay.io with the release tag (e.g. `{X}.{Y}.{Z}{PRE}`)
159+
- `latest` tag is updated
160+
- Image starts and passes basic health check
161+
162+
<!-- type: Task -->
163+
### LCORE-???? Publish release images to Quay.io for rag-content
164+
165+
**Description**: Verify that release container images for rag-content are built and pushed to Quay.io.
166+
167+
**Scope**:
168+
169+
- Confirm the git tag push triggered the rag-content build pipeline
170+
- Verify images are available on Quay.io for all compute flavors (cpu, cuda-12.9, etc.)
171+
- Smoke-test the published images
172+
173+
**Acceptance criteria**:
174+
175+
- Images are available on Quay.io with the release tag for each compute flavor
176+
- Images start and pass basic health check
177+
178+
<!-- type: Task -->
179+
### LCORE-???? Publish release images to registry.redhat.io
180+
181+
**Description**: Promote release images to `registry.redhat.io` via Konflux for both lightspeed-stack and rag-content.
182+
183+
**Scope**:
184+
185+
- Update RPA `componentTags` to include the new immutable tag (e.g. `{X}.{Y}.{Z}{PRE}`) for all shipping components
186+
- Select the correct Snapshot in the Konflux Application
187+
- Create a `Release` referencing the Snapshot and the application's `ReleasePlan`
188+
- Verify tags on `registry.redhat.io` after the release pipeline completes
189+
190+
**Acceptance criteria**:
191+
192+
- Immutable tag (e.g. `{X}.{Y}.{Z}{PRE}`) is present on `registry.redhat.io` for lightspeed-stack and all rag-content flavors
193+
- Floating tags (`{X}.{Y}-latest`, `latest` if applicable) are updated
194+
- Images are pullable from `registry.redhat.io`
195+
196+
<!-- type: Task -->
197+
### LCORE-???? Update docs/versions document
198+
199+
**Description**: Update the `docs/versions*` document to reflect the new release version and any version-specific notes.
200+
201+
**Scope**:
202+
203+
- Add the new version entry to the versions document
204+
- Update supported-versions matrix if applicable
205+
- Note any deprecations or breaking changes for this version
206+
207+
**Acceptance criteria**:
208+
209+
- Versions document includes the new release
210+
- Version information is accurate and consistent with the shipped artifacts

0 commit comments

Comments
 (0)