Skip to content

Commit

Permalink
scripts: add versionary.py to drive FCOS versioning
Browse files Browse the repository at this point in the history
This script implements the versioning scheme as described in:
coreos/fedora-coreos-tracker#211

The tricky bit is the Y component: the "snapshot" date of the Fedora
content. The algorithm I went with here is to use the git timestamp of
the commit that last changed the base manifest lockfile (e.g.
`manifest-lock.x86_64.json`).

The reasoning is that this lockfile defines the base content from which
we build. These lockfiles are updated by bots daily and are promoted as
is through e.g. `testing-devel` -> `testing`, so we correctly maintain
the X.Y components of the version during releases.

OTOH, the overrides lockfile is maintained by humans, and will be what
we use to override content if we need to backport/revert packages and
respin a release (and thus, the base lockfile remains the same).

The next step is to use this in the FCOS pipeline to drive versioning.
  • Loading branch information
jlebon committed Oct 21, 2019
1 parent 7e7ffb5 commit c932c34
Showing 1 changed file with 138 additions and 0 deletions.
138 changes: 138 additions & 0 deletions scripts/versionary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/usr/bin/python3 -u

'''
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
'''

import argparse
import json
import os
import re
import subprocess
import sys
import time
import yaml


def main():
args = parse_args()
if args.workdir is not None:
os.chdir(args.workdir)
assert os.path.isfile('builds/builds.json'), 'Missing builds/builds.json!'

x, y, z = (get_x(), get_y(), get_z())
n = get_next_iteration(x, y, z)
new_version = f'{x}.{y}.{z}.{n}'

# 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('--workdir', help="path to cosa workdir")
return parser.parse_args()


def get_x():
"""
X is the Fedora release version on which we're based.
"""
manifest = get_flattened_manifest()
releasever = manifest['releasever']
eprint(f"Got releasever {releasever} from manifest")
return int(releasever)


def get_y():
"""
Y is the base snapshot date in YYYYMMDD format of Fedora. We derive
this using the timestamp of the base lockfile.
"""
strftime_format = '%Y%m%d'
# XXX: should sanity check that the lockfiles for all the basearches have
# matching timestamps
date = subprocess.check_output(['git', 'log', '-1', '--format=format:%ad',
f'--date=format:{strftime_format}',
'manifest-lock.x86_64.json'],
cwd='src/config', encoding='utf-8').strip()
eprint(f"Got lockfile timestamp {date}")
# sanity check we can parse it back as a valid date
time.strptime(date, strftime_format)
return int(date)


def get_z():
"""
Z is the stream indicator.
"""
# https://github.com/coreos/fedora-coreos-tracker/issues/211#issuecomment-543547587
stream_to_int = {
'next': 1,
'testing': 2,
'stable': 3,
'next-devel': 10,
'testing-devel': 20,
'rawhide': 91,
'branched': 92,
'bodhi-updates-testing': 93,
'bodhi-updates': 94,
}
manifest = get_flattened_manifest()
stream = manifest['add-commit-metadata']['fedora-coreos.stream']
eprint(f"Got stream {stream} from manifest")
assert stream in stream_to_int, f"Unknown stream: {stream}"
mapped = stream_to_int[stream]
eprint(f"Mapped stream {stream} to {mapped}")
return mapped


def get_next_iteration(x, y, z):
with open('builds/builds.json') as f:
builds = json.load(f)

if len(builds['builds']) == 0:
eprint(f"No previous builds")
return 0

last_buildid = builds['builds'][0]['id']
last_version = parse_version(last_buildid)
if not last_version:
eprint(f"Previous version {last_buildid} does not match scheme")
return 0

if (x, y, z) != last_version[:3]:
eprint(f"Previous version {last_buildid} x.y.z does not match")
return 0

return last_version[3] + 1


def get_flattened_manifest():
return yaml.safe_load(
subprocess.check_output(['rpm-ostree', 'compose', 'tree', '--repo',
'tmp/repo', '--print-only',
'src/config/manifest.yaml']))


def parse_version(version):
m = re.match(r'^([0-9]{2})\.([0-9]{8})\.([0-9]+)\.([0-9]+)$', version)
if m is None:
return None
# sanity-check date
try:
time.strptime(m.group(2), '%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())

0 comments on commit c932c34

Please sign in to comment.