Merge lp:~ltrager/maas-image-builder/update_ks into lp:maas-image-builder/0.9

Proposed by Lee Trager
Status: Rejected
Rejected by: Blake Rouse
Proposed branch: lp:~ltrager/maas-image-builder/update_ks
Merge into: lp:maas-image-builder/0.9
Diff against target: 6442 lines (+3545/-2237)
49 files modified
.bzrignore (+2/-16)
LICENSE (+1/-1)
Makefile (+10/-110)
README (+9/-2)
buildout.cfg (+0/-49)
contrib/centos/centos6/centos6-amd64.ks (+75/-123)
contrib/centos/centos6/curtin/curtin-hooks (+0/-342)
contrib/centos/centos6/curtin/curtin-hooks.py (+342/-0)
contrib/centos/centos6/curtin/finalize (+0/-100)
contrib/centos/centos6/curtin/finalize.py (+99/-0)
contrib/centos/centos6/curtin/python_wrapper (+11/-0)
contrib/centos/centos7/centos7-amd64.ks (+93/-70)
contrib/centos/centos7/curtin/curtin-hooks (+0/-333)
contrib/centos/centos7/curtin/curtin-hooks.py (+338/-0)
contrib/centos/centos7/curtin/finalize (+0/-100)
contrib/centos/centos7/curtin/finalize.py (+100/-0)
contrib/centos/centos7/curtin/python_wrapper (+11/-0)
contrib/rhel/rhel7-amd64.ks (+128/-0)
contrib/windows/Autounattend.xml (+148/-0)
contrib/windows/curtin/curtin-hooks (+1/-0)
contrib/windows/curtin/finalize.py (+165/-0)
contrib/windows/curtin/python_wrapper (+11/-0)
contrib/windows/scripts/firstlogon.ps1 (+104/-0)
contrib/windows/scripts/logon.ps1 (+124/-0)
debian/changelog (+49/-0)
debian/control (+18/-11)
debian/python3-mib.install (+1/-1)
debian/rules (+4/-4)
pylintrc (+425/-0)
required-packages/base (+6/-1)
required-packages/dev (+1/-16)
requirements.txt (+2/-0)
scripts/maas-image-builder (+42/-3)
setup.py (+64/-35)
src/mib/__init__.py (+41/-8)
src/mib/builders/__init__.py (+64/-42)
src/mib/builders/centos.py (+94/-27)
src/mib/builders/rhel.py (+187/-0)
src/mib/builders/windows.py (+497/-0)
src/mib/core.py (+45/-9)
src/mib/net.py (+42/-8)
src/mib/parser.py (+42/-7)
src/mib/utils.py (+86/-32)
src/mib/virt.py (+48/-12)
test_requirements.txt (+2/-0)
tox.ini (+13/-0)
utilities/format-imports (+0/-421)
utilities/python_standard_libs.py (+0/-321)
versions.cfg (+0/-33)
To merge this branch: bzr merge lp:~ltrager/maas-image-builder/update_ks
Reviewer Review Type Date Requested Status
Blake Rouse (community) Disapprove
Review via email: mp+325576@code.launchpad.net

Commit message

Cleanup and standardize all kickstart config files.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

This is into the wrong code repository. This project has also switched to git, please update this branch to merge into the git repo.

review: Disapprove

Unmerged revisions

55. By Lee Trager

Clean up kickstart files

54. By Blake Rouse

Fix release 1.0.5 revision number.

53. By Blake Rouse

Release 1.0.5.

52. By Blake Rouse

Only allow VNC to windows build locally.

51. By Blake Rouse

Make kpartx_del handle failure and retry for up to 10 seconds.

50. By Blake Rouse

Fix --windows-drivers to work.

49. By Blake Rouse

Fix PEP8 errors in CentOS curtin hooks.

48. By Blake Rouse

Update changelog for 1.0.4 release.

47. By Blake Rouse

Modify RHEL to use the CentOS Curtin hooks.

46. By Blake Rouse

Allow customization of CentOS and RHEL images

Adds the --custom-kickstart flag to the centos and rhel commands. This command allows a user to specify a custom kickstart file which is run after the builtin one runs.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2015-03-14 19:29:17 +0000
3+++ .bzrignore 2017-06-13 16:13:40 +0000
4@@ -1,18 +1,4 @@
5-*.egg
6-*.egg-info
7-./.installed.cfg
8-./bin
9+./.tox
10 ./build
11 ./debian/changelog
12-./develop-eggs
13-./dist
14-./eggs
15-./include
16-./lib
17-./local
18-./parts
19-./run/*
20-./TAGS
21-./tags
22-dropin.cache
23-.idea
24+*.egg-info
25
26=== modified file 'LICENSE'
27--- LICENSE 2015-03-10 20:42:06 +0000
28+++ LICENSE 2017-06-13 16:13:40 +0000
29@@ -1,4 +1,4 @@
30-MAAS Image Builder is Copyright 2015 Canonical Ltd.
31+MAAS Image Builder is Copyright 2017 Canonical Ltd.
32
33 Canonical Ltd ("Canonical") distributes the MAAS Image Builder source code
34 under the GNU Affero General Public License, version 3 ("AGPLv3").
35
36=== modified file 'Makefile'
37--- Makefile 2015-03-15 13:36:38 +0000
38+++ Makefile 2017-06-13 16:13:40 +0000
39@@ -1,21 +1,3 @@
40-python := python2.7
41-
42-# Network activity can be suppressed by setting offline=true (or any
43-# non-empty string) at the command-line.
44-ifeq ($(offline),)
45-buildout := bin/buildout
46-virtualenv := virtualenv
47-else
48-buildout := bin/buildout buildout:offline=true
49-virtualenv := virtualenv --never-download
50-endif
51-
52-build: \
53- bin/buildout \
54- bin/maas-image-builder
55-
56-all: build
57-
58 # Install all packages required for MAAS image builder development & operation
59 # on the system. This may prompt for a password.
60 install-dependencies:
61@@ -23,107 +5,25 @@
62 --no-install-recommends install $(shell sort -u \
63 $(addprefix required-packages/,base build dev))
64
65-bin/python:
66- $(virtualenv) --python=$(python) --system-site-packages $(CURDIR)
67-
68-bin/buildout: bin/python bootstrap/zc.buildout-1.5.2.tar.gz
69- bin/python -m pip --quiet install --ignore-installed \
70- --no-dependencies bootstrap/zc.buildout-1.5.2.tar.gz
71- $(RM) -f README.txt # zc.buildout installs an annoying README.txt.
72- @touch --no-create $@ # Ensure it's newer than its dependencies.
73-
74-bin/maas-image-builder: \
75- bin/buildout buildout.cfg setup.py
76- # Install the egg-info so stevedore can find the entry points.
77- bin/python -m pip --quiet install --ignore-installed \
78- --no-dependencies -e .
79- $(buildout) install mib
80- @touch --no-create $@
81-
82-bin/flake8: bin/buildout buildout.cfg versions.cfg setup.py
83- $(buildout) install flake8
84- @touch --no-create $@
85-
86-lint: lint-py
87-
88-pocketlint = $(call available,pocketlint,python-pocket-lint)
89-
90-# Python lint checks are time-intensive, so we run them in parallel. It may
91-# make things matters worse if the files need to be read from disk, though, so
92-# this may need more tuning.
93-# The -n50 -P4 setting roughly doubled speed on a high-end system with SSD and
94-# all the files in cache.
95-lint-py: sources = $(wildcard *.py contrib/*.py) src contrib
96-lint-py: bin/flake8
97- @find $(sources) -name '*.py' -print0 \
98- | xargs -r0 -n50 -P4 bin/flake8 --ignore=E123 --config=/dev/null
99-
100-lint-doc:
101- @./utilities/doc-lint
102-
103-# Apply automated formatting to all Python files.
104-format: sources = $(wildcard *.py contrib/*.py) src contrib
105-format:
106- @find $(sources) -name '*.py' -print0 | xargs -r0 ./utilities/format-imports
107-
108-check: clean
109-
110-clean:
111- find . -type f -name '*.py[co]' -print0 | xargs -r0 $(RM)
112- find . -type f -name '*~' -print0 | xargs -r0 $(RM)
113- find . -type f -name dropin.cache -print0 | xargs -r0 $(RM)
114- $(RM) coverage.data coverage.xml
115- $(RM) -r coverage
116-
117-distclean: clean
118- $(RM) -r bin include lib local
119- $(RM) -r eggs develop-eggs
120- $(RM) -r build dist logs/* parts
121- $(RM) tags TAGS .installed.cfg
122- $(RM) -r *.egg *.egg-info src/*.egg-info
123+lint:
124+ @tox
125
126 package_export: VER = $(shell dpkg-parsechangelog -ldebian/changelog | sed -rne 's,^Version: ([^-]+).*,\1,p')
127 package_export: TARBALL = maas-image-builder_$(VER).orig.tar.gz
128 package_export:
129 @$(RM) -f build/$(TARBALL)
130 @mkdir -p build
131- @bzr export --root=maas-image-builder-$(VER).orig build/$(TARBALL) $(CURDIR)
132+ @bzr export $(packaging-export-extra) \
133+ --root=maas-image-builder-$(VER).orig build/$(TARBALL) $(CURDIR)
134
135 package: package_export
136- bzr bd --result-dir=build --build-dir=build
137+ bzr bd --result-dir=build --build-dir=build -- $(packaging-build-extra)
138+
139+package-dev: packaging-export-extra = --uncommitted
140+package-dev: packaging-build-extra = -uc -us
141+package-dev: package
142
143 source_package: package_export
144 bzr bd --result-dir=build --build-dir=build -S
145
146-#
147-# Phony stuff.
148-#
149-
150-define phony
151- build
152- check
153- clean
154- distclean
155- format
156- install-dependencies
157- lint
158- lint-py
159- package
160- source_package
161-endef
162-
163-phony := $(sort $(strip $(phony)))
164-
165-.PHONY: $(phony)
166-
167-#
168-# Functions.
169-#
170-
171-# Check if a command is found on PATH. Raise an error if not, citing
172-# the package to install. Return the command otherwise.
173-# Usage: $(call available,<command>,<package>)
174-define available
175- $(if $(shell which $(1)),$(1),$(error $(1) not found; \
176- install it with 'sudo apt-get install $(2)'))
177-endef
178+.PHONY: check install-dependencies
179
180=== modified file 'README'
181--- README 2015-07-31 18:46:41 +0000
182+++ README 2017-06-13 16:13:40 +0000
183@@ -1,8 +1,8 @@
184 .. -*- mode: rst -*-
185
186-************************
187+******************
188 MAAS Image Builder
189-************************
190+******************
191
192 Automated builder for creating images that can be deployed by MAAS and
193 installed by the curtin installer. For information on MAAS visit
194@@ -12,3 +12,10 @@
195 Supported Operating Systems:
196 - CentOS 6 (i386, amd64)
197 - CentOS 7 (amd64)
198+ - RedHat Enterprise Linux 7 (amd64)
199+ - Windows Server 2008 (i386, amd64)
200+ - Windows Server 2008 R2 (i386, amd64)
201+ - Windows Server 2012 (i386, amd64)
202+ - Windows Server 2012 R2 (i386, amd64)
203+ - Windows Hyper-V Server 2012 (i386, amd64)
204+ - Windows Hyper-V Server 2012 R2 (i386, amd64)
205
206=== removed directory 'bootstrap'
207=== removed file 'bootstrap/zc.buildout-1.5.2.tar.gz'
208Binary files bootstrap/zc.buildout-1.5.2.tar.gz 2015-03-10 20:42:06 +0000 and bootstrap/zc.buildout-1.5.2.tar.gz 1970-01-01 00:00:00 +0000 differ
209=== removed file 'buildout.cfg'
210--- buildout.cfg 2015-03-11 15:52:11 +0000
211+++ buildout.cfg 1970-01-01 00:00:00 +0000
212@@ -1,49 +0,0 @@
213-[buildout]
214-parts =
215- mib
216-extensions = buildout-versions
217-buildout_versions_file = versions.cfg
218-versions = versions
219-extends = versions.cfg
220-offline = false
221-newest = false
222-include-site-packages = true
223-prefer-final = true
224-allow-picked-versions = true
225-
226-[common]
227-extra-paths =
228- ${buildout:directory}/src
229-test-eggs =
230- coverage
231- fixtures
232- mock
233- nose
234- python-subunit
235- sst
236- testresources
237- testscenarios
238- testtools
239-initialization =
240- from os import environ
241- environ.setdefault("MIB_CONTRIB_DIR", "${buildout:directory}/contrib")
242-
243-[mib]
244-recipe = zc.recipe.egg
245-eggs =
246- ${common:test-eggs}
247-entry-points =
248- maas-image-builder=mib.core:execute
249-initialization =
250- ${common:initialization}
251-scripts =
252- maas-image-builder
253-extra-paths =
254- ${common:extra-paths}
255-
256-[flake8]
257-recipe = zc.recipe.egg
258-eggs =
259- flake8
260-entry-points =
261- flake8=flake8.run:main
262
263=== modified file 'contrib/centos/centos6/centos6-amd64.ks'
264--- contrib/centos/centos6/centos6-amd64.ks 2015-03-11 18:57:11 +0000
265+++ contrib/centos/centos6/centos6-amd64.ks 2017-06-13 16:13:40 +0000
266@@ -1,159 +1,111 @@
267-#version=DEVEL
268+#version=CentOS6
269+install
270+# Poweroff after installation
271+poweroff
272+# System authorization information
273+auth --enableshadow --passalgo=sha512
274 # Firewall configuration
275 firewall --enabled --service=ssh
276-repo --name="repo0" --baseurl=http://mirror.centos.org/centos/6/os/x86_64/
277-repo --name="repo1" --baseurl=http://mirror.centos.org/centos/6/updates/x86_64/
278-repo --name="repo2" --baseurl=http://dl.fedoraproject.org/pub/epel/6/x86_64/
279-# Root password
280-rootpw --iscrypted --lock $1$2e74e5$wMj25e4rEb4rJxqm7BAnk0
281-# System authorization information
282-auth --useshadow --enablemd5
283-# System keyboard
284+firstboot --disable
285+ignoredisk --only-use=vda
286+# Keyboard layouts
287 keyboard us
288 # System language
289 lang en_US.UTF-8
290-# SELinux configuration
291-selinux --enforcing
292-# Installation logging level
293-logging --level=info
294-# Reboot after installation
295-reboot
296-# System services
297-services --disabled="avahi-daemon,iscsi,iscsid,firstboot,kdump" --enabled="network,sshd,rsyslog,tuned"
298-# System timezone
299-timezone --isUtc America/New_York
300+repo --name "os" --baseurl="http://mirror.centos.org/centos/6/os/x86_64"
301+repo --name "updates" --baseurl="http://mirror.centos.org/centos/6/updates/x86_64"
302+repo --name="epel" --baseurl=http://dl.fedoraproject.org/pub/epel/6/x86_64/
303 # Network information
304 network --bootproto=dhcp --device=eth0 --onboot=on
305 # System bootloader configuration
306-bootloader --append="console=ttyS0,115200n8 console=tty0" --location=mbr --driveorder="sda" --timeout=1
307-# Clear the Master Boot Record
308+bootloader --append="console=ttyS0,115200n8 console=tty0" --location=mbr --driveorder="vda" --timeout=1
309+# Root password
310+rootpw --iscrypted nothing
311+selinux --enforcing
312+services --disabled="kdump" --enabled="network,sshd,rsyslog,chronyd"
313+timezone UTC --isUtc
314+# Disk
315 zerombr
316-# Partition clearing information
317-clearpart --all
318-# Disk partitioning information
319+clearpart --all --initlabel
320 part / --fstype="ext4" --size=3072
321
322-%post
323+%post --erroronfail
324+
325+# workaround anaconda requirements
326+passwd -d root
327+passwd -l root
328+
329+# remove avahi and networkmanager
330+echo "Removing avahi/zeroconf and NetworkManager"
331+yum -C -y remove avahi\* Network\*
332
333 # make sure firstboot doesn't start
334 echo "RUN_FIRSTBOOT=NO" > /etc/sysconfig/firstboot
335
336-cat <<EOL >> /etc/rc.local
337-if [ ! -d /root/.ssh ] ; then
338- mkdir -p /root/.ssh
339- chmod 0700 /root/.ssh
340- restorecon /root/.ssh
341-fi
342-EOL
343-
344-cat <<EOL >> /etc/ssh/sshd_config
345-UseDNS no
346-PermitRootLogin without-password
347-EOL
348-
349-# bz705572
350-ln -s /boot/grub/grub.conf /etc/grub.conf
351-
352-# bz688608
353-sed -i 's|\(^PasswordAuthentication \)yes|\1no|' /etc/ssh/sshd_config
354-
355-# allow sudo powers to cloud-user
356-echo -e 'cloud-user\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers
357-
358-#bz912801
359-# prevent udev rules from remapping nics
360-touch /etc/udev/rules.d/75-persistent-net-generator.rules
361-
362-#setup getty on ttyS0
363-echo "ttyS0" >> /etc/securetty
364-cat <<EOF > /etc/init/ttyS0.conf
365-start on stopped rc RUNLEVEL=[2345]
366-stop on starting runlevel [016]
367-respawn
368-instance /dev/ttyS0
369-exec /sbin/agetty /dev/ttyS0 115200 vt100-nav
370+echo "Cleaning old yum repodata."
371+yum clean all
372+
373+# chance dhcp client retry/timeouts to resolve #6866
374+cat >> /etc/dhcp/dhclient.conf << EOF
375+
376+timeout 300;
377+retry 60;
378 EOF
379
380-# lock root password
381-passwd -d root
382-passwd -l root
383-
384 # clean up installation logs"
385-yum clean all
386 rm -rf /var/log/yum.log
387 rm -rf /var/lib/yum/*
388 rm -rf /root/install.log
389 rm -rf /root/install.log.syslog
390 rm -rf /root/anaconda-ks.cfg
391 rm -rf /var/log/anaconda*
392+rm -rf /root/anac*
393+
394 %end
395
396-%packages --nobase
397-acpid
398-attr
399-audit
400-authconfig
401-basesystem
402-bash
403+%packages
404+@core
405+chrony
406 cloud-init
407-coreutils
408-cpio
409-cronie
410-device-mapper
411-dhclient
412-dracut
413-e2fsprogs
414+cloud-utils-growpart
415 efibootmgr
416-filesystem
417-glibc
418+epel-release
419 grub
420-heat-cfntools
421-initscripts
422-iproute
423-iptables
424-iptables-ipv6
425-iputils
426-kbd
427 kernel
428-kpartx
429-ncurses
430-net-tools
431-nfs-utils
432-openssh-clients
433-openssh-server
434-parted
435-passwd
436-policycoreutils
437-procps
438-python-oauth
439-rootfiles
440-rpm
441 rsync
442-rsyslog
443-selinux-policy
444-selinux-policy-targeted
445-sendmail
446-setup
447-shadow-utils
448-sudo
449-syslinux
450 tar
451-tuned
452-util-linux-ng
453-vim-minimal
454-yum
455-yum-metadata-parser
456+yum-utils
457+python-oauth
458 -NetworkManager
459--b43-openfwwf
460--biosdevname
461--fprintd
462--fprintd-pam
463--gtk2
464--libfprint
465--mcelog
466+-aic94xx-firmware
467+-alsa-firmware
468+-alsa-lib
469+-alsa-tools-firmware
470+-iprutils
471+-ivtv-firmware
472+-iwl100-firmware
473+-iwl1000-firmware
474+-iwl105-firmware
475+-iwl135-firmware
476+-iwl2000-firmware
477+-iwl2030-firmware
478+-iwl3160-firmware
479+-iwl3945-firmware
480+-iwl4965-firmware
481+-iwl5000-firmware
482+-iwl5150-firmware
483+-iwl6000-firmware
484+-iwl6000g2a-firmware
485+-iwl6000g2b-firmware
486+-iwl6050-firmware
487+-iwl7260-firmware
488+-iwl7265-firmware
489+-libertas-sd8686-firmware
490+-libertas-sd8787-firmware
491+-libertas-usb8388-firmware
492 -plymouth
493--redhat-support-tool
494--system-config-*
495--wireless-tools
496+-postfix
497+-wpa_supplicant
498
499 %end
500+
501
502=== modified file 'contrib/centos/centos6/curtin/curtin-hooks'
503--- contrib/centos/centos6/curtin/curtin-hooks 2015-07-28 22:00:23 +0000
504+++ contrib/centos/centos6/curtin/curtin-hooks 1970-01-01 00:00:00 +0000
505@@ -1,342 +0,0 @@
506-#!/usr/bin/env python
507-
508-from __future__ import (
509- absolute_import,
510- print_function,
511- unicode_literals,
512- )
513-
514-import os
515-import re
516-import sys
517-
518-sys.path.append('/curtin')
519-from curtin import (
520- block,
521- net,
522- util,
523- )
524-
525-"""
526-CentOS 6
527-
528-Currently Support:
529-
530-- Legacy boot
531-- DHCP of BOOTIF
532-
533-Not Supported:
534-
535-- UEFI boot (*Bad support, most likely wont support)
536-- Multiple network configration
537-- IPv6
538-"""
539-
540-FSTAB_PREPEND = """\
541-#
542-# /etc/fstab
543-# Created by MAAS fast-path installer.
544-#
545-# Accessible filesystems, by reference, are maintained under '/dev/disk'
546-# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
547-#
548-"""
549-
550-FSTAB_APPEND = """\
551-tmpfs /dev/shm tmpfs defaults 0 0
552-devpts /dev/pts devpts gid=5,mode=620 0 0
553-sysfs /sys sysfs defaults 0 0
554-proc /proc proc defaults 0 0
555-"""
556-
557-GRUB_CONF = """\
558-#
559-# /boot/grub/grub.conf
560-# Created by MAAS fast-path installer.
561-#
562-default 0
563-timeout 0
564-title MAAS
565- root {grub_root}
566- kernel /boot/{vmlinuz} root=UUID={root_uuid} {extra_opts}
567- initrd /boot/{initrd}
568-"""
569-
570-
571-def get_block_devices(target):
572- """Returns list of block devices for the given target."""
573- devs = block.get_devices_for_mp(target)
574- blockdevs = set()
575- for maybepart in devs:
576- (blockdev, part) = block.get_blockdev_for_partition(maybepart)
577- blockdevs.add(blockdev)
578- return list(blockdevs)
579-
580-
581-def get_root_info(target):
582- """Returns the root partitions information."""
583- rootpath = block.get_devices_for_mp(target)[0]
584- rootdev = os.path.basename(rootpath)
585- blocks = block._lsblock()
586- return blocks[rootdev]
587-
588-
589-def read_file(path):
590- """Returns content of a file."""
591- with open(path, 'rb') as stream:
592- return stream.read().encode('utf-8')
593-
594-
595-def write_fstab(target, curtin_fstab):
596- """Writes the new fstab, using the fstab provided
597- from curtin."""
598- fstab_path = os.path.join(target, 'etc', 'fstab')
599- fstab_data = read_file(curtin_fstab)
600- with open(fstab_path, 'w') as stream:
601- stream.write(FSTAB_PREPEND)
602- stream.write(fstab_data)
603- stream.write(FSTAB_APPEND)
604-
605-
606-def extract_kernel_params(data):
607- """Extracts the kernel parametes from the provided
608- grub config data."""
609- match = re.search('^\s+kernel (.+?)$', data, re.MULTILINE)
610- return match.group(0)
611-
612-
613-def strip_kernel_params(params, strip_params=[]):
614- """Removes un-needed kernel parameters."""
615- new_params = []
616- for param in params:
617- remove = False
618- for strip in strip_params:
619- if param.startswith(strip):
620- remove = True
621- break
622- if remove is False:
623- new_params.append(param)
624- return new_params
625-
626-
627-def get_boot_file(target, filename):
628- """Return the full filename of file in /boot on target."""
629- boot_dir = os.path.join(target, 'boot')
630- files = [
631- fname
632- for fname in os.listdir(boot_dir)
633- if fname.startswith(filename)
634- ]
635- if not files:
636- return None
637- return files[0]
638-
639-
640-def write_grub_conf(target, grub_root, extra=[]):
641- """Writes a new /boot/grub/grub.conf with the correct
642- boot arguments."""
643- root_info = get_root_info(target)
644- grub_path = os.path.join(target, 'boot', 'grub', 'grub.conf')
645- extra_opts = ' '.join(extra)
646- vmlinuz = get_boot_file(target, 'vmlinuz')
647- initrd = get_boot_file(target, 'initramfs')
648- with open(grub_path, 'w') as stream:
649- stream.write(
650- GRUB_CONF.format(
651- grub_root=grub_root,
652- vmlinuz=vmlinuz,
653- initrd=initrd,
654- root_uuid=root_info['UUID'],
655- extra_opts=extra_opts) + '\n')
656-
657-
658-def get_extra_kernel_parameters():
659- """Extracts the extra kernel commands from /proc/cmdline
660- that should be placed onto the host.
661-
662- Any command following the '--' entry should be placed
663- onto the host.
664- """
665- cmdline = read_file('/proc/cmdline')
666- cmdline = cmdline.split()
667- if '--' not in cmdline:
668- return []
669- idx = cmdline.index('--') + 1
670- if idx >= len(cmdline) + 1:
671- return []
672- return strip_kernel_params(
673- cmdline[idx:],
674- strip_params=['initrd=', 'BOOT_IMAGE=', 'BOOTIF='])
675-
676-
677-def get_grub_root(target):
678- """Extracts the grub root (hdX,X) from the grub command.
679-
680- This is used so the correct root device is used to install
681- stage1/stage2 boot loader.
682-
683- Note: grub-install normally does all of this for you, but
684- since the grub is older, it has an issue with the ISCSI
685- target as /dev/sda and cannot enumarate it with the BIOS.
686- """
687- with util.RunInChroot(target) as in_chroot:
688- data = '\n'.join([
689- 'find /boot/grub/stage1',
690- 'quit',
691- ])
692- out, err = in_chroot(['grub', '--batch'],
693- data=data, capture=True)
694- regex = re.search('^\s+(\(.+?\))$', out, re.MULTILINE)
695- return regex.groups()[0]
696-
697-
698-def grub_install(target, root):
699- """Installs grub onto the root."""
700- root_dev = root.split(',')[0] + ')'
701- with util.RunInChroot(target) as in_chroot:
702- data = '\n'.join([
703- 'root %s' % root,
704- 'setup %s' % root_dev,
705- 'quit',
706- ])
707- in_chroot(['grub', '--batch'],
708- data=data)
709-
710-
711-def set_autorelabel(target):
712- """Creates file /.autorelabel.
713-
714- This is used by SELinux to relabel all of the
715- files on the filesystem to have the correct
716- security context. Without this SSH login will
717- fail.
718- """
719- path = os.path.join(target, '.autorelabel')
720- open(path, 'a').close()
721-
722-
723-def get_boot_mac():
724- """Return the mac address of the booting interface."""
725- cmdline = read_file('/proc/cmdline')
726- cmdline = cmdline.split()
727- try:
728- bootif = [
729- option
730- for option in cmdline
731- if option.startswith('BOOTIF')
732- ][0]
733- except IndexError:
734- return None
735- _, mac = bootif.split('=')
736- mac = mac.split('-')[1:]
737- return ':'.join(mac)
738-
739-
740-def get_interface_names():
741- """Return a dictionary mapping mac addresses to interface names."""
742- sys_path = "/sys/class/net"
743- ifaces = {}
744- for iname in os.listdir(sys_path):
745- mac = read_file(os.path.join(sys_path, iname, "address"))
746- mac = mac.strip().lower()
747- ifaces[mac] = iname
748- return ifaces
749-
750-
751-def get_ipv4_config(iface, data):
752- """Returns the contents of the interface file for ipv4."""
753- config = [
754- 'TYPE="Ethernet"',
755- 'NM_CONTROLLED="no"',
756- 'USERCTL="yes"',
757- ]
758- if 'hwaddress' in data:
759- config.append('HWADDR="%s"' % data['hwaddress'])
760- else:
761- # Last ditch effort, use the device name, it probably won't match though!
762- config.append('DEVICE="%s"' % iface)
763- if data['auto']:
764- config.append('ONBOOT="yes"')
765- else:
766- config.append('ONBOOT="no"')
767-
768- method = data['method']
769- if method == 'dhcp':
770- config.append('BOOTPROTO="dhcp"')
771- config.append('PEERDNS="yes"')
772- config.append('PERSISTENT_DHCLIENT="1"')
773- if 'hostname' in data:
774- config.append('DHCP_HOSTNAME="%s"' % data['hostname'])
775- elif method == 'static':
776- config.append('BOOTPROTO="none"')
777- config.append('IPADDR="%s"' % data['address'])
778- config.append('NETMASK="%s"' % data['netmask'])
779- if 'broadcast' in data:
780- config.append('BROADCAST="%s"' % data['broadcast'])
781- if 'gateway' in data:
782- config.append('GATEWAY="%s"' % data['gateway'])
783- elif method == 'manual':
784- config.append('BOOTPROTO="none"')
785- return '\n'.join(config)
786-
787-
788-def write_interface_config(target, iface, data):
789- """Writes config for interface."""
790- family = data['family']
791- if family != "inet":
792- # Only supporting ipv4 currently
793- print(
794- "WARN: unsupported family %s, "
795- "failed to configure interface: %s" (family, iface))
796- return
797- config = get_ipv4_config(iface, data)
798- path = os.path.join(
799- target, 'etc', 'sysconfig', 'network-scripts', 'ifcfg-%s' % iface)
800- with open(path, 'w') as stream:
801- stream.write(config + '\n')
802-
803-
804-def write_network_config(target, mac):
805- """Write network configuration for the given MAC address."""
806- inames = get_interface_names()
807- iname = inames[mac.lower()]
808- write_interface_config(
809- target, iname, {
810- 'family': 'inet',
811- 'hwaddress': mac.upper(),
812- 'auto': True,
813- 'method': 'dhcp'
814- })
815-
816-
817-def main():
818- state = util.load_command_environment()
819- target = state['target']
820- if target is None:
821- print("Target was not provided in the environment.")
822- sys.exit(1)
823- fstab = state['fstab']
824- if fstab is None:
825- print("/etc/fstab output was not provided in the environment.")
826- sys.exit(1)
827- bootmac = get_boot_mac()
828- if bootmac is None:
829- print("Unable to determine boot interface.")
830- sys.exit(1)
831- devices = get_block_devices(target)
832- if not devices:
833- print("Unable to find block device for: %s" % target)
834- sys.exit(1)
835-
836- write_fstab(target, fstab)
837-
838- grub_root = get_grub_root(target)
839- write_grub_conf(target, grub_root, extra=get_extra_kernel_parameters())
840- grub_install(target, grub_root)
841-
842- set_autorelabel(target)
843- write_network_config(target, bootmac)
844-
845-
846-if __name__ == "__main__":
847- main()
848
849=== target is u'python_wrapper'
850=== added file 'contrib/centos/centos6/curtin/curtin-hooks.py'
851--- contrib/centos/centos6/curtin/curtin-hooks.py 1970-01-01 00:00:00 +0000
852+++ contrib/centos/centos6/curtin/curtin-hooks.py 2017-06-13 16:13:40 +0000
853@@ -0,0 +1,342 @@
854+#!/usr/bin/env python
855+
856+from __future__ import (
857+ absolute_import,
858+ print_function,
859+ unicode_literals,
860+ )
861+
862+import codecs
863+import os
864+import re
865+import sys
866+
867+from curtin import (
868+ block,
869+ util,
870+ )
871+
872+"""
873+CentOS 6
874+
875+Currently Support:
876+
877+- Legacy boot
878+- DHCP of BOOTIF
879+
880+Not Supported:
881+
882+- UEFI boot (*Bad support, most likely wont support)
883+- Multiple network configration
884+- IPv6
885+"""
886+
887+FSTAB_PREPEND = """\
888+#
889+# /etc/fstab
890+# Created by MAAS fast-path installer.
891+#
892+# Accessible filesystems, by reference, are maintained under '/dev/disk'
893+# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
894+#
895+"""
896+
897+FSTAB_APPEND = """\
898+tmpfs /dev/shm tmpfs defaults 0 0
899+devpts /dev/pts devpts gid=5,mode=620 0 0
900+sysfs /sys sysfs defaults 0 0
901+proc /proc proc defaults 0 0
902+"""
903+
904+GRUB_CONF = """\
905+#
906+# /boot/grub/grub.conf
907+# Created by MAAS fast-path installer.
908+#
909+default 0
910+timeout 0
911+title MAAS
912+ root {grub_root}
913+ kernel /boot/{vmlinuz} root=UUID={root_uuid} {extra_opts}
914+ initrd /boot/{initrd}
915+"""
916+
917+
918+def get_block_devices(target):
919+ """Returns list of block devices for the given target."""
920+ devs = block.get_devices_for_mp(target)
921+ blockdevs = set()
922+ for maybepart in devs:
923+ (blockdev, part) = block.get_blockdev_for_partition(maybepart)
924+ blockdevs.add(blockdev)
925+ return list(blockdevs)
926+
927+
928+def get_root_info(target):
929+ """Returns the root partitions information."""
930+ rootpath = block.get_devices_for_mp(target)[0]
931+ rootdev = os.path.basename(rootpath)
932+ blocks = block._lsblock()
933+ return blocks[rootdev]
934+
935+
936+def read_file(path):
937+ """Returns content of a file."""
938+ with codecs.open(path, encoding='utf-8') as stream:
939+ return stream.read()
940+
941+
942+def write_fstab(target, curtin_fstab):
943+ """Writes the new fstab, using the fstab provided
944+ from curtin."""
945+ fstab_path = os.path.join(target, 'etc', 'fstab')
946+ fstab_data = read_file(curtin_fstab)
947+ with open(fstab_path, 'w') as stream:
948+ stream.write(FSTAB_PREPEND)
949+ stream.write(fstab_data)
950+ stream.write(FSTAB_APPEND)
951+
952+
953+def extract_kernel_params(data):
954+ """Extracts the kernel parametes from the provided
955+ grub config data."""
956+ match = re.search('^\s+kernel (.+?)$', data, re.MULTILINE)
957+ return match.group(0)
958+
959+
960+def strip_kernel_params(params, strip_params=[]):
961+ """Removes un-needed kernel parameters."""
962+ new_params = []
963+ for param in params:
964+ remove = False
965+ for strip in strip_params:
966+ if param.startswith(strip):
967+ remove = True
968+ break
969+ if remove is False:
970+ new_params.append(param)
971+ return new_params
972+
973+
974+def get_boot_file(target, filename):
975+ """Return the full filename of file in /boot on target."""
976+ boot_dir = os.path.join(target, 'boot')
977+ files = [
978+ fname
979+ for fname in os.listdir(boot_dir)
980+ if fname.startswith(filename)
981+ ]
982+ if not files:
983+ return None
984+ return files[0]
985+
986+
987+def write_grub_conf(target, grub_root, extra=[]):
988+ """Writes a new /boot/grub/grub.conf with the correct
989+ boot arguments."""
990+ root_info = get_root_info(target)
991+ grub_path = os.path.join(target, 'boot', 'grub', 'grub.conf')
992+ extra_opts = ' '.join(extra)
993+ vmlinuz = get_boot_file(target, 'vmlinuz')
994+ initrd = get_boot_file(target, 'initramfs')
995+ with open(grub_path, 'w') as stream:
996+ stream.write(
997+ GRUB_CONF.format(
998+ grub_root=grub_root,
999+ vmlinuz=vmlinuz,
1000+ initrd=initrd,
1001+ root_uuid=root_info['UUID'],
1002+ extra_opts=extra_opts) + '\n')
1003+
1004+
1005+def get_extra_kernel_parameters():
1006+ """Extracts the extra kernel commands from /proc/cmdline
1007+ that should be placed onto the host.
1008+
1009+ Any command following the '--' entry should be placed
1010+ onto the host.
1011+ """
1012+ cmdline = read_file('/proc/cmdline')
1013+ cmdline = cmdline.split()
1014+ if '--' not in cmdline:
1015+ return []
1016+ idx = cmdline.index('--') + 1
1017+ if idx >= len(cmdline) + 1:
1018+ return []
1019+ return strip_kernel_params(
1020+ cmdline[idx:],
1021+ strip_params=['initrd=', 'BOOT_IMAGE=', 'BOOTIF='])
1022+
1023+
1024+def get_grub_root(target):
1025+ """Extracts the grub root (hdX,X) from the grub command.
1026+
1027+ This is used so the correct root device is used to install
1028+ stage1/stage2 boot loader.
1029+
1030+ Note: grub-install normally does all of this for you, but
1031+ since the grub is older, it has an issue with the ISCSI
1032+ target as /dev/sda and cannot enumarate it with the BIOS.
1033+ """
1034+ with util.RunInChroot(target) as in_chroot:
1035+ data = '\n'.join([
1036+ 'find /boot/grub/stage1',
1037+ 'quit',
1038+ ]).encode('utf-8')
1039+ out, err = in_chroot(['grub', '--batch'],
1040+ data=data, capture=True)
1041+ regex = re.search('^\s+(\(.+?\))$', out, re.MULTILINE)
1042+ return regex.groups()[0]
1043+
1044+
1045+def grub_install(target, root):
1046+ """Installs grub onto the root."""
1047+ root_dev = root.split(',')[0] + ')'
1048+ with util.RunInChroot(target) as in_chroot:
1049+ data = '\n'.join([
1050+ 'root %s' % root,
1051+ 'setup %s' % root_dev,
1052+ 'quit',
1053+ ]).encode('utf-8')
1054+ in_chroot(['grub', '--batch'],
1055+ data=data)
1056+
1057+
1058+def set_autorelabel(target):
1059+ """Creates file /.autorelabel.
1060+
1061+ This is used by SELinux to relabel all of the
1062+ files on the filesystem to have the correct
1063+ security context. Without this SSH login will
1064+ fail.
1065+ """
1066+ path = os.path.join(target, '.autorelabel')
1067+ open(path, 'a').close()
1068+
1069+
1070+def get_boot_mac():
1071+ """Return the mac address of the booting interface."""
1072+ cmdline = read_file('/proc/cmdline')
1073+ cmdline = cmdline.split()
1074+ try:
1075+ bootif = [
1076+ option
1077+ for option in cmdline
1078+ if option.startswith('BOOTIF')
1079+ ][0]
1080+ except IndexError:
1081+ return None
1082+ _, mac = bootif.split('=')
1083+ mac = mac.split('-')[1:]
1084+ return ':'.join(mac)
1085+
1086+
1087+def get_interface_names():
1088+ """Return a dictionary mapping mac addresses to interface names."""
1089+ sys_path = "/sys/class/net"
1090+ ifaces = {}
1091+ for iname in os.listdir(sys_path):
1092+ mac = read_file(os.path.join(sys_path, iname, "address"))
1093+ mac = mac.strip().lower()
1094+ ifaces[mac] = iname
1095+ return ifaces
1096+
1097+
1098+def get_ipv4_config(iface, data):
1099+ """Returns the contents of the interface file for ipv4."""
1100+ config = [
1101+ 'TYPE="Ethernet"',
1102+ 'NM_CONTROLLED="no"',
1103+ 'USERCTL="yes"',
1104+ ]
1105+ if 'hwaddress' in data:
1106+ config.append('HWADDR="%s"' % data['hwaddress'])
1107+ else:
1108+ # Last ditch effort, use the device name, it probably won't match
1109+ # though!
1110+ config.append('DEVICE="%s"' % iface)
1111+ if data['auto']:
1112+ config.append('ONBOOT="yes"')
1113+ else:
1114+ config.append('ONBOOT="no"')
1115+
1116+ method = data['method']
1117+ if method == 'dhcp':
1118+ config.append('BOOTPROTO="dhcp"')
1119+ config.append('PEERDNS="yes"')
1120+ config.append('PERSISTENT_DHCLIENT="1"')
1121+ if 'hostname' in data:
1122+ config.append('DHCP_HOSTNAME="%s"' % data['hostname'])
1123+ elif method == 'static':
1124+ config.append('BOOTPROTO="none"')
1125+ config.append('IPADDR="%s"' % data['address'])
1126+ config.append('NETMASK="%s"' % data['netmask'])
1127+ if 'broadcast' in data:
1128+ config.append('BROADCAST="%s"' % data['broadcast'])
1129+ if 'gateway' in data:
1130+ config.append('GATEWAY="%s"' % data['gateway'])
1131+ elif method == 'manual':
1132+ config.append('BOOTPROTO="none"')
1133+ return '\n'.join(config)
1134+
1135+
1136+def write_interface_config(target, iface, data):
1137+ """Writes config for interface."""
1138+ family = data['family']
1139+ if family != "inet":
1140+ # Only supporting ipv4 currently
1141+ print(
1142+ "WARN: unsupported family %s, "
1143+ "failed to configure interface: %s" (family, iface))
1144+ return
1145+ config = get_ipv4_config(iface, data)
1146+ path = os.path.join(
1147+ target, 'etc', 'sysconfig', 'network-scripts', 'ifcfg-%s' % iface)
1148+ with open(path, 'w') as stream:
1149+ stream.write(config + '\n')
1150+
1151+
1152+def write_network_config(target, mac):
1153+ """Write network configuration for the given MAC address."""
1154+ inames = get_interface_names()
1155+ iname = inames[mac.lower()]
1156+ write_interface_config(
1157+ target, iname, {
1158+ 'family': 'inet',
1159+ 'hwaddress': mac.upper(),
1160+ 'auto': True,
1161+ 'method': 'dhcp'
1162+ })
1163+
1164+
1165+def main():
1166+ state = util.load_command_environment()
1167+ target = state['target']
1168+ if target is None:
1169+ print("Target was not provided in the environment.")
1170+ sys.exit(1)
1171+ fstab = state['fstab']
1172+ if fstab is None:
1173+ print("/etc/fstab output was not provided in the environment.")
1174+ sys.exit(1)
1175+ bootmac = get_boot_mac()
1176+ if bootmac is None:
1177+ print("Unable to determine boot interface.")
1178+ sys.exit(1)
1179+ devices = get_block_devices(target)
1180+ if not devices:
1181+ print("Unable to find block device for: %s" % target)
1182+ sys.exit(1)
1183+
1184+ write_fstab(target, fstab)
1185+
1186+ grub_root = get_grub_root(target)
1187+ write_grub_conf(target, grub_root, extra=get_extra_kernel_parameters())
1188+ grub_install(target, grub_root)
1189+
1190+ set_autorelabel(target)
1191+ write_network_config(target, bootmac)
1192+
1193+
1194+if __name__ == "__main__":
1195+ main()
1196
1197=== modified file 'contrib/centos/centos6/curtin/finalize'
1198--- contrib/centos/centos6/curtin/finalize 2015-02-05 13:50:16 +0000
1199+++ contrib/centos/centos6/curtin/finalize 1970-01-01 00:00:00 +0000
1200@@ -1,100 +0,0 @@
1201-#!/usr/bin/env python
1202-
1203-from __future__ import (
1204- absolute_import,
1205- print_function,
1206- unicode_literals,
1207- )
1208-
1209-import json
1210-import os
1211-import sys
1212-
1213-sys.path.append('/curtin')
1214-from curtin import util
1215-
1216-
1217-DATASOURCE_LIST = """\
1218-datasource_list: [ MAAS ]
1219-"""
1220-
1221-DATASOURCE = """\
1222-datasource:
1223- MAAS: {{consumer_key: {consumer_key}, metadata_url: '{url}',
1224- token_key: {token_key}, token_secret: {token_secret}}}
1225-"""
1226-
1227-
1228-def get_datasource(**kwargs):
1229- """Returns the format cloud-init datasource."""
1230- return DATASOURCE_LIST + DATASOURCE.format(**kwargs)
1231-
1232-
1233-def load_config(path):
1234- """Loads the curtin config."""
1235- with open(path, 'r') as stream:
1236- return json.load(stream)
1237-
1238-
1239-def extract_maas_parameters(config):
1240- """Extracts the needed values from the debconf
1241- entry."""
1242- params = {}
1243- for line in config.splitlines():
1244- cloud, key, type, value = line.split()[:4]
1245- if key == "cloud-init/maas-metadata-url":
1246- params['url'] = value
1247- elif key == "cloud-init/maas-metadata-credentials":
1248- values = value.split("&")
1249- for oauth in values:
1250- key, value = oauth.split('=')
1251- if key == 'oauth_token_key':
1252- params['token_key'] = value
1253- elif key == 'oauth_token_secret':
1254- params['token_secret'] = value
1255- elif key == 'oauth_consumer_key':
1256- params['consumer_key'] = value
1257- return params
1258-
1259-
1260-def get_maas_debconf_selections(config):
1261- """Gets the debconf selections from the curtin config."""
1262- try:
1263- return config['debconf_selections']['maas']
1264- except KeyError:
1265- return None
1266-
1267-
1268-def write_datasource(target, data):
1269- """Writes the cloudinit config into
1270- /etc/cloud/cloud.cfg.d/90_datasource.cfg."""
1271- path = os.path.join(
1272- target, 'etc', 'cloud', 'cloud.cfg.d', '90_datasource.cfg')
1273- with open(path, 'w') as stream:
1274- stream.write(data + '\n')
1275-
1276-
1277-def main():
1278- state = util.load_command_environment()
1279- target = state['target']
1280- if target is None:
1281- print("Target was not provided in the environment.")
1282- sys.exit(1)
1283- config_f = state['config']
1284- if config_f is None:
1285- print("Config was not provided in the environment.")
1286- sys.exit(1)
1287- config = load_config(config_f)
1288-
1289- debconf = get_maas_debconf_selections(config)
1290- if debconf is None:
1291- print("Failed to get the debconf_selections.")
1292- sys.exit(1)
1293-
1294- params = extract_maas_parameters(debconf)
1295- datasource = get_datasource(**params)
1296- write_datasource(target, datasource)
1297-
1298-
1299-if __name__ == "__main__":
1300- main()
1301
1302=== target is u'python_wrapper'
1303=== added file 'contrib/centos/centos6/curtin/finalize.py'
1304--- contrib/centos/centos6/curtin/finalize.py 1970-01-01 00:00:00 +0000
1305+++ contrib/centos/centos6/curtin/finalize.py 2017-06-13 16:13:40 +0000
1306@@ -0,0 +1,99 @@
1307+#!/usr/bin/env python
1308+
1309+from __future__ import (
1310+ absolute_import,
1311+ print_function,
1312+ unicode_literals,
1313+ )
1314+
1315+import json
1316+import os
1317+import sys
1318+
1319+from curtin import util
1320+
1321+
1322+DATASOURCE_LIST = """\
1323+datasource_list: [ MAAS ]
1324+"""
1325+
1326+DATASOURCE = """\
1327+datasource:
1328+ MAAS: {{consumer_key: {consumer_key}, metadata_url: '{url}',
1329+ token_key: {token_key}, token_secret: {token_secret}}}
1330+"""
1331+
1332+
1333+def get_datasource(**kwargs):
1334+ """Returns the format cloud-init datasource."""
1335+ return DATASOURCE_LIST + DATASOURCE.format(**kwargs)
1336+
1337+
1338+def load_config(path):
1339+ """Loads the curtin config."""
1340+ with open(path, 'r') as stream:
1341+ return json.load(stream)
1342+
1343+
1344+def extract_maas_parameters(config):
1345+ """Extracts the needed values from the debconf
1346+ entry."""
1347+ params = {}
1348+ for line in config.splitlines():
1349+ cloud, key, type, value = line.split()[:4]
1350+ if key == "cloud-init/maas-metadata-url":
1351+ params['url'] = value
1352+ elif key == "cloud-init/maas-metadata-credentials":
1353+ values = value.split("&")
1354+ for oauth in values:
1355+ key, value = oauth.split('=')
1356+ if key == 'oauth_token_key':
1357+ params['token_key'] = value
1358+ elif key == 'oauth_token_secret':
1359+ params['token_secret'] = value
1360+ elif key == 'oauth_consumer_key':
1361+ params['consumer_key'] = value
1362+ return params
1363+
1364+
1365+def get_maas_debconf_selections(config):
1366+ """Gets the debconf selections from the curtin config."""
1367+ try:
1368+ return config['debconf_selections']['maas']
1369+ except KeyError:
1370+ return None
1371+
1372+
1373+def write_datasource(target, data):
1374+ """Writes the cloudinit config into
1375+ /etc/cloud/cloud.cfg.d/90_datasource.cfg."""
1376+ path = os.path.join(
1377+ target, 'etc', 'cloud', 'cloud.cfg.d', '90_datasource.cfg')
1378+ with open(path, 'w') as stream:
1379+ stream.write(data + '\n')
1380+
1381+
1382+def main():
1383+ state = util.load_command_environment()
1384+ target = state['target']
1385+ if target is None:
1386+ print("Target was not provided in the environment.")
1387+ sys.exit(1)
1388+ config_f = state['config']
1389+ if config_f is None:
1390+ print("Config was not provided in the environment.")
1391+ sys.exit(1)
1392+ config = load_config(config_f)
1393+
1394+ debconf = get_maas_debconf_selections(config)
1395+ if debconf is None:
1396+ print("Failed to get the debconf_selections.")
1397+ sys.exit(1)
1398+
1399+ params = extract_maas_parameters(debconf)
1400+ datasource = get_datasource(**params)
1401+ write_datasource(target, datasource)
1402+
1403+
1404+if __name__ == "__main__":
1405+ main()
1406
1407=== added file 'contrib/centos/centos6/curtin/python_wrapper'
1408--- contrib/centos/centos6/curtin/python_wrapper 1970-01-01 00:00:00 +0000
1409+++ contrib/centos/centos6/curtin/python_wrapper 2017-06-13 16:13:40 +0000
1410@@ -0,0 +1,11 @@
1411+#!/bin/bash
1412+
1413+export PYTHONPATH='/curtin'
1414+
1415+# Ubuntu 16.04 only ships with Python 3 while previous versions only ship
1416+# with Python 2.
1417+if type -p python > /dev/null; then
1418+ exec python "$0.py" "$@"
1419+else
1420+ exec python3 "$0.py" "$@"
1421+fi
1422
1423=== modified file 'contrib/centos/centos7/centos7-amd64.ks'
1424--- contrib/centos/centos7/centos7-amd64.ks 2015-07-23 20:03:39 +0000
1425+++ contrib/centos/centos7/centos7-amd64.ks 2017-06-13 16:13:40 +0000
1426@@ -1,103 +1,126 @@
1427-#version=RHEL7
1428+#version=CentOS7
1429 install
1430+# Poweroff after installation
1431+poweroff
1432+# System authorization information
1433+auth --enableshadow --passalgo=sha512
1434 # Firewall configuration
1435 firewall --enabled --service=ssh
1436-repo --name="repo0" --baseurl=http://mirror.centos.org/centos/7/os/x86_64
1437-repo --name="repo1" --baseurl=http://mirror.centos.org/centos/7/updates/x86_64
1438-repo --name="repo2" --baseurl=http://dl.fedoraproject.org/pub/epel/7/x86_64/
1439-repo --name="repo3" --baseurl=http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/20/Everything/x86_64/os/ --includepkgs=python-oauth
1440-# Root password
1441-rootpw --iscrypted $6$c78cFcbEdD2FcfE1$W0v5nUb1j1T8E3szv01CoBWFnl1TEWpt43WSZqtVP5kNih6zLiixQWS1umh1bDGnzWIqkwCwjIR8lHr.W0ua21
1442-# System authorization information
1443-auth --useshadow --enablemd5
1444-# System keyboard
1445-keyboard us
1446+firstboot --disable
1447+ignoredisk --only-use=vda
1448+# Keyboard layouts
1449+# old format: keyboard us
1450+# new format:
1451+keyboard --vckeymap=us --xlayouts='us'
1452 # System language
1453 lang en_US.UTF-8
1454-# SELinux configuration
1455-selinux --enforcing
1456-# Installation logging level
1457-logging --level=info
1458-# Poweroff after installation
1459-poweroff
1460-# System services
1461-services --disabled="avahi-daemon,iscsi,iscsid,firstboot,kdump" --enabled="network,sshd,rsyslog,tuned,chronyd"
1462-# System timezone
1463-timezone --isUtc America/New_York
1464+repo --name "os" --baseurl="http://mirror.centos.org/centos/7/os/x86_64"
1465+repo --name "updates" --baseurl="http://mirror.centos.org/centos/7/updates/x86_64"
1466+repo --name "extras" --baseurl="http://mirror.centos.org/centos/7/extras/x86_64"
1467+repo --name="python-oauth" --baseurl="http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/20/Everything/x86_64/os/" --includepkgs=python-oauth
1468 # Network information
1469 network --bootproto=dhcp --device=eth0 --onboot=on
1470 # System bootloader configuration
1471-bootloader --append="console=ttyS0,115200n8 console=tty0" --location=mbr --driveorder="sda" --timeout=1
1472-# Clear the Master Boot Record
1473+bootloader --append="console=ttyS0,115200n8 console=tty0" --location=mbr --driveorder="vda" --timeout=1
1474+# Root password
1475+rootpw --iscrypted nothing
1476+selinux --enforcing
1477+services --disabled="kdump" --enabled="network,sshd,rsyslog,chronyd"
1478+timezone UTC --isUtc
1479+# Disk
1480 zerombr
1481-# Partition clearing information
1482-clearpart --all
1483-# Disk partitioning information
1484+clearpart --all --initlabel
1485 part / --fstype="ext4" --size=3072
1486
1487-%post
1488+%post --erroronfail
1489+
1490+# workaround anaconda requirements
1491+passwd -d root
1492+passwd -l root
1493+
1494+# remove avahi and networkmanager
1495+echo "Removing avahi/zeroconf and NetworkManager"
1496+yum -C -y remove avahi\* Network\*
1497
1498 # make sure firstboot doesn't start
1499 echo "RUN_FIRSTBOOT=NO" > /etc/sysconfig/firstboot
1500
1501-cat <<EOL >> /etc/rc.local
1502-if [ ! -d /root/.ssh ] ; then
1503- mkdir -p /root/.ssh
1504- chmod 0700 /root/.ssh
1505- restorecon /root/.ssh
1506-fi
1507-EOL
1508-
1509-cat <<EOL >> /etc/ssh/sshd_config
1510-UseDNS no
1511-PermitRootLogin without-password
1512-EOL
1513-
1514-# bz705572
1515-ln -s /boot/grub/grub.conf /etc/grub.conf
1516-
1517-# bz688608
1518-sed -i 's|\(^PasswordAuthentication \)yes|\1no|' /etc/ssh/sshd_config
1519-
1520-# allow sudo powers to cloud-user
1521-echo -e 'cloud-user\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers
1522-
1523-#setup getty on ttyS0
1524-echo "ttyS0" >> /etc/securetty
1525-cat <<EOF > /etc/init/ttyS0.conf
1526-start on stopped rc RUNLEVEL=[2345]
1527-stop on starting runlevel [016]
1528-respawn
1529-instance /dev/ttyS0
1530-exec /sbin/agetty /dev/ttyS0 115200 vt100-nav
1531+echo "Cleaning old yum repodata."
1532+yum clean all
1533+
1534+# chance dhcp client retry/timeouts to resolve #6866
1535+cat >> /etc/dhcp/dhclient.conf << EOF
1536+
1537+timeout 300;
1538+retry 60;
1539 EOF
1540
1541-# lock root password
1542-passwd -d root
1543-passwd -l root
1544-
1545-# fix the cloud-init config, so its for rhel and not for fedora
1546-sed -i 's/name: fedora/name: cloud-user/g' /etc/cloud/cloud.cfg
1547-sed -i 's/gecos: Fedora Cloud User/gecos: CentOS Cloud User/g' /etc/cloud/cloud.cfg
1548-
1549-# delete the eth0 config
1550-rm -rf /etc/sysconfig/network-scripts/ifcfg-eth0
1551-
1552 # clean up installation logs"
1553-yum clean all
1554 rm -rf /var/log/yum.log
1555 rm -rf /var/lib/yum/*
1556 rm -rf /root/install.log
1557 rm -rf /root/install.log.syslog
1558 rm -rf /root/anaconda-ks.cfg
1559 rm -rf /var/log/anaconda*
1560+rm -rf /root/anac*
1561+
1562+echo "Fixing SELinux contexts."
1563+touch /var/log/cron
1564+touch /var/log/boot.log
1565+mkdir -p /var/cache/yum
1566+/usr/sbin/fixfiles -R -a restore
1567+
1568+# reorder console entries
1569+sed -i 's/console=tty0/console=tty0 console=ttyS0,115200n8/' /boot/grub2/grub.cfg
1570+
1571 %end
1572
1573 %packages
1574 @core
1575+chrony
1576 cloud-init
1577+cloud-utils-growpart
1578+dracut-config-generic
1579+dracut-norescue
1580+efibootmgr
1581+firewalld
1582+grub2
1583+grub2-efi-modules
1584+kernel
1585+rsync
1586+tar
1587+yum-utils
1588 python-oauth
1589-# Don't install NetworkManager
1590 -NetworkManager
1591+-aic94xx-firmware
1592+-alsa-firmware
1593+-alsa-lib
1594+-alsa-tools-firmware
1595+-iprutils
1596+-ivtv-firmware
1597+-iwl100-firmware
1598+-iwl1000-firmware
1599+-iwl105-firmware
1600+-iwl135-firmware
1601+-iwl2000-firmware
1602+-iwl2030-firmware
1603+-iwl3160-firmware
1604+-iwl3945-firmware
1605+-iwl4965-firmware
1606+-iwl5000-firmware
1607+-iwl5150-firmware
1608+-iwl6000-firmware
1609+-iwl6000g2a-firmware
1610+-iwl6000g2b-firmware
1611+-iwl6050-firmware
1612+-iwl7260-firmware
1613+-iwl7265-firmware
1614+-libertas-sd8686-firmware
1615+-libertas-sd8787-firmware
1616+-libertas-usb8388-firmware
1617+-plymouth
1618+-postfix
1619+-wpa_supplicant
1620
1621 %end
1622+
1623
1624=== modified file 'contrib/centos/centos7/curtin/curtin-hooks'
1625--- contrib/centos/centos7/curtin/curtin-hooks 2015-07-23 20:03:39 +0000
1626+++ contrib/centos/centos7/curtin/curtin-hooks 1970-01-01 00:00:00 +0000
1627@@ -1,333 +0,0 @@
1628-#!/usr/bin/env python
1629-
1630-from __future__ import (
1631- absolute_import,
1632- print_function,
1633- unicode_literals,
1634- )
1635-
1636-import os
1637-import re
1638-import sys
1639-import shutil
1640-
1641-sys.path.append('/curtin')
1642-from curtin import (
1643- block,
1644- net,
1645- util,
1646- )
1647-
1648-"""
1649-CentOS 7
1650-
1651-Currently Support:
1652-
1653-- Legacy boot
1654-- UEFI boot
1655-- DHCP of BOOTIF
1656-
1657-Not Supported:
1658-
1659-- Multiple network configration
1660-- IPv6
1661-"""
1662-
1663-FSTAB_PREPEND = """\
1664-#
1665-# /etc/fstab
1666-# Created by MAAS fast-path installer.
1667-#
1668-# Accessible filesystems, by reference, are maintained under '/dev/disk'
1669-# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
1670-#
1671-"""
1672-
1673-FSTAB_UEFI = """\
1674-LABEL=uefi-boot /boot/efi vfat defaults 0 0
1675-"""
1676-
1677-GRUB_PREPEND = """\
1678-# Set by MAAS fast-path installer.
1679-GRUB_TIMEOUT=0
1680-GRUB_TERMINAL_OUTPUT=console
1681-GRUB_DISABLE_OS_PROBER=true
1682-"""
1683-
1684-
1685-def get_block_devices(target):
1686- """Returns list of block devices for the given target."""
1687- devs = block.get_devices_for_mp(target)
1688- blockdevs = set()
1689- for maybepart in devs:
1690- (blockdev, part) = block.get_blockdev_for_partition(maybepart)
1691- blockdevs.add(blockdev)
1692- return list(blockdevs)
1693-
1694-
1695-def get_root_info(target):
1696- """Returns the root partitions information."""
1697- rootpath = block.get_devices_for_mp(target)[0]
1698- rootdev = os.path.basename(rootpath)
1699- blocks = block._lsblock()
1700- return blocks[rootdev]
1701-
1702-
1703-def get_uefi_partition():
1704- """Return the UEFI partition."""
1705- for _, value in block._lsblock().items():
1706- if value['LABEL'] == 'uefi-boot':
1707- return value
1708- return None
1709-
1710-
1711-def read_file(path):
1712- """Returns content of a file."""
1713- with open(path, 'rb') as stream:
1714- return stream.read().encode('utf-8')
1715-
1716-
1717-def write_fstab(target, curtin_fstab):
1718- """Writes the new fstab, using the fstab provided
1719- from curtin."""
1720- fstab_path = os.path.join(target, 'etc', 'fstab')
1721- fstab_data = read_file(curtin_fstab)
1722- with open(fstab_path, 'w') as stream:
1723- stream.write(FSTAB_PREPEND)
1724- stream.write(fstab_data)
1725- if util.is_uefi_bootable():
1726- stream.write(FSTAB_UEFI)
1727-
1728-
1729-def strip_kernel_params(params, strip_params=[]):
1730- """Removes un-needed kernel parameters."""
1731- new_params = []
1732- for param in params:
1733- remove = False
1734- for strip in strip_params:
1735- if param.startswith(strip):
1736- remove = True
1737- break
1738- if remove is False:
1739- new_params.append(param)
1740- return new_params
1741-
1742-
1743-def get_extra_kernel_parameters():
1744- """Extracts the extra kernel commands from /proc/cmdline
1745- that should be placed onto the host.
1746-
1747- Any command following the '--' entry should be placed
1748- onto the host.
1749- """
1750- cmdline = read_file('/proc/cmdline')
1751- cmdline = cmdline.split()
1752- if '--' not in cmdline:
1753- return []
1754- idx = cmdline.index('--') + 1
1755- if idx >= len(cmdline) + 1:
1756- return []
1757- return strip_kernel_params(
1758- cmdline[idx:],
1759- strip_params=['initrd=', 'BOOT_IMAGE=', 'BOOTIF='])
1760-
1761-
1762-def update_grub_default(target, extra=[]):
1763- """Updates /etc/default/grub with the correct options."""
1764- grub_default_path = os.path.join(target, 'etc', 'default', 'grub')
1765- kernel_cmdline = ' '.join(extra)
1766- with open(grub_default_path, 'a') as stream:
1767- stream.write(GRUB_PREPEND)
1768- stream.write('GRUB_CMDLINE_LINUX=\"%s\"\n' % kernel_cmdline)
1769-
1770-
1771-def grub2_install(target, root):
1772- """Installs grub2 to the root."""
1773- with util.RunInChroot(target) as in_chroot:
1774- in_chroot(['grub2-install', '--recheck', root])
1775-
1776-
1777-def grub2_mkconfig(target):
1778- """Writes the new grub2 config."""
1779- with util.RunInChroot(target) as in_chroot:
1780- in_chroot(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'])
1781-
1782-
1783-def install_efi(target, uefi_path):
1784- """Install the EFI data from /boot into efi partition."""
1785- # Create temp mount point for uefi partition.
1786- tmp_efi = os.path.join(target, 'boot', 'efi_part')
1787- os.mkdir(tmp_efi)
1788- util.subp(['mount', uefi_path, tmp_efi])
1789-
1790- # Copy the data over.
1791- try:
1792- efi_path = os.path.join(target, 'boot', 'efi')
1793- if os.path.exists(os.path.join(tmp_efi, 'EFI')):
1794- shutil.rmtree(os.path.join(tmp_efi, 'EFI'))
1795- shutil.copytree(
1796- os.path.join(efi_path, 'EFI'),
1797- os.path.join(tmp_efi, 'EFI'))
1798- finally:
1799- # Clean up tmp mount
1800- util.subp(['umount', tmp_efi])
1801- os.rmdir(tmp_efi)
1802-
1803- # Mount and do grub install
1804- util.subp(['mount', uefi_path, efi_path])
1805- try:
1806- with util.RunInChroot(target) as in_chroot:
1807- in_chroot([
1808- 'grub2-install', '--target=x86_64-efi',
1809- '--efi-directory', '/boot/efi',
1810- '--recheck'])
1811- finally:
1812- util.subp(['umount', efi_path])
1813-
1814-
1815-def set_autorelabel(target):
1816- """Creates file /.autorelabel.
1817-
1818- This is used by SELinux to relabel all of the
1819- files on the filesystem to have the correct
1820- security context. Without this SSH login will
1821- fail.
1822- """
1823- path = os.path.join(target, '.autorelabel')
1824- open(path, 'a').close()
1825-
1826-
1827-def get_boot_mac():
1828- """Return the mac address of the booting interface."""
1829- cmdline = read_file('/proc/cmdline')
1830- cmdline = cmdline.split()
1831- try:
1832- bootif = [
1833- option
1834- for option in cmdline
1835- if option.startswith('BOOTIF')
1836- ][0]
1837- except IndexError:
1838- return None
1839- _, mac = bootif.split('=')
1840- mac = mac.split('-')[1:]
1841- return ':'.join(mac)
1842-
1843-
1844-def get_interface_names():
1845- """Return a dictionary mapping mac addresses to interface names."""
1846- sys_path = "/sys/class/net"
1847- ifaces = {}
1848- for iname in os.listdir(sys_path):
1849- mac = read_file(os.path.join(sys_path, iname, "address"))
1850- mac = mac.strip().lower()
1851- ifaces[mac] = iname
1852- return ifaces
1853-
1854-
1855-def get_ipv4_config(iface, data):
1856- """Returns the contents of the interface file for ipv4."""
1857- config = [
1858- 'TYPE="Ethernet"',
1859- 'NM_CONTROLLED="no"',
1860- 'USERCTL="yes"',
1861- ]
1862- if 'hwaddress' in data:
1863- config.append('HWADDR="%s"' % data['hwaddress'])
1864- # Fallback to using device name
1865- else:
1866- config.append('DEVICE="%"' % iface)
1867- if data['auto']:
1868- config.append('ONBOOT="yes"')
1869- else:
1870- config.append('ONBOOT="no"')
1871-
1872- method = data['method']
1873- if method == 'dhcp':
1874- config.append('BOOTPROTO="dhcp"')
1875- config.append('PEERDNS="yes"')
1876- config.append('PERSISTENT_DHCLIENT="1"')
1877- if 'hostname' in data:
1878- config.append('DHCP_HOSTNAME="%s"' % data['hostname'])
1879- elif method == 'static':
1880- config.append('BOOTPROTO="none"')
1881- config.append('IPADDR="%s"' % data['address'])
1882- config.append('NETMASK="%s"' % data['netmask'])
1883- if 'broadcast' in data:
1884- config.append('BROADCAST="%s"' % data['broadcast'])
1885- if 'gateway' in data:
1886- config.append('GATEWAY="%s"' % data['gateway'])
1887- elif method == 'manual':
1888- config.append('BOOTPROTO="none"')
1889- return '\n'.join(config)
1890-
1891-
1892-def write_interface_config(target, iface, data):
1893- """Writes config for interface."""
1894- family = data['family']
1895- if family != "inet":
1896- # Only supporting ipv4 currently
1897- print(
1898- "WARN: unsupported family %s, "
1899- "failed to configure interface: %s" (family, iface))
1900- return
1901- config = get_ipv4_config(iface, data)
1902- path = os.path.join(
1903- target, 'etc', 'sysconfig', 'network-scripts', 'ifcfg-%s' % iface)
1904- with open(path, 'w') as stream:
1905- stream.write(config + '\n')
1906-
1907-
1908-def write_network_config(target, mac):
1909- """Write network configuration for the given MAC address."""
1910- inames = get_interface_names()
1911- iname = inames[mac.lower()]
1912- write_interface_config(
1913- target, iname, {
1914- 'family': 'inet',
1915- 'hwaddress': mac.upper(),
1916- 'auto': True,
1917- 'method': 'dhcp'
1918- })
1919-
1920-
1921-def main():
1922- state = util.load_command_environment()
1923- target = state['target']
1924- if target is None:
1925- print("Target was not provided in the environment.")
1926- sys.exit(1)
1927- fstab = state['fstab']
1928- if fstab is None:
1929- print("/etc/fstab output was not provided in the environment.")
1930- sys.exit(1)
1931- bootmac = get_boot_mac()
1932- if bootmac is None:
1933- print("Unable to determine boot interface.")
1934- sys.exit(1)
1935- devices = get_block_devices(target)
1936- if not devices:
1937- print("Unable to find block device for: %s" % target)
1938- sys.exit(1)
1939-
1940- write_fstab(target, fstab)
1941-
1942- update_grub_default(
1943- target, extra=get_extra_kernel_parameters())
1944- grub2_mkconfig(target)
1945- if util.is_uefi_bootable():
1946- uefi_part = get_uefi_partition()
1947- if uefi_part is None:
1948- print('Unable to determine UEFI parition.')
1949- sys.exit(1)
1950- install_efi(target, uefi_part['device_path'])
1951- else:
1952- for dev in devices:
1953- grub2_install(target, dev)
1954-
1955- set_autorelabel(target)
1956- write_network_config(target, bootmac)
1957-
1958-
1959-if __name__ == "__main__":
1960- main()
1961
1962=== target is u'python_wrapper'
1963=== added file 'contrib/centos/centos7/curtin/curtin-hooks.py'
1964--- contrib/centos/centos7/curtin/curtin-hooks.py 1970-01-01 00:00:00 +0000
1965+++ contrib/centos/centos7/curtin/curtin-hooks.py 2017-06-13 16:13:40 +0000
1966@@ -0,0 +1,338 @@
1967+#!/usr/bin/env python
1968+
1969+from __future__ import (
1970+ absolute_import,
1971+ print_function,
1972+ unicode_literals,
1973+ )
1974+
1975+import os
1976+import re
1977+import sys
1978+import shutil
1979+
1980+sys.path.append('/curtin')
1981+from curtin import (
1982+ block,
1983+ net,
1984+ util,
1985+ )
1986+
1987+"""
1988+CentOS/RHEL 7
1989+
1990+Currently Support:
1991+
1992+- Legacy boot
1993+- UEFI boot
1994+- DHCP of BOOTIF
1995+
1996+Not Supported:
1997+
1998+- Multiple network configration
1999+- IPv6
2000+"""
2001+
2002+FSTAB_PREPEND = """\
2003+#
2004+# /etc/fstab
2005+# Created by MAAS fast-path installer.
2006+#
2007+# Accessible filesystems, by reference, are maintained under '/dev/disk'
2008+# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
2009+#
2010+"""
2011+
2012+FSTAB_UEFI = """\
2013+LABEL=uefi-boot /boot/efi vfat defaults 0 0
2014+"""
2015+
2016+GRUB_PREPEND = """\
2017+# Set by MAAS fast-path installer.
2018+GRUB_TIMEOUT=0
2019+GRUB_TERMINAL_OUTPUT=console
2020+GRUB_DISABLE_OS_PROBER=true
2021+"""
2022+
2023+
2024+def get_block_devices(target):
2025+ """Returns list of block devices for the given target."""
2026+ devs = block.get_devices_for_mp(target)
2027+ blockdevs = set()
2028+ for maybepart in devs:
2029+ (blockdev, part) = block.get_blockdev_for_partition(maybepart)
2030+ blockdevs.add(blockdev)
2031+ return list(blockdevs)
2032+
2033+
2034+def get_root_info(target):
2035+ """Returns the root partitions information."""
2036+ rootpath = block.get_devices_for_mp(target)[0]
2037+ rootdev = os.path.basename(rootpath)
2038+ blocks = block._lsblock()
2039+ return blocks[rootdev]
2040+
2041+
2042+def read_file(path):
2043+ """Returns content of a file."""
2044+ with open(path, encoding="utf-8") as stream:
2045+ return stream.read()
2046+
2047+
2048+def write_fstab(target, curtin_fstab):
2049+ """Writes the new fstab, using the fstab provided
2050+ from curtin."""
2051+ fstab_path = os.path.join(target, 'etc', 'fstab')
2052+ fstab_data = read_file(curtin_fstab)
2053+ with open(fstab_path, 'w') as stream:
2054+ stream.write(FSTAB_PREPEND)
2055+ stream.write(fstab_data)
2056+ if util.is_uefi_bootable():
2057+ stream.write(FSTAB_UEFI)
2058+
2059+
2060+def strip_kernel_params(params, strip_params=[]):
2061+ """Removes un-needed kernel parameters."""
2062+ new_params = []
2063+ for param in params:
2064+ remove = False
2065+ for strip in strip_params:
2066+ if param.startswith(strip):
2067+ remove = True
2068+ break
2069+ if remove is False:
2070+ new_params.append(param)
2071+ return new_params
2072+
2073+
2074+def get_extra_kernel_parameters():
2075+ """Extracts the extra kernel commands from /proc/cmdline
2076+ that should be placed onto the host.
2077+
2078+ Any command following the '--' entry should be placed
2079+ onto the host.
2080+ """
2081+ cmdline = read_file('/proc/cmdline')
2082+ cmdline = cmdline.split()
2083+ if '--' not in cmdline:
2084+ return []
2085+ idx = cmdline.index('--') + 1
2086+ if idx >= len(cmdline) + 1:
2087+ return []
2088+ return strip_kernel_params(
2089+ cmdline[idx:],
2090+ strip_params=['initrd=', 'BOOT_IMAGE=', 'BOOTIF='])
2091+
2092+
2093+def update_grub_default(target, extra=[]):
2094+ """Updates /etc/default/grub with the correct options."""
2095+ grub_default_path = os.path.join(target, 'etc', 'default', 'grub')
2096+ kernel_cmdline = ' '.join(extra)
2097+ with open(grub_default_path, 'a') as stream:
2098+ stream.write(GRUB_PREPEND)
2099+ stream.write('GRUB_CMDLINE_LINUX=\"%s\"\n' % kernel_cmdline)
2100+
2101+
2102+def grub2_install(target, root):
2103+ """Installs grub2 to the root."""
2104+ with util.RunInChroot(target) as in_chroot:
2105+ in_chroot(['grub2-install', '--recheck', root])
2106+
2107+
2108+def grub2_mkconfig(target):
2109+ """Writes the new grub2 config."""
2110+ with util.RunInChroot(target) as in_chroot:
2111+ in_chroot(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'])
2112+
2113+
2114+def get_efibootmgr_value(output, key):
2115+ """Parses the `output` from 'efibootmgr' to return value for `key`."""
2116+ for line in output.splitlines():
2117+ split = line.split(':')
2118+ if len(split) == 2:
2119+ curr_key = split[0].strip()
2120+ value = split[1].strip()
2121+ if curr_key == key:
2122+ return value
2123+
2124+
2125+def get_file_efi_loaders(output):
2126+ """Parses the `output` from 'efibootmgr' to return all loaders that exist
2127+ in '\EFI' path."""
2128+ return re.findall(
2129+ r"^Boot(?P<hex>[0-9a-fA-F]{4})\*?\s*\S+\s+.*File\(\\EFI.*$",
2130+ output, re.MULTILINE)
2131+
2132+
2133+def grub2_install_efi(target):
2134+ """Configure for EFI.
2135+
2136+ First capture the currently booted loader (normally a network device),
2137+ then perform grub installation (adds a new bootloader and adjusts the
2138+ boot order), finally re-adjust the boot order so that the currently booted
2139+ loader is set to boot first in the new order.
2140+ """
2141+ with util.RunInChroot(target) as in_chroot:
2142+ stdout, _ = in_chroot(['efibootmgr', '-v'], capture=True)
2143+ currently_booted = get_efibootmgr_value(stdout, 'BootCurrent')
2144+ loaders = get_file_efi_loaders(stdout)
2145+ if currently_booted in loaders:
2146+ loaders.remove(currently_booted)
2147+ for loader in loaders:
2148+ in_chroot(['efibootmgr', '-B', '-b', loader], capture=True)
2149+ in_chroot([
2150+ 'grub2-install', '--target=x86_64-efi',
2151+ '--efi-directory', '/boot/efi',
2152+ '--recheck'])
2153+ stdout, _ = in_chroot(['efibootmgr'], capture=True)
2154+ currently_booted = get_efibootmgr_value(stdout, 'BootCurrent')
2155+ boot_order = get_efibootmgr_value(stdout, 'BootOrder').split(',')
2156+ if currently_booted in boot_order:
2157+ boot_order.remove(currently_booted)
2158+ boot_order = [currently_booted] + boot_order
2159+ new_boot_order = ','.join(boot_order)
2160+ in_chroot(['efibootmgr', '-o', new_boot_order])
2161+
2162+
2163+def set_autorelabel(target):
2164+ """Creates file /.autorelabel.
2165+
2166+ This is used by SELinux to relabel all of the
2167+ files on the filesystem to have the correct
2168+ security context. Without this SSH login will
2169+ fail.
2170+ """
2171+ path = os.path.join(target, '.autorelabel')
2172+ open(path, 'a').close()
2173+
2174+
2175+def get_boot_mac():
2176+ """Return the mac address of the booting interface."""
2177+ cmdline = read_file('/proc/cmdline')
2178+ cmdline = cmdline.split()
2179+ try:
2180+ bootif = [
2181+ option
2182+ for option in cmdline
2183+ if option.startswith('BOOTIF')
2184+ ][0]
2185+ except IndexError:
2186+ return None
2187+ _, mac = bootif.split('=')
2188+ mac = mac.split('-')[1:]
2189+ return ':'.join(mac)
2190+
2191+
2192+def get_interface_names():
2193+ """Return a dictionary mapping mac addresses to interface names."""
2194+ sys_path = "/sys/class/net"
2195+ ifaces = {}
2196+ for iname in os.listdir(sys_path):
2197+ mac = read_file(os.path.join(sys_path, iname, "address"))
2198+ mac = mac.strip().lower()
2199+ ifaces[mac] = iname
2200+ return ifaces
2201+
2202+
2203+def get_ipv4_config(iface, data):
2204+ """Returns the contents of the interface file for ipv4."""
2205+ config = [
2206+ 'TYPE="Ethernet"',
2207+ 'NM_CONTROLLED="no"',
2208+ 'USERCTL="yes"',
2209+ ]
2210+ if 'hwaddress' in data:
2211+ config.append('HWADDR="%s"' % data['hwaddress'])
2212+ # Fallback to using device name
2213+ else:
2214+ config.append('DEVICE="%"' % iface)
2215+ if data['auto']:
2216+ config.append('ONBOOT="yes"')
2217+ else:
2218+ config.append('ONBOOT="no"')
2219+
2220+ method = data['method']
2221+ if method == 'dhcp':
2222+ config.append('BOOTPROTO="dhcp"')
2223+ config.append('PEERDNS="yes"')
2224+ config.append('PERSISTENT_DHCLIENT="1"')
2225+ if 'hostname' in data:
2226+ config.append('DHCP_HOSTNAME="%s"' % data['hostname'])
2227+ elif method == 'static':
2228+ config.append('BOOTPROTO="none"')
2229+ config.append('IPADDR="%s"' % data['address'])
2230+ config.append('NETMASK="%s"' % data['netmask'])
2231+ if 'broadcast' in data:
2232+ config.append('BROADCAST="%s"' % data['broadcast'])
2233+ if 'gateway' in data:
2234+ config.append('GATEWAY="%s"' % data['gateway'])
2235+ elif method == 'manual':
2236+ config.append('BOOTPROTO="none"')
2237+ return '\n'.join(config)
2238+
2239+
2240+def write_interface_config(target, iface, data):
2241+ """Writes config for interface."""
2242+ family = data['family']
2243+ if family != "inet":
2244+ # Only supporting ipv4 currently
2245+ print(
2246+ "WARN: unsupported family %s, "
2247+ "failed to configure interface: %s" (family, iface))
2248+ return
2249+ config = get_ipv4_config(iface, data)
2250+ path = os.path.join(
2251+ target, 'etc', 'sysconfig', 'network-scripts', 'ifcfg-%s' % iface)
2252+ with open(path, 'w') as stream:
2253+ stream.write(config + '\n')
2254+
2255+
2256+def write_network_config(target, mac):
2257+ """Write network configuration for the given MAC address."""
2258+ inames = get_interface_names()
2259+ iname = inames[mac.lower()]
2260+ write_interface_config(
2261+ target, iname, {
2262+ 'family': 'inet',
2263+ 'hwaddress': mac.upper(),
2264+ 'auto': True,
2265+ 'method': 'dhcp'
2266+ })
2267+
2268+
2269+def main():
2270+ state = util.load_command_environment()
2271+ target = state['target']
2272+ if target is None:
2273+ print("Target was not provided in the environment.")
2274+ sys.exit(1)
2275+ fstab = state['fstab']
2276+ if fstab is None:
2277+ print("/etc/fstab output was not provided in the environment.")
2278+ sys.exit(1)
2279+ bootmac = get_boot_mac()
2280+ if bootmac is None:
2281+ print("Unable to determine boot interface.")
2282+ sys.exit(1)
2283+ devices = get_block_devices(target)
2284+ if not devices:
2285+ print("Unable to find block device for: %s" % target)
2286+ sys.exit(1)
2287+
2288+ write_fstab(target, fstab)
2289+
2290+ update_grub_default(
2291+ target, extra=get_extra_kernel_parameters())
2292+ grub2_mkconfig(target)
2293+ if util.is_uefi_bootable():
2294+ grub2_install_efi(target)
2295+ else:
2296+ for dev in devices:
2297+ grub2_install(target, dev)
2298+
2299+ set_autorelabel(target)
2300+ write_network_config(target, bootmac)
2301+
2302+
2303+if __name__ == "__main__":
2304+ main()
2305
2306=== modified file 'contrib/centos/centos7/curtin/finalize'
2307--- contrib/centos/centos7/curtin/finalize 2015-02-05 13:50:16 +0000
2308+++ contrib/centos/centos7/curtin/finalize 1970-01-01 00:00:00 +0000
2309@@ -1,100 +0,0 @@
2310-#!/usr/bin/env python
2311-
2312-from __future__ import (
2313- absolute_import,
2314- print_function,
2315- unicode_literals,
2316- )
2317-
2318-import json
2319-import os
2320-import sys
2321-
2322-sys.path.append('/curtin')
2323-from curtin import util
2324-
2325-
2326-DATASOURCE_LIST = """\
2327-datasource_list: [ MAAS ]
2328-"""
2329-
2330-DATASOURCE = """\
2331-datasource:
2332- MAAS: {{consumer_key: {consumer_key}, metadata_url: '{url}',
2333- token_key: {token_key}, token_secret: {token_secret}}}
2334-"""
2335-
2336-
2337-def get_datasource(**kwargs):
2338- """Returns the format cloud-init datasource."""
2339- return DATASOURCE_LIST + DATASOURCE.format(**kwargs)
2340-
2341-
2342-def load_config(path):
2343- """Loads the curtin config."""
2344- with open(path, 'r') as stream:
2345- return json.load(stream)
2346-
2347-
2348-def extract_maas_parameters(config):
2349- """Extracts the needed values from the debconf
2350- entry."""
2351- params = {}
2352- for line in config.splitlines():
2353- cloud, key, type, value = line.split()[:4]
2354- if key == "cloud-init/maas-metadata-url":
2355- params['url'] = value
2356- elif key == "cloud-init/maas-metadata-credentials":
2357- values = value.split("&")
2358- for oauth in values:
2359- key, value = oauth.split('=')
2360- if key == 'oauth_token_key':
2361- params['token_key'] = value
2362- elif key == 'oauth_token_secret':
2363- params['token_secret'] = value
2364- elif key == 'oauth_consumer_key':
2365- params['consumer_key'] = value
2366- return params
2367-
2368-
2369-def get_maas_debconf_selections(config):
2370- """Gets the debconf selections from the curtin config."""
2371- try:
2372- return config['debconf_selections']['maas']
2373- except KeyError:
2374- return None
2375-
2376-
2377-def write_datasource(target, data):
2378- """Writes the cloudinit config into
2379- /etc/cloud/cloud.cfg.d/90_datasource.cfg."""
2380- path = os.path.join(
2381- target, 'etc', 'cloud', 'cloud.cfg.d', '90_datasource.cfg')
2382- with open(path, 'w') as stream:
2383- stream.write(data + '\n')
2384-
2385-
2386-def main():
2387- state = util.load_command_environment()
2388- target = state['target']
2389- if target is None:
2390- print("Target was not provided in the environment.")
2391- sys.exit(1)
2392- config_f = state['config']
2393- if config_f is None:
2394- print("Config was not provided in the environment.")
2395- sys.exit(1)
2396- config = load_config(config_f)
2397-
2398- debconf = get_maas_debconf_selections(config)
2399- if debconf is None:
2400- print("Failed to get the debconf_selections.")
2401- sys.exit(1)
2402-
2403- params = extract_maas_parameters(debconf)
2404- datasource = get_datasource(**params)
2405- write_datasource(target, datasource)
2406-
2407-
2408-if __name__ == "__main__":
2409- main()
2410
2411=== target is u'python_wrapper'
2412=== added file 'contrib/centos/centos7/curtin/finalize.py'
2413--- contrib/centos/centos7/curtin/finalize.py 1970-01-01 00:00:00 +0000
2414+++ contrib/centos/centos7/curtin/finalize.py 2017-06-13 16:13:40 +0000
2415@@ -0,0 +1,100 @@
2416+#!/usr/bin/env python
2417+
2418+from __future__ import (
2419+ absolute_import,
2420+ print_function,
2421+ unicode_literals,
2422+ )
2423+
2424+import json
2425+import os
2426+import sys
2427+
2428+sys.path.append('/curtin')
2429+from curtin import util
2430+
2431+
2432+DATASOURCE_LIST = """\
2433+datasource_list: [ MAAS ]
2434+"""
2435+
2436+DATASOURCE = """\
2437+datasource:
2438+ MAAS: {{consumer_key: {consumer_key}, metadata_url: '{url}',
2439+ token_key: {token_key}, token_secret: {token_secret}}}
2440+"""
2441+
2442+
2443+def get_datasource(**kwargs):
2444+ """Returns the format cloud-init datasource."""
2445+ return DATASOURCE_LIST + DATASOURCE.format(**kwargs)
2446+
2447+
2448+def load_config(path):
2449+ """Loads the curtin config."""
2450+ with open(path, 'r') as stream:
2451+ return json.load(stream)
2452+
2453+
2454+def extract_maas_parameters(config):
2455+ """Extracts the needed values from the debconf
2456+ entry."""
2457+ params = {}
2458+ for line in config.splitlines():
2459+ cloud, key, type, value = line.split()[:4]
2460+ if key == "cloud-init/maas-metadata-url":
2461+ params['url'] = value
2462+ elif key == "cloud-init/maas-metadata-credentials":
2463+ values = value.split("&")
2464+ for oauth in values:
2465+ key, value = oauth.split('=')
2466+ if key == 'oauth_token_key':
2467+ params['token_key'] = value
2468+ elif key == 'oauth_token_secret':
2469+ params['token_secret'] = value
2470+ elif key == 'oauth_consumer_key':
2471+ params['consumer_key'] = value
2472+ return params
2473+
2474+
2475+def get_maas_debconf_selections(config):
2476+ """Gets the debconf selections from the curtin config."""
2477+ try:
2478+ return config['debconf_selections']['maas']
2479+ except KeyError:
2480+ return None
2481+
2482+
2483+def write_datasource(target, data):
2484+ """Writes the cloudinit config into
2485+ /etc/cloud/cloud.cfg.d/90_datasource.cfg."""
2486+ path = os.path.join(
2487+ target, 'etc', 'cloud', 'cloud.cfg.d', '90_datasource.cfg')
2488+ with open(path, 'w') as stream:
2489+ stream.write(data + '\n')
2490+
2491+
2492+def main():
2493+ state = util.load_command_environment()
2494+ target = state['target']
2495+ if target is None:
2496+ print("Target was not provided in the environment.")
2497+ sys.exit(1)
2498+ config_f = state['config']
2499+ if config_f is None:
2500+ print("Config was not provided in the environment.")
2501+ sys.exit(1)
2502+ config = load_config(config_f)
2503+
2504+ debconf = get_maas_debconf_selections(config)
2505+ if debconf is None:
2506+ print("Failed to get the debconf_selections.")
2507+ sys.exit(1)
2508+
2509+ params = extract_maas_parameters(debconf)
2510+ datasource = get_datasource(**params)
2511+ write_datasource(target, datasource)
2512+
2513+
2514+if __name__ == "__main__":
2515+ main()
2516
2517=== added file 'contrib/centos/centos7/curtin/python_wrapper'
2518--- contrib/centos/centos7/curtin/python_wrapper 1970-01-01 00:00:00 +0000
2519+++ contrib/centos/centos7/curtin/python_wrapper 2017-06-13 16:13:40 +0000
2520@@ -0,0 +1,11 @@
2521+#!/bin/bash
2522+
2523+export PYTHONPATH='/curtin'
2524+
2525+# Ubuntu 16.04 only ships with Python 3 while previous versions only ship
2526+# with Python 2.
2527+if type -p python > /dev/null; then
2528+ exec python "$0.py" "$@"
2529+else
2530+ exec python3 "$0.py" "$@"
2531+fi
2532
2533=== added directory 'contrib/rhel'
2534=== added symlink 'contrib/rhel/curtin'
2535=== target is u'../centos/centos7/curtin'
2536=== added file 'contrib/rhel/rhel7-amd64.ks'
2537--- contrib/rhel/rhel7-amd64.ks 1970-01-01 00:00:00 +0000
2538+++ contrib/rhel/rhel7-amd64.ks 2017-06-13 16:13:40 +0000
2539@@ -0,0 +1,128 @@
2540+#version=RHEL7
2541+install
2542+# Poweroff after installation
2543+poweroff
2544+# System authorization information
2545+auth --enableshadow --passalgo=sha512
2546+# Firewall configuration
2547+firewall --enabled --service=ssh
2548+firstboot --disable
2549+ignoredisk --only-use=vda
2550+# Keyboard layouts
2551+# old format: keyboard us
2552+# new format:
2553+keyboard --vckeymap=us --xlayouts='us'
2554+# System language
2555+lang en_US.UTF-8
2556+# Use CDROM installation media
2557+cdrom
2558+repo --name="repo0" --baseurl=http://mirrors.kernel.org/centos/7/os/x86_64 --includepkgs=python-pygments
2559+repo --name="repo1" --baseurl=http://mirrors.kernel.org/centos/7/updates/x86_64 --includepkgs=python-pygments
2560+repo --name="repo2" --baseurl=http://mirrors.kernel.org/centos/7/extras/x86_64
2561+repo --name="repo3" --baseurl=http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/20/Everything/x86_64/os/ --includepkgs=python-oauth
2562+# Network information
2563+network --bootproto=dhcp --device=eth0 --onboot=on
2564+# System bootloader configuration
2565+bootloader --append="console=ttyS0,115200n8 console=tty0" --location=mbr --driveorder="vda" --timeout=1
2566+# Root password
2567+rootpw --iscrypted nothing
2568+selinux --enforcing
2569+services --disabled="kdump" --enabled="network,sshd,rsyslog,chronyd"
2570+timezone UTC --isUtc
2571+# Disk
2572+zerombr
2573+clearpart --all --initlabel
2574+part / --fstype="ext4" --size=3072
2575+
2576+%post --erroronfail
2577+
2578+# workaround anaconda requirements
2579+passwd -d root
2580+passwd -l root
2581+
2582+# remove avahi and networkmanager
2583+echo "Removing avahi/zeroconf and NetworkManager"
2584+yum -C -y remove avahi\* Network\*
2585+
2586+# make sure firstboot doesn't start
2587+echo "RUN_FIRSTBOOT=NO" > /etc/sysconfig/firstboot
2588+
2589+echo "Cleaning old yum repodata."
2590+yum clean all
2591+
2592+# chance dhcp client retry/timeouts to resolve #6866
2593+cat >> /etc/dhcp/dhclient.conf << EOF
2594+
2595+timeout 300;
2596+retry 60;
2597+EOF
2598+
2599+# clean up installation logs"
2600+rm -rf /var/log/yum.log
2601+rm -rf /var/lib/yum/*
2602+rm -rf /root/install.log
2603+rm -rf /root/install.log.syslog
2604+rm -rf /root/anaconda-ks.cfg
2605+rm -rf /var/log/anaconda*
2606+rm -rf /root/anac*
2607+
2608+echo "Fixing SELinux contexts."
2609+touch /var/log/cron
2610+touch /var/log/boot.log
2611+mkdir -p /var/cache/yum
2612+/usr/sbin/fixfiles -R -a restore
2613+
2614+# reorder console entries
2615+sed -i 's/console=tty0/console=tty0 console=ttyS0,115200n8/' /boot/grub2/grub.cfg
2616+
2617+%end
2618+
2619+%packages
2620+@core
2621+chrony
2622+cloud-init
2623+cloud-utils-growpart
2624+dracut-config-generic
2625+dracut-norescue
2626+efibootmgr
2627+firewalld
2628+grub2
2629+grub2-efi-modules
2630+kernel
2631+rsync
2632+tar
2633+yum-utils
2634+python-oauth
2635+-NetworkManager
2636+-aic94xx-firmware
2637+-alsa-firmware
2638+-alsa-lib
2639+-alsa-tools-firmware
2640+-iprutils
2641+-ivtv-firmware
2642+-iwl100-firmware
2643+-iwl1000-firmware
2644+-iwl105-firmware
2645+-iwl135-firmware
2646+-iwl2000-firmware
2647+-iwl2030-firmware
2648+-iwl3160-firmware
2649+-iwl3945-firmware
2650+-iwl4965-firmware
2651+-iwl5000-firmware
2652+-iwl5150-firmware
2653+-iwl6000-firmware
2654+-iwl6000g2a-firmware
2655+-iwl6000g2b-firmware
2656+-iwl6050-firmware
2657+-iwl7260-firmware
2658+-iwl7265-firmware
2659+-libertas-sd8686-firmware
2660+-libertas-sd8787-firmware
2661+-libertas-usb8388-firmware
2662+-plymouth
2663+-postfix
2664+-wpa_supplicant
2665+
2666+%end
2667+
2668
2669=== added directory 'contrib/windows'
2670=== added file 'contrib/windows/Autounattend.xml'
2671--- contrib/windows/Autounattend.xml 1970-01-01 00:00:00 +0000
2672+++ contrib/windows/Autounattend.xml 2017-06-13 16:13:40 +0000
2673@@ -0,0 +1,148 @@
2674+<?xml version="1.0" encoding="utf-8"?>
2675+<unattend xmlns="urn:schemas-microsoft-com:unattend">
2676+ <settings pass="windowsPE">
2677+ <component name="Microsoft-Windows-Setup" processorArchitecture="{{arch}}" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2678+ <DiskConfiguration>
2679+ <WillShowUI>OnError</WillShowUI>
2680+ <Disk wcm:action="add">
2681+ <CreatePartitions>
2682+ <CreatePartition wcm:action="add">
2683+ <Order>1</Order>
2684+ <Size>100</Size>
2685+ <Type>Primary</Type>
2686+ </CreatePartition>
2687+ <CreatePartition wcm:action="add">
2688+ <Order>2</Order>
2689+ <Extend>true</Extend>
2690+ <Type>Primary</Type>
2691+ </CreatePartition>
2692+ </CreatePartitions>
2693+ <ModifyPartitions>
2694+ <ModifyPartition wcm:action="add">
2695+ <Active>true</Active>
2696+ <Label>Boot</Label>
2697+ <Format>NTFS</Format>
2698+ <Order>1</Order>
2699+ <PartitionID>1</PartitionID>
2700+ </ModifyPartition>
2701+ <ModifyPartition wcm:action="add">
2702+ <Format>NTFS</Format>
2703+ <Order>2</Order>
2704+ <PartitionID>2</PartitionID>
2705+ <Label>System</Label>
2706+ </ModifyPartition>
2707+ </ModifyPartitions>
2708+ <DiskID>0</DiskID>
2709+ <WillWipeDisk>true</WillWipeDisk>
2710+ </Disk>
2711+ </DiskConfiguration>
2712+ <ImageInstall>
2713+ <OSImage>
2714+ <InstallTo>
2715+ <PartitionID>2</PartitionID>
2716+ <DiskID>0</DiskID>
2717+ </InstallTo>
2718+ <InstallToAvailablePartition>false</InstallToAvailablePartition>
2719+ <WillShowUI>OnError</WillShowUI>
2720+ <InstallFrom>
2721+ <MetaData wcm:action="add">
2722+ <Key>/IMAGE/NAME</Key>
2723+ <Value>{{image_name}}</Value>
2724+ </MetaData>
2725+ </InstallFrom>
2726+ </OSImage>
2727+ </ImageInstall>
2728+ <UserData>
2729+ <AcceptEula>true</AcceptEula>
2730+ {{if license_key}}
2731+ <ProductKey>
2732+ <Key>{{license_key}}</Key>
2733+ <WillShowUI>OnError</WillShowUI>
2734+ </ProductKey>
2735+ {{endif}}
2736+ </UserData>
2737+ </component>
2738+ <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="{{arch}}" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2739+ <SetupUILanguage>
2740+ <UILanguage>{{language}}</UILanguage>
2741+ </SetupUILanguage>
2742+ <InputLocale>{{language}}</InputLocale>
2743+ <UILanguage>{{language}}</UILanguage>
2744+ <SystemLocale>{{language}}</SystemLocale>
2745+ <UserLocale>{{language}}</UserLocale>
2746+ </component>
2747+ </settings>
2748+ <settings pass="oobeSystem">
2749+ <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="{{arch}}" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2750+ <VisualEffects>
2751+ <FontSmoothing>ClearType</FontSmoothing>
2752+ </VisualEffects>
2753+ <OOBE>
2754+ <HideEULAPage>true</HideEULAPage>
2755+ <ProtectYourPC>3</ProtectYourPC>
2756+ <NetworkLocation>Work</NetworkLocation>
2757+ <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
2758+ <!-- Comment the following two options on Windows Vista / 7 and Windows Server 2008 / 2008 R2 -->
2759+ <!--
2760+ <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
2761+ <HideLocalAccountScreen>true</HideLocalAccountScreen>
2762+ -->
2763+ </OOBE>
2764+ {{if enable_updates}}
2765+ <LogonCommands>
2766+ <AsynchronousCommand wcm:action="add">
2767+ <CommandLine>%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell -NoLogo -NonInteractive -ExecutionPolicy RemoteSigned -File E:\scripts\logon.ps1</CommandLine>
2768+ <Order>1</Order>
2769+ </AsynchronousCommand>
2770+ </LogonCommands>
2771+ {{else}}
2772+ <FirstLogonCommands>
2773+ <SynchronousCommand wcm:action="add">
2774+ <CommandLine>%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell -NoLogo -NonInteractive -ExecutionPolicy RemoteSigned -File E:\scripts\firstlogon.ps1</CommandLine>
2775+ <Order>1</Order>
2776+ </SynchronousCommand>
2777+ </FirstLogonCommands>
2778+ {{endif}}
2779+ <UserAccounts>
2780+ <AdministratorPassword>
2781+ <Value>Passw0rd</Value>
2782+ <PlainText>true</PlainText>
2783+ </AdministratorPassword>
2784+ </UserAccounts>
2785+ <AutoLogon>
2786+ <Password>
2787+ <Value>Passw0rd</Value>
2788+ <PlainText>true</PlainText>
2789+ </Password>
2790+ <Enabled>true</Enabled>
2791+ <LogonCount>50</LogonCount>
2792+ <Username>Administrator</Username>
2793+ </AutoLogon>
2794+ <ComputerName>*</ComputerName>
2795+ </component>
2796+ </settings>
2797+ <settings pass="specialize">
2798+ <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="{{arch}}" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2799+ <fDenyTSConnections>false</fDenyTSConnections>
2800+ </component>
2801+ <component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="{{arch}}" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2802+ <UserAuthentication>0</UserAuthentication>
2803+ </component>
2804+ <component name="Networking-MPSSVC-Svc" processorArchitecture="{{arch}}" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2805+ <FirewallGroups>
2806+ <FirewallGroup wcm:action="add" wcm:keyValue="RemoteDesktop">
2807+ <Active>true</Active>
2808+ <Profile>all</Profile>
2809+ <Group>@FirewallAPI.dll,-28752</Group>
2810+ </FirewallGroup>
2811+ </FirewallGroups>
2812+ </component>
2813+ <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="{{arch}}" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="NonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2814+ <TimeZone>UTC</TimeZone>
2815+ <ComputerName>*</ComputerName>
2816+ </component>
2817+ <component name="Microsoft-Windows-SQMApi" processorArchitecture="{{arch}}" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="NonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2818+ <CEIPEnabled>0</CEIPEnabled>
2819+ </component>
2820+ </settings>
2821+</unattend>
2822
2823=== added directory 'contrib/windows/curtin'
2824=== added file 'contrib/windows/curtin/curtin-hooks'
2825--- contrib/windows/curtin/curtin-hooks 1970-01-01 00:00:00 +0000
2826+++ contrib/windows/curtin/curtin-hooks 2017-06-13 16:13:40 +0000
2827@@ -0,0 +1,1 @@
2828+#!/bin/bash
2829
2830=== added symlink 'contrib/windows/curtin/finalize'
2831=== target is u'python_wrapper'
2832=== added file 'contrib/windows/curtin/finalize.py'
2833--- contrib/windows/curtin/finalize.py 1970-01-01 00:00:00 +0000
2834+++ contrib/windows/curtin/finalize.py 2017-06-13 16:13:40 +0000
2835@@ -0,0 +1,165 @@
2836+#!/usr/bin/env python
2837+
2838+from __future__ import (
2839+ absolute_import,
2840+ print_function,
2841+ unicode_literals,
2842+ )
2843+
2844+import os
2845+import sys
2846+import json
2847+
2848+sys.path.append('/curtin')
2849+from curtin import util
2850+
2851+CLOUDBASE_INIT_CONFIG = """\
2852+metadata_services=cloudbaseinit.metadata.services.maasservice.MaaSHttpService
2853+maas_metadata_url={url}
2854+maas_oauth_consumer_key={consumer_key}
2855+maas_oauth_consumer_secret=''
2856+maas_oauth_token_key={token_key}
2857+maas_oauth_token_secret={token_secret}
2858+"""
2859+
2860+
2861+LICENSE_KEY_SCRIPT = """\
2862+slmgr /ipk {license_key}
2863+Remove-Item $MyInvocation.InvocationName
2864+"""
2865+
2866+
2867+def load_config(path):
2868+ """Loads the curtin config."""
2869+ with open(path, 'r') as stream:
2870+ return json.load(stream)
2871+
2872+
2873+def get_maas_debconf_selections(config):
2874+ """Gets the debconf selections from the curtin config."""
2875+ try:
2876+ return config['debconf_selections']['maas']
2877+ except KeyError:
2878+ return None
2879+
2880+
2881+def extract_maas_parameters(config):
2882+ """Extracts the needed values from the debconf
2883+ entry."""
2884+ params = {}
2885+ for line in config.splitlines():
2886+ cloud, key, type, value = line.split()[:4]
2887+ if key == "cloud-init/maas-metadata-url":
2888+ params['url'] = value
2889+ elif key == "cloud-init/maas-metadata-credentials":
2890+ values = value.split("&")
2891+ for oauth in values:
2892+ key, value = oauth.split('=')
2893+ if key == 'oauth_token_key':
2894+ params['token_key'] = value
2895+ elif key == 'oauth_token_secret':
2896+ params['token_secret'] = value
2897+ elif key == 'oauth_consumer_key':
2898+ params['consumer_key'] = value
2899+ return params
2900+
2901+
2902+def get_cloudbase_init_config(params):
2903+ """Returns the cloudbase-init config file."""
2904+ config = CLOUDBASE_INIT_CONFIG.format(**params)
2905+ output = ""
2906+ for line in config.splitlines():
2907+ output += "%s\r\n" % line
2908+ return output
2909+
2910+
2911+def write_cloudbase_init(target, params):
2912+ """Writes the configuration files for cloudbase-init."""
2913+ cloudbase_init_cfg = os.path.join(
2914+ target,
2915+ "Program Files",
2916+ "Cloudbase Solutions",
2917+ "Cloudbase-Init",
2918+ "conf",
2919+ "cloudbase-init.conf")
2920+ cloudbase_init_unattended_cfg = os.path.join(
2921+ target,
2922+ "Program Files",
2923+ "Cloudbase Solutions",
2924+ "Cloudbase-Init",
2925+ "conf",
2926+ "cloudbase-init-unattend.conf")
2927+
2928+ config = get_cloudbase_init_config(params)
2929+ with open(cloudbase_init_cfg, 'a') as stream:
2930+ stream.write(config)
2931+ with open(cloudbase_init_unattended_cfg, 'a') as stream:
2932+ stream.write(config)
2933+
2934+
2935+def get_license_key(config):
2936+ """Return license_key from the curtin config."""
2937+ try:
2938+ license_key = config['license_key']
2939+ except KeyError:
2940+ return None
2941+ if license_key is None:
2942+ return None
2943+ license_key = license_key.strip()
2944+ if license_key == '':
2945+ return None
2946+ return license_key
2947+
2948+
2949+def write_license_key_script(target, license_key):
2950+ local_scripts_path = os.path.join(
2951+ target,
2952+ "Program Files",
2953+ "Cloudbase Solutions",
2954+ "Cloudbase-Init",
2955+ "LocalScripts")
2956+ script_path = os.path.join(local_scripts_path, 'set_license_key.ps1')
2957+ set_key_script = LICENSE_KEY_SCRIPT.format(license_key=license_key)
2958+ with open(script_path, 'w') as stream:
2959+ for line in set_key_script.splitlines():
2960+ stream.write("%s\r\n" % line)
2961+
2962+
2963+def write_network_config(target, config):
2964+ network_config_path = os.path.join(target, 'network.json')
2965+ config_json = config.get('network', None)
2966+ if config_json is not None:
2967+ config_json = json.dumps(config_json)
2968+ with open(network_config_path, 'w') as stream:
2969+ for line in config_json.splitlines():
2970+ stream.write("%s\r\n" % line)
2971+
2972+
2973+def main():
2974+ state = util.load_command_environment()
2975+ target = state['target']
2976+ if target is None:
2977+ print("Target was not provided in the environment.")
2978+ sys.exit(1)
2979+ config_f = state['config']
2980+ if config_f is None:
2981+ print("Config was not provided in the environment.")
2982+ sys.exit(1)
2983+ config = load_config(config_f)
2984+
2985+ debconf = get_maas_debconf_selections(config)
2986+ if debconf is None:
2987+ print("Failed to get the debconf_selections.")
2988+ sys.exit(1)
2989+
2990+ params = extract_maas_parameters(debconf)
2991+ write_cloudbase_init(target, params)
2992+ write_network_config(target, config)
2993+
2994+ license_key = get_license_key(config)
2995+ if license_key is not None:
2996+ write_license_key_script(target, license_key)
2997+
2998+
2999+if __name__ == "__main__":
3000+ main()
3001
3002=== added file 'contrib/windows/curtin/python_wrapper'
3003--- contrib/windows/curtin/python_wrapper 1970-01-01 00:00:00 +0000
3004+++ contrib/windows/curtin/python_wrapper 2017-06-13 16:13:40 +0000
3005@@ -0,0 +1,11 @@
3006+#!/bin/bash
3007+
3008+export PYTHONPATH='/curtin'
3009+
3010+# Ubuntu 16.04 only ships with Python 3 while previous versions only ship
3011+# with Python 2.
3012+if type -p python > /dev/null; then
3013+ exec python "$0.py" "$@"
3014+else
3015+ exec python3 "$0.py" "$@"
3016+fi
3017
3018=== added directory 'contrib/windows/scripts'
3019=== added file 'contrib/windows/scripts/firstlogon.ps1'
3020--- contrib/windows/scripts/firstlogon.ps1 1970-01-01 00:00:00 +0000
3021+++ contrib/windows/scripts/firstlogon.ps1 2017-06-13 16:13:40 +0000
3022@@ -0,0 +1,104 @@
3023+$ErrorActionPreference = "Stop"
3024+
3025+function WaitForNetwork ($seconds) {
3026+ while (1) {
3027+ # Get a list of DHCP-enabled interfaces that have a
3028+ # non-$null DefaultIPGateway property.
3029+ $x = gwmi -class Win32_NetworkAdapterConfiguration `
3030+ -filter DHCPEnabled=TRUE |
3031+ where { $_.DefaultIPGateway -ne $null }
3032+
3033+ # If there is (at least) one available, exit the loop.
3034+ if ( ($x | measure).count -gt 0 ) {
3035+ break
3036+ }
3037+
3038+ # If $seconds > 0 and we have tried $seconds times without
3039+ # success, throw an exception.
3040+ if ( $seconds -gt 0 -and $try++ -ge $seconds ) {
3041+ throw "Network unavaiable after $try seconds."
3042+ }
3043+
3044+ # Wait one second.
3045+ start-sleep -s 1
3046+ }
3047+}
3048+
3049+try
3050+{
3051+ $osArch = (Get-WmiObject Win32_OperatingSystem).OSArchitecture
3052+ if($osArch.StartsWith("64"))
3053+ {
3054+ $archDir = "x64"
3055+ $programFilesDir = ${ENV:ProgramFiles(x86)}
3056+ }
3057+ else
3058+ {
3059+ $archDir = "x86"
3060+ $programFilesDir = $ENV:ProgramFiles
3061+ }
3062+
3063+ # Inject extra drivers if the infs directory is present on the attached iso
3064+ if (Test-Path -Path "E:\infs")
3065+ {
3066+ # To install extra drivers the Windows Driver Kit is needed for dpinst.exe.
3067+ # Sadly you cannot just download dpinst.exe. The whole driver kit must be
3068+ # installed.
3069+
3070+ # Need to have network connection to continue, wait a maximum of 60
3071+ # seconds for the network to be active.
3072+ WaitForNetwork 60
3073+
3074+ # Download the WDK installer.
3075+ $Host.UI.RawUI.WindowTitle = "Downloading Windows Driver Kit..."
3076+ $webclient = New-Object System.Net.WebClient
3077+ $wdksetup = [IO.Path]::GetFullPath("$ENV:TEMP\wdksetup.exe")
3078+ $wdkurl = "http://download.microsoft.com/download/0/8/C/08C7497F-8551-4054-97DE-60C0E510D97A/wdk/wdksetup.exe"
3079+ $webclient.DownloadFile($wdkurl, $wdksetup)
3080+
3081+ # Run the installer.
3082+ $Host.UI.RawUI.WindowTitle = "Installing Windows Driver Kit..."
3083+ $p = Start-Process -PassThru -Wait -FilePath "$wdksetup" -ArgumentList "/features OptionId.WindowsDriverKitComplete /q /ceip off /norestart"
3084+ if ($p.ExitCode -ne 0)
3085+ {
3086+ throw "Installing $wdksetup failed."
3087+ }
3088+
3089+ # Run dpinst.exe with the path to the drivers.
3090+ $Host.UI.RawUI.WindowTitle = "Injecting Windows drivers..."
3091+ $dpinst = "$programFilesDir\Windows Kits\8.1\redist\DIFx\dpinst\EngMui\$archDir\dpinst.exe"
3092+ Start-Process -Wait -FilePath "$dpinst" -ArgumentList "/S /C /F /SA /Path E:\infs"
3093+
3094+ # Uninstall the WDK
3095+ $Host.UI.RawUI.WindowTitle = "Uninstalling Windows Driver Kit..."
3096+ Start-Process -Wait -FilePath "$wdksetup" -ArgumentList "/features + /q /uninstall /norestart"
3097+ }
3098+
3099+ $Host.UI.RawUI.WindowTitle = "Installing Cloudbase-Init..."
3100+ $cloudbaseInitPath = "E:\cloudbase\cloudbase_init.msi"
3101+ $cloudbaseInitLog = "$ENV:Temp\cloudbase_init.log"
3102+ $serialPortName = @(Get-WmiObject Win32_SerialPort)[0].DeviceId
3103+ $p = Start-Process -Wait -PassThru -FilePath msiexec -ArgumentList "/i $cloudbaseInitPath /qn /l*v $cloudbaseInitLog LOGGINGSERIALPORTNAME=$serialPortName"
3104+ if ($p.ExitCode -ne 0)
3105+ {
3106+ throw "Installing $cloudbaseInitPath failed. Log: $cloudbaseInitLog"
3107+ }
3108+
3109+ # We're done, disable AutoLogon
3110+ Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name AutoLogonCount
3111+
3112+ $Host.UI.RawUI.WindowTitle = "Running Cloudbase-Init SetSetupComplete..."
3113+ & "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\bin\SetSetupComplete.cmd"
3114+
3115+ # Write success, this is used to check that this process made it this far
3116+ New-Item -Path C:\success.tch -Type file -Force
3117+
3118+ $Host.UI.RawUI.WindowTitle = "Running Cloudbase-Init Sysprep..."
3119+ $unattendedXmlPath = "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml"
3120+ & "$ENV:SystemRoot\System32\Sysprep\Sysprep.exe" `/generalize `/oobe `/shutdown `/unattend:"$unattendedXmlPath"
3121+}
3122+catch
3123+{
3124+ $_ | Out-File C:\error_log.txt
3125+ shutdown /s /f /t 0
3126+}
3127
3128=== added file 'contrib/windows/scripts/logon.ps1'
3129--- contrib/windows/scripts/logon.ps1 1970-01-01 00:00:00 +0000
3130+++ contrib/windows/scripts/logon.ps1 2017-06-13 16:13:40 +0000
3131@@ -0,0 +1,124 @@
3132+$ErrorActionPreference = "Stop"
3133+
3134+function WaitForNetwork ($seconds) {
3135+ while (1) {
3136+ # Get a list of DHCP-enabled interfaces that have a
3137+ # non-$null DefaultIPGateway property.
3138+ $x = gwmi -class Win32_NetworkAdapterConfiguration `
3139+ -filter DHCPEnabled=TRUE |
3140+ where { $_.DefaultIPGateway -ne $null }
3141+
3142+ # If there is (at least) one available, exit the loop.
3143+ if ( ($x | measure).count -gt 0 ) {
3144+ break
3145+ }
3146+
3147+ # If $seconds > 0 and we have tried $seconds times without
3148+ # success, throw an exception.
3149+ if ( $seconds -gt 0 -and $try++ -ge $seconds ) {
3150+ throw "Network unavaiable after $try seconds."
3151+ }
3152+
3153+ # Wait one second.
3154+ start-sleep -s 1
3155+ }
3156+}
3157+
3158+try
3159+{
3160+ # Need to have network connection to continue, wait a maximum of 60
3161+ # seconds for the network to be active.
3162+ WaitForNetwork 60
3163+
3164+ # Install PSWindowsUpdate modules for PowerShell
3165+ if (!(Test-Path -Path "$ENV:SystemRoot\System32\WindowsPowerShell\v1.0\Modules\PSWindowsUpdate"))
3166+ {
3167+ $Host.UI.RawUI.WindowTitle = "Installing PSWindowsUpdate..."
3168+ Copy-Item E:\PSWindowsUpdate $ENV:SystemRoot\System32\WindowsPowerShell\v1.0\Modules -recurse
3169+ }
3170+
3171+ # Start the Update process.
3172+ Import-Module PSWindowsUpdate
3173+ $Host.UI.RawUI.WindowTitle = "Installing updates..."
3174+ Get-WUInstall -AcceptAll -IgnoreReboot -IgnoreUserInput -NotCategory "Language packs"
3175+ if (Get-WURebootStatus -Silent)
3176+ {
3177+ $Host.UI.RawUI.WindowTitle = "Updates installation finished. Rebooting."
3178+ shutdown /r /t 0
3179+ }
3180+ else
3181+ {
3182+ $osArch = (Get-WmiObject Win32_OperatingSystem).OSArchitecture
3183+ if($osArch.StartsWith("64"))
3184+ {
3185+ $archDir = "x64"
3186+ $programFilesDir = ${ENV:ProgramFiles(x86)}
3187+ }
3188+ else
3189+ {
3190+ $archDir = "x86"
3191+ $programFilesDir = $ENV:ProgramFiles
3192+ }
3193+
3194+ # Inject extra drivers if the infs directory is present on the attached iso
3195+ if (Test-Path -Path "E:\infs")
3196+ {
3197+ # To install extra drivers the Windows Driver Kit is needed for dpinst.exe.
3198+ # Sadly you cannot just download dpinst.exe. The whole driver kit must be
3199+ # installed.
3200+
3201+ # Download the WDK installer.
3202+ $Host.UI.RawUI.WindowTitle = "Downloading Windows Driver Kit..."
3203+ $webclient = New-Object System.Net.WebClient
3204+ $wdksetup = [IO.Path]::GetFullPath("$ENV:TEMP\wdksetup.exe")
3205+ $wdkurl = "http://download.microsoft.com/download/0/8/C/08C7497F-8551-4054-97DE-60C0E510D97A/wdk/wdksetup.exe"
3206+ $webclient.DownloadFile($wdkurl, $wdksetup)
3207+
3208+ # Run the installer.
3209+ $Host.UI.RawUI.WindowTitle = "Installing Windows Driver Kit..."
3210+ $p = Start-Process -PassThru -Wait -FilePath "$wdksetup" -ArgumentList "/features OptionId.WindowsDriverKitComplete /q /ceip off /norestart"
3211+ if ($p.ExitCode -ne 0)
3212+ {
3213+ throw "Installing $wdksetup failed."
3214+ }
3215+
3216+ # Run dpinst.exe with the path to the drivers.
3217+ $Host.UI.RawUI.WindowTitle = "Injecting Windows drivers..."
3218+ $dpinst = "$programFilesDir\Windows Kits\8.1\redist\DIFx\dpinst\EngMui\$archDir\dpinst.exe"
3219+ Start-Process -Wait -FilePath "$dpinst" -ArgumentList "/S /C /F /SA /Path E:\infs"
3220+
3221+ # Uninstall the WDK
3222+ $Host.UI.RawUI.WindowTitle = "Uninstalling Windows Driver Kit..."
3223+ Start-Process -Wait -FilePath "$wdksetup" -ArgumentList "/features + /q /uninstall /norestart"
3224+ }
3225+
3226+ $Host.UI.RawUI.WindowTitle = "Installing Cloudbase-Init..."
3227+ $cloudbaseInitPath = "E:\cloudbase\cloudbase_init.msi"
3228+ $cloudbaseInitLog = "$ENV:Temp\cloudbase_init.log"
3229+ $serialPortName = @(Get-WmiObject Win32_SerialPort)[0].DeviceId
3230+ $p = Start-Process -Wait -PassThru -FilePath msiexec -ArgumentList "/i $cloudbaseInitPath /qn /l*v $cloudbaseInitLog LOGGINGSERIALPORTNAME=$serialPortName"
3231+ if ($p.ExitCode -ne 0)
3232+ {
3233+ throw "Installing $cloudbaseInitPath failed. Log: $cloudbaseInitLog"
3234+ }
3235+
3236+ # We're done, remove LogonScript and disable AutoLogon
3237+ Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name Unattend*
3238+ Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name AutoLogonCount
3239+
3240+ $Host.UI.RawUI.WindowTitle = "Running SetSetupComplete..."
3241+ & "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\bin\SetSetupComplete.cmd"
3242+
3243+ # Write success, this is used to check that this process made it this far
3244+ New-Item -Path C:\success.tch -Type file -Force
3245+
3246+ $Host.UI.RawUI.WindowTitle = "Running Sysprep..."
3247+ $unattendedXmlPath = "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml"
3248+ & "$ENV:SystemRoot\System32\Sysprep\Sysprep.exe" `/generalize `/oobe `/shutdown `/unattend:"$unattendedXmlPath"
3249+ }
3250+}
3251+catch
3252+{
3253+ $_ | Out-File C:\error_log.txt
3254+ shutdown /s /f /t 0
3255+}
3256
3257=== modified file 'debian/changelog'
3258--- debian/changelog 2015-07-31 18:56:35 +0000
3259+++ debian/changelog 2017-06-13 16:13:40 +0000
3260@@ -1,3 +1,52 @@
3261+maas-image-builder (1.0.5+bzr54-0ubuntu1) UNRELEASED; urgency=low
3262+
3263+ * Fix --windows-drivers to not fail during logon and firstlogon.
3264+ * Fix kpartx_del to handle failures and retry up to 10 seconds.
3265+ * Prevent windows qemu VNC from listening on all addresses.
3266+
3267+ -- Blake Rouse <blake.rouse@canonical.com> Fri, 9 Jun 2017 10:51:15 -0400
3268+
3269+maas-image-builder (1.0.4+bzr48-0ubuntu1) UNRELEASED; urgency=low
3270+
3271+ * Add --cloudbase-init option to Windows driver.
3272+ * Support appending custom kickstarter option to CentOS and RHEL drivers.
3273+ * RHEL driver use CentOS hook files.
3274+
3275+ -- Blake Rouse <blake.rouse@canonical.com> Wed, 31 May 2017 13:50:18 -0400
3276+
3277+maas-image-builder (1.0.3+bzr44-0ubuntu1) UNRELEASED; urgency=low
3278+
3279+ * Fix Cent OS 7 UEFI to remove old EFI entries and place current boot
3280+ in-front of centos EFI loader.
3281+
3282+ -- Blake Rouse <blake.rouse@canonical.com> Fri, 3 May 2017 20:08:32 -0400
3283+
3284+maas-image-builder (1.0.2+bzr42-0ubuntu1) UNRELEASED; urgency=low
3285+
3286+ * Fix Windows image builder to work with python3.
3287+
3288+ -- Blake Rouse <blake.rouse@canonical.com> Fri, 21 Apr 2017 11:42:32 -0400
3289+
3290+maas-image-builder (1.0.1+bzr40-0ubuntu1) UNRELEASED; urgency=low
3291+
3292+ * Fix UEFI installation with CentOS 7
3293+
3294+ -- Blake Rouse <blake.rouse@canonical.com> Fri, 21 Apr 2017 09:14:08 -0400
3295+
3296+maas-image-builder (1.0.0+bzr36-0ubuntu1) UNRELEASED; urgency=low
3297+
3298+ * Fix virt-install to work on systems with apparmor.
3299+ * Change from python2.7 to python3.
3300+
3301+ -- Blake Rouse <blake.rouse@canonical.com> Wed, 19 Apr 2017 16:54:04 -0400
3302+
3303+maas-image-builder (0.9.0+bzr27-0ubuntu1) UNRELEASED; urgency=low
3304+
3305+ * Merge Windows and RHEL into the same code base. Make all of
3306+ maas-image-builder private and update the copyright..
3307+
3308+ -- Blake Rouse <blake.rouse@canonical.com> Wed, 6 Jul 2016 11:14:18 -0400
3309+
3310 maas-image-builder (0.9.0+bzr24-0ubuntu1) UNRELEASED; urgency=low
3311
3312 * Fix CentOS 6 & 7 to not use udev rules.
3313
3314=== modified file 'debian/control'
3315--- debian/control 2015-03-11 19:32:46 +0000
3316+++ debian/control 2017-06-13 16:13:40 +0000
3317@@ -5,39 +5,46 @@
3318 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
3319 Build-Depends: debhelper (>= 7),
3320 dh-python,
3321- python (>= 2.7),
3322- python-setuptools
3323+ python3,
3324+ python3-setuptools
3325 Homepage: http://launchpad.net/maas-image-builder
3326-X-Python-Version: >= 2.7
3327+X-Python3-Version: >= 3.5
3328
3329 Package: maas-image-builder
3330 Architecture: all
3331-Depends: python,
3332- python-mib (= ${binary:Version}),
3333+Depends: python3,
3334+ python3-mib (= ${binary:Version}),
3335 ${misc:Depends},
3336- ${python:Depends}
3337+ ${python3:Depends}
3338 Description: Library and tools for the MAAS Image Builder
3339 This package provides the MAAS Image Builder.
3340
3341 Package: mib-common
3342 Architecture: all
3343-Depends: util-linux (>= 2.20.1-1ubuntu3), ${misc:Depends}, ${python:Depends}
3344+Depends: util-linux (>= 2.20.1-1ubuntu3), ${misc:Depends}, ${python3:Depends}
3345 Description: Library and tools for the MAAS Image Builder
3346 This package provides the MAAS Image Builder.
3347
3348-Package: python-mib
3349+Package: python3-mib
3350 Section: python
3351 Architecture: all
3352-Depends: genisoimage,
3353+Depends: dos2unix,
3354+ dosfstools,
3355+ genisoimage,
3356 kpartx,
3357 kvm,
3358 libvirt-bin,
3359 mib-common (= ${binary:Version}),
3360- python-stevedore,
3361+ ntfs-3g,
3362+ python3-stevedore,
3363+ python3-tempita,
3364+ qemu-kvm-spice,
3365 qemu-utils,
3366+ unzip,
3367 util-linux (>= 2.20.1-1ubuntu3),
3368 virtinst,
3369+ wget,
3370 ${misc:Depends},
3371- ${python:Depends}
3372+ ${python3:Depends}
3373 Description: Library and tools for the MAAS Image Builder
3374 This package provides the MAAS Image Builder.
3375
3376=== renamed file 'debian/python-mib.install' => 'debian/python3-mib.install'
3377--- debian/python-mib.install 2015-03-11 19:32:46 +0000
3378+++ debian/python3-mib.install 2017-06-13 16:13:40 +0000
3379@@ -1,1 +1,1 @@
3380-usr/lib/python2*/dist-packages/*
3381+usr/lib/python3*/dist-packages/*
3382
3383=== modified file 'debian/rules'
3384--- debian/rules 2015-03-14 19:32:31 +0000
3385+++ debian/rules 2017-06-13 16:13:40 +0000
3386@@ -1,16 +1,16 @@
3387 #!/usr/bin/make -f
3388
3389-PYTHON = $(shell pyversions -d)
3390+PYTHON3 = $(shell py3versions -d)
3391
3392 %:
3393- dh $@ --with=python2
3394+ dh $@ --with=python3
3395
3396 override_dh_auto_build:
3397 @echo "Auto build does nothing."
3398
3399 override_dh_auto_install:
3400- $(PYTHON) setup.py build --executable=/usr/bin/python
3401- $(PYTHON) setup.py install --root=$(CURDIR)/debian/tmp --install-layout=deb
3402+ $(PYTHON3) setup.py build --executable=/usr/bin/python3
3403+ $(PYTHON3) setup.py install --root=$(CURDIR)/debian/tmp --install-layout=deb
3404
3405 dh_install --list-missing
3406
3407
3408=== added file 'pylintrc'
3409--- pylintrc 1970-01-01 00:00:00 +0000
3410+++ pylintrc 2017-06-13 16:13:40 +0000
3411@@ -0,0 +1,425 @@
3412+[MASTER]
3413+
3414+# A comma-separated list of package or module names from where C extensions may
3415+# be loaded. Extensions are loading into the active Python interpreter and may
3416+# run arbitrary code
3417+extension-pkg-whitelist=
3418+
3419+# Add files or directories to the blacklist. They should be base names, not
3420+# paths.
3421+ignore=CVS
3422+
3423+# Add files or directories matching the regex patterns to the blacklist. The
3424+# regex matches against base names, not paths.
3425+ignore-patterns=
3426+
3427+# Python code to execute, usually for sys.path manipulation such as
3428+# pygtk.require().
3429+#init-hook=
3430+
3431+# Use multiple processes to speed up Pylint.
3432+jobs=1
3433+
3434+# List of plugins (as comma separated values of python modules names) to load,
3435+# usually to register additional checkers.
3436+load-plugins=
3437+
3438+# Pickle collected data for later comparisons.
3439+persistent=yes
3440+
3441+# Specify a configuration file.
3442+#rcfile=
3443+
3444+# Allow loading of arbitrary C extensions. Extensions are imported into the
3445+# active Python interpreter and may run arbitrary code.
3446+unsafe-load-any-extension=no
3447+
3448+
3449+[MESSAGES CONTROL]
3450+
3451+# Only show warnings with the listed confidence levels. Leave empty to show
3452+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
3453+confidence=
3454+
3455+# Disable the message, report, category or checker with the given id(s). You
3456+# can either give multiple identifiers separated by comma (,) or put this
3457+# option multiple times (only on the command line, not in the configuration
3458+# file where it should appear only once).You can also use "--disable=all" to
3459+# disable everything first and then reenable specific checks. For example, if
3460+# you want to run only the similarities checker, you can use "--disable=all
3461+# --enable=similarities". If you want to run only the classes checker, but have
3462+# no Warning level messages displayed, use"--disable=all --enable=classes
3463+# --disable=W"
3464+disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,similarities
3465+
3466+# Enable the message, report, category or checker with the given id(s). You can
3467+# either give multiple identifier separated by comma (,) or put this option
3468+# multiple time (only on the command line, not in the configuration file where
3469+# it should appear only once). See also the "--disable" option for examples.
3470+enable=
3471+
3472+
3473+[REPORTS]
3474+
3475+# Python expression which should return a note less than 10 (10 is the highest
3476+# note). You have access to the variables errors warning, statement which
3477+# respectively contain the number of errors / warnings messages and the total
3478+# number of statements analyzed. This is used by the global evaluation report
3479+# (RP0004).
3480+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
3481+
3482+# Template used to display messages. This is a python new-style format string
3483+# used to format the message information. See doc for all details
3484+#msg-template=
3485+
3486+# Set the output format. Available formats are text, parseable, colorized, json
3487+# and msvs (visual studio).You can also give a reporter class, eg
3488+# mypackage.mymodule.MyReporterClass.
3489+output-format=text
3490+
3491+# Tells whether to display a full report or only the messages
3492+reports=no
3493+
3494+# Activate the evaluation score.
3495+score=yes
3496+
3497+
3498+[REFACTORING]
3499+
3500+# Maximum number of nested blocks for function / method body
3501+max-nested-blocks=5
3502+
3503+
3504+[TYPECHECK]
3505+
3506+# List of decorators that produce context managers, such as
3507+# contextlib.contextmanager. Add to this list to register other decorators that
3508+# produce valid context managers.
3509+contextmanager-decorators=contextlib.contextmanager
3510+
3511+# List of members which are set dynamically and missed by pylint inference
3512+# system, and so shouldn't trigger E1101 when accessed. Python regular
3513+# expressions are accepted.
3514+generated-members=
3515+
3516+# Tells whether missing members accessed in mixin class should be ignored. A
3517+# mixin class is detected if its name ends with "mixin" (case insensitive).
3518+ignore-mixin-members=yes
3519+
3520+# This flag controls whether pylint should warn about no-member and similar
3521+# checks whenever an opaque object is returned when inferring. The inference
3522+# can return multiple potential results while evaluating a Python object, but
3523+# some branches might not be evaluated, which results in partial inference. In
3524+# that case, it might be useful to still emit no-member and other checks for
3525+# the rest of the inferred objects.
3526+ignore-on-opaque-inference=yes
3527+
3528+# List of class names for which member attributes should not be checked (useful
3529+# for classes with dynamically set attributes). This supports the use of
3530+# qualified names.
3531+ignored-classes=optparse.Values,thread._local,_thread._local
3532+
3533+# List of module names for which member attributes should not be checked
3534+# (useful for modules/projects where namespaces are manipulated during runtime
3535+# and thus existing member attributes cannot be deduced by static analysis. It
3536+# supports qualified module names, as well as Unix pattern matching.
3537+ignored-modules=
3538+
3539+# Show a hint with possible names when a member name was not found. The aspect
3540+# of finding the hint is based on edit distance.
3541+missing-member-hint=yes
3542+
3543+# The minimum edit distance a name should have in order to be considered a
3544+# similar match for a missing member name.
3545+missing-member-hint-distance=1
3546+
3547+# The total number of similar names that should be taken in consideration when
3548+# showing a hint for a missing member.
3549+missing-member-max-choices=1
3550+
3551+
3552+[VARIABLES]
3553+
3554+# List of additional names supposed to be defined in builtins. Remember that
3555+# you should avoid to define new builtins when possible.
3556+additional-builtins=
3557+
3558+# Tells whether unused global variables should be treated as a violation.
3559+allow-global-unused-variables=yes
3560+
3561+# List of strings which can identify a callback function by name. A callback
3562+# name must start or end with one of those strings.
3563+callbacks=cb_,_cb
3564+
3565+# A regular expression matching the name of dummy variables (i.e. expectedly
3566+# not used).
3567+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
3568+
3569+# Argument names that match this expression will be ignored. Default to name
3570+# with leading underscore
3571+ignored-argument-names=_.*|^ignored_|^unused_
3572+
3573+# Tells whether we should check for unused import in __init__ files.
3574+init-import=no
3575+
3576+# List of qualified module names which can have objects that can redefine
3577+# builtins.
3578+redefining-builtins-modules=six.moves,future.builtins
3579+
3580+
3581+[BASIC]
3582+
3583+# Naming hint for argument names
3584+argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
3585+
3586+# Regular expression matching correct argument names
3587+argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
3588+
3589+# Naming hint for attribute names
3590+attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
3591+
3592+# Regular expression matching correct attribute names
3593+attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
3594+
3595+# Bad variable names which should always be refused, separated by a comma
3596+bad-names=foo,bar,baz,toto,tutu,tata
3597+
3598+# Naming hint for class attribute names
3599+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
3600+
3601+# Regular expression matching correct class attribute names
3602+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
3603+
3604+# Naming hint for class names
3605+class-name-hint=[A-Z_][a-zA-Z0-9]+$
3606+
3607+# Regular expression matching correct class names
3608+class-rgx=[A-Z_][a-zA-Z0-9]+$
3609+
3610+# Naming hint for constant names
3611+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
3612+
3613+# Regular expression matching correct constant names
3614+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
3615+
3616+# Minimum line length for functions/classes that require docstrings, shorter
3617+# ones are exempt.
3618+docstring-min-length=-1
3619+
3620+# Naming hint for function names
3621+function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
3622+
3623+# Regular expression matching correct function names
3624+function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
3625+
3626+# Good variable names which should always be accepted, separated by a comma
3627+good-names=i,j,k,ex,Run,_
3628+
3629+# Include a hint for the correct naming format with invalid-name
3630+include-naming-hint=no
3631+
3632+# Naming hint for inline iteration names
3633+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
3634+
3635+# Regular expression matching correct inline iteration names
3636+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
3637+
3638+# Naming hint for method names
3639+method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
3640+
3641+# Regular expression matching correct method names
3642+method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
3643+
3644+# Naming hint for module names
3645+module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
3646+
3647+# Regular expression matching correct module names
3648+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
3649+
3650+# Colon-delimited sets of names that determine each other's naming style when
3651+# the name regexes allow several styles.
3652+name-group=
3653+
3654+# Regular expression which should only match function or class names that do
3655+# not require a docstring.
3656+no-docstring-rgx=^_
3657+
3658+# List of decorators that produce properties, such as abc.abstractproperty. Add
3659+# to this list to register other decorators that produce valid properties.
3660+property-classes=abc.abstractproperty
3661+
3662+# Naming hint for variable names
3663+variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
3664+
3665+# Regular expression matching correct variable names
3666+variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
3667+
3668+
3669+[FORMAT]
3670+
3671+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
3672+expected-line-ending-format=
3673+
3674+# Regexp for a line that is allowed to be longer than the limit.
3675+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
3676+
3677+# Number of spaces of indent required inside a hanging or continued line.
3678+indent-after-paren=4
3679+
3680+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
3681+# tab).
3682+indent-string=' '
3683+
3684+# Maximum number of characters on a single line.
3685+max-line-length=100
3686+
3687+# Maximum number of lines in a module
3688+max-module-lines=1000
3689+
3690+# List of optional constructs for which whitespace checking is disabled. `dict-
3691+# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
3692+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
3693+# `empty-line` allows space-only lines.
3694+no-space-check=trailing-comma,dict-separator
3695+
3696+# Allow the body of a class to be on the same line as the declaration if body
3697+# contains single statement.
3698+single-line-class-stmt=no
3699+
3700+# Allow the body of an if to be on the same line as the test if there is no
3701+# else.
3702+single-line-if-stmt=no
3703+
3704+
3705+[SIMILARITIES]
3706+
3707+# Ignore comments when computing similarities.
3708+ignore-comments=yes
3709+
3710+# Ignore docstrings when computing similarities.
3711+ignore-docstrings=yes
3712+
3713+# Ignore imports when computing similarities.
3714+ignore-imports=no
3715+
3716+# Minimum lines number of a similarity.
3717+min-similarity-lines=4
3718+
3719+
3720+[SPELLING]
3721+
3722+# Spelling dictionary name. Available dictionaries: none. To make it working
3723+# install python-enchant package.
3724+spelling-dict=
3725+
3726+# List of comma separated words that should not be checked.
3727+spelling-ignore-words=
3728+
3729+# A path to a file that contains private dictionary; one word per line.
3730+spelling-private-dict-file=
3731+
3732+# Tells whether to store unknown words to indicated private dictionary in
3733+# --spelling-private-dict-file option instead of raising a message.
3734+spelling-store-unknown-words=no
3735+
3736+
3737+[MISCELLANEOUS]
3738+
3739+# List of note tags to take in consideration, separated by a comma.
3740+notes=FIXME,XXX,TODO
3741+
3742+
3743+[LOGGING]
3744+
3745+# Logging modules to check that the string format arguments are in logging
3746+# function parameter format
3747+logging-modules=logging
3748+
3749+
3750+[IMPORTS]
3751+
3752+# Allow wildcard imports from modules that define __all__.
3753+allow-wildcard-with-all=no
3754+
3755+# Analyse import fallback blocks. This can be used to support both Python 2 and
3756+# 3 compatible code, which means that the block might have code that exists
3757+# only in one or another interpreter, leading to false positives when analysed.
3758+analyse-fallback-blocks=no
3759+
3760+# Deprecated modules which should not be used, separated by a comma
3761+deprecated-modules=optparse,tkinter.tix
3762+
3763+# Create a graph of external dependencies in the given file (report RP0402 must
3764+# not be disabled)
3765+ext-import-graph=
3766+
3767+# Create a graph of every (i.e. internal and external) dependencies in the
3768+# given file (report RP0402 must not be disabled)
3769+import-graph=
3770+
3771+# Create a graph of internal dependencies in the given file (report RP0402 must
3772+# not be disabled)
3773+int-import-graph=
3774+
3775+# Force import order to recognize a module as part of the standard
3776+# compatibility libraries.
3777+known-standard-library=
3778+
3779+# Force import order to recognize a module as part of a third party library.
3780+known-third-party=enchant
3781+
3782+
3783+[CLASSES]
3784+
3785+# List of method names used to declare (i.e. assign) instance attributes.
3786+defining-attr-methods=__init__,__new__,setUp
3787+
3788+# List of member names, which should be excluded from the protected access
3789+# warning.
3790+exclude-protected=_asdict,_fields,_replace,_source,_make
3791+
3792+# List of valid names for the first argument in a class method.
3793+valid-classmethod-first-arg=cls
3794+
3795+# List of valid names for the first argument in a metaclass class method.
3796+valid-metaclass-classmethod-first-arg=mcs
3797+
3798+
3799+[DESIGN]
3800+
3801+# Maximum number of arguments for function / method
3802+max-args=14
3803+
3804+# Maximum number of attributes for a class (see R0902).
3805+max-attributes=7
3806+
3807+# Maximum number of boolean expressions in a if statement
3808+max-bool-expr=5
3809+
3810+# Maximum number of branch for function / method body
3811+max-branches=12
3812+
3813+# Maximum number of locals for function / method body
3814+max-locals=15
3815+
3816+# Maximum number of parents for a class (see R0901).
3817+max-parents=7
3818+
3819+# Maximum number of public methods for a class (see R0904).
3820+max-public-methods=25
3821+
3822+# Maximum number of return / yield for function / method body
3823+max-returns=6
3824+
3825+# Maximum number of statements in function / method body
3826+max-statements=50
3827+
3828+# Minimum number of public methods for a class (see R0903).
3829+min-public-methods=2
3830+
3831+
3832+[EXCEPTIONS]
3833+
3834+# Exceptions that will emit a warning when being caught. Defaults to
3835+# "Exception"
3836+overgeneral-exceptions=Exception
3837
3838=== modified file 'required-packages/base'
3839--- required-packages/base 2015-03-11 15:52:11 +0000
3840+++ required-packages/base 2017-06-13 16:13:40 +0000
3841@@ -1,7 +1,12 @@
3842+dos2unix
3843+dosfstools
3844 genisoimage
3845 kpartx
3846 kvm
3847 libvirt-bin
3848-python-stevedore
3849+ntfs-3g
3850+qemu-kvm-spice
3851 qemu-utils
3852+unzip
3853 virtinst
3854+wget
3855
3856=== modified file 'required-packages/dev'
3857--- required-packages/dev 2015-03-10 20:42:06 +0000
3858+++ required-packages/dev 2017-06-13 16:13:40 +0000
3859@@ -1,18 +1,3 @@
3860 build-essential
3861+tox
3862 make
3863-pep8
3864-pyflakes
3865-python-coverage
3866-python-extras
3867-python-fixtures
3868-python-flake8
3869-python-mock
3870-python-nose
3871-python-pip
3872-python-pocket-lint
3873-python-subunit
3874-python-testresources
3875-python-testscenarios
3876-python-testtools
3877-python-unittest2
3878-python-virtualenv
3879
3880=== added file 'requirements.txt'
3881--- requirements.txt 1970-01-01 00:00:00 +0000
3882+++ requirements.txt 2017-06-13 16:13:40 +0000
3883@@ -0,0 +1,2 @@
3884+stevedore
3885+tempita
3886
3887=== modified file 'scripts/maas-image-builder'
3888--- scripts/maas-image-builder 2015-03-11 13:40:58 +0000
3889+++ scripts/maas-image-builder 2017-06-13 16:13:40 +0000
3890@@ -1,6 +1,45 @@
3891-#!/usr/bin/env python2.7
3892-# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
3893-# GNU Affero General Public License version 3 (see the file LICENSE).
3894+#!/usr/bin/env python3
3895+# vi: ts=4 expandtab
3896+# Upstream Author:
3897+#
3898+# Canonical Ltd.
3899+#
3900+# Copyright:
3901+#
3902+# (c) 2014-2016 Canonical Ltd.
3903+#
3904+# Licence:
3905+#
3906+# If you have an executed agreement with a Canonical group company which
3907+# includes a licence to this software, your use of this software is governed
3908+# by that agreement. Otherwise, the following applies:
3909+#
3910+# Canonical Ltd. hereby grants to you a world-wide, non-exclusive,
3911+# non-transferable, revocable, perpetual (unless revoked) licence, to (i) use
3912+# this software in connection with Canonical's MAAS software to install Windows
3913+# in non-production environments and (ii) to make a reasonable number of copies
3914+# of this software for backup and installation purposes. You may not: use,
3915+# copy, modify, disassemble, decompile, reverse engineer, or distribute the
3916+# software except as expressly permitted in this licence; permit access to the
3917+# software to any third party other than those acting on your behalf; or use
3918+# this software in connection with a production environment.
3919+#
3920+# CANONICAL LTD. MAKES THIS SOFTWARE AVAILABLE "AS-IS". CANONICAL LTD. MAKES
3921+# NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, WHETHER ORAL OR WRITTEN,
3922+# WHETHER EXPRESS, IMPLIED, OR ARISING BY STATUTE, CUSTOM, COURSE OF DEALING
3923+# OR TRADE USAGE, WITH RESPECT TO THIS SOFTWARE. CANONICAL LTD. SPECIFICALLY
3924+# DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OR CONDITIONS OF TITLE, SATISFACTORY
3925+# QUALITY, MERCHANTABILITY, SATISFACTORINESS, FITNESS FOR A PARTICULAR PURPOSE
3926+# AND NON-INFRINGEMENT.
3927+#
3928+# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
3929+# CANONICAL LTD. OR ANY OF ITS AFFILIATES, BE LIABLE TO YOU FOR DAMAGES,
3930+# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
3931+# OUT OF THE USE OR INABILITY TO USE THIS SOFTWARE (INCLUDING BUT NOT LIMITED
3932+# TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU
3933+# OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
3934+# PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
3935+# POSSIBILITY OF SUCH DAMAGES.
3936
3937 from __future__ import (
3938 absolute_import,
3939
3940=== modified file 'setup.py'
3941--- setup.py 2015-07-31 18:46:41 +0000
3942+++ setup.py 2017-06-13 16:13:40 +0000
3943@@ -1,17 +1,47 @@
3944-#!/usr/bin/env python2.7
3945-# Copyright 2015 Canonical Ltd. This software is licensed under the
3946-# GNU Affero General Public License version 3 (see the file LICENSE).
3947+#!/usr/bin/env python3
3948+# Upstream Author:
3949+#
3950+# Canonical Ltd.
3951+#
3952+# Copyright:
3953+#
3954+# (c) 2014-2017 Canonical Ltd.
3955+#
3956+# Licence:
3957+#
3958+# If you have an executed agreement with a Canonical group company which
3959+# includes a licence to this software, your use of this software is governed
3960+# by that agreement. Otherwise, the following applies:
3961+#
3962+# Canonical Ltd. hereby grants to you a world-wide, non-exclusive,
3963+# non-transferable, revocable, perpetual (unless revoked) licence, to (i) use
3964+# this software in connection with Canonical's MAAS software to install Windows
3965+# in non-production environments and (ii) to make a reasonable number of copies
3966+# of this software for backup and installation purposes. You may not: use,
3967+# copy, modify, disassemble, decompile, reverse engineer, or distribute the
3968+# software except as expressly permitted in this licence; permit access to the
3969+# software to any third party other than those acting on your behalf; or use
3970+# this software in connection with a production environment.
3971+#
3972+# CANONICAL LTD. MAKES THIS SOFTWARE AVAILABLE "AS-IS". CANONICAL LTD. MAKES
3973+# NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, WHETHER ORAL OR WRITTEN,
3974+# WHETHER EXPRESS, IMPLIED, OR ARISING BY STATUTE, CUSTOM, COURSE OF DEALING
3975+# OR TRADE USAGE, WITH RESPECT TO THIS SOFTWARE. CANONICAL LTD. SPECIFICALLY
3976+# DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OR CONDITIONS OF TITLE, SATISFACTORY
3977+# QUALITY, MERCHANTABILITY, SATISFACTORINESS, FITNESS FOR A PARTICULAR PURPOSE
3978+# AND NON-INFRINGEMENT.
3979+#
3980+# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
3981+# CANONICAL LTD. OR ANY OF ITS AFFILIATES, BE LIABLE TO YOU FOR DAMAGES,
3982+# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
3983+# OUT OF THE USE OR INABILITY TO USE THIS SOFTWARE (INCLUDING BUT NOT LIMITED
3984+# TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU
3985+# OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
3986+# PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
3987+# POSSIBILITY OF SUCH DAMAGES.
3988
3989 """Distribute/Setuptools installer for MAAS Image Builder."""
3990
3991-from __future__ import (
3992- absolute_import,
3993- print_function,
3994- unicode_literals,
3995- )
3996-
3997-str = None
3998-
3999 from glob import glob
4000 from os.path import (
4001 dirname,
4002@@ -35,32 +65,37 @@
4003 return fin.read().strip()
4004
4005
4006-__version__ = "0.9"
4007+__version__ = "1.0.2"
4008
4009 setup(
4010 name="maas-image-builder",
4011 version=__version__,
4012 url="https://launchpad.net/maas-image-builder",
4013- license="AGPLv3",
4014+ license="Proprietary",
4015 description="MAAS Image Builder",
4016- long_description=read('README'),
4017+ long_description="",
4018
4019 author="Blake Rouse",
4020 author_email="blake.rouse@canonical.com",
4021
4022 packages=find_packages(
4023- where=b'src',
4024+ where='src',
4025 exclude=[
4026- b"*.testing",
4027- b"*.tests",
4028+ "*.testing",
4029+ "*.tests",
4030 ],
4031 ),
4032- package_dir={'': b'src'},
4033+ package_dir={'': 'src'},
4034 include_package_data=True,
4035
4036 entry_points={
4037+ 'console_scripts': [
4038+ 'maas-image-builder = mib.core:execute',
4039+ ],
4040 'mib.builder': [
4041 'centos = mib.builders.centos:CentOSBuilder',
4042+ 'rhel = mib.builders.rhel:RHELBuilder',
4043+ 'windows = mib.builders.windows:WindowsOSBuilder',
4044 ],
4045 },
4046
4047@@ -75,29 +110,23 @@
4048 [f for f in glob('contrib/centos/centos7/*') if isfile(f)]),
4049 ('/usr/lib/maas-image-builder/contrib/centos/centos7/curtin',
4050 [f for f in glob('contrib/centos/centos7/curtin/*') if isfile(f)]),
4051+ ('/usr/lib/maas-image-builder/contrib/rhel',
4052+ [f for f in glob('contrib/rhel/*') if isfile(f)]),
4053+ ('/usr/lib/maas-image-builder/contrib/rhel/curtin',
4054+ [f for f in glob('contrib/rhel/curtin/*') if isfile(f)]),
4055+ ('/usr/lib/maas-image-builder/contrib/windows',
4056+ [f for f in glob('contrib/windows/*') if isfile(f)]),
4057+ ('/usr/lib/maas-image-builder/contrib/windows/curtin',
4058+ [f for f in glob('contrib/windows/curtin/*') if isfile(f)]),
4059+ ('/usr/lib/maas-image-builder/contrib/windows/scripts',
4060+ [f for f in glob('contrib/windows/scripts/*') if isfile(f)]),
4061 ],
4062
4063- install_requires=[
4064- 'python-stevedore'
4065- ],
4066 classifiers=[
4067 'Development Status :: 4 - Beta',
4068 'Intended Audience :: Developers',
4069 "Intended Audience :: System Administrators",
4070- 'License :: OSI Approved :: GPL License',
4071 'Operating System :: OS Independent',
4072 'Programming Language :: Python',
4073- ],
4074- extras_require=dict(
4075- tests=[
4076- 'coverage',
4077- 'fixtures',
4078- 'mock',
4079- 'nose',
4080- 'python-subunit',
4081- 'testresources',
4082- 'testscenarios',
4083- 'testtools',
4084- ],
4085- )
4086+ ],
4087 )
4088
4089=== modified file 'src/mib/__init__.py'
4090--- src/mib/__init__.py 2015-03-11 13:40:58 +0000
4091+++ src/mib/__init__.py 2017-06-13 16:13:40 +0000
4092@@ -1,8 +1,41 @@
4093-# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
4094-# GNU Affero General Public License version 3 (see the file LICENSE).
4095-
4096-from __future__ import (
4097- absolute_import,
4098- print_function,
4099- unicode_literals,
4100- )
4101+# vi: ts=4 expandtab
4102+# Upstream Author:
4103+#
4104+# Canonical Ltd.
4105+#
4106+# Copyright:
4107+#
4108+# (c) 2014-2017 Canonical Ltd.
4109+#
4110+# Licence:
4111+#
4112+# If you have an executed agreement with a Canonical group company which
4113+# includes a licence to this software, your use of this software is governed
4114+# by that agreement. Otherwise, the following applies:
4115+#
4116+# Canonical Ltd. hereby grants to you a world-wide, non-exclusive,
4117+# non-transferable, revocable, perpetual (unless revoked) licence, to (i) use
4118+# this software in connection with Canonical's MAAS software to install Windows
4119+# in non-production environments and (ii) to make a reasonable number of copies
4120+# of this software for backup and installation purposes. You may not: use,
4121+# copy, modify, disassemble, decompile, reverse engineer, or distribute the
4122+# software except as expressly permitted in this licence; permit access to the
4123+# software to any third party other than those acting on your behalf; or use
4124+# this software in connection with a production environment.
4125+#
4126+# CANONICAL LTD. MAKES THIS SOFTWARE AVAILABLE "AS-IS". CANONICAL LTD. MAKES
4127+# NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, WHETHER ORAL OR WRITTEN,
4128+# WHETHER EXPRESS, IMPLIED, OR ARISING BY STATUTE, CUSTOM, COURSE OF DEALING
4129+# OR TRADE USAGE, WITH RESPECT TO THIS SOFTWARE. CANONICAL LTD. SPECIFICALLY
4130+# DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OR CONDITIONS OF TITLE, SATISFACTORY
4131+# QUALITY, MERCHANTABILITY, SATISFACTORINESS, FITNESS FOR A PARTICULAR PURPOSE
4132+# AND NON-INFRINGEMENT.
4133+#
4134+# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
4135+# CANONICAL LTD. OR ANY OF ITS AFFILIATES, BE LIABLE TO YOU FOR DAMAGES,
4136+# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
4137+# OUT OF THE USE OR INABILITY TO USE THIS SOFTWARE (INCLUDING BUT NOT LIMITED
4138+# TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU
4139+# OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
4140+# PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
4141+# POSSIBILITY OF SUCH DAMAGES.
4142
4143=== modified file 'src/mib/builders/__init__.py'
4144--- src/mib/builders/__init__.py 2015-03-11 18:57:11 +0000
4145+++ src/mib/builders/__init__.py 2017-06-13 16:13:40 +0000
4146@@ -1,11 +1,46 @@
4147-# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
4148-# GNU Affero General Public License version 3 (see the file LICENSE).
4149+# vi: ts=4 expandtab
4150+# Upstream Author:
4151+#
4152+# Canonical Ltd.
4153+#
4154+# Copyright:
4155+#
4156+# (c) 2014-2017 Canonical Ltd.
4157+#
4158+# Licence:
4159+#
4160+# If you have an executed agreement with a Canonical group company which
4161+# includes a licence to this software, your use of this software is governed
4162+# by that agreement. Otherwise, the following applies:
4163+#
4164+# Canonical Ltd. hereby grants to you a world-wide, non-exclusive,
4165+# non-transferable, revocable, perpetual (unless revoked) licence, to (i) use
4166+# this software in connection with Canonical's MAAS software to install Windows
4167+# in non-production environments and (ii) to make a reasonable number of copies
4168+# of this software for backup and installation purposes. You may not: use,
4169+# copy, modify, disassemble, decompile, reverse engineer, or distribute the
4170+# software except as expressly permitted in this licence; permit access to the
4171+# software to any third party other than those acting on your behalf; or use
4172+# this software in connection with a production environment.
4173+#
4174+# CANONICAL LTD. MAKES THIS SOFTWARE AVAILABLE "AS-IS". CANONICAL LTD. MAKES
4175+# NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, WHETHER ORAL OR WRITTEN,
4176+# WHETHER EXPRESS, IMPLIED, OR ARISING BY STATUTE, CUSTOM, COURSE OF DEALING
4177+# OR TRADE USAGE, WITH RESPECT TO THIS SOFTWARE. CANONICAL LTD. SPECIFICALLY
4178+# DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OR CONDITIONS OF TITLE, SATISFACTORY
4179+# QUALITY, MERCHANTABILITY, SATISFACTORINESS, FITNESS FOR A PARTICULAR PURPOSE
4180+# AND NON-INFRINGEMENT.
4181+#
4182+# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
4183+# CANONICAL LTD. OR ANY OF ITS AFFILIATES, BE LIABLE TO YOU FOR DAMAGES,
4184+# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
4185+# OUT OF THE USE OR INABILITY TO USE THIS SOFTWARE (INCLUDING BUT NOT LIMITED
4186+# TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU
4187+# OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
4188+# PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
4189+# POSSIBILITY OF SUCH DAMAGES.
4190
4191-from __future__ import (
4192- absolute_import,
4193- print_function,
4194- unicode_literals,
4195- )
4196+"""Built-in builders."""
4197
4198 from abc import (
4199 ABCMeta,
4200@@ -82,22 +117,8 @@
4201
4202 def build_image(self, params):
4203 """Builds the image with virt-install."""
4204- output = params.output
4205- interface = params.interface
4206- ram = params.ram
4207- vcpus = params.vcpus
4208- arch = params.arch
4209- os_type = self.os_type
4210- os_variant = self.os_variant
4211- disk_size = self.disk_size
4212- interface_model = self.nic_model
4213- install_location = self.install_location
4214- install_cdrom = self.install_cdrom
4215- extra_arguments = self.extra_arguments
4216- initrd_inject = self.initrd_inject
4217-
4218 # Check for valid location
4219- if install_location is None and install_cdrom is None:
4220+ if self.install_location is None and self.install_cdrom is None:
4221 raise BuildError(
4222 "Missing install_location or install_cdrom for virt-install.")
4223
4224@@ -113,39 +134,40 @@
4225 # Create the disk, and set the permissions
4226 # that will allow virt-install to access it
4227 disk_path = os.path.join(workdir, 'disk.img')
4228- virt.create_disk(disk_path, disk_size, format='raw')
4229+ virt.create_disk(disk_path, self.disk_size, disk_format='raw')
4230 utils.subp(['chmod', '777', disk_path])
4231 disk_str = "path=%s,format=raw" % disk_path
4232
4233 # Start the installation
4234 vm_name = 'img-build-%s' % full_name
4235- network_str = 'bridge=%s' % interface
4236- if interface_model is not None:
4237- network_str = '%s,model=%s' % (network_str, interface_model)
4238- if install_location:
4239+ network_str = 'bridge=%s' % params.interface
4240+ if self.nic_model is not None:
4241+ network_str = '%s,model=%s' % (
4242+ network_str, self.nic_model)
4243+ if self.install_location:
4244 virt.install_location(
4245 vm_name,
4246- ram,
4247- arch,
4248- vcpus,
4249- os_type,
4250- os_variant,
4251+ params.ram,
4252+ params.arch,
4253+ params.vcpus,
4254+ self.os_type,
4255+ self.os_variant,
4256 disk_str,
4257 network_str,
4258- install_location,
4259- initrd_inject=initrd_inject,
4260- extra_args=extra_arguments)
4261+ self.install_location,
4262+ initrd_inject=self.initrd_inject,
4263+ extra_args=self.extra_arguments)
4264 else:
4265 virt.install_cdrom(
4266 vm_name,
4267- ram,
4268- arch,
4269- vcpus,
4270- os_type,
4271- os_variant,
4272+ params.ram,
4273+ params.arch,
4274+ params.vcpus,
4275+ self.os_type,
4276+ self.os_variant,
4277 disk_str,
4278 network_str,
4279- install_cdrom)
4280+ self.install_cdrom)
4281
4282 # Remove the finished installation from virsh
4283 virt.undefine(vm_name)
4284@@ -167,4 +189,4 @@
4285 utils.umount_loop(disk_path, mount_path)
4286
4287 # Place in output
4288- shutil.move(output_path, output)
4289+ shutil.move(output_path, params.output)
4290
4291=== modified file 'src/mib/builders/centos.py'
4292--- src/mib/builders/centos.py 2015-03-11 18:57:11 +0000
4293+++ src/mib/builders/centos.py 2017-06-13 16:13:40 +0000
4294@@ -1,19 +1,52 @@
4295-# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
4296-# GNU Affero General Public License version 3 (see the file LICENSE).
4297+# vi: ts=4 expandtab
4298+# Upstream Author:
4299+#
4300+# Canonical Ltd.
4301+#
4302+# Copyright:
4303+#
4304+# (c) 2014-2017 Canonical Ltd.
4305+#
4306+# Licence:
4307+#
4308+# If you have an executed agreement with a Canonical group company which
4309+# includes a licence to this software, your use of this software is governed
4310+# by that agreement. Otherwise, the following applies:
4311+#
4312+# Canonical Ltd. hereby grants to you a world-wide, non-exclusive,
4313+# non-transferable, revocable, perpetual (unless revoked) licence, to (i) use
4314+# this software in connection with Canonical's MAAS software to install Windows
4315+# in non-production environments and (ii) to make a reasonable number of copies
4316+# of this software for backup and installation purposes. You may not: use,
4317+# copy, modify, disassemble, decompile, reverse engineer, or distribute the
4318+# software except as expressly permitted in this licence; permit access to the
4319+# software to any third party other than those acting on your behalf; or use
4320+# this software in connection with a production environment.
4321+#
4322+# CANONICAL LTD. MAKES THIS SOFTWARE AVAILABLE "AS-IS". CANONICAL LTD. MAKES
4323+# NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, WHETHER ORAL OR WRITTEN,
4324+# WHETHER EXPRESS, IMPLIED, OR ARISING BY STATUTE, CUSTOM, COURSE OF DEALING
4325+# OR TRADE USAGE, WITH RESPECT TO THIS SOFTWARE. CANONICAL LTD. SPECIFICALLY
4326+# DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OR CONDITIONS OF TITLE, SATISFACTORY
4327+# QUALITY, MERCHANTABILITY, SATISFACTORINESS, FITNESS FOR A PARTICULAR PURPOSE
4328+# AND NON-INFRINGEMENT.
4329+#
4330+# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
4331+# CANONICAL LTD. OR ANY OF ITS AFFILIATES, BE LIABLE TO YOU FOR DAMAGES,
4332+# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
4333+# OUT OF THE USE OR INABILITY TO USE THIS SOFTWARE (INCLUDING BUT NOT LIMITED
4334+# TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU
4335+# OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
4336+# PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
4337+# POSSIBILITY OF SUCH DAMAGES.
4338
4339-from __future__ import (
4340- absolute_import,
4341- print_function,
4342- unicode_literals,
4343- )
4344+"""Builder for CentOS."""
4345
4346 import os
4347 import shutil
4348+import tempfile
4349
4350-from mib.builders import (
4351- BuildError,
4352- VirtInstallBuilder,
4353- )
4354+from mib.builders import BuildError, VirtInstallBuilder
4355
4356
4357 class CentOSBuilder(VirtInstallBuilder):
4358@@ -23,21 +56,29 @@
4359 name = "centos"
4360 arches = ["i386", "amd64"]
4361 os_type = "linux"
4362- os_variant = "rhel5"
4363 disk_size = 5
4364 nic_model = "virtio"
4365 install_location = ""
4366
4367+ @property
4368+ def os_variant(self):
4369+ if self.edition == '6':
4370+ return 'centos6.5'
4371+ return 'centos7.0'
4372+
4373 def full_name(self, params):
4374 return 'centos%s-%s' % (params.edition, params.arch)
4375
4376- def populate_parser(self, parser):
4377+ def populate_parser(self, parser): # pylint: disable=no-self-use
4378 """Add parser options."""
4379 parser.add_argument(
4380 '--edition', default='7',
4381 help="CentOS edition to generate. (Default: 7)")
4382+ parser.add_argument(
4383+ '--custom-kickstart', default=None,
4384+ help="Path to a custom kickstart file used to customize the image")
4385
4386- def validate_params(self, params):
4387+ def validate_params(self, params): # pylint: disable=no-self-use
4388 """Validates the command line parameters."""
4389 if params.edition not in ['6', '7']:
4390 raise BuildError(
4391@@ -46,6 +87,11 @@
4392 raise BuildError(
4393 "Cannot generate CentOS 7 for i386, as only amd64 is "
4394 "supported.")
4395+ if (params.custom_kickstart is not None and
4396+ not os.path.exists(params.custom_kickstart)):
4397+ raise BuildError(
4398+ "Custom kickstart file '%s' does not exist!" %
4399+ params.custom_kickstart)
4400
4401 def modify_mount(self, mount_path):
4402 """Install the curtin directory into mount point."""
4403@@ -61,29 +107,50 @@
4404
4405 def build_image(self, params):
4406 self.validate_params(params)
4407+ # pylint: disable=attribute-defined-outside-init
4408 self.edition = params.edition
4409+
4410 if self.edition == '6':
4411- arch = params.arch
4412- if arch == 'i386':
4413+ if params.arch == 'i386':
4414 self.install_location = (
4415 "http://mirror.centos.org/centos/6/os/i386")
4416- self.extra_arguments = (
4417- "console=ttyS0 ks=file:/centos6-i386.ks text utf8")
4418- self.initrd_inject = self.get_contrib_path(
4419+ base_kickstart_file = self.get_contrib_path(
4420 "centos6/centos6-i386.ks")
4421- elif arch == 'amd64':
4422+ elif params.arch == 'amd64':
4423 self.install_location = (
4424 "http://mirror.centos.org/centos/6/os/x86_64")
4425- self.extra_arguments = (
4426- "console=ttyS0 ks=file:/centos6-amd64.ks text utf8")
4427- self.initrd_inject = self.get_contrib_path(
4428+ base_kickstart_file = self.get_contrib_path(
4429 "centos6/centos6-amd64.ks")
4430+ extra_arguments_template = "console=ttyS0 ks=file:/%s text utf8"
4431 elif self.edition == '7':
4432 self.install_location = (
4433 "http://mirror.centos.org/centos/7/os/x86_64")
4434- self.extra_arguments = (
4435- "console=ttyS0 inst.ks=file:/centos7-amd64.ks text "
4436+ base_kickstart_file = self.get_contrib_path(
4437+ "centos7/centos7-amd64.ks")
4438+ extra_arguments_template = (
4439+ "console=ttyS0 inst.ks=file:/%s text "
4440 "inst.cmdline inst.headless")
4441- self.initrd_inject = self.get_contrib_path(
4442- "centos7/centos7-amd64.ks")
4443+
4444+ if params.custom_kickstart is None:
4445+ self.extra_arguments = extra_arguments_template % os.path.basename(
4446+ base_kickstart_file)
4447+ self.initrd_inject = base_kickstart_file
4448+ else:
4449+ # If a custom kickstart file was given create a new file which
4450+ # concatenates the custom kickstart file to the end of ours.
4451+ tmp_file_path = tempfile.mktemp(prefix='maas-image-builder-')
4452+ with open(tmp_file_path, 'w') as tmp_file:
4453+ for ks_file_path in (
4454+ base_kickstart_file, params.custom_kickstart):
4455+ tmp_file.write('#\n# From %s\n#\n\n' % ks_file_path)
4456+ with open(ks_file_path, 'r') as ks_file:
4457+ for line in ks_file:
4458+ tmp_file.write(line)
4459+ self.extra_arguments = extra_arguments_template % os.path.basename(
4460+ tmp_file_path)
4461+ self.initrd_inject = tmp_file_path
4462+
4463 super(CentOSBuilder, self).build_image(params)
4464+
4465+ if params.custom_kickstart is not None:
4466+ os.remove(self.initrd_inject)
4467
4468=== added file 'src/mib/builders/rhel.py'
4469--- src/mib/builders/rhel.py 1970-01-01 00:00:00 +0000
4470+++ src/mib/builders/rhel.py 2017-06-13 16:13:40 +0000
4471@@ -0,0 +1,187 @@
4472+# vi: ts=4 expandtab
4473+# Upstream Author:
4474+#
4475+# Canonical Ltd.
4476+#
4477+# Copyright:
4478+#
4479+# (c) 2014-2017 Canonical Ltd.
4480+#
4481+# Licence:
4482+#
4483+# If you have an executed agreement with a Canonical group company which
4484+# includes a licence to this software, your use of this software is governed
4485+# by that agreement. Otherwise, the following applies:
4486+#
4487+# Canonical Ltd. hereby grants to you a world-wide, non-exclusive,
4488+# non-transferable, revocable, perpetual (unless revoked) licence, to (i) use
4489+# this software in connection with Canonical's MAAS software to install Windows
4490+# in non-production environments and (ii) to make a reasonable number of copies
4491+# of this software for backup and installation purposes. You may not: use,
4492+# copy, modify, disassemble, decompile, reverse engineer, or distribute the
4493+# software except as expressly permitted in this licence; permit access to the
4494+# software to any third party other than those acting on your behalf; or use
4495+# this software in connection with a production environment.
4496+#
4497+# CANONICAL LTD. MAKES THIS SOFTWARE AVAILABLE "AS-IS". CANONICAL LTD. MAKES
4498+# NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, WHETHER ORAL OR WRITTEN,
4499+# WHETHER EXPRESS, IMPLIED, OR ARISING BY STATUTE, CUSTOM, COURSE OF DEALING
4500+# OR TRADE USAGE, WITH RESPECT TO THIS SOFTWARE. CANONICAL LTD. SPECIFICALLY
4501+# DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OR CONDITIONS OF TITLE, SATISFACTORY
4502+# QUALITY, MERCHANTABILITY, SATISFACTORINESS, FITNESS FOR A PARTICULAR PURPOSE
4503+# AND NON-INFRINGEMENT.
4504+#
4505+# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
4506+# CANONICAL LTD. OR ANY OF ITS AFFILIATES, BE LIABLE TO YOU FOR DAMAGES,
4507+# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
4508+# OUT OF THE USE OR INABILITY TO USE THIS SOFTWARE (INCLUDING BUT NOT LIMITED
4509+# TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU
4510+# OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
4511+# PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
4512+# POSSIBILITY OF SUCH DAMAGES.
4513+
4514+"""Builder for RHEL."""
4515+
4516+import os
4517+import shutil
4518+
4519+from mib import utils
4520+from mib.builders import BuildError, VirtInstallBuilder
4521+
4522+ISOLINUX_CFG = (
4523+ "default text\n"
4524+ "timeout 0\n"
4525+ "\n"
4526+ "label text\n"
4527+ " kernel vmlinuz\n"
4528+ " append initrd=initrd.img linux text console=ttyS0 inst.repo=cdrom "
4529+ "inst.ks=cdrom:/ks.cfg inst.cmdline inst.headless\n")
4530+
4531+
4532+class RHELBuilder(VirtInstallBuilder):
4533+ """Builds the RHEL image for amd64. Uses virt-install
4534+ to perform this installation process."""
4535+
4536+ name = "rhel"
4537+ arches = ["amd64"]
4538+ os_type = "linux"
4539+ os_variant = "rhel7.0"
4540+ disk_size = 5
4541+ nic_model = "virtio"
4542+
4543+ def populate_parser(self, parser):
4544+ """Add parser arguments."""
4545+ parser.add_argument(
4546+ '--rhel-iso', required=True,
4547+ help="Path to RHEL installation ISO.")
4548+ parser.add_argument(
4549+ '--custom-kickstart', default=None,
4550+ help="Path to a custom kickstart file used to customize the image")
4551+
4552+ def validate_params(self, params):
4553+ """Validates the command line parameters."""
4554+ self.install_cdrom = params.rhel_iso
4555+ if self.install_cdrom is None:
4556+ raise BuildError(
4557+ "RHEL requires the --rhel-iso option.")
4558+ if not os.path.exists(self.install_cdrom):
4559+ raise BuildError(
4560+ "Invalid RHEL iso. File does not exist.")
4561+ if (params.custom_kickstart is not None and
4562+ not os.path.exists(params.custom_kickstart)):
4563+ raise BuildError(
4564+ "Custom kickstart file '%s' does not exist!" %
4565+ params.custom_kickstart)
4566+
4567+ def mount_iso(self, workdir, source): # pylint: disable=no-self-use
4568+ """Mounts iso in 'iso' directory under workdir."""
4569+ iso_dir = os.path.join(workdir, 'iso')
4570+ os.mkdir(iso_dir)
4571+ utils.subp([
4572+ 'mount',
4573+ source,
4574+ iso_dir,
4575+ ])
4576+ return iso_dir
4577+
4578+ def umount_iso(self, iso_dir): # pylint: disable=no-self-use
4579+ """Unmounts iso at path."""
4580+ utils.subp(['umount', iso_dir])
4581+
4582+ def copy_iso(self, workdir, iso_dir): # pylint: disable=no-self-use
4583+ """Copies the contents of the iso_dir, into output dir."""
4584+ output = os.path.join(workdir, 'output')
4585+ shutil.copytree(iso_dir, output)
4586+ return output
4587+
4588+ def write_ks(self, output_dir, custom_kickstart=None):
4589+ """Writes the kickstarter config into the output_dir at 'ks.cfg'."""
4590+ base_kickstart_file = self.get_contrib_path('rhel7-amd64.ks')
4591+ output_file = os.path.join(output_dir, 'ks.cfg')
4592+ if custom_kickstart is None:
4593+ shutil.copyfile(base_kickstart_file, output_file)
4594+ else:
4595+ with open(output_file, 'w') as output:
4596+ for ks_file_path in (base_kickstart_file, custom_kickstart):
4597+ output.write('#\n# From %s\n#\n\n' % ks_file_path)
4598+ with open(ks_file_path, 'r') as ks_file:
4599+ for line in ks_file:
4600+ output.write(line)
4601+
4602+ def set_timeout_zero(self, output_dir): # pylint: disable=no-self-use
4603+ """Sets the isolinux.cfg timeout to zero."""
4604+ isolinux_cfg = os.path.join(output_dir, 'isolinux', 'isolinux.cfg')
4605+ with open(isolinux_cfg, 'w') as stream:
4606+ stream.write(ISOLINUX_CFG + '\n')
4607+
4608+ def create_iso(self, workdir, source): # pylint: disable=no-self-use
4609+ """Creates iso at output, containing files at source."""
4610+ output = os.path.join(workdir, 'output.iso')
4611+ utils.subp([
4612+ 'mkisofs',
4613+ '-o', output,
4614+ '-b', 'isolinux/isolinux.bin',
4615+ '-c', 'isolinux/boot.cat',
4616+ '-no-emul-boot',
4617+ '-boot-load-size', '4',
4618+ '-boot-info-table', '-R', '-J', '-v',
4619+ '-T', source
4620+ ])
4621+ utils.subp(['chmod', '777', workdir])
4622+ utils.subp(['chmod', '777', output])
4623+ return output
4624+
4625+ def modify_mount(self, mount_path):
4626+ """Install the curtin directory into mount point."""
4627+ path = self.get_contrib_path('curtin')
4628+ if not os.path.exists(path):
4629+ return
4630+ opt_path = os.path.join(mount_path, 'curtin')
4631+ shutil.copytree(path, opt_path)
4632+
4633+ def build_image(self, params):
4634+ self.validate_params(params)
4635+
4636+ # Create work space
4637+ with utils.tempdir() as workdir:
4638+ # Copy out the contents of the ISO file.
4639+ iso_dir = self.mount_iso(workdir, self.install_cdrom)
4640+ try:
4641+ output_dir = self.copy_iso(workdir, iso_dir)
4642+ finally:
4643+ self.umount_iso(iso_dir)
4644+ shutil.rmtree(iso_dir)
4645+
4646+ # Write the kickstarter config.
4647+ self.write_ks(output_dir, params.custom_kickstart)
4648+
4649+ # Update isolinux to not have a timeout.
4650+ self.set_timeout_zero(output_dir)
4651+
4652+ # Create the final ISO for installation.
4653+ try:
4654+ self.install_cdrom = self.create_iso(workdir, output_dir)
4655+ finally:
4656+ shutil.rmtree(output_dir)
4657+
4658+ super(RHELBuilder, self).build_image(params)
4659
4660=== added file 'src/mib/builders/windows.py'
4661--- src/mib/builders/windows.py 1970-01-01 00:00:00 +0000
4662+++ src/mib/builders/windows.py 2017-06-13 16:13:40 +0000
4663@@ -0,0 +1,497 @@
4664+# vi: ts=4 expandtab
4665+# Upstream Author:
4666+#
4667+# Canonical Ltd.
4668+#
4669+# Copyright:
4670+#
4671+# (c) 2014-2017 Canonical Ltd.
4672+#
4673+# Licence:
4674+#
4675+# If you have an executed agreement with a Canonical group company which
4676+# includes a licence to this software, your use of this software is governed
4677+# by that agreement. Otherwise, the following applies:
4678+#
4679+# Canonical Ltd. hereby grants to you a world-wide, non-exclusive,
4680+# non-transferable, revocable, perpetual (unless revoked) licence, to (i) use
4681+# this software in connection with Canonical's MAAS software to install Windows
4682+# in non-production environments and (ii) to make a reasonable number of copies
4683+# of this software for backup and installation purposes. You may not: use,
4684+# copy, modify, disassemble, decompile, reverse engineer, or distribute the
4685+# software except as expressly permitted in this licence; permit access to the
4686+# software to any third party other than those acting on your behalf; or use
4687+# this software in connection with a production environment.
4688+#
4689+# CANONICAL LTD. MAKES THIS SOFTWARE AVAILABLE "AS-IS". CANONICAL LTD. MAKES
4690+# NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, WHETHER ORAL OR WRITTEN,
4691+# WHETHER EXPRESS, IMPLIED, OR ARISING BY STATUTE, CUSTOM, COURSE OF DEALING
4692+# OR TRADE USAGE, WITH RESPECT TO THIS SOFTWARE. CANONICAL LTD. SPECIFICALLY
4693+# DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OR CONDITIONS OF TITLE, SATISFACTORY
4694+# QUALITY, MERCHANTABILITY, SATISFACTORINESS, FITNESS FOR A PARTICULAR PURPOSE
4695+# AND NON-INFRINGEMENT.
4696+#
4697+# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
4698+# CANONICAL LTD. OR ANY OF ITS AFFILIATES, BE LIABLE TO YOU FOR DAMAGES,
4699+# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
4700+# OUT OF THE USE OR INABILITY TO USE THIS SOFTWARE (INCLUDING BUT NOT LIMITED
4701+# TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU
4702+# OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
4703+# PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
4704+# POSSIBILITY OF SUCH DAMAGES.
4705+
4706+"""Builder for Windows."""
4707+
4708+import os
4709+import re
4710+import shutil
4711+import tempfile
4712+
4713+from tempita import Template
4714+
4715+from mib import net, utils
4716+from mib.builders import Builder, BuildError
4717+
4718+EDITIONS = {
4719+ 'win2008r2': "Windows Server 2008 R2 SERVERSTANDARD",
4720+ 'win2008hvr2': "Windows Server 2008 R2 SERVERHYPERCORE",
4721+ 'win2012': "Windows Server 2012 SERVERSTANDARD",
4722+ 'win2012hv': "Hyper-V Server 2012 SERVERHYPERCORE",
4723+ 'win2012r2': "Windows Server 2012 R2 SERVERSTANDARD",
4724+ 'win2012hvr2': "Hyper-V Server 2012 R2 SERVERHYPERCORE",
4725+ 'win2016': "Windows Server 2016 SERVERSTANDARD",
4726+ 'win2016hv': "Hyper-V Server 2016 SERVERHYPERCORE",
4727+ }
4728+
4729+
4730+class WindowsOSBuilder(Builder):
4731+ """Builds the Windows image using kvm-spice."""
4732+
4733+ name = "windows"
4734+ arches = ["i386", "amd64"]
4735+
4736+ def populate_parser(self, parser):
4737+ """Add parser options."""
4738+ parser.add_argument(
4739+ '--windows-iso',
4740+ help="Path to Windows installation ISO.")
4741+ parser.add_argument(
4742+ '--windows-edition',
4743+ help="Windows edition to install from the ISO.")
4744+ parser.add_argument(
4745+ '--windows-license-key',
4746+ help="Windows license key to embed into generated image.")
4747+ parser.add_argument(
4748+ '--windows-updates', action='store_true',
4749+ help=(
4750+ "Install all Windows updates into generated image. "
4751+ "(Requires access to microsoft.com)"))
4752+ parser.add_argument(
4753+ '--windows-drivers',
4754+ help=(
4755+ "Folder containing drivers to be injected into the Windows "
4756+ "installation before the image is generated."))
4757+ parser.add_argument(
4758+ '--windows-language',
4759+ default='en-US',
4760+ help="Windows installation language. Default: en-US")
4761+ parser.add_argument(
4762+ '--cloudbase-init',
4763+ help=(
4764+ "Path to the cloudbase-init installer to use. By default it "
4765+ "will be pulled from cloudbase.it"))
4766+
4767+ def validate_params(self, params):
4768+ """Validates the command line parameters."""
4769+ iso = params.windows_iso
4770+ if iso is None:
4771+ raise BuildError(
4772+ "Windows requires the --windows-iso option.")
4773+ if not os.path.exists(iso):
4774+ raise BuildError(
4775+ "Failed to access Windows ISO at: %s" % iso)
4776+ edition = params.windows_edition
4777+ if edition is None or edition == '':
4778+ raise BuildError(
4779+ "Windows requires the --windows-edition option.")
4780+ if edition not in EDITIONS.keys():
4781+ raise BuildError(
4782+ "Invalid Windows edition, should be one of %s." % (
4783+ EDITIONS.keys()))
4784+ license_key = params.windows_license_key
4785+ if license_key is not None and license_key != '':
4786+ if not self.validate_license_key(license_key):
4787+ raise BuildError(
4788+ "Invalid Windows license key.")
4789+ drivers = params.windows_drivers
4790+ if drivers is not None and not os.path.isdir(drivers):
4791+ raise BuildError(
4792+ "Invalid driver path: %s" % drivers)
4793+
4794+ def validate_license_key(self, license_key): # pylint: disable=no-self-use
4795+ """Validates that license key is in the correct format. It does not
4796+ validate, if that license key will work with the selected edition of
4797+ Windows."""
4798+ regex = re.compile('^([A-Za-z0-9]{5}-){4}[A-Za-z0-9]{5}$')
4799+ return regex.match(license_key)
4800+
4801+ def load_unattended_template(self):
4802+ """Loads the unattended template that is used for installation."""
4803+ path = self.get_contrib_path('Autounattend.xml')
4804+ with open(path, "rb") as stream:
4805+ return Template(stream.read().decode('utf-8'))
4806+
4807+ def write_unattended(self, output_path, arch, edition, language,
4808+ license_key=None, enable_updates=False):
4809+ """Outputs the effective unattended.xml file that will be used by
4810+ Windows during the installation."""
4811+ template = self.load_unattended_template()
4812+ image_name = EDITIONS[edition]
4813+ # Windows doesn't accept i386, instead that maps to x86.
4814+ if arch == 'i386':
4815+ arch = 'x86'
4816+ output = template.substitute(
4817+ arch=arch, image_name=image_name, language=language,
4818+ license_key=license_key, enable_updates=enable_updates)
4819+ with open(output_path, 'w') as stream:
4820+ for line in output.splitlines():
4821+ stream.write("%s\r\n" % line)
4822+
4823+ def create_floppy_disk(self, output_path): # pylint: disable=no-self-use
4824+ """Creates an empty floppy disk, formatted with vfat."""
4825+ utils.subp([
4826+ 'dd', 'if=/dev/zero',
4827+ 'of=%s' % output_path,
4828+ 'bs=1024', 'count=1440',
4829+ ])
4830+ utils.subp([
4831+ 'mkfs.vfat',
4832+ output_path
4833+ ])
4834+
4835+ def prepare_floppy_disk(self, workdir, arch, edition, language,
4836+ license_key=None, enable_updates=False):
4837+ """Prepares the working directory with Autounattend.vfd."""
4838+ # Create the disk
4839+ vfd_path = os.path.join(workdir, 'Autounattend.vfd')
4840+ self.create_floppy_disk(vfd_path)
4841+
4842+ # Mount the disk
4843+ mount_path = os.path.join(workdir, 'vfd_mount')
4844+ os.mkdir(mount_path)
4845+ utils.subp([
4846+ 'mount',
4847+ '-t', 'vfat',
4848+ '-o', 'loop',
4849+ vfd_path, mount_path,
4850+ ])
4851+
4852+ # Place the generated Autounattend.xml file
4853+ xml_path = os.path.join(mount_path, 'Autounattend.xml')
4854+ try:
4855+ self.write_unattended(
4856+ xml_path, arch, edition, language,
4857+ license_key=license_key, enable_updates=enable_updates)
4858+ finally:
4859+ utils.subp(['umount', mount_path])
4860+ os.rmdir(mount_path)
4861+ return vfd_path
4862+
4863+ def download_cloudbase_init( # pylint: disable=no-self-use
4864+ self, workdir, arch, cloudbase_init=None):
4865+ """Downloads cloudbase init."""
4866+ output_path = os.path.join(workdir, 'cloudbase_init.msi')
4867+ if arch == 'amd64':
4868+ msi_file = "CloudbaseInitSetup_x64.msi"
4869+ elif arch == 'i386':
4870+ msi_file = "CloudbaseInitSetup_x86.msi"
4871+ download_path = "http://www.cloudbase.it/downloads/" + msi_file
4872+
4873+ # --cloudbase-init passed in, don't download.
4874+ if cloudbase_init:
4875+ shutil.copyfile(cloudbase_init, output_path)
4876+ return output_path
4877+
4878+ # Remove me, testing only
4879+ tmp_path = os.path.join('/tmp', msi_file)
4880+ if os.path.exists(tmp_path):
4881+ shutil.copyfile(tmp_path, output_path)
4882+ return output_path
4883+
4884+ utils.subp([
4885+ 'wget',
4886+ '-O', output_path,
4887+ download_path
4888+ ])
4889+ return output_path
4890+
4891+ def download_ps_windows_update( # pylint: disable=no-self-use
4892+ self, workdir):
4893+ """Downloads the PSWindowsUpdate package."""
4894+ output_path = os.path.join(workdir, 'pswindowsupdate.zip')
4895+ download_path = (
4896+ "http://gallery.technet.microsoft.com/scriptcenter/"
4897+ "2d191bcd-3308-4edd-9de2-88dff796b0bc/file/41459/43/"
4898+ "PSWindowsUpdate.zip")
4899+ utils.subp([
4900+ 'wget',
4901+ '-O', output_path,
4902+ download_path
4903+ ])
4904+ return output_path
4905+
4906+ def unzip_archive(self, src, dest): # pylint: disable=no-self-use
4907+ """Un-zips an archive into destination."""
4908+ utils.subp([
4909+ 'unzip', '-q',
4910+ src,
4911+ '-d', dest,
4912+ ])
4913+
4914+ def create_iso(self, output, source): # pylint: disable=no-self-use
4915+ """Creates iso at output, containing files at source."""
4916+ utils.subp([
4917+ 'genisoimage',
4918+ '-o', output,
4919+ '-V', 'SCRIPTS',
4920+ '-J', source
4921+ ])
4922+
4923+ def build_install_iso(self, workdir, arch, with_updates=False,
4924+ drivers_path=None, cloudbase_init=None):
4925+ """Builds the iso that is mounted to Windows, to complete the
4926+ installation process."""
4927+ install_path = os.path.join(workdir, 'install')
4928+ os.mkdir(install_path)
4929+
4930+ # Download cloudbase-init into install/cloudbase
4931+ cloudbase_dir = os.path.join(install_path, 'cloudbase')
4932+ os.mkdir(cloudbase_dir)
4933+ self.download_cloudbase_init(
4934+ cloudbase_dir, arch, cloudbase_init=cloudbase_init)
4935+
4936+ # Copy contrib scripts into install/scripts
4937+ contrib_path = self.get_contrib_path('scripts')
4938+ scripts_path = os.path.join(install_path, 'scripts')
4939+ shutil.copytree(contrib_path, scripts_path)
4940+
4941+ # Copy the drivers if provided
4942+ if drivers_path is not None:
4943+ shutil.copytree(
4944+ drivers_path,
4945+ os.path.join(install_path, 'infs'))
4946+
4947+ # Place PSWindowsUpdate modules if using with_updates
4948+ if with_updates:
4949+ zip_path = self.download_ps_windows_update(workdir)
4950+ self.unzip_archive(zip_path, install_path)
4951+
4952+ # Create the iso
4953+ output_iso = os.path.join(workdir, 'install.iso')
4954+ self.create_iso(output_iso, install_path)
4955+ shutil.rmtree(install_path)
4956+ return output_iso
4957+
4958+ def create_disk_image( # pylint: disable=no-self-use
4959+ self, output_path, size):
4960+ """Creates the disk image that Windows will install to."""
4961+ utils.subp([
4962+ 'qemu-img', 'create',
4963+ '-f', 'raw',
4964+ output_path, size
4965+ ])
4966+
4967+ def spawn_vm( # pylint: disable=no-self-use
4968+ self, ram, vcpus, cdrom, floppy, install_iso, disk,
4969+ tap=None):
4970+ """Spawns the qemu vm for Windows to install."""
4971+ args = [
4972+ 'kvm-spice',
4973+ '-m', '%s' % ram, '-smp', vcpus,
4974+ '-cdrom', cdrom,
4975+ '-drive', 'file=%s,index=0,format=raw,if=ide,media=disk' % disk,
4976+ '-drive', 'file=%s,index=1,format=raw,if=floppy' % floppy,
4977+ '-drive', 'file=%s,index=3,format=raw,if=ide,media=cdrom' % install_iso,
4978+ ]
4979+ if tap is not None:
4980+ mac = net.get_random_qemu_mac()
4981+ args.extend([
4982+ '-device', 'rtl8139,netdev=net00,mac=%s' % mac,
4983+ '-netdev',
4984+ 'type=tap,id=net00,script=no,downscript=no,ifname=%s' % tap,
4985+ ])
4986+ args.extend([
4987+ '-boot', 'd', '-vga', 'std',
4988+ '-k', 'en-us',
4989+ # Debug *Remove*
4990+ '-vnc', 'localhost:1',
4991+ ])
4992+ utils.subp(args)
4993+
4994+ def mount_partition( # pylint: disable=no-self-use
4995+ self, workdir, disk_path, partition):
4996+ """Mounts the parition from the disk."""
4997+ mount_path = os.path.join(workdir, 'disk_mount')
4998+ os.mkdir(mount_path)
4999+ utils.mount_loop(disk_path, mount_path, partition)
5000+ return mount_path
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches