-
Notifications
You must be signed in to change notification settings - Fork 173
Expand file tree
/
Copy pathversionary
More file actions
executable file
·224 lines (183 loc) · 6.95 KB
/
versionary
File metadata and controls
executable file
·224 lines (183 loc) · 6.95 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
#!/usr/bin/python3 -u
# This file originally lived in
# https://github.com/coreos/fedora-coreos-releng-automation. See that repo for
# archeological git research.
'''
Implements the Fedora CoreOS versioning scheme as per:
https://github.com/coreos/fedora-coreos-tracker/issues/81
https://github.com/coreos/fedora-coreos-tracker/issues/211
And also the RHCOS/SCOS versioning scheme such as:
9.8.20260125-0
'''
import argparse
import dotenv
import json
import os
import platform
import re
import subprocess
import sys
import time
import yaml
from datetime import datetime
# https://github.com/coreos/fedora-coreos-tracker/issues/211#issuecomment-543547587
FCOS_STREAM_TO_NUM = {
'next': 1,
'testing': 2,
'stable': 3,
'next-devel': 10,
'testing-devel': 20,
'rawhide': 91,
'branched': 92,
'bodhi-updates-testing': 93,
'bodhi-updates': 94,
}
def main():
args = parse_args()
if args.workdir is not None:
os.chdir(args.workdir)
assert os.path.isdir('builds'), 'Missing builds/ dir'
# Initialize all the components of our versions
x, y, z, n = (None, None, None, None)
# Pick up values from our build-args.
config = dotenv.dotenv_values(args.build_args)
# Grab the current datetime object representing the timestamp
# for the timestamp component of our version.
dt = get_timestamp()
# The base version in FCOS is a single number (i.e. 43) while
# in RHCOS/SCOS it's two numbers separated by . (i.e. 9.8 or 10.0)
x, y = split_base_version(config['VERSION'])
# The y component in FCOS is the timestamp, while in RHCOS/SCOS
# it's the z component. We'll convert to a YYYYMMDD formatted string.
if y is None:
y = int(dt.strftime('%Y%m%d'))
else:
z = int(dt.strftime('%Y%m%d'))
# At this point if z isn't defined then we're FCOS
if z is None:
z = FCOS_STREAM_TO_NUM[config['STREAM']]
# For !FCOS and in dev mode we'll default to getting the build ID
# n component by incrementing on top of the last build. For FCOS
# not in dev mode we'll calculate the n by looking at git history.
if args.dev or config['ID'] != 'fedora':
n = get_next_iteration_from_builds(x, y, z)
else:
n = get_next_iteration_from_git(str(dt))
# On !FCOS the delimeter for the `n` component is a -
n_delimiter = '.' if config['ID'] == 'fedora' else '-'
# Now we can compute the final version. Note we prepend the
# `dev` string for the n component if --dev was passed.
dev = 'dev' if args.dev else ''
new_version = f'{x}.{y}.{z}{n_delimiter}{dev}{n}'
eprint(f'VERSIONARY: selected new version for build: {new_version}')
# sanity check the new version by trying to re-parse it
assert parse_version(new_version) is not None
print(new_version)
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--build-args', help="path to build-args.conf",
default='src/config/build-args.conf')
parser.add_argument('--workdir', help="path to cosa workdir")
parser.add_argument(
"--dev", action="store_true", help="generate a developer version"
)
return parser.parse_args()
def get_timestamp():
"""
Get the timestamp from either the lockfiles, or use the
current time if no lockfiles exist.
"""
# XXX: should sanity check that the lockfiles for all the basearches have
# matching timestamps
exts = ['json', 'yaml']
basearch = platform.machine()
for ext in exts:
try:
with open(f"src/config/manifest-lock.{basearch}.{ext}") as f:
lockfile = yaml.safe_load(f)
generated = lockfile.get('metadata', {}).get('generated')
if not generated:
raise Exception("Missing 'metadata.generated' key "
f"from {lockfile}")
dt = datetime.strptime(generated, '%Y-%m-%dT%H:%M:%SZ')
msg_src = "from lockfile"
break
except FileNotFoundError:
continue
else:
msg_src = "from datetime.now()"
dt = datetime.now()
eprint(f"timestamp: {dt.strftime('%Y%m%d')} ({msg_src})")
return dt
def split_base_version(base_version):
components = base_version.split('.')
if len(components) == 1:
return int(components[0]), None
else:
return int(components[0]), int(components[1])
def get_next_iteration_from_builds(x, y, z):
try:
with open('builds/builds.json') as f:
builds = json.load(f)
except FileNotFoundError:
builds = {'builds': []}
if len(builds['builds']) == 0:
eprint("n: 0 (no previous builds)")
return 0
last_buildid = builds['builds'][0]['id']
last_version_tuple = parse_version(last_buildid)
if not last_version_tuple:
eprint(f"n: 0 (previous version {last_buildid} does not match scheme)")
return 0
if (x, y, z) != last_version_tuple[:3]:
eprint(f"n: 0 (previous version {last_buildid} x.y.z does not match)")
return 0
n = last_version_tuple[3] + 1
eprint(f"n: {n} (incremented from previous version {last_buildid})")
return n
def get_next_iteration_from_git(timestamp):
"""
Compute the next iteration number based on git commit history.
Given the Y component of the version (YYYY-MM-DD HH:MM:SS date),
this counts all commits from the start of that date up to HEAD.
This guarantees that multiple builds on the same day each receive a
unique `.n` value, even if several changes occur.
See: https://github.com/coreos/fedora-coreos-tracker/issues/2015
"""
try:
# Count commits after that point
commit_count_since_change = subprocess.check_output(
['git', 'rev-list', '--count', '--after', timestamp, 'HEAD'],
cwd="src/config", text=True
).strip()
eprint(
f"n: {commit_count_since_change} "
"(calculated using git commit history)"
)
return int(commit_count_since_change)
except subprocess.CalledProcessError as err:
msg = (
"Git command failed: unable to determine the next "
f"iteration value ({err})"
)
raise RuntimeError(msg) from err
def parse_version(version):
# Note that (?:pattern) os a non-matching group in python regex so
# it won't show up in the matched m.groups()
m = re.match(r'^([0-9]+)\.([0-9]+)\.([0-9]+)(?:\.|-)(?:dev)?([0-9]+)$', version)
if m is None:
return None
# sanity-check date. The time could be in the y component or z component
# so we have to look for it in either.
timegroup = 2
if len(m.group(3)) == 8:
timegroup = 3
try:
time.strptime(m.group(timegroup), '%Y%m%d')
except ValueError:
return None
return tuple(map(int, m.groups()))
def eprint(*args):
print(*args, file=sys.stderr)
if __name__ == "__main__":
sys.exit(main())