Skip to content

Commit

Permalink
Merge pull request #554 from nvgoldin/jinja_template_sysprep
Browse files Browse the repository at this point in the history
 Move sysprep to Jinja2 templates
  • Loading branch information
ovirt-infra authored Jun 22, 2017
2 parents 7838c92 + b5a6ae9 commit 5a7b499
Show file tree
Hide file tree
Showing 16 changed files with 244 additions and 174 deletions.
1 change: 1 addition & 0 deletions automation/check-patch.packages
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ git
grubby
libffi-devel
libvirt-devel
libguestfs-devel
libxslt-devel
openssl-devel
pandoc
Expand Down
1 change: 1 addition & 0 deletions automation/check-patch.packages.el7
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ bats
git
libffi-devel
libvirt-devel
libguestfs-devel
libxslt-devel
openssl-devel
pandoc
Expand Down
6 changes: 6 additions & 0 deletions lago.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ BuildRequires: python-wrapt
%if 0%{?fedora} >= 24
BuildRequires: python2-configparser
BuildRequires: python2-paramiko >= 2.1.1
BuildRequires: python2-jinja2
%else
BuildRequires: python-jinja2
BuildRequires: python-configparser
BuildRequires: python2-paramiko
%endif
Expand Down Expand Up @@ -98,9 +100,11 @@ Requires: python-enum
Requires: pyxdg
Requires: python-wrapt
%if 0%{?fedora} >= 24
Requires: python2-jinja2
Requires: python2-configparser
Requires: python2-paramiko >= 2.1.1
%else
Requires: python-jinja2
Requires: python-configparser
Requires: python2-paramiko
%endif
Expand All @@ -121,9 +125,11 @@ Requires: sudo
%doc AUTHORS COPYING README.rst
%{python2_sitelib}/%{name}/*.py*
%{python2_sitelib}/%{name}/plugins/*.py*
%{python2_sitelib}/%{name}/templates/*.j2
%{python2_sitelib}/%{name}/providers/*.py*
%{python2_sitelib}/%{name}/providers/libvirt/*.py*
%{python2_sitelib}/%{name}/providers/libvirt/templates/*.xml

%{python2_sitelib}/%{name}-%{version}-py*.egg-info
%{_bindir}/lagocli
%{_bindir}/lago
Expand Down
48 changes: 16 additions & 32 deletions lago/providers/libvirt/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,38 +187,22 @@ def bootstrap(self):
if self.vm._spec['disks'][0]['type'] != 'empty' and self.vm._spec[
'disks'
][0]['format'] != 'iso':
sysprep_cmd = [
sysprep.set_hostname(self.vm.name()),
sysprep.delete_file(KDUMP_SERVICE),
sysprep.delete_file(POSTFIX_SERVICE),
sysprep.set_root_password(self.vm.root_password()),
sysprep.add_ssh_key(
self.vm.virt_env.prefix.paths.ssh_id_rsa_pub(),
),
sysprep.set_iscsi_initiator_name(self.vm.iscsi_name())
]

if self.vm.distro() in ('fc24', 'fc25', 'debian', 'el7'):
path = '/boot/grub2/grub.cfg'
if self.vm.distro() == 'debian':
path = '/boot/grub/grub.cfg'
sysprep_cmd.append(
sysprep.edit(path, 's/set timeout=5/set timeout=0/s')
)

# In fc25 NetworkManager configures the interfaces successfuly
# on boot.
if self.vm.distro() not in ('fc25', 'fc26'):
ifaces = [
('eth{0}'.format(idx), utils.ipv4_to_mac(nic['ip']))
for idx, nic in enumerate(self.vm.spec['nics'])
]
sysprep_cmd.extend(
sysprep.
config_net_ifaces_dhcp(self.vm.distro(), ifaces)
)

sysprep.sysprep(self.vm._spec['disks'][0]['path'], sysprep_cmd)
root_disk = self.vm._spec['disks'][0]['path']
mappings = {
'eth{0}'.format(idx): utils.ipv4_to_mac(nic['ip'])
for idx, nic in enumerate(self.vm.spec['nics'])
}
public_ssh_key = self.vm.virt_env.prefix.paths.ssh_id_rsa_pub()

sysprep.sysprep(
disk=root_disk,
mappings=mappings,
distro=self.vm.distro(),
root_password=self.vm.root_password(),
public_key=public_ssh_key,
iscsi_name=self.vm.iscsi_name(),
hostname=self.vm.name(),
)

def state(self):
"""
Expand Down
199 changes: 59 additions & 140 deletions lago/sysprep.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2015 Red Hat, Inc.
# Copyright 2015-2017 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -20,164 +20,83 @@
import os

import utils
from textwrap import dedent
import tempfile
import logging
LOGGER = logging.getLogger(__name__)
_DOT_SSH = '/root/.ssh'
_AUTHORIZED_KEYS = os.path.join(_DOT_SSH, 'authorized_keys')
_SELINUX_CONF_DIR = '/etc/selinux'
_SELINUX_CONF_PATH = os.path.join(_SELINUX_CONF_DIR, 'config')
_ISCSI_DIR = '/etc/iscsi'


def set_hostname(hostname):
return ('--hostname', hostname)


def set_root_password(password):
return ('--root-password', 'password:%s' % password)


def _write_file(path, content):
return ('--write', '%s:%s' % (path, content))

import tempfile
from jinja2 import Environment, PackageLoader
import textwrap
import sys

def _upload_file(local_path, remote_path):
return ('--upload', '%s:%s' % (remote_path, local_path))
LOGGER = logging.getLogger(__name__)

try:
import guestfs
except ImportError:
LOGGER.debug('guestfs not available, ignoring')

def set_iscsi_initiator_name(name):
return ('--mkdir', _ISCSI_DIR, '--chmod',
'0755:%s' % _ISCSI_DIR, ) + _write_file(
os.path.join(_ISCSI_DIR, 'initiatorname.iscsi'),
'InitiatorName=%s' % name,
) # noqa: E126

def _guestfs_version(default={'major': 1L, 'minor': 20L}):
if 'guestfs' in sys.modules:
g = guestfs.GuestFS(python_return_dict=True)
guestfs_ver = g.version()
g.close()
else:
guestfs_ver = default

def add_ssh_key(key, with_restorecon_fix=False):
extra_options = ('--mkdir', _DOT_SSH, '--chmod', '0700:%s' %
_DOT_SSH, ) + _upload_file(_AUTHORIZED_KEYS, key)
if (not os.stat(key).st_uid == 0 or not os.stat(key).st_gid == 0):
extra_options += (
'--run-command', 'chown root.root %s' % _AUTHORIZED_KEYS,
)
if with_restorecon_fix:
# Fix for fc23 not relabeling on boot
# https://bugzilla.redhat.com/1049656
extra_options += ('--firstboot-command', 'restorecon -R /root/.ssh', )
return extra_options


def set_selinux_mode(mode):
return (
'--mkdir', _SELINUX_CONF_DIR, '--chmod', '0755:%s' % _SELINUX_CONF_DIR,
) + _write_file(
_SELINUX_CONF_PATH,
('SELINUX=%s\n'
'SELINUXTYPE=targeted\n') % mode,
)
return guestfs_ver


def _config_net_interface(path, iface, type, bootproto, onboot, hwaddr):
iface_path = os.path.join(path, 'ifcfg-{0}'.format(iface))
cfg = dedent(
"""
HWADDR="{hwaddr}"
BOOTPROTO="{bootproto}"
ONBOOT="{onboot}"
TYPE="{type}"
NAME="{iface}"
""".format(
hwaddr=hwaddr,
bootproto=bootproto,
onboot=onboot,
type=type,
iface=iface
)
).lstrip()
with tempfile.NamedTemporaryFile(delete=False) as ifcfg_file:
ifcfg_file.write(cfg)
LOGGER.debug('generated %s for %s:\n%s', ifcfg_file.name, iface_path, cfg)
return ('--mkdir', path, '--chmod', '0755:{0}'.format(path)
) + _upload_file(iface_path, ifcfg_file.name)


def config_net_iface_debian(name, mac):
iface = dedent(
"""
auto {name}
iface {name} inet6 auto
iface {name} inet dhcp
hwaddress ether {mac}
""".format(name=name, mac=mac)
def _render_template(distro, loader, **kwargs):
env = Environment(
loader=loader,
trim_blocks=True,
lstrip_blocks=True,
)
return (
_write_file(
os.path.join(
'/etc/network/interfaces.d', 'ifcfg-{0}.cfg'.format(name)
), iface
)
env.filters['dedent'] = textwrap.dedent
template_name = 'sysprep-{0}.j2'.format(distro)
template = env.select_template([template_name, 'sysprep-base.j2'])
sysprep_content = template.render(guestfs_ver=_guestfs_version(), **kwargs)
with tempfile.NamedTemporaryFile(delete=False) as sysprep_file:
sysprep_file.write('# {0}\n'.format(template.name))
sysprep_file.write(sysprep_content)

LOGGER.debug(
('Generated sysprep template '
'at {0}:\n{1}').format(sysprep_file.name, sysprep_content)
)
return sysprep_file.name


def config_net_iface_loop_debian():
loop_device = dedent(
"""
auto lo
iface lo inet loopback
source /etc/network/interfaces.d/*.cfg
def sysprep(disk, distro, loader=None, backend='direct', **kwargs):
"""
Run virt-sysprep on the ``disk``, commands are built from the distro
specific template and arguments passed in ``kwargs``. If no template is
available it will default to ``sysprep-base.j2``.
Args:
disk(str): path to disk
distro(str): distro to render template for
loader(jinja2.BaseLoader): Jinja2 template loader, if not passed,
will search Lago's package.
backend(str): libguestfs backend to use
**kwargs(dict): environment variables for Jinja2 template
Returns:
None
Raises:
RuntimeError: On virt-sysprep none 0 exit code.
"""
)
return (_write_file('/etc/network/interfaces', loop_device))


def config_net_ifaces_dhcp(distro, mapping):
if distro == 'debian':
cmd = [config_net_iface_loop_debian()]
cmd.extend(
[config_net_iface_debian(name, mac) for name, mac in mapping]
)
else:
cmd = [config_net_iface_dhcp(name, mac) for name, mac in mapping]

return cmd


def config_net_iface_dhcp(
iface, hwaddr, path='/etc/sysconfig/network-scripts'
):
return _config_net_interface(
path=path,
iface=iface,
type='Ethernet',
bootproto='dhcp',
onboot='yes',
hwaddr=hwaddr,
)


def edit(filename, expression):
editstr = '%s:""%s""' % (filename, expression)
return ('--edit', editstr, )


def delete_file(filename):
return ('--delete', filename)


def update():
return ('--update', '--network', )
if loader is None:
loader = PackageLoader('lago', 'templates')
sysprep_file = _render_template(distro, loader=loader, **kwargs)

cmd = ['virt-sysprep', '-a', disk]
cmd.extend(['--commands-from-file', sysprep_file])

def sysprep(disk, mods, backend='direct'):
cmd = ['virt-sysprep', '-a', disk, '--selinux-relabel']
env = os.environ.copy()
if 'LIBGUESTFS_BACKEND' not in env:
env['LIBGUESTFS_BACKEND'] = backend
for mod in mods:
cmd.extend(mod)

ret = utils.run_command(cmd, env=env)
if ret:
Expand Down
12 changes: 12 additions & 0 deletions lago/templates/sysprep-base.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
selinux-relabel
hostname {{ hostname }}
root-password password:{{ root_password }}
{% if guestfs_ver['major'] >= 1 and guestfs_ver['minor'] >= 34 %}
ssh-inject root:file:{{public_key}}
{% else %}
mkdir /root/.ssh
chmod 0700:/root/.ssh
upload {{ public_key }}:/root/.ssh/authorized_keys
chmod 0600:/root/.ssh/authorized_keys
run-command chown root:root /root/.ssh/authorized_keys
{% endif -%}
6 changes: 6 additions & 0 deletions lago/templates/sysprep-debian.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{% import 'sysprep-macros.j2' as macros %}
{% include 'sysprep-base.j2' %}

{{ macros.network_devices_debian(mappings=mappings) }}

edit /boot/grub/grub.cfg:s/set timeout=5/set timeout=0/g
5 changes: 5 additions & 0 deletions lago/templates/sysprep-el6.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{% import 'sysprep-macros.j2' as macros %}
{% include 'sysprep-base.j2' %}

{{ macros.iscsi(name=iscsi_name, hostname=hostname) }}
{{ macros.network_devices_el(mappings=mappings) }}
10 changes: 10 additions & 0 deletions lago/templates/sysprep-el7.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% import 'sysprep-macros.j2' as macros %}
{% include 'sysprep-base.j2' %}

{{ macros.iscsi(name=iscsi_name, hostname=hostname) }}
{{ macros.network_devices_el(mappings=mappings) }}

edit /boot/grub2/grub.cfg:s/set timeout=5/set timeout=0/g

delete /etc/systemd/system/multi-user.target.wants/kdump.service
delete /etc/systemd/system/multi-user.target.wants/postfix.service
9 changes: 9 additions & 0 deletions lago/templates/sysprep-fc24.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% import 'sysprep-macros.j2' as macros %}
{% include 'sysprep-base.j2' %}

{{ macros.iscsi(name=iscsi_name, hostname=hostname) }}

edit /boot/grub2/grub.cfg:s/set timeout=5/set timeout=0/g

delete /etc/systemd/system/multi-user.target.wants/kdump.service
delete /etc/systemd/system/multi-user.target.wants/postfix.service
1 change: 1 addition & 0 deletions lago/templates/sysprep-fc25.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% include 'sysprep-fc24.j2' %}
1 change: 1 addition & 0 deletions lago/templates/sysprep-fc26.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% include 'sysprep-fc24.j2' %}
Loading

0 comments on commit 5a7b499

Please sign in to comment.