Skip to content

Commit fa5f509

Browse files
author
Allen Bai
committed
coreos-release-note-generator: init script for generating release notes
`coreos-release-note-generator` generates `release-notes.yaml` from `release-notes.d` yaml snippets using the specified build id using `--build-id` option. Supported options include: - `--build-id`(required): build id of the latest release note - `--config-dir`(required): path to the directory where `release-notes.d/` lives - `--release-notes-file`(optional): path to the input `release-notes.yaml` file for update, the script uses this option to determine whether to update an existing `release-notes.yaml` or to create a new one. By default create a new `release-notes.yaml` - `--output-dir`(optional): output directory for `release-notes.yaml`, by default outputs to STDOUT - `--json`(optional): output JSON format instead of YAML for easier consumption by Fedora CoreOS website (https://getfedora.org/en/coreos?stream=stable), by default uses YAML Related: coreos/fedora-coreos-tracker#194 Signed-off-by: Allen Bai <abai@redhat.com>
1 parent 96d915a commit fa5f509

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# coreos-release-note-generator
2+
3+
`coreos-release-note-generator` generates `release-notes.yaml` from `release-notes.d` yaml snippets using the specified build id using `--build-id` option.
4+
5+
## Options
6+
- `--build-id`: build id of the latest release note
7+
- `--config-dir`: path to the directory where `release-notes.d/` lives
8+
- `--release-notes-file`: path to the input `release-notes.yaml` file for update
9+
- `--output-dir`: output directory for `release-notes.yaml`
10+
- `--json`: output JSON format instead of YAML for easier consumption by Fedora CoreOS website (https://getfedora.org/en/coreos?stream=stable)
11+
12+
As an example, assuming following structure:
13+
```
14+
.
15+
├── fedora-coreos-config
16+
└── fedora-coreos-releng-automation
17+
18+
```
19+
20+
## Generate a new `release-notes.yaml`
21+
22+
To generate a new `release-notes.yaml`, simply omit `--release-notes.yaml`:
23+
```
24+
$ ./release-note-generator.py --config-dir ../../fedora-coreos-config/ \
25+
--build-id 32.20200817.2.1
26+
27+
- 32.20200817.2.1:
28+
coreos-installer:
29+
- subject: installer 4
30+
- body: installer body 1
31+
subject: installer 1
32+
- subject: installer 2
33+
- subject: installer 3
34+
ignition:
35+
- body: ignition body 1
36+
subject: ignition 1
37+
- subject: igntiion 2
38+
miscellaneous:
39+
- subject: misc 2
40+
- subject: misc 1
41+
42+
```
43+
44+
## Update existing `release-notes.yaml`
45+
46+
To update existing `release-notes.yaml`:
47+
```
48+
$ cat ../../fedora-coreos-config/release-notes.yaml
49+
- 32.20200801.2.1:
50+
afterburn:
51+
- body: afterburn body 1
52+
subject: afterburn 1
53+
- subject: afterburn 2
54+
miscellaneous:
55+
- subject: misc 1
56+
57+
$ ./release-note-generator.py --release-notes-file ../../fedora-coreos-config/release-notes.yaml \
58+
--config-dir ../../fedora-coreos-config/ \
59+
--build-id 32.20200817.2.1
60+
- 32.20200817.2.1:
61+
coreos-installer:
62+
- subject: installer 4
63+
- body: installer body 1
64+
subject: installer 1
65+
- subject: installer 2
66+
- subject: installer 3
67+
ignition:
68+
- body: ignition body 1
69+
subject: ignition 1
70+
- subject: igntiion 2
71+
miscellaneous:
72+
- subject: misc 2
73+
- subject: misc 1
74+
- 32.20200801.2.1:
75+
afterburn:
76+
- body: afterburn body 1
77+
subject: afterburn 1
78+
- subject: afterburn 2
79+
miscellaneous:
80+
- subject: misc 1
81+
82+
```
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"""Fedora CoreOS Release Note Generator
2+
3+
This script allows the developer to generate a YAML / JSON format Fedora
4+
CoreOS release note file with specified build id.
5+
6+
This script updates existing `release-notes.yaml` if specified by the flag
7+
`--release-notes-file` or creates a new one if none is specified.
8+
9+
This script writes the output to STDOUT by default or to the `release-notes.yaml`
10+
or `release-notes.json` specified by the flags `--output-dir` and `--json`.
11+
12+
This file contains the following functions:
13+
14+
* parse_args - parse command line arguments
15+
* read_yaml_snippets - read yaml snippets from `release-notes.d` directory
16+
* write_yaml_snippets - write yaml snippets to STDOUT or `release-notes.yaml` or `release-notes.json`
17+
* main - the main function of the script
18+
"""
19+
20+
21+
import yaml
22+
import argparse
23+
import os
24+
import glob
25+
import json
26+
import sys
27+
28+
29+
def parse_args():
30+
"""
31+
Returns:
32+
Namspace: Parsed command line attributes
33+
"""
34+
parser = argparse.ArgumentParser(
35+
description="Builds 'release-notes.yaml' from yaml snippets \
36+
under 'release-notes.d' directory with specified build id. \
37+
Outputs to STDOUT by default.")
38+
39+
parser.add_argument(
40+
'--build-id', help='build id for release notes', required=True)
41+
parser.add_argument(
42+
'--config-dir', help="FCOS config directory where 'release-notes.d' lives", required=True)
43+
parser.add_argument(
44+
'--release-notes-file', help="input 'release-notes.yaml' for update, omit to generate a new one", required=False)
45+
parser.add_argument(
46+
'--output-dir', help="output directory for 'release-notes.yaml'", required=False)
47+
parser.add_argument('--json', action='store_true',
48+
help='output json instead of yaml', required=False)
49+
args = parser.parse_args()
50+
return args
51+
52+
53+
def read_yaml_snippets(args):
54+
"""Reads and parses yaml snippets under `release-notes.d` directory
55+
56+
Args:
57+
args (Namespace): Parsed command line attributes
58+
59+
Returns:
60+
dictionary: A dictionary consists of a new release note item generated from
61+
yaml snippets under `release-notes.yaml`. As an example:
62+
{"ignition": [{subject: "", body: ""}]}
63+
"""
64+
if not os.path.exists(args.config_dir):
65+
raise Exception(
66+
"config directory '{}' does not exist".format(args.config_dir))
67+
68+
if not os.path.exists(os.path.join(args.config_dir, 'release-notes.d')):
69+
raise Exception(
70+
"release-notes.d does not exist under {}".format(args.config_dir))
71+
72+
snippet_yaml_list = glob.glob(os.path.join(
73+
args.config_dir, 'release-notes.d/*.yaml'))
74+
if len(snippet_yaml_list) == 0:
75+
print("release-notes.d/ does not contain any yaml snippets under '{}'".format(args.config_dir))
76+
exit(0)
77+
78+
snippet_dict = dict()
79+
for snippet_yaml in snippet_yaml_list:
80+
with open(snippet_yaml, 'r') as f:
81+
snippet = yaml.load(f, Loader=yaml.FullLoader)
82+
for item in snippet:
83+
note = {'subject': item.get(
84+
'subject', ''), 'body': item.get('body', '')}
85+
# purposely avoid item.get('component', '') to error out if the component key does not exist
86+
project_name = item['component']
87+
snippet_dict[project_name] = [*snippet_dict.get(project_name, []), note]
88+
89+
# clean up empty fields
90+
for project in snippet_dict.copy():
91+
# filter out empty note item that has empty component line
92+
if project == '':
93+
snippet_dict.pop(project, '')
94+
continue
95+
# filter out empty note item that has empty subject line
96+
snippet_dict[project] = list(
97+
filter(lambda item: len(item['subject']) > 0, snippet_dict[project]))
98+
# remove empty note body from note item
99+
for i, item in enumerate(snippet_dict.copy()[project]):
100+
if item.get('body', '') == '':
101+
item.pop('body', '')
102+
snippet_dict[project][i] = item
103+
return snippet_dict
104+
105+
106+
def write_yaml_snippets(args, snippet_dict):
107+
"""Writes the generated release note item to STDOUT or file
108+
109+
Writes a new release note if `--release-notes-file` is not specified and
110+
writes to STDOUT if `--outptu-dir` is not specified. Default format is
111+
YAML unless `--json` is specified.
112+
113+
Args:
114+
args (Namespace): Parsed command line attributes
115+
snippet_dict (dictionary): The newly created release note item returned
116+
by `read_yaml_snippets`
117+
"""
118+
# output file name and format depending on the --json flag
119+
outfile = 'release-notes.json' if args.json else 'release-notes.yaml'
120+
121+
# store list of release note dictionaries
122+
release_notes = []
123+
if args.release_notes_file:
124+
if not os.path.exists(args.release_notes_file):
125+
raise Exception(
126+
"intput file '{}' does not exist".format(args.release_notes_file))
127+
with open(args.release_notes_file, 'r') as f:
128+
release_notes = yaml.load(f, Loader=yaml.FullLoader)
129+
release_notes.insert(0, {args.build_id: snippet_dict})
130+
131+
if args.output_dir:
132+
if not os.path.exists(args.output_dir):
133+
raise Exception(
134+
"output directory '{}' does not exist".format(args.output_dir))
135+
if not os.path.isdir(args.output_dir):
136+
raise Exception(
137+
"output path '{}' is not a directory".format(args.output_dir))
138+
outfile = os.path.join(args.output_dir, outfile)
139+
if args.json:
140+
with open(outfile, 'w') as f:
141+
json.dump(release_notes, f, indent=2)
142+
else:
143+
print(yaml.dump(release_notes, default_flow_style=False),
144+
file=open(outfile, 'w'))
145+
print(f"successfully wrote release note file at {outfile}")
146+
else:
147+
if args.json:
148+
json.dump(release_notes, sys.stdout, indent=2)
149+
else:
150+
print(yaml.dump(release_notes, default_flow_style=False))
151+
return
152+
153+
154+
def main():
155+
"""Main function of the script
156+
157+
Parses command line argument, then reads and parses yaml snippets under
158+
`release-notes.d/`, then writes the newly generated release note item to
159+
either STDOUT(default) or a file.
160+
"""
161+
args = parse_args()
162+
snippet_dict = read_yaml_snippets(args)
163+
write_yaml_snippets(args, snippet_dict)
164+
165+
166+
if __name__ == "__main__":
167+
main()

0 commit comments

Comments
 (0)