Merge pull request #25906 from saltstack/dgm_pkgbuild

Cherry-pick of pkgbuild changes from develop branch
This commit is contained in:
Justin Findlay 2015-07-31 16:00:29 -06:00
commit 76d08d93f4
3 changed files with 416 additions and 19 deletions

311
salt/modules/debbuild.py Normal file
View File

@ -0,0 +1,311 @@
# -*- coding: utf-8 -*-
'''
Debian Package builder system
.. versionadded:: Beryllium
This system allows for all of the components to build debs safely in chrooted
environments. This also provides a function to generate debian repositories
This module impliments the pkgbuild interface
'''
# import python libs
from __future__ import absolute_import, print_function
import os
import tempfile
import shutil
from salt.ext.six.moves.urllib.parse import urlparse as _urlparse # pylint: disable=no-name-in-module,import-error
# Import salt libs
import salt.utils
# pylint: disable=import-error
__virtualname__ = 'pkgbuild'
def __virtual__():
'''
Confirm this module is on a Debian based system
'''
if __grains__.get('os_family', False) in ('Kali', 'Debian'):
return __virtualname__
return False
def _create_pbuilders():
'''
Create the .pbuilder family of files in user's home directory
'''
hook_text = '''#!/bin/sh
set -e
cat > "/etc/apt/preferences" << EOF
Package: python-abalaster
Pin: release a=testing
Pin-Priority: 950
Package: python-sphinx
Pin: release a=experimental
Pin-Priority: 900
Package: sphinx-common
Pin: release a=experimental
Pin-Priority: 900
Package: *
Pin: release a=jessie-backports
Pin-Priority: 750
Package: *
Pin: release a=stable
Pin-Priority: 700
Package: *
Pin: release a=testing
Pin-Priority: 650
Package: *
Pin: release a=unstable
Pin-Priority: 600
Package: *
Pin: release a=experimental
Pin-Priority: 550
EOF
'''
pbldrc_text = '''DIST="jessie"
if [ -n "${DIST}" ]; then
TMPDIR=/tmp
BASETGZ="`dirname $BASETGZ`/$DIST-base.tgz"
DISTRIBUTION=$DIST
APTCACHE="/var/cache/pbuilder/$DIST/aptcache"
fi
HOOKDIR="${HOME}/.pbuilder-hooks"
OTHERMIRROR="deb http://ftp.us.debian.org/debian/ testing main contrib non-free | deb http://ftp.us.debian.org/debian/ experimental main contrib non-free"
'''
home = os.path.expanduser('~')
pbuilder_hooksdir = os.path.join(home, '.pbuilder-hooks')
if not os.path.isdir(pbuilder_hooksdir):
os.makedirs(pbuilder_hooksdir)
d05hook = os.path.join(pbuilder_hooksdir, 'D05apt-preferences')
with open(d05hook, "w") as fow:
fow.write('{0}'.format(hook_text))
pbuilderrc = os.path.join(home, '.pbuilderrc')
with open(pbuilderrc, "w") as fow:
fow.write('{0}'.format(pbldrc_text))
def _mk_tree():
'''
Create the debian build area
'''
basedir = tempfile.mkdtemp()
return basedir
def _get_spec(tree_base, spec, template, saltenv='base'):
'''
Get the spec file (tarball of the debian sub-dir to use)
and place it in build area
'''
spec_tgt = os.path.basename(spec)
dest = os.path.join(tree_base, spec_tgt)
return __salt__['cp.get_url'](spec, dest, saltenv=saltenv)
def _get_src(tree_base, source, saltenv='base'):
'''
Get the named sources and place them into the tree_base
'''
parsed = _urlparse(source)
sbase = os.path.basename(source)
dest = os.path.join(tree_base, sbase)
if parsed.scheme:
__salt__['cp.get_url'](source, dest, saltenv=saltenv)
else:
shutil.copy(source, dest)
def make_src_pkg(dest_dir, spec, sources, template=None, saltenv='base'):
'''
Create a platform specific source package from the given platform spec/control file and sources
CLI Example:
Debian
salt '*' pkgbuild.make_src_pkg /var/www/html/ https://raw.githubusercontent.com/saltstack/libnacl/master/pkg/deb/python-libnacl.control.tar.xz https://pypi.python.org/packages/source/l/libnacl/libnacl-1.3.5.tar.gz
This example command should build the libnacl SOURCE package and place it in
/var/www/html/ on the minion
'''
_create_pbuilders()
tree_base = _mk_tree()
ret = []
if not os.path.isdir(dest_dir):
os.makedirs(dest_dir)
spec_pathfile = _get_spec(tree_base, spec, template, saltenv)
# build salt equivalents from scratch
if isinstance(sources, str):
sources = sources.split(',')
for src in sources:
_get_src(tree_base, src, saltenv)
#.dsc then assumes sources already build
if spec_pathfile.endswith('.dsc'):
for efile in os.listdir(tree_base):
full = os.path.join(tree_base, efile)
trgt = os.path.join(dest_dir, efile)
shutil.copy(full, trgt)
ret.append(trgt)
trgt = os.path.join(dest_dir, os.path.basename(spec_pathfile))
shutil.copy(spec_pathfile, trgt)
ret.append(trgt)
return ret
# obtain name of 'python setup.py sdist' generated tarball, extract the version
# and manipulate the name for debian use (convert minix and add '+ds')
salttarball = None
for afile in os.listdir(tree_base):
if afile.startswith('salt-') and afile.endswith('.tar.gz'):
salttarball = afile
break
else:
return ret
frontname = salttarball.split('.tar.gz')
salttar_name = frontname[0]
debname = salttar_name.replace('-', '_')
debname += '+ds'
debname_orig = debname + '.orig.tar.gz'
abspath_debname = os.path.join(tree_base, debname)
cmd = 'tar -xvzf {0}'.format(salttarball)
__salt__['cmd.run'](cmd, cwd=tree_base)
cmd = 'mv {0} {1}'.format(salttar_name, debname)
__salt__['cmd.run'](cmd, cwd=tree_base)
cmd = 'tar -cvzf {0} {1}'.format(os.path.join(tree_base, debname_orig), debname)
__salt__['cmd.run'](cmd, cwd=tree_base)
cmd = 'rm -f {0}'.format(salttarball)
__salt__['cmd.run'](cmd, cwd=tree_base)
cmd = 'cp {0} {1}'.format(spec_pathfile, abspath_debname)
__salt__['cmd.run'](cmd, cwd=abspath_debname)
cmd = 'tar -xvJf {0}'.format(spec_pathfile)
__salt__['cmd.run'](cmd, cwd=abspath_debname)
cmd = 'rm -f {0}'.format(os.path.basename(spec_pathfile))
__salt__['cmd.run'](cmd, cwd=abspath_debname)
cmd = 'debuild -S -uc -us'
__salt__['cmd.run'](cmd, cwd=abspath_debname, python_shell=True)
cmd = 'rm -fR {0}'.format(abspath_debname)
__salt__['cmd.run'](cmd)
for dfile in os.listdir(tree_base):
if dfile.startswith('salt_'):
if not dfile.endswith('.build'):
full = os.path.join(tree_base, dfile)
trgt = os.path.join(dest_dir, dfile)
shutil.copy(full, trgt)
ret.append(trgt)
return ret
def build(runas, tgt, dest_dir, spec, sources, deps, template, saltenv='base'):
'''
Given the package destination directory, the tarball containing debian files (e.g. control)
and package sources, use pbuilder to safely build the platform package
CLI Example:
Debian
salt '*' pkgbuild.make_src_pkg deb-8-x86_64 /var/www/html/ https://raw.githubusercontent.com/saltstack/libnacl/master/pkg/deb/python-libnacl.control https://pypi.python.org/packages/source/l/libnacl/libnacl-1.3.5.tar.gz
This example command should build the libnacl package for Debian using pbuilder
and place it in /var/www/html/ on the minion
'''
ret = {}
if not os.path.isdir(dest_dir):
try:
os.makedirs(dest_dir)
except (IOError, OSError):
pass
dsc_dir = tempfile.mkdtemp()
dscs = make_src_pkg(dsc_dir, spec, sources, template, saltenv)
# dscs should only contain salt orig and debian tarballs and dsc file
for dsc in dscs:
afile = os.path.basename(dsc)
adist = os.path.join(dest_dir, afile)
shutil.copy(dsc, adist)
if dsc.endswith('.dsc'):
dbase = os.path.dirname(dsc)
cmd = 'chown {0} -R {1}'.format(runas, dbase)
__salt__['cmd.run'](cmd)
results_dir = tempfile.mkdtemp()
cmd = 'chown {0} -R {1}'.format(runas, results_dir)
__salt__['cmd.run'](cmd)
cmd = 'pbuilder create'
__salt__['cmd.run'](cmd, runas=runas, python_shell=True)
cmd = 'pbuilder --build --buildresult {1} {0}'.format(dsc, results_dir)
__salt__['cmd.run'](cmd, runas=runas, python_shell=True)
for bfile in os.listdir(results_dir):
full = os.path.join(results_dir, bfile)
if bfile.endswith('.deb'):
bdist = os.path.join(dest_dir, bfile)
shutil.copy(full, bdist)
else:
with salt.utils.fopen(full, 'r') as fp_:
ret[bfile] = fp_.read()
shutil.rmtree(results_dir)
shutil.rmtree(dsc_dir)
return ret
def make_repo(repodir):
'''
Given the repodir, create a Debian repository out of the dsc therein
CLI Example::
salt '*' pkgbuild.make_repo /var/www/html/
'''
repocfg_text = '''Origin: SaltStack
Label: salt_debian
Suite: unstable
Codename: jessie
Architectures: i386 amd64 source
Components: contrib
Description: SaltStack debian package repo
Pull: jessie
'''
repoconf = os.path.join(repodir, 'conf')
if not os.path.isdir(repoconf):
os.makedirs(repoconf)
repoconfdist = os.path.join(repoconf, 'distributions')
with open(repoconfdist, "w") as fow:
fow.write('{0}'.format(repocfg_text))
for debfile in os.listdir(repodir):
if debfile.endswith('.changes'):
cmd = 'reprepro -Vb . include jessie {0}'.format(os.path.join(repodir, debfile))
__salt__['cmd.run'](cmd, cwd=repodir)
if debfile.endswith('.deb'):
cmd = 'reprepro -Vb . includedeb jessie {0}'.format(os.path.join(repodir, debfile))
__salt__['cmd.run'](cmd, cwd=repodir)

View File

@ -19,6 +19,7 @@ from salt.ext.six.moves.urllib.parse import urlparse as _urlparse # pylint: dis
# Import salt libs
import salt.utils
from salt.exceptions import SaltInvocationError
__virtualname__ = 'pkgbuild'
@ -32,6 +33,26 @@ def __virtual__():
return False
def _create_rpmmacros():
'''
Create the .rpmmacros file in user's home directory
'''
home = os.path.expanduser('~')
rpmbuilddir = os.path.join(home, 'rpmbuild')
if not os.path.isdir(rpmbuilddir):
os.makedirs(rpmbuilddir)
mockdir = os.path.join(home, 'mock')
if not os.path.isdir(mockdir):
os.makedirs(mockdir)
rpmmacros = os.path.join(home, '.rpmmacros')
with open(rpmmacros, "w") as afile:
afile.write('%_topdir {0}\n'.format(rpmbuilddir))
afile.write('%signature gpg\n')
afile.write('%_gpg_name packaging@saltstack.com\n')
def _mk_tree():
'''
Create the rpm build tree
@ -51,10 +72,9 @@ def _get_spec(tree_base, spec, template, saltenv='base'):
spec_tgt = os.path.basename(spec)
dest = os.path.join(tree_base, 'SPECS', spec_tgt)
return __salt__['cp.get_url'](
spec,
dest,
saltenv=saltenv,
template=template)
spec,
dest,
saltenv=saltenv)
def _get_src(tree_base, source, saltenv='base'):
@ -70,6 +90,46 @@ def _get_src(tree_base, source, saltenv='base'):
shutil.copy(source, dest)
def _get_distset(tgt):
'''
Get the distribution string for use with rpmbuild and mock
'''
# Centos adds that string to rpm names, removing that to have
# consistent naming on Centos and Redhat
tgtattrs = tgt.split('-')
if tgtattrs[1] in ['5', '6', '7']:
distset = '--define "dist .el{0}"'.format(tgtattrs[1])
else:
distset = ""
return distset
def _get_deps(deps, tree_base, saltenv='base'):
'''
Get include string for list of dependent rpms to build package
'''
deps_list = ""
if deps is None:
return deps_list
if not isinstance(deps, list):
raise SaltInvocationError(
'\'deps\' must be a Python list or comma-separated string'
)
for deprpm in deps:
parsed = _urlparse(deprpm)
depbase = os.path.basename(deprpm)
dest = os.path.join(tree_base, depbase)
if parsed.scheme:
__salt__['cp.get_url'](deprpm, dest, saltenv=saltenv)
else:
shutil.copy(deprpm, dest)
deps_list += ' --install {0}'.format(dest)
return deps_list
def make_src_pkg(dest_dir, spec, sources, template=None, saltenv='base'):
'''
Create a source rpm from the given spec file and sources
@ -81,13 +141,16 @@ def make_src_pkg(dest_dir, spec, sources, template=None, saltenv='base'):
This example command should build the libnacl SOURCE package and place it in
/var/www/html/ on the minion
'''
_create_rpmmacros()
tree_base = _mk_tree()
spec_path = _get_spec(tree_base, spec, template, saltenv)
if isinstance(sources, str):
sources = sources.split(',')
for src in sources:
_get_src(tree_base, src, saltenv)
cmd = 'rpmbuild --define "_topdir {0}" -bs {1}'.format(tree_base, spec_path)
# make source rpms for dist el5, usable with mock on other dists
cmd = 'rpmbuild --define "_topdir {0}" -bs --define "_source_filedigest_algorithm md5" --define "_binary_filedigest_algorithm md5" --define "dist .el5" {1}'.format(tree_base, spec_path)
__salt__['cmd.run'](cmd)
srpms = os.path.join(tree_base, 'SRPMS')
ret = []
@ -101,14 +164,14 @@ def make_src_pkg(dest_dir, spec, sources, template=None, saltenv='base'):
return ret
def build(runas, tgt, dest_dir, spec, sources, template, saltenv='base'):
def build(runas, tgt, dest_dir, spec, sources, deps, template, saltenv='base'):
'''
Given the package destination directory, the spec file source and package
sources, use mock to safely build the rpm defined in the spec file
CLI Example:
salt '*' pkgbuild.make_src_pkg mock epel-7-x86_64 /var/www/html/ https://raw.githubusercontent.com/saltstack/libnacl/master/pkg/rpm/python-libnacl.spec https://pypi.python.org/packages/source/l/libnacl/libnacl-1.3.5.tar.gz
salt '*' pkgbuild.build mock epel-7-x86_64 /var/www/html/ https://raw.githubusercontent.com/saltstack/libnacl/master/pkg/rpm/python-libnacl.spec https://pypi.python.org/packages/source/l/libnacl/libnacl-1.3.5.tar.gz
This example command should build the libnacl package for rhel 7 using user
"mock" and place it in /var/www/html/ on the minion
@ -121,6 +184,17 @@ def build(runas, tgt, dest_dir, spec, sources, template, saltenv='base'):
pass
srpm_dir = tempfile.mkdtemp()
srpms = make_src_pkg(srpm_dir, spec, sources, template, saltenv)
distset = _get_distset(tgt)
noclean = ""
deps_dir = tempfile.mkdtemp()
deps_list = _get_deps(deps, deps_dir, saltenv)
if deps_list and not deps_list.isspace():
cmd = 'mock --root={0} {1}'.format(tgt, deps_list)
__salt__['cmd.run'](cmd, runas=runas)
noclean += " --no-clean"
for srpm in srpms:
dbase = os.path.dirname(srpm)
cmd = 'chown {0} -R {1}'.format(runas, dbase)
@ -128,10 +202,12 @@ def build(runas, tgt, dest_dir, spec, sources, template, saltenv='base'):
results_dir = tempfile.mkdtemp()
cmd = 'chown {0} -R {1}'.format(runas, results_dir)
__salt__['cmd.run'](cmd)
cmd = 'mock -r {0} --rebuild {1} --resultdir {2}'.format(
tgt,
srpm,
results_dir)
cmd = 'mock --root={0} --resultdir={1} {2} {3} {4}'.format(
tgt,
results_dir,
distset,
noclean,
srpm)
__salt__['cmd.run'](cmd, runas=runas)
for rpm in os.listdir(results_dir):
full = os.path.join(results_dir, rpm)
@ -150,6 +226,7 @@ def build(runas, tgt, dest_dir, spec, sources, template, saltenv='base'):
with salt.utils.fopen(full, 'r') as fp_:
ret[rpm] = fp_.read()
shutil.rmtree(results_dir)
shutil.rmtree(deps_dir)
shutil.rmtree(srpm_dir)
return ret

View File

@ -53,6 +53,7 @@ def built(
sources,
template,
tgt,
deps=None,
results=None,
always=False,
saltenv='base'):
@ -80,6 +81,13 @@ def built(
tgt
The target platform to run the build on
deps
Packages required to ensure that the named package is built
can be hosted on either the salt master server or on an HTTP
or FTP server. Both HTTPS and HTTP are supported as well as
downloading directly from Amazon S3 compatible URLs with both
pre-configured and automatic IAM credentials
results
The names of the expected rpms that will be built
@ -101,7 +109,7 @@ def built(
present = set()
if os.path.isdir(dest_dir):
for fn_ in os.listdir(dest_dir):
present.appd(fn_)
present.add(fn_)
need = results.difference(present)
if not need:
ret['comment'] = 'All needed packages exist'
@ -111,13 +119,14 @@ def built(
ret['result'] = None
return ret
ret['changes'] = __salt__['pkgbuild.build'](
runas,
tgt,
dest_dir,
spec,
sources,
template,
saltenv)
runas,
tgt,
dest_dir,
spec,
sources,
deps,
template,
saltenv)
ret['comment'] = 'Packages Built'
return ret