[gnome-ostree/wip/qa] qa_make_disk/qa_pull_deploy: Scripts to generate disk images as non-root
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-ostree/wip/qa] qa_make_disk/qa_pull_deploy: Scripts to generate disk images as non-root
- Date: Mon, 7 Jan 2013 14:18:09 +0000 (UTC)
commit 3bc91f18831bd1424f15f8d29dc174b0bf448fb7
Author: Colin Walters <walters verbum org>
Date: Sat Jan 5 08:11:25 2013 -0500
qa_make_disk/qa_pull_deploy: Scripts to generate disk images as non-root
Using guestfish.
Makefile-ostbuild.am | 6 +-
src/libgsystem | 2 +-
src/ostbuild/js/builtins/qa_make_disk.js | 104 +++++++++++++++++
src/ostbuild/js/builtins/qa_pull_deploy.js | 172 ++++++++++++++++++++++++++++
src/ostbuild/js/main.js | 4 +-
src/ostbuild/js/procutil.js | 78 +++++++++++++
src/ostbuild/ostbuild-qa-qemu-pull-deploy | 81 +++++++++++++
7 files changed, 444 insertions(+), 3 deletions(-)
---
diff --git a/Makefile-ostbuild.am b/Makefile-ostbuild.am
index 7a685c4..9740386 100644
--- a/Makefile-ostbuild.am
+++ b/Makefile-ostbuild.am
@@ -27,7 +27,9 @@ ostbuild: src/ostbuild/ostbuild.in Makefile
EXTRA_DIST += ostbuild/ostbuild.in
if BUILDSYSTEM
-bin_SCRIPTS += ostbuild $(srcdir)/src/ostbuild/ostbuild-qemu-pull-deploy
+bin_SCRIPTS += ostbuild \
+ $(srcdir)/src/ostbuild/ostbuild-qemu-pull-deploy \
+ $(NULL)
utils_SCRIPTS = \
src/ostbuild/ostree-build-compile-one \
@@ -57,6 +59,8 @@ jsostbuiltins_DATA= \
src/ostbuild/js/builtins/build.js \
src/ostbuild/js/builtins/checkout.js \
src/ostbuild/js/builtins/git_mirror.js \
+ src/ostbuild/js/builtins/qa_make_disk.js \
+ src/ostbuild/js/builtins/qa_pull_deploy.js \
src/ostbuild/js/builtins/prefix.js \
src/ostbuild/js/builtins/resolve.js \
$(NULL)
diff --git a/src/libgsystem b/src/libgsystem
index b142443..bfae179 160000
--- a/src/libgsystem
+++ b/src/libgsystem
@@ -1 +1 @@
-Subproject commit b1424432d7cc7af622ef1dbaaf71282cec683df6
+Subproject commit bfae179c1f6aa15b3a0858b6b7ca6f4b5c4f8278
diff --git a/src/ostbuild/js/builtins/qa_make_disk.js b/src/ostbuild/js/builtins/qa_make_disk.js
new file mode 100644
index 0000000..3aa6185
--- /dev/null
+++ b/src/ostbuild/js/builtins/qa_make_disk.js
@@ -0,0 +1,104 @@
+// -*- indent-tabs-mode: nil; tab-width: 2; -*-
+// Copyright (C) 2013 Colin Walters <walters verbum org>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Lang = imports.lang;
+const Format = imports.format;
+
+const GSystem = imports.gi.GSystem;
+
+const ArgParse = imports.argparse;
+const ProcUtil = imports.procutil;
+
+const loop = GLib.MainLoop.new(null, true);
+
+const QaMakeDisk = new Lang.Class({
+ Name: 'QaMakeDisk',
+
+ execute: function(argv) {
+ let cancellable = null;
+ let parser = new ArgParse.ArgumentParser("Generate a disk image");
+ parser.addArgument('diskpath');
+
+ let args = parser.parse(argv);
+
+ let path = Gio.File.new_for_path(args.diskpath);
+ if (path.query_exists(null))
+ throw new Error("" + path.get_path() + " exists");
+
+ let tmppath = path.get_parent().get_child(path.get_basename() + '.tmp');
+ GSystem.shutil_rm_rf(tmppath, cancellable);
+ let sizeMb = 8 * 1024;
+ let bootsizeMb = 200;
+ let swapsizeMb = 64;
+
+ let guestfishProcess;
+
+ ProcUtil.runSync(['qemu-img', 'create', tmppath.get_path(), '' + sizeMb + 'M'], cancellable);
+ let lines = ProcUtil.runProcWithInputSyncGetLines(['guestfish', '-a', tmppath.get_path()],
+ cancellable,
+ 'launch\n\
+part-init /dev/vda mbr\n\
+blockdev-getsize64 /dev/vda\n\
+blockdev-getss /dev/vda\n');
+
+ if (lines.length != 2)
+ throw new Error("guestfish returned unexpected output lines (" + lines.length + ", expected 2");
+ let diskBytesize = parseInt(lines[0]);
+ let diskSectorsize = parseInt(lines[1]);
+ print(Format.vprintf("bytesize: %s sectorsize: %s", [diskBytesize, diskSectorsize]));
+ let bootsizeSectors = bootsizeMb * 1024 / diskSectorsize * 1024;
+ let swapsizeSectors = swapsizeMb * 1024 / diskSectorsize * 1024;
+ let rootsizeSectors = diskBytesize / diskSectorsize - bootsizeSectors - swapsizeSectors - 64;
+ let bootOffset = 64;
+ let swapOffset = bootOffset + bootsizeSectors;
+ let rootOffset = swapOffset + swapsizeSectors;
+ let endOffset = rootOffset + rootsizeSectors;
+
+ let partconfig = Format.vprintf('launch\n\
+part-add /dev/vda p %s %s\n\
+part-add /dev/vda p %s %s\n\
+part-add /dev/vda p %s %s\n\
+mkfs ext4 /dev/vda1\n\
+set-e2uuid /dev/vda1 random
+set-e2label /dev/vda1 gnome-ostree-boot
+mkswap-L gnome-ostree-swap /dev/vda2\n\
+mkfs ext4 /dev/vda3\n\
+set-e2uuid /dev/vda3 random
+set-e2label /dev/vda3 gnome-ostree-root
+mount /dev/vda3 /\n\
+mkdir /boot\n\
+', [bootOffset, swapOffset - 1,
+ swapOffset, rootOffset - 1,
+ rootOffset, endOffset - 1]);
+ print("partition config: ", partconfig);
+ lines = ProcUtil.runProcWithInputSyncGetLines(['guestfish', '-a', tmppath.get_path()], cancellable, partconfig);
+ GSystem.file_rename(tmppath, path, cancellable);
+ print("Created: " + path.get_path());
+ }
+});
+
+function main(argv) {
+ let ecode = 1;
+ var app = new QaMakeDisk();
+ GLib.idle_add(GLib.PRIORITY_DEFAULT,
+ function() { try { app.execute(argv); ecode = 0; } finally { loop.quit(); }; return false; });
+ loop.run();
+ return ecode;
+}
diff --git a/src/ostbuild/js/builtins/qa_pull_deploy.js b/src/ostbuild/js/builtins/qa_pull_deploy.js
new file mode 100644
index 0000000..0daa235
--- /dev/null
+++ b/src/ostbuild/js/builtins/qa_pull_deploy.js
@@ -0,0 +1,172 @@
+// -*- indent-tabs-mode: nil; tab-width: 2; -*-
+// Copyright (C) 2013 Colin Walters <walters verbum org>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Lang = imports.lang;
+const Format = imports.format;
+
+const GSystem = imports.gi.GSystem;
+
+const ArgParse = imports.argparse;
+const ProcUtil = imports.procutil;
+
+const loop = GLib.MainLoop.new(null, true);
+
+const QaPullDeploy = new Lang.Class({
+ Name: 'QaPullDeploy',
+
+ _findCurrentKernel: function(mntdir, cancellable) {
+ let deployBootdir = mntdir.resolve_relative_path('ostree/current/boot');
+ let d = deployBootdir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
+ let finfo;
+ while ((finfo = d.next_file(cancellable)) != null) {
+ let child = d.get_child(finfo.get_name());
+ if (child.get_basename().indexOf('vmlinuz-') == 0) {
+ return child;
+ }
+ }
+ d.close();
+ throw new Error("Couldn't find vmlinuz- in " + deployBootdir.get_path());
+ },
+
+ _parseKernelRelease: function(kernelPath) {
+ let name = kernelPath.get_basename();
+ let idx = name.indexOf('-');
+ if (idx == -1) throw new Error("Invalid kernel name " + kernelPath.get_path());
+ let kernelRelease = name.substr(idx + 1);
+ return kernelRelease;
+ },
+
+ _getInitramfsPath: function(mntdir, kernelRelease) {
+ let bootdir = mntdir.get_child('boot');
+ let initramfsName = 'initramfs-' + kernelRelease + '.img';
+ let path = bootdir.resolve_relative_path('ostree/' + initramfsName);
+ if (!path.query_exists(null))
+ throw new Error("Couldn't find initramfs " + path.get_path());
+ return path;
+ },
+
+ execute: function(argv) {
+ let cancellable = null;
+ let parser = new ArgParse.ArgumentParser("Generate a disk image");
+ parser.addArgument('diskpath');
+ parser.addArgument('srcrepo');
+ parser.addArgument('osname');
+ parser.addArgument('target');
+
+ let args = parser.parse(argv);
+
+ let diskpath = Gio.File.new_for_path(args.diskpath);
+
+ let workdir = Gio.File.new_for_path('.');
+ let mntdir = workdir.get_child('mnt');
+ GSystem.file_ensure_directory(mntdir, true, cancellable);
+ let ostreedir = mntdir.get_child('ostree');
+ let guestmountPidFile = workdir.get_child('guestmount.pid');
+
+ if (guestmountPidFile.query_exists(null)) {
+ throw new Error("guestmount pid file exists (unclean shutdown?): " + guestmountPidFile.get_path());
+ }
+
+ try {
+ let procContext = new GSystem.SubprocessContext({ argv: ['guestmount', '--rw', '-o', 'allow_root',
+ '--pid-file', guestmountPidFile.get_path(),
+ '-a', diskpath.get_path(),
+ '-m', '/dev/sda3',
+ '-m', '/dev/sda1:/boot',
+ mntdir.get_path()] });
+ let guestfishProc = new GSystem.Subprocess({context: procContext});
+ print("starting guestfish");
+ guestfishProc.init(cancellable);
+ guestfishProc.wait_sync_check(cancellable);
+ // guestfish should have daemonized now (unfortunately, if
+ // there was a way to avoid that we would).
+
+ let adminCmd = ['ostree', 'admin', '--ostree-dir=' + ostreedir.get_path(),
+ '--boot-dir=' + mntdir.get_child('boot').get_path()];
+ let adminEnv = GLib.get_environ();
+ adminEnv.push('LIBGSYSTEM_ENABLE_GUESTFS_FUSE_WORKAROUND=1');
+ ProcUtil.runSync(adminCmd.concat(['os-init', args.osname]), cancellable,
+ {logInitiation: true, env: adminEnv});
+ ProcUtil.runSync(['ostree', '--repo=' + ostreedir.get_child('repo').get_path(),
+ 'pull-local', args.srcrepo, args.target], cancellable,
+ {logInitiation: true, env: adminEnv});
+ ProcUtil.runSync(adminCmd.concat(['deploy', args.osname, args.target]), cancellable,
+ {logInitiation: true, env: adminEnv});
+ ProcUtil.runSync(adminCmd.concat(['prune', args.osname]), cancellable,
+ {logInitiation: true, env: adminEnv});
+
+ let kernelPath = this._findCurrentKernel(mntdir, cancellable);
+ let kernelRelease = this._parseKernelRelease(kernelPath);
+ let initramfsPath = this._getInitramfsPath(mntdir, kernelRelease);
+
+ let defaultFstab = 'LABEL=gnome-ostree-root / ext4 defaults 1 1\n\
+LABEL=gnome-ostree-boot /boot ext4 defaults 1 2\n\
+LABEL=gnome-ostree-swap swap swap defaults 0 0\n';
+ let fstabPath = ostreedir.resolve_relative_path('deploy/gnome-ostree/current-etc/fstab');
+ fstabPath.replace_contents(defaultFstab, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
+
+ let grubDir = mntdir.resolve_relative_path('boot/grub');
+ GSystem.file_ensure_directory(grubDir, false, cancellable);
+ let bootRelativeKernelPath = bootdir.get_relative_path(kernelPath);
+ let bootRelativeInitramfsPath = bootdir.get_relative_path(initramfsPath);
+ let grubConfPath = grubDir.get_child('grub.conf');
+ let grubConf = Format.vprintf('default=0\n\
+timeout=5\n\
+title GNOME-OSTree\n\
+ root (hd0,0)\n\
+ kernel %s root=LABEL=gnome-ostree-root\n\
+ initrd %s\n', [bootRelativeKernelPath, bootRelativeInitramfsPath]);
+ grubConf.replace_contents(grubConf, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
+ } finally {
+ if (guestmountPidFile.query_exists(null)) {
+ let pidStr = GSystem.file_load_contents_utf8(guestmountPidFile, cancellable);
+ if (pidStr.length > 0) {
+ ProcUtil.runSync(['fusermount', '-u', mntdir.get_path()], cancellable,
+ {logInitiation: true});
+ let pid = parseInt(pidStr);
+ for (let i = 0; i < 30; i++) {
+ let killContext = new GSystem.SubprocessContext({argv: ['kill', '-0', ''+pid]});
+ killContext.set_stderr_disposition(GSystem.SubprocessStreamDisposition.NULL);
+ let killProc = new GSystem.Subprocess({context: killContext});
+ killProc.init(null);
+ let [waitSuccess, ecode] = killProc.wait_sync(null);
+ let [killSuccess, statusStr] = ProcUtil.getExitStatusAndString(ecode);
+ if (killSuccess) {
+ print("Awaiting termination of guestfish, pid=" + pid + " timeout=" + (30 - i) + "s");
+ GLib.usleep(GLib.USEC_PER_SEC);
+ } else {
+ print("Complete!");
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+});
+
+function main(argv) {
+ let ecode = 1;
+ var app = new QaPullDeploy();
+ GLib.idle_add(GLib.PRIORITY_DEFAULT,
+ function() { try { app.execute(argv); ecode = 0; } finally { loop.quit(); }; return false; });
+ loop.run();
+ return ecode;
+}
diff --git a/src/ostbuild/js/main.js b/src/ostbuild/js/main.js
index 55ed988..4f4a319 100755
--- a/src/ostbuild/js/main.js
+++ b/src/ostbuild/js/main.js
@@ -22,7 +22,9 @@ const BUILTINS = {'autobuilder': "Run resolve and build",
'prefix': "Display or modify \"prefix\" (build target)",
'git-mirror': "Update internal git mirror for one or more components",
'resolve': "Expand git revisions in source to exact targets",
- 'build': "Build multiple components and generate trees"};
+ 'build': "Build multiple components and generate trees",
+ 'qa-make-disk': "Generate a bare disk image",
+ 'qa-pull-deploy': "Copy OSTree repo into virtual disk and deploy it"};
function usage(ecode) {
print("Builtins:");
diff --git a/src/ostbuild/js/procutil.js b/src/ostbuild/js/procutil.js
index f1a6480..0af1ab3 100644
--- a/src/ostbuild/js/procutil.js
+++ b/src/ostbuild/js/procutil.js
@@ -141,3 +141,81 @@ function asyncWaitCheckFinish(process, result) {
let [waitSuccess, ecode] = process.wait_finish(result);
return getExitStatusAndString(ecode);
}
+
+function runWithTempContextAndLoop(func) {
+ let mainContext = new GLib.MainContext();
+ let mainLoop = GLib.MainLoop.new(mainContext, true);
+ try {
+ mainContext.push_thread_default();
+ return func(mainLoop);
+ } finally {
+ mainContext.pop_thread_default();
+ }
+}
+
+function _runProcWithInputSyncGetLinesInternal(mainLoop, argv, cancellable, input) {
+ let context = new GSystem.SubprocessContext({ argv: argv });
+ context.set_stdout_disposition(GSystem.SubprocessStreamDisposition.PIPE);
+ context.set_stdin_disposition(GSystem.SubprocessStreamDisposition.PIPE);
+ let proc = new GSystem.Subprocess({context: context});
+ proc.init(cancellable);
+ let stdinPipe = proc.get_stdin_pipe();
+ let memStream = Gio.MemoryInputStream.new_from_bytes(new GLib.Bytes(input));
+ let asyncOps = 3;
+ function asyncOpComplete() {
+ asyncOps--;
+ if (asyncOps == 0)
+ mainLoop.quit();
+ }
+ function onSpliceComplete(stdinPipe, result) {
+ try {
+ let bytesWritten = stdinPipe.splice_finish(result);
+ } finally {
+ asyncOpComplete();
+ }
+ }
+ let closeBoth = Gio.OutputStreamSpliceFlags.CLOSE_SOURCE | Gio.OutputStreamSpliceFlags.CLOSE_TARGET;
+ stdinPipe.splice_async(memStream, closeBoth, GLib.PRIORITY_DEFAULT,
+ cancellable, onSpliceComplete);
+
+ let procException = null;
+ function onProcExited(proc, result) {
+ try {
+ let [success, statusText] = asyncWaitCheckFinish(proc, result);
+ if (!success)
+ procException = statusText;
+ } finally {
+ asyncOpComplete();
+ }
+ }
+ proc.wait(cancellable, onProcExited);
+
+ let stdoutPipe = proc.get_stdout_pipe();
+ let stdoutData = Gio.DataInputStream.new(stdoutPipe);
+ let lines = [];
+ function onReadLine(datastream, result) {
+ try {
+ let [line, len] = stdoutData.read_line_finish_utf8(result);
+ if (line == null)
+ asyncOpComplete();
+ else {
+ lines.push(line);
+ stdoutData.read_line_async(GLib.PRIORITY_DEFAULT, cancellable, onReadLine);
+ }
+ } catch (e) {
+ asyncOpComplete();
+ throw e;
+ }
+ }
+ stdoutData.read_line_async(GLib.PRIORITY_DEFAULT, cancellable, onReadLine);
+
+ mainLoop.run();
+
+ return lines;
+}
+
+function runProcWithInputSyncGetLines(argv, cancellable, input) {
+ return runWithTempContextAndLoop(function (loop) {
+ return _runProcWithInputSyncGetLinesInternal(loop, argv, cancellable, input);
+ });
+}
diff --git a/src/ostbuild/ostbuild-qa-qemu-pull-deploy b/src/ostbuild/ostbuild-qa-qemu-pull-deploy
new file mode 100644
index 0000000..04ad625
--- /dev/null
+++ b/src/ostbuild/ostbuild-qa-qemu-pull-deploy
@@ -0,0 +1,81 @@
+#!/bin/bash
+# Copyright (C) 2012 Colin Walters <walters verbum org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+set -e
+set -x
+
+# See gsystem-file-utils.c
+export LIBGSYSTEM_ENABLE_GUESTFS_FUSE_WORKAROUND=1
+
+diskpath=$1
+shift
+srcrepo=$1
+shift
+osname=$1
+shift
+target="$1"
+
+workdir=$(pwd)
+mntdir=${workdir}/mnt
+ostreedir=${mntdir}/ostree
+
+test -n "$osname" || (echo 1>&2 "usage: $0: DISKPATH SRCREPO OSNAME TARGET"; exit 1)
+
+# This crap is from man guestmount
+cleanup() {
+ # Save the PID of guestmount *before* calling fusermount.
+ pid="$(cat ${workdir}/guestmount.pid)"
+ timeout=10
+ count=$timeout
+
+ if test -n "$pid"; then
+ fusermount -u ${mntdir}
+
+ while kill -0 "$pid" 2>/dev/null && [ $count -gt 0 ]; do
+ sleep 1
+ ((count--))
+ done
+ if [ $count -eq 0 ]; then
+ echo "$0: wait for guestmount to exit failed after $timeout seconds"
+ exit 1
+ fi
+ fi
+}
+
+die() {
+ echo "$@" 1>&2
+ cleanup
+ exit 1
+}
+
+mkdir -p ${mntdir}
+if ! test -f ${diskpath}; then
+fi
+
+guestmount --rw -o allow_root --pid-file ${workdir}/guestmount.pid -a ${diskpath} -m /dev/sda3 -m /dev/sda1:/boot ${mntdir}
+
+if ! test -d ${ostreedir}; then
+ ostree admin init-fs ${mntdir} || die "init-fs"
+fi
+
+ostree admin --ostree-dir=${ostreedir} os-init ${osname} || die "os-init"
+ostree --repo=${ostreedir}/repo pull-local ${srcrepo} ${target} || die "pull-local"
+ostree admin --ostree-dir=${ostreedir} --boot-dir=${mntdir}/boot deploy ${osname} ${target} || die "deploy"
+ostree admin --ostree-dir=${ostreedir} prune ${osname} || die "prune"
+
+cleanup
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]