Compare commits

...

10 Commits

Author SHA1 Message Date
clrbuilder 0b39553aa6 Update packages file for version 30040
Signed-off-by: clrbuilder <clrbuilder@intel.com>
2019-06-21 18:03:19 +00:00
Tan, Yew Wayne 7cd52ed2fd ltsutils: Fix PackageRepo.getNVR for package names that contain '-'
Refactor unit tests, add test case for MySQL-python package
2019-06-21 09:12:15 -07:00
Tan, Yew Wayne a379bdd1e5 ltsutils: prompt: Timeout after 60 seconds
Signed-off-by: Tan, Yew Wayne <yew.wayne.tan@intel.com>
2019-06-21 09:12:15 -07:00
Tan, Yew Wayne b8eaa3c103 ltsutils: make lts-koji: Modify koji target for building on LTS infrastructure
Differences:
- Restrict to submit from an active branch instead of master branch
- git pull --rebase from matching remote LTS branch if available
- Add "-lts" suffix to git tag
- Assume koji buildtag name is identical to branch name

Signed-off-by: Tan, Yew Wayne <yew.wayne.tan@intel.com>
2019-06-21 09:12:15 -07:00
Tan, Yew Wayne 0664274e0a ltsutils: make lts-build: Build RPM or reuse existing build
Todo: can-reuse-binary simply compares package versions to determine if
Koji builds can be shared between branches. Work is in progress to
implement ABI compatiblity testing.

Signed-off-by: Tan, Yew Wayne <yew.wayne.tan@intel.com>
2019-06-21 09:12:15 -07:00
Tan, Yew Wayne 69cee2fcd7 LTS package maintenance utility (ltsutils): initial commit
This tooling is designed to automate 2 main tasks that are part of
the package maintenance workflow of Clear Linux LTS. These tasks are:
- Back-porting of a patch (e.g. security fix) to older branches.
- (Not implemented yet) Building RPMs with the intent of sharing binaries of
  older LTS branches to newer branches whenever possible.

2 new targets are defined in Makefile.common.lts:
- lts-show: Show a summary of active LTS branches
- lts-backport: Attempt to fast-forward the previous active branch to the current branch

"Active" branches correspond to LTS releases that currently have support.
They are listed in a flat file "active-branches" in "lts" directory, from
oldest to newest. New entries are added by Clear Linux LTS developers as
new releases become available, and entries removed as releases become
obsolete.

Note: For CVE patching, the tool is not aware of CVE severity levels or
the minimum supported severity level of each LTS branch. For now it is
the user's responsibility to know when a CVE does not apply to older
branches and stop calling "make lts-backport".

Signed-off-by: Tan, Yew Wayne <yew.wayne.tan@intel.com>
2019-06-21 09:12:15 -07:00
clrbuilder 19d7dfcc56 Update packages file for version 30030
Signed-off-by: clrbuilder <clrbuilder@intel.com>
2019-06-21 12:02:15 +00:00
clrbuilder 25e60b8d7f Update packages file for version 30020
Signed-off-by: clrbuilder <clrbuilder@intel.com>
2019-06-20 18:02:35 +00:00
clrbuilder c6749e0569 Update packages file for version 30010
Signed-off-by: clrbuilder <clrbuilder@intel.com>
2019-06-19 20:21:46 +00:00
Geoffroy Van Cutsem 79b17335fa Really set 'latest_builds' to the latest value of the day
Consider the following scenario:
* System being set-up on day 0 (by running user-setup.sh script)
* The github.com/clearlinux/common repo is cloned under projects/common
* Fast-forward in the future
* Clone a package for which there is no reachable upstream URL
	(e.g. clr-power-tweaks or clr-systemd-config
* Run 'make sources'
That operation will fail because it will search for the src.rpm files starting
with the Clear Linux build determined by looking up the latest tag in the
project/common repo (and the latest is the one from day 0). So it will only find
older versions of those src.rpm, and will eventually fail.

This patch adds a line that fetches all the latest tags from the upstream
projects/common repo so the 'latest_builds' variable is *really* set to the
latest of the day.

Signed-off-by: Geoffroy Van Cutsem <geoffroy.vancutsem@intel.com>
2019-06-19 11:03:00 -07:00
12 changed files with 478 additions and 1 deletions
+4
View File
@@ -279,6 +279,7 @@ sources:
@$(MAKE) generateupstream; \
[ $$? -eq 0 ] && exit 0; \
nvr="$$(rpmspec --srpm -q --qf '%{NAME}\t%{VERSION}\t%{RELEASE}\n' $(SPECFILE))"; \
git -C $(TOPLVL)/projects/common fetch --tags >/dev/null 2>&1; \
latest_builds=$$(git -C $(TOPLVL)/projects/common tag -l | sort -rn); \
for b in $$latest_builds; do \
url="$(DOWNLOAD_MIRROR)/releases/$$b/clear/source/package-sources"; \
@@ -502,6 +503,9 @@ cloc: $(SRPMFILE)
@$(MOCK) --clean --scrub=chroot --uniqueext=$(PKG_NAME)
cat results/cloc.txt
# Define LTS-specific targets in a separate makefile
-include $(TOPLVL)/projects/common/Makefile.common.lts
# Define site local common targets in a separate makefile
-include $(TOPLVL)/projects/common/Makefile.common.site_local
+90
View File
@@ -0,0 +1,90 @@
#-*-makefile-*-
LTSUTILS = python $(TOPLVL)/projects/common/lts/main.py $(PKG_NAME)
#help lts-show: Display a summary of active LTS branches.
lts-show:
@$(LTSUTILS) sanity-check
@while read b; do \
if git show-ref $$b > /dev/null; then \
echo $$b $$(git log --oneline -1 $$b); \
else \
echo $$b Not found; \
fi; \
done < $(TOPLVL)/projects/common/lts/active-branches
#help lts-backport: Fast-forward the previous active branch to the current
#help branch.
lts-backport:
@$(LTSUTILS) sanity-check
@newer=$$(git symbolic-ref HEAD); \
newer=$${newer#refs/heads/}; \
if ! $(LTSUTILS) prev-branch --checkout; then \
echo Could not check out previous active branch.; \
exit 0; \
fi; \
if $(LTSUTILS) is-same-version $$newer; then \
$(LTSUTILS) fast-forward $$newer; \
else \
echo Most likely a patch needs to be manually re-applied for this version.; \
echo Alternatively, use \"git merge --ff-only $$newer\" to upgrade the package version.; \
fi
#help lts-build: Build RPM in Koji, or reuse existing build from older LTS
#help branch.
lts-build:
@$(LTSUTILS) sanity-check
@tag=$$(git tag --points-at); \
if [[ -z $$tag ]]; then \
echo This commit does not have a tag.; \
echo Proceeding to run \"make lts-koji\".; \
$(LTSUTILS) prompt || exit; \
$(MAKE) lts-koji; \
exit; \
fi; \
older=$$($(LTSUTILS) prev-branch); \
if [[ -z $$older ]]; then \
echo Could not determine previous active branch.; \
exit 1; \
fi; \
current=$$($(LTSUTILS) current-branch); \
if [[ $$(git rev-parse $$older) != $$(git rev-parse $$current) ]]; then \
echo Previous branch and current branch have different commits.; \
for b in $$older $$current; do echo $$b $$(git log --oneline -1 $$b); done; \
echo Proceeding to run \"make lts-koji\".; \
$(LTSUTILS) prompt || exit; \
$(MAKE) lts-koji; \
exit; \
fi; \
if $(LTSUTILS) can-reuse-binary $$older; then \
echo Reusing binary from $$older.; \
$(KOJI_CMD) tag-build $$current $(SRPMVERS); \
else \
echo Could not reuse binary from $$older.; \
echo Proceeding to run \"make bump lts-koji\".; \
$(LTSUTILS) prompt || exit; \
$(MAKE) bump lts-koji; \
fi
@$(LTSUTILS) next-branch --checkout
#help lts-koji: Same as "koji" but for working on LTS branches. Should not
#help be called directly, use lts-build instead.
lts-koji: prekoji-checks kojidef
@$(LTSUTILS) sanity-check
@$(MAKE) spdxcheck
@$(MAKE) checkblacklist
@current=$$($(LTSUTILS) current-branch); \
if ! grep $$current $(TOPLVL)/projects/common/lts/active-branches; then \
echo "Error: Must be on an active branch to submit to koji" >&2; \
exit 1; \
fi; \
if ! git diff --quiet HEAD ${SPECFILE}; then \
echo "Error: All changes to ${SPECFILE} must be committed first" >&2; \
exit 1; \
fi; \
if git rev-parse --verify --quiet origin/$$current > /dev/null; then \
git pull --rebase; \
fi
git tag $(SRPMVERS)-lts
git push origin $$($(LTSUTILS) current-branch) refs/tags/$(SRPMVERS)-lts
$(KOJI_CMD) build $$KOJI_NOWAIT $$($(LTSUTILS) current-branch) $(PKG_BASE_URL)/$(PKG_NAME)?#$(SRPMVERS)-lts
+5
View File
@@ -0,0 +1,5 @@
TEST = test.test_ltsutils
.PHONY: test
test:
PYTHONPATH=. python -m unittest -v -k .Test $(TEST)
+19
View File
@@ -0,0 +1,19 @@
# LTS package maintenance utility
This tooling is designed to automate 2 main tasks that are part of
the package maintenance workflow of Clear Linux LTS. These tasks are:
- Back-porting of a patch (e.g. security fix) to older branches.
- Building RPMs with the intent of sharing binaries of older LTS branches to
newer branches whenever possible.
There should be no need to run this tool directly. Instead use the following
targets defined in Makefile.common.lts:
- lts-show: Show a summary of active LTS branches
- lts-backport: Attempt to fast-forward the previous active branch to the current branch
- lts-build: Build RPM in Koji, or reuse existing build from older branch
"Active" branches correspond to LTS releases that currently have support.
They are listed in a flat file "active-branches" in "lts" directory, from
oldest to newest. New entries are added by Clear Linux LTS developers as
new releases become available, and entries removed as releases become
obsolete.
View File
View File
+52
View File
@@ -0,0 +1,52 @@
import pathlib
from subprocess import PIPE, CalledProcessError
from .shell import Shell
class PackageRepo:
'''Represents a package repository. Most methods are wrappers of git commands.'''
class UnknownCurrentBranchException(Exception): pass
class InvalidBranchException(Exception): pass
def __init__(self, name, path):
self.name = name
self.path = pathlib.Path(path)
self.sh = Shell(self.path)
def getNVR(self, commit='HEAD'):
with self.sh.popen(['git', 'show', '{}:{}.spec'.format(commit, self.name)], stdout=PIPE) as specfile:
nvr = self.sh.run('rpmspec --srpm -q --queryformat %{NVR} /dev/stdin', stdin=specfile.stdout)
return tuple(nvr.stdout.strip().rsplit('-', maxsplit=2))
def checkoutBranch(self, branch, allow_remote=False):
# allow_remote=True allows checking out a new remote-tracking branch
if not allow_remote and not self.hasBranch(branch):
raise self.InvalidBranchException(branch)
self.sh.run_args(['git', 'checkout', branch], capture_output=False)
def fastForwardBranch(self, old, new):
self.checkoutBranch(old)
if not self.hasBranch(new):
raise self.InvalidBranchException(new)
self.sh.run_args(['git', 'merge', '--ff-only', new], capture_output=False)
def getActiveBranches(self):
toplvl = pathlib.Path(self.path) / '../..'
common = toplvl / 'projects/common'
active_branches = common / 'lts/active-branches'
with active_branches.open() as f:
return [line.rstrip() for line in f]
def getCurrentBranch(self):
try:
head = self.sh.run('git symbolic-ref HEAD').stdout.strip()
except CalledProcessError:
raise self.UnknownCurrentBranchException
refs_heads = 'refs/heads/'
assert head.startswith(refs_heads)
head = head[len(refs_heads):]
return head
def hasBranch(self, branch):
p = self.sh.run_args(['git', 'rev-parse', 'refs/heads/'+branch], check=False)
return p.returncode == 0
+27
View File
@@ -0,0 +1,27 @@
import subprocess, shlex
class Shell:
# Default options passed to subprocess.run. May be customized per-instance.
cwd = None
check = True
capture_output = True
text = True
def __init__(self, cwd=None):
if cwd: self.cwd = cwd
def run_args(self, args, **kwargs):
kwargs1 = {
'check': self.check,
'capture_output': self.capture_output,
'text': self.text,
'cwd': self.cwd
}
kwargs1.update(kwargs)
return subprocess.run(args, **kwargs1)
def run(self, cmd, **kwargs):
return self.run_args(shlex.split(cmd), **kwargs)
def popen(self, args, **kwargs):
return subprocess.Popen(args, cwd=self.cwd, **kwargs)
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/python
import sys,argparse
from ltsutils.package_repo import PackageRepo
def log(msg, **kwargs):
print(msg, file=sys.stderr)
def init_parser():
main = argparse.ArgumentParser()
main.add_argument('package_name', nargs=1)
subparsers = main.add_subparsers(dest='command', metavar='command', required=True)
# Package maintenance commands
p = subparsers.add_parser('prev-branch',
help='show previous branch')
p.add_argument('--checkout', action='store_true', help='checkout the branch')
p = subparsers.add_parser('next-branch',
help='show next branch')
p.add_argument('--checkout', action='store_true', help='checkout the branch')
p = subparsers.add_parser('current-branch',
help='show current branch')
p = subparsers.add_parser('is-same-version',
help='return true if package version is the same as the given branch')
p.add_argument('branch', nargs=1)
p = subparsers.add_parser('fast-forward',
help='fast-forward current branch to a newer branch')
p.add_argument('branch', nargs=1)
# RPM build commands
p = subparsers.add_parser('can-reuse-binary',
help='check if binary from another branch can be used in current branch')
p.add_argument('branch', nargs=1)
# Other commands
p = subparsers.add_parser('prompt',
help='prompt user to continue and return appropriate exit code')
p = subparsers.add_parser('sanity-check',
help='run sanity checks for data consistency')
return main
def prev_branch(args, repo):
active_branches = repo.getActiveBranches()
current = repo.getCurrentBranch()
i = active_branches.index(current)
if i == 0:
log('Already on oldest active branch.')
return False
prev = active_branches[i-1]
print(prev)
if args.checkout:
repo.checkoutBranch(prev, allow_remote=True)
def next_branch(args, repo):
active_branches = repo.getActiveBranches()
current = repo.getCurrentBranch()
i = active_branches.index(current)
if i == len(active_branches)-1:
log('Already on newest active branch.')
return False
next_ = active_branches[i+1]
print(next_)
if args.checkout:
repo.checkoutBranch(next_, allow_remote=False)
def current_branch(args, repo):
current = repo.getCurrentBranch()
print(current)
def is_same_version(args, other):
current = repo.getCurrentBranch()
other = args.branch[0]
assert repo.hasBranch(other), 'Branch %s not found' % other
v1, v2 = [repo.getNVR('refs/heads/'+b)[1] for b in (current, other)]
if v1 != v2:
log('Current version {} does not match version {} on branch {}'.format(v1, v2, other))
return v1 == v2
def fast_forward(args, repo):
current = repo.getCurrentBranch()
newer = args.branch[0]
log('Fast-forwarding {} to {}'.format(current, newer))
repo.fastForwardBranch(current, newer)
def sanity_check(args, repo):
ok = True
# HEAD must point to a branch
try:
current = repo.getCurrentBranch()
except PackageRepo.UnknownCurrentBranchException:
log('Unknown current branch. Has a branch been checked out?')
current = None
ok = False
# active-branches file must not be empty
active_branches = repo.getActiveBranches()
if not len(active_branches):
log('No active branches defined. Is active-branches file empty?')
ok = False
# current branch must be an active branch
elif current and current not in active_branches:
log('%s is not an active LTS branch.' % current)
ok = False
return ok
def can_reuse_binary(args, repo):
# Just compare versions for now
# TODO: ABI compatibility testing
current = repo.getCurrentBranch()
older = args.branch[0]
v1, v2 = [repo.getNVR('refs/heads/'+b)[1] for b in (older, current)]
return v1 == v2
def prompt(args, repo):
import selectors
timeout = 60
while True:
print('Continue? (y/N): ', end='', flush=True)
with selectors.DefaultSelector() as sel:
sel.register(sys.stdin, selectors.EVENT_READ)
events = sel.select(timeout)
if not len(events):
print('Timed out after {}s.'.format(timeout))
return False
else:
s = sys.stdin.readline().rstrip('\n')
if s in ('Y', 'y', 'N', 'n', ''):
break
if s in ('Y', 'y'):
return True
else:
print('Cancelled.')
return False
if __name__=='__main__':
args = init_parser().parse_args()
repo = PackageRepo(args.package_name[0], '.')
commands = {
'prev-branch': prev_branch,
'next-branch': next_branch,
'current-branch': current_branch,
'is-same-version': is_same_version,
'fast-forward': fast_forward,
'can-reuse-binary': can_reuse_binary,
'prompt': prompt,
'sanity-check': sanity_check,
}
ret = commands[args.command](args, repo)
if ret is not None:
exit(0 if ret else 1)
View File
+97
View File
@@ -0,0 +1,97 @@
import unittest
import os, pathlib, tempfile, logging
import subprocess, functools
from ltsutils.package_repo import PackageRepo
import ltsutils.shell
run = functools.partial(subprocess.run, check=True)
class Package:
def __init__(self, name):
self.name = name
self.repo_url = 'https://github.com/clearlinux-pkgs/{}.git'.format(name)
class LTSUtilsTestCase(unittest.TestCase):
toplvl = pathlib.Path('../../..')
packages = toplvl / 'packages'
def cloneOrExtractRepo(self):
tmpdir = pathlib.Path('/var/tmp/common-lts-test')
tmpdir.mkdir(mode=0o700, exist_ok=True)
tarball = tmpdir / '{}.tar.gz'.format(self.package.name)
if tarball.exists():
run(['tar', 'xf', tarball, '-C', self.workdir])
else:
run(['git', 'clone', self.package.repo_url, self.workdir])
run(['tar', 'czf', tarball, '-C', self.workdir, '.'])
def setUp(self):
self._tmpdir = tempfile.TemporaryDirectory(prefix='test-{}-'.format(self.package.name), dir=self.packages)
self.workdir = pathlib.Path(self._tmpdir.name)
self.cloneOrExtractRepo()
self.repo = PackageRepo(self.package.name, self.workdir)
self._sh = ltsutils.shell.Shell(self.workdir)
self._sh.capture_output = False
def sh(self, cmd):
return self._sh.run(cmd)
def sh_stdout(self, cmd):
return self._sh.run(cmd, capture_output=True).stdout.rstrip()
def tearDown(self):
self._tmpdir.cleanup()
class PackageRepoTestCase(LTSUtilsTestCase):
def setUp(self):
super().setUp()
self.L1 = self.package.L1
self.L2 = self.package.L2
self.sh('git branch L1 %s' % self.L1)
self.sh('git branch L2 %s' % self.L2)
def testGetNVR(self):
raise NotImplementedError
def testHasBranch(self):
self.assertTrue(self.repo.hasBranch('L2'))
self.assertFalse(self.repo.hasBranch('L3'))
def testCheckoutBranch(self):
self.repo.checkoutBranch('L2')
self.assertEqual(self.sh_stdout('git rev-parse HEAD'), self.L2)
self.assertRaises(PackageRepo.InvalidBranchException, self.repo.checkoutBranch, 'L3')
def testFastForward(self):
self.repo.fastForwardBranch('L1', 'L2')
self.assertEqual(self.sh_stdout('git rev-parse L1'), self.L2)
self.assertEqual(self.sh_stdout('git rev-parse L2'), self.L2)
def testGetCurrentBranch(self):
self.repo.checkoutBranch('L2')
b = self.repo.getCurrentBranch()
self.assertEqual(b, 'L2')
self.sh('git checkout --detach L2')
self.assertRaises(PackageRepo.UnknownCurrentBranchException, self.repo.getCurrentBranch)
class TestNano(PackageRepoTestCase):
package = Package('nano')
package.L1 = '3dcfa09f5217eedf6ec7539af7e243655d3abdb6' # 3.2-54
package.L2 = 'b8243dd54e8feb16a11474f848b8735f5591cf12' # 3.2-55
def testGetNVR(self):
nvr = self.repo.getNVR(self.L2)
self.assertEqual(nvr, ('nano', '3.2', '55'))
class TestMySQL_Python(PackageRepoTestCase):
package = Package('MySQL-python')
package.L1 = '386163d8fc9c857c7194c4e958374af4c4f071ed' # 1.2.5-31
package.L2 = 'f85bc5ec2141384f45f224d4464a0a44a981a4d4' # 1.2.5-33
def testGetNVR(self):
nvr = self.repo.getNVR(self.L2)
self.assertEqual(nvr, ('MySQL-python', '1.2.5', '33'))
+23 -1
View File
@@ -991,6 +991,8 @@ apache-kafka
apache-libcloud
apache-maven
apache-spark
apache-tomcat
apache-tomcat-dep
apache-zookeeper
apipkg
appdirs
@@ -1855,6 +1857,7 @@ hadoop-dep
hamlib
hammock
haproxy
hardinfo
hardlink
harfbuzz
haskell-random
@@ -2712,18 +2715,24 @@ mvn-beanshell
mvn-biz.aQute.bndlib
mvn-bndlib
mvn-build-helper-maven-plugin
mvn-buildnumber-maven-plugin
mvn-cdi-api
mvn-checker-compat-qual
mvn-checkstyle
mvn-codehaus-jackson
mvn-commons-beanutils
mvn-commons-cli
mvn-commons-codec
mvn-commons-collections
mvn-commons-compress
mvn-commons-digester
mvn-commons-httpclient
mvn-commons-io
mvn-commons-jxpath
mvn-commons-lang
mvn-commons-lang3
mvn-commons-logging
mvn-commons-parent
mvn-commons-validator
mvn-decentxml
mvn-doxia
@@ -2732,13 +2741,16 @@ mvn-enforcer
mvn-error_prone_annotations
mvn-extra-enforcer-rules
mvn-file-management
mvn-google
mvn-google-collections
mvn-guava
mvn-guice
mvn-hamcrest
mvn-httpcomponents-client
mvn-httpcomponents-core
mvn-j2objc-annotations
mvn-jackson-databind
mvn-jansi
mvn-java-boot-classpath-detector
mvn-javax.inject
mvn-jaxb-impl
@@ -2770,6 +2782,7 @@ mvn-maven-compat
mvn-maven-core
mvn-maven-embedder
mvn-maven-error-diagnostics
mvn-maven-model
mvn-maven-model-builder
mvn-maven-monitor
mvn-maven-parent
@@ -2788,10 +2801,11 @@ mvn-maven-repository-metadata
mvn-maven-resolver
mvn-maven-resolver-provider
mvn-maven-scm
mvn-maven-settings
mvn-maven-shared
mvn-maven-slf4j-provider
mvn-maven-surefire
mvn-maven-toolchain
mvn-maven-wagon
mvn-mockito-core
mvn-modello
mvn-mojo-parent
@@ -2808,7 +2822,9 @@ mvn-org.eclipse.sisu.plexus
mvn-org.osgi.compendium
mvn-org.osgi.core
mvn-oro
mvn-ow2
mvn-pdfbox
mvn-plexus
mvn-plexus-archiver
mvn-plexus-build-api
mvn-plexus-cipher
@@ -2817,6 +2833,7 @@ mvn-plexus-cli
mvn-plexus-compiler
mvn-plexus-containers
mvn-plexus-i18n
mvn-plexus-interactivity
mvn-plexus-interpolation
mvn-plexus-io
mvn-plexus-resources
@@ -2838,9 +2855,13 @@ mvn-struts
mvn-trilead-ssh2
mvn-tycho
mvn-validation-api
mvn-velocity
mvn-wagon
mvn-xbean-reflect
mvn-xercesImpl
mvn-xercesMinimal
mvn-xml-apis
mvn-xmlunit
mvn-xmlunit-core
mxnet
mycroft-core
@@ -4161,6 +4182,7 @@ setuptools_scm_git_archive
setxkbmap
sg3_utils
shadow
shapelib
shared-mime-info
sharutils
shell