core: Remove src/autobuilder

This will move to a separate repository.  This allows a clearer
separation between the core tool (which is shipped on client systems
too), and the compose infrastructure.

Furthermore, I want to make the autobuilder a Docker container.
This commit is contained in:
Colin Walters 2014-05-03 07:32:28 -04:00
parent 0ad262b2c4
commit df2b355f38
40 changed files with 1 additions and 5393 deletions

View File

@ -1,64 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.22 (GNU/Linux)
mQINBFLqoTIBEADWmOEk9BrBQT2PJpkX/Bba6UgPmZaUN7J6TL85vOtO/Kv+HuNk
HnU5LfnSNzyizIW/KppXZyF88Mw7Ol7VH1NF3w58nnQcIWSS5lDUr4nAnngpGJEb
lZh/9Fuz6dQUJKG8GhqHQumrseKDvR99nBEm2V+PpCvTR9p7ZTgjcqktuZGNZ++Z
lwdGX71xcN6HiwM7TGFZGWrSuNg8uubnFg+0Jfbm0rGpC0eNBkxzlLp6hWxb1PFD
OjTvvo+RTbTcmRv/xO8tL5LZLMEHKT8a0G4z3NknbBcVeezhjCACh46RNzMeiTWX
Kkp7rKxgt1HLsQlxFnSQpAyXNumGIngGJGDTLTB5HyyHxFEUkcY9jZbE4C13sa1P
MaYb6B/LnX0XjgEqc1Hg2bqOrI1N9PN0mv5kExbjy5P3XA7ef4wdV/HyyipB5aw5
tiDd0dAbbdMqoQZ7/BmcqxVPd0R5LH+wkDhEH9rCxmr8hQlZEn6nWDE4dnNyn55F
NqXouCa4IDocgSlNE6KFB6T+4IDufoO/Drmx+RNsuz5qI1RuwKal6IKSvnzTJlL9
0kjSQmL1u49obvmLZUCb5kiiznZFs0f0/VeP8jXdbU70Ygu/h5AXZlGcSbMHsgil
OJY0jTZXBT+PShLcX4XqttN0iCylqIPIcpH4q3B5XoJHlaE2iHpVYFhLbQARAQAB
tElSUE0tT1NUcmVlIERlbW8gU2lnbmluZyBLZXkgKE5vdCBmb3IgcHJvZHVjdGlv
biB1c2UpIDx3YWx0ZXJzQHZlcmJ1bS5vcmc+iQI5BBMBAgAjBQJS6qEyAhsDBwsJ
CAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQ6QluiIQ4M9+mhhAAnWbmKUl9pHIk
GG9wXEb6dv8KLuvGz2LW/fE26TsJgL0uEvUfYh3PoUwHszFhAjgZYqU8V1BrChQR
y0g6SGlbQypktRj4nCSfAP5UZqESmKyV8zdZuvbi6RJ3HB3U9TTmLJXpMor41RuP
iqDnXyjsu+bPDTcsgHvapDgE3mMj++a3ahUFTFCCAdPefSuD6Ddb6yDgNKeJskaB
iMPvZcxEJFF1NAWtFG1WkyJgJYWALXNXggxpMlh2SJbTZSHJ5O0HPypvvPmZpBwL
A8y3PbyIvx65AkOQyXFDuZyxfpHKuz2LVAodduKm32S8ZyImKqcURqVYjkkS5juY
Bx08DB+BnaZq7Yq58A6wHSNMXxGmUrb7LhTXfRn3FQkUq9K44U+wG19k7nBxNJTU
1d+k1U+PpHA9uHEvplicyBKmjlbz7FlygZSVkresnTGQvjNQgoykXaOjVcxeeOPg
TPNKhauU8grCRAVM+/YgRPYYoh4QF/rbzwGhD00UKh9SHq33rtyFx6Ku7nRXvmDg
8v3Aksv4YbmzNXcQNen67ov6Oqv5W6kfTOnYICSNt001OWVmsFToKZnN3PHyet/E
Jp3Dm9dkBcmPxnDbdo7Q+huw9D1g3iCOgEHFKBNG6QZyht2q/L7YutAdLep3F0xm
FS3xaQLgmOLd4jbRienpejVhQoIZQQOJAhwEEAECAAYFAlLusyYACgkQqGbXzK4I
cpEoqQ/+PZhpz2ahZPwf0lS3NRFe60U4rILAHs07PgkfEmlZt11jnP0noUlvrYB5
xCqKIM5t6/1DE4d0jPXx7A338tJ1pjkYEzxo1BqRsVKvYxO2L1vndb7NUou/WXGb
nh/Xc6VE+Dvq9F0K41FXJ74VXhsJsHQTXeGlgec/L74HZSCbBU9PuyTgw0iz/KOO
gcnxIybuK04lNsJ82t7unB2t4oGVUcZp9b+OdIjwT6aPl364gLSEf70/x8A3qitu
7MqKrHfKSoEHtBibGPrBT7aafTGfNwL5tlh4HnHo7EsVjmKA0nzBsXgle//vn1m8
YyBwE1BS03bcN2cSu56yZ8RGKFalVjrpKD+MzzFaL+4v0FYRESyFdChQviA7NOjr
SMjdz0I19Q2d/Uz7Wyrlt/b6a+dkBXePwABsvFsAETbW2Bo9y1isSe9mT4D0Abee
cR3c06+NGez1i/t6BEF82UZrdUXqB/uqe2uGxcnnnrjfQQtRtl8XDdUWE2rHRGB1
xJ2vjELiUlEFpcDwx4UC6xVJm/g/sRTgVc7wEfjiNCKX3mrkP3zBAWP+/DeHx7ud
V7gZ9AATkEvrGrP0Q/53d95UljYUsZ/TLEDpR6Josdz2CiEvOzf4niAbCEAN9TuW
ON1L1dzYmiVYAb3f9j7B2MhD+SGBUDO9X9EvU3tWRcxp6xuOuxK5Ag0EUuqhMgEQ
ANMTgMR6sMB2DCliJ4Ej49to7i4ulEKmkkDwHUyZHGBtELItTrV64xWg55jgHRo+
1NvzDhbcQENhylZncsaKCkbNjQwr+tDYGnvg5r/yM7oVTndKBu0WOKoTIolmn9S1
Cv5ak+zVV0LiHqD6g5C7CVryYLdrypf5jV+g9urS5JUN+z33J8btB85ZGVHIvdeD
Bb5QkIr7ttEX2nEtssqGc34VjG4+hnWLDhtGDl7y4YY9KrwT59PGN3NkI9K5gH6s
k4O8TOOCSHp7o+s+Y6q5GWRSq4eNMHCmaSPrYMI6T7UAYtTVFOMM+Vqh5TDTuf0j
j4Q4/pOQ/P48rTjVYExw6zZxSK0+zxWxsMxRGoKtjpxz/G2DlNut9qDmjN1OA+BR
EvawaS5ay58uQjEU5C3m4hMtSSleV0eJBtNAwdCrrYvIIjXoFUTZA905U1VvCDs5
UCUOxWo/6h8SQUYqxEcJSDW+cdSeUXk1vh/ajEdQ//nVY1L7MNXv5t8Wn21KUw0q
HgStMhOYgM1kgY9A7ib0oqR3TrNGGWUIoFtbNoNgGvYhq8ylPFe24J3h9z4p5hSo
2N5TYEXZFRns8qN4PQdf3Y0TT+QG9SxukrV0vrdcX6ma1/WRZWG3INqneaAaFHY3
MiHYOFjetZxUAxAAsOhwj3zIPtiPDenZ3GQvxza2mIjXABEBAAGJAh8EGAECAAkF
AlLqoTICGwwACgkQ6QluiIQ4M9/0NRAAosUFqqcXr8HfshHCzgFef5ItDInDL6cc
ozvIBUPGJNOmP/KS+jTu8SBlc8dEhb/rFmWWpYytpV19QBXrH0ASkUYhknTaZWxP
MlonsGDgO+P902KCmByGZp5VrnhNaITyEmN14y8+tUagdUUSrd+eo8n6heA6Egip
327ftqgt/7kgmum0mX2I6ayhBEjfQOSP2p2Uplx504EZygp02MALJ2ZiGhhOpThP
cGPmQAL52gQlOGg9drTFPfrvQG57ZGyTP/WNUlcKNW4xRKyMcfUnEGvlbaqLXSRv
4WrAkr6GJglzUvQyak3mW7ZmoJ14AqVWxPwRrd5M9CU0Xi8Bvh8wp/oSI5PKkm6A
lFEe2zdCAW1vs63aFu5/Q7OW8HQiQ23do2rXykxm4aKuI6iZppORMSzMF0m0kV3w
EFLcfI1gJ+X4b+klJ8j3XyXrA8dpuMbYTODSWV/RLhDx7Nj6NNCLC7ejeKox8MD5
G76mbi73uk0oyuZz/tDpXCv+bepxbj0TJI0SCVIsQsqfyGsEqdpjTwRmz605kXJ6
zUMkCcw2VnIu8J1H5eU8UCeLh9lz3qXZy9XExzgmiF0F1Gj/GqUGFU+S59lKyuI0
hJk+rhJ/3rJorVYmOatV8P9WKwMyYLZx4X7jVosoT5G8XEJ6IuPUbXo21ndLmno6
YUsDeX1U94I=
=9PDu
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -1,84 +0,0 @@
# Copyright (C) 2011,2014 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.
substitutions= \
-e s,@libdir\@,$(libdir), \
-e s,@pkglibdir\@,$(pkglibdir), \
-e s,@datarootdir\@,$(datarootdir), \
-e s,@pkgdatadir\@,$(pkgdatadir), \
$(NULL)
rpm-ostree-autobuilder: src/autobuilder/rpm-ostree-autobuilder.in Makefile
sed $(substitutions) $< > $@.tmp && mv $@.tmp $@
EXTRA_DIST += src/autobuilder/rpm-ostree-autobuilder.in
autobuilder_privlibdir=$(libdir)/$(PACKAGE)-autobuilder
autobuilder_privlib_PROGRAMS = rpm-ostree-autobuilder-builtin-console
rpm_ostree_autobuilder_builtin_console_SOURCES = src/autobuilder/rpm-ostree-autobuilder-builtin-console.c
rpm_ostree_autobuilder_builtin_console_CFLAGS = $(AM_CFLAGS) $(PKGDEP_RPMOSTREE_CFLAGS)
rpm_ostree_autobuilder_builtin_console_LDADD = $(AM_LDFLAGS) $(PKGDEP_RPMOSTREE_LIBS) -lreadline
autobuilder_privlib_SCRIPTS = src/autobuilder/rpmqa-sorted
bin_SCRIPTS += rpm-ostree-autobuilder \
$(NULL)
jsautobuilderdir=$(pkgdatadir)-autobuilder/js
jsautobuilder_DATA= \
src/autobuilder/js/argparse.js \
src/autobuilder/js/asyncutil.js \
src/autobuilder/js/buildutil.js \
src/autobuilder/js/builtin.js \
src/autobuilder/js/fileutil.js \
src/autobuilder/js/task.js \
src/autobuilder/js/jsonutil.js \
src/autobuilder/js/jsutil.js \
src/autobuilder/js/main.js \
src/autobuilder/js/libqa.js \
src/autobuilder/js/guestfish.js \
src/autobuilder/js/params.js \
src/autobuilder/js/procutil.js \
src/autobuilder/js/snapshot.js \
src/autobuilder/js/streamutil.js \
src/autobuilder/js/vcs.js \
src/autobuilder/js/versioneddir.js \
$(NULL)
jsautobuiltinsdir=$(jsautobuilderdir)/builtins
jsautobuiltins_DATA= \
src/autobuilder/js/builtins/autobuilder.js \
src/autobuilder/js/builtins/git_mirror.js \
src/autobuilder/js/builtins/make.js \
src/autobuilder/js/builtins/qa_make_disk.js \
src/autobuilder/js/builtins/run_task.js \
src/autobuilder/js/builtins/shell.js \
$(NULL)
jsautotasksdir=$(jsautobuilderdir)/tasks
jsautotasks_DATA= \
src/autobuilder/js/tasks/task-treecompose.js \
src/autobuilder/js/tasks/task-ensure-disk-caches.js \
src/autobuilder/js/tasks/task-repoweb.js \
src/autobuilder/js/tasks/task-smoketest.js \
src/autobuilder/js/tasks/task-zdisks.js \
src/autobuilder/js/tasks/testbase.js \
$(NULL)
testdatadir=$(pkgdatadir)-autobuilder
testdata_DATA = src/autobuilder/rpm-ostree-export-journal-to-serialdev \
src/autobuilder/rpm-ostree-export-journal-to-serialdev.service \
$(NULL)

View File

@ -1,16 +0,0 @@
INTROSPECTION_GIRS += GSystem-1.0.gir
GSystem-1.0.gir: libgsystem.la Makefile
GSystem_1_0_gir_NAMESPACE = GSystem
GSystem_1_0_gir_VERSION = 1.0
GSystem_1_0_gir_LIBS = libgsystem.la
GSystem_1_0_gir_CFLAGS = $(libgsystem_cflags)
GSystem_1_0_gir_SCANNERFLAGS = \
--warn-all \
--warn-error \
--symbol-prefix=gs_ \
--identifier-prefix=GS \
--c-include="libgsystem.h" \
$(NULL)
GSystem_1_0_gir_INCLUDES = Gio-2.0
GSystem_1_0_gir_FILES = $(libgsystem_la_SOURCES)

View File

@ -30,4 +30,3 @@ EXTRA_DIST += autogen.sh COPYING
include Makefile-rpm-ostree.am
include Makefile-man.am
include Makefile-autobuilder.am

View File

@ -1,6 +1,6 @@
Summary: Commit RPMs to an OSTree repository
Name: rpm-ostree
Version: 2015.1
Version: 2014.1
Release: 1%{?dist}
#VCS: https://github.com/cgwalters/rpm-ostree
# This tarball is generated via "make -f Makefile.dist-packaging dist-snapshot"
@ -24,19 +24,6 @@ Requires: /usr/bin/yum
This tool takes a set of packages, and commits them to an OSTree
repository. At the moment, it is intended for use on build servers.
%package autobuilder
Summary: Build server for rpm-ostree
Group: System Environment/Base
Requires: %{name}%{?_isa} = %{version}-%{release}
Requires: /usr/bin/gjs
Requires: /usr/bin/guestmount
Requires: libguestfs-gobject
BuildRequires: /usr/bin/g-ir-scanner
BuildRequires: readline-devel
%description autobuilder
An automatic build server for rpm-ostree.
%prep
%setup -q -n %{name}-%{version}
@ -53,12 +40,6 @@ make install DESTDIR=$RPM_BUILD_ROOT INSTALL="install -p -c"
%{_bindir}/rpm-ostree
%{_libdir}/%{name}/
%files autobuilder
%{_bindir}/rpm-ostree-autobuilder
%{_libdir}/%{name}-autobuilder/
%{_datadir}/%{name}-autobuilder/
%{_mandir}/man1/*.gz
%changelog
* Fri Mar 07 2014 Colin Walters <walters@verbum.org> - 2014.5-1
- Initial package

View File

@ -1,181 +0,0 @@
// Copyright (C) 2012,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 ArgumentParser = new Lang.Class({
Name: 'ArgumentParser',
_init: function(description) {
this.description = description;
this._opts = [];
this._namedArgs = [];
this._optNames = {};
this._argNames = {};
},
usage: function() {
let buf = 'Usage: ' + this.description + '\n';
for (let i = 0; i < this._opts.length; i++) {
let opt = this._opts[i];
let names = opt._names;
for (let j = 0; j < names.length; j++) {
let name = names[j];
buf += name;
if (j < names.length - 1)
buf += ", ";
}
if (opt.description)
buf += ' ' + opt.description;
buf += '\n';
}
for (let i = 0; i < this._namedArgs.length; i++) {
let arg = this._namedArgs[i];
buf += arg._varName + "\n";
}
return buf;
},
addArgument: function(nameOrNames, opts) {
if (!opts)
opts = {};
let names;
if (nameOrNames instanceof Array)
names = nameOrNames;
else
names = [nameOrNames];
if (opts.action == undefined)
opts.action = 'store';
if (opts.nargs == undefined)
opts.nargs = '1';
if (names.length == 0) {
throw new Error("Must specify at least one argument");
} else if (names.length == 1 && names[0][0] != '-') {
let name = names[0];
this._namedArgs.push(opts);
this._argNames[name] = opts;
opts._varName = name;
} else {
opts._names = names;
this._opts.push(opts);
opts._varName = null;
let shortOpt = null;
for (let i = 0; i < names.length; i++) {
let name = names[i];
if (this._optNames[name]) {
throw new Error("Argument " + name + " already added");
} else if (names.length != 1 && name[0] != '-') {
throw new Error("Argument " + name + " does not start with -");
}
this._optNames[name] = opts;
if (opts._varName == null) {
if (name.indexOf('--') == 0)
opts._varName = name.substr(2).replace(/-/g, '_');
else if (shortOpt == null && name[0] == '-' && name[1] != '-')
shortOpt = name.substr(1);
}
}
if (opts._varName == null)
opts._varName = shortOpt;
}
},
_failed: function() {
print(this.usage());
throw new Error("Argument parsing failed");
},
parse: function(argv) {
let result = {};
let rest = [];
for (let name in this._optNames) {
let opts = this._optNames[name];
if (opts.action == 'store') {
result[opts._varName] = null;
} else if (opts.action == 'storeTrue') {
result[opts._varName] = false;
} else if (opts.action == 'append') {
result[opts._varName] = [];
}
}
for (let name in this._argNames) {
let opts = this._argNames[name];
if (opts.nargs == '*')
result[name] = [];
else
result[name] = null;
}
let rest = [];
for (let i = 0; i < argv.length; i++) {
let arg = argv[i];
if (arg[0] == '-') {
let equalsIdx = arg.indexOf('=');
let opts;
if (equalsIdx != -1)
opts = this._optNames[arg.substr(0, equalsIdx)];
else
opts = this._optNames[arg];
if (!opts) this._failed();
if (opts.action == 'store' || opts.action == 'append') {
let val;
if (equalsIdx == -1) {
if (i == argv.length - 1) this._failed();
val = argv[i+1];
i++;
} else {
val = arg.substr(equalsIdx+1);
}
if (opts.action == 'store')
result[opts._varName] = val;
else
result[opts._varName].push(val);
} else if (opts.action == 'storeTrue') {
result[opts._varName] = true;
}
} else {
rest.push(arg);
}
}
for (let i = 0; i < this._namedArgs.length; i++) {
let opts = this._namedArgs[i];
let varName = opts._varName;
if (opts.nargs == '*') {
result[varName].push.apply(result[varName], rest);
} else {
if (rest.length == 0) this._failed();
let value = rest.shift();
result[varName] = value;
}
}
return result;
}
});

View File

@ -1,71 +0,0 @@
// Copyright (C) 2012,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 AsyncSet = new Lang.Class({
Name: 'AsyncSet',
_init: function(callback, cancellable) {
this._callback = callback;
this._cancellable = cancellable;
this._results = {};
this._err = null;
this._children = [];
},
addGAsyncResult: function(name, callback) {
this._children.push(callback);
let wrapped = Lang.bind(this, function(object, result) {
let success = false;
try {
this._results[name] = callback(object, result);
success = true;
} catch (e) {
if (this._cancellable)
this._cancellable.cancel();
if (!this._err) {
this._err = e.toString();
this._checkCallback();
return;
}
}
let i;
for (i = 0; i < this._children.length; i++) {
let child = this._children[i];
if (child === callback) {
break;
}
}
if (i == this._children.length)
throw new Error("Failed to find child task");
this._children.splice(i, 1);
this._checkCallback();
});
return wrapped;
},
_checkCallback: function() {
if (this._err)
this._callback(null, this._err);
else if (this._children.length == 0)
this._callback(this._results, null);
}
});

View File

@ -1,137 +0,0 @@
// Copyright (C) 2011 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 GSystem = imports.gi.GSystem;
const BUILD_ENV = {
'HOME' : '/',
'HOSTNAME' : 'rpm-ostree-autobuilder',
'LANG': 'C',
'PATH' : '/usr/bin:/bin:/usr/sbin:/sbin',
'SHELL' : '/bin/bash',
'TERM' : 'vt100',
'TMPDIR' : '/tmp',
'TZ': 'EST5EDT'
};
function getPatchPathsForComponent(patchdir, component) {
let patches = component['patches'];
if (!patches)
return [];
let patchSubdir = patches['subdir'];
let subPatchdir;
if (patchSubdir) {
subPatchdir = patchdir.get_child(patchSubdir);
} else {
subPatchdir = patchdir;
}
let result = [];
let files = patches['files'];
for (let i = 0; i < files.length; i++) {
result.push(subPatchdir.get_child(files[i]));
}
return result;
}
function findUserChrootPath() {
// We need to search PATH here manually so we correctly pick up an
// ostree install in e.g. ~/bin even though we're going to set PATH
// below for our children inside the chroot.
let userChrootPath = null;
let elts = GLib.getenv('PATH').split(':');
for (let i = 0; i < elts.length; i++) {
let dir = Gio.File.new_for_path(elts[i]);
let child = dir.get_child('linux-user-chroot');
if (child.query_exists(null)) {
userChrootPath = child;
break;
}
}
return userChrootPath;
}
function getBaseUserChrootArgs() {
let path = findUserChrootPath();
return [path.get_path(), '--unshare-pid', '--unshare-ipc', '--unshare-net'];
}
function compareVersions(a, b) {
let adot = a.indexOf('.');
while (adot != -1) {
let bdot = b.indexOf('.');
if (bdot == -1)
return 1;
let aSub = parseInt(a.substr(0, adot));
let bSub = parseInt(b.substr(0, bdot));
if (aSub > bSub)
return 1;
else if (aSub < bSub)
return -1;
a = a.substr(adot + 1);
b = b.substr(bdot + 1);
adot = a.indexOf('.');
}
if (b.indexOf('.') != -1)
return -1;
let aSub = parseInt(a);
let bSub = parseInt(b);
if (aSub > bSub)
return 1;
else if (aSub < bSub)
return -1;
return 0;
}
function atomicSymlinkSwap(linkPath, newTarget, cancellable) {
let parent = linkPath.get_parent();
let tmpLinkPath = parent.get_child('current-new.tmp');
GSystem.shutil_rm_rf(tmpLinkPath, cancellable);
let relpath = GSystem.file_get_relpath(parent, newTarget);
tmpLinkPath.make_symbolic_link(relpath, cancellable);
GSystem.file_rename(tmpLinkPath, linkPath, cancellable);
}
function checkIsWorkDirectory(dir) {
let manifest = dir.get_child('products.json');
if (!manifest.query_exists(null)) {
throw new Error("No products.json found in " + dir.get_path());
}
let dotGit = dir.get_child('.git');
if (dotGit.query_exists(null)) {
throw new Error(".git found in " + dir.get_path() + "; are you in a rpm-ostree checkout?");
}
}
function formatElapsedTime(microseconds) {
let millis = microseconds / 1000;
if (millis > 1000) {
let seconds = millis / 1000;
return Format.vprintf("%.1f s", [seconds]);
}
return Format.vprintf("%.1f ms", [millis]);
}
function timeSubtask(name, cb) {
let startTime = GLib.get_monotonic_time();
cb();
let endTime = GLib.get_monotonic_time();
print("Subtask " + name + " complete in " + formatElapsedTime(endTime - startTime));
}

View File

@ -1,67 +0,0 @@
// Copyright (C) 2012,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 GSystem = imports.gi.GSystem;
const Params = imports.params;
const JsonUtil = imports.jsonutil;
const ArgParse = imports.argparse;
const Snapshot = imports.snapshot;
const BuildUtil = imports.buildutil;
const Builtin = new Lang.Class({
Name: 'Builtin',
DESCRIPTION: null,
_init: function() {
this.parser = new ArgParse.ArgumentParser(this.DESCRIPTION);
this._workdirInitialized = false;
},
_initWorkdir: function(workdir, cancellable) {
if (this._workdirInitialized)
return;
this._workdirInitialized = true;
if (workdir === null)
workdir = Gio.File.new_for_path('.');
else if (typeof(workdir) == 'string')
workdir = Gio.File.new_for_path(workdir);
BuildUtil.checkIsWorkDirectory(workdir);
this.workdir = workdir;
this.mirrordir = workdir.get_child('src');
GSystem.file_ensure_directory(this.mirrordir, true, cancellable);
this.patchdir = this.workdir.get_child('patches');
},
_initSnapshot: function(workdir, snapshotPath, cancellable) {
this._initWorkdir(workdir, cancellable);
let path = Gio.File.new_for_path(snapshotPath);
this._snapshot = Snapshot.fromFile(path, cancellable);
},
main: function(argv, loop, cancellable) {
let args = this.parser.parse(argv);
this.execute(args, loop, cancellable);
}
});

View File

@ -1,288 +0,0 @@
// Copyright (C) 2012,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 GSystem = imports.gi.GSystem;
const Builtin = imports.builtin;
const Task = imports.task;
const JsonUtil = imports.jsonutil;
const BuildUtil = imports.buildutil;
const ProcUtil = imports.procutil;
const VersionedDir = imports.versioneddir;
const Autobuilder = new Lang.Class({
Name: 'Autobuilder',
Extends: Builtin.Builtin,
DESCRIPTION: "Trigger builds every 3 hours",
_init: function() {
this.parent();
this._buildNeeded = true;
this._initialResolveNeeded = true;
this._fullResolveNeeded = true;
this._resolveTimeout = 0;
this._resolveSrcUrls = [];
},
execute: function(args, loop, cancellable) {
this._initWorkdir(null, cancellable);
this._buildsDir = new VersionedDir.VersionedDir(this.workdir.get_child('builds'));
this._resultsDir = this.workdir.get_child('results');
GSystem.file_ensure_directory(this._resultsDir, true, cancellable);
this._taskmaster = new Task.TaskMaster(this.workdir);
this._taskmaster.connect('task-executing', Lang.bind(this, this._onTaskExecuting));
this._taskmaster.connect('task-complete', Lang.bind(this, this._onTaskCompleted));
// Build every hour
this._buildTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
60 * 60,
Lang.bind(this, this._triggerTreeCompose));
this._currentBuildPath = null;
let path = Gio.File.new_for_path("cmd.socket");
GSystem.shutil_rm_rf(path, cancellable);
let commandSocketAddress = Gio.UnixSocketAddress.new(path.get_path());
this._cmdSocketService = Gio.SocketService.new();
this._cmdSocketService.add_address(commandSocketAddress,
Gio.SocketType.STREAM,
Gio.SocketProtocol.DEFAULT,
null);
this._updateStatus();
this._triggerTreeCompose();
this._cmdSocketService.connect('incoming', this._onCmdSocketIncoming.bind(this));
this._clientIdSerial = 0;
this._clients = {};
loop.run();
},
_onCmdSocketIncoming: function(svc, connection, source) {
this._clientIdSerial++;
let clientData = { 'serial': this._clientIdSerial,
'connection': connection,
'datainstream': Gio.DataInputStream.new(connection.get_input_stream()),
'outstandingWrite': false,
'bufs': [] };
this._clients[this._clientIdSerial] = clientData;
print("Connection from client " + clientData.serial);
clientData.datainstream.read_line_async(GLib.PRIORITY_DEFAULT, null,
this._onClientLineReady.bind(this, clientData));
},
_onClientSpliceComplete: function(clientData, stream, result) {
stream.splice_finish(result);
clientData.outstandingWrite = false;
this._rescheduleClientWrite(clientData);
},
_rescheduleClientWrite: function(clientData) {
if (clientData.bufs.length == 0 ||
clientData.outstandingWrite)
return;
let buf = clientData.bufs.shift();
let membuf = Gio.MemoryInputStream.new_from_bytes(GLib.Bytes.new(buf));
clientData.outstandingWrite = true;
clientData.connection.get_output_stream()
.splice_async(membuf, 0, GLib.PRIORITY_DEFAULT, null,
this._onClientSpliceComplete.bind(this, clientData));
},
_writeClient: function(clientData, buf) {
clientData.bufs.push(buf + '\n');
this._rescheduleClientWrite(clientData);
},
_parseParameters: function(paramStrings) {
let params = {};
for (let i = 0; i < paramStrings.length; i++) {
let param = paramStrings[i];
let idx = param.indexOf('=');
if (idx == -1)
throw new Error("Invalid key=value syntax");
let k = param.substr(0, idx);
let v = JSON.parse(param.substr(idx+1));
params[k] = v;
}
return params;
},
_onClientLineReady: function(clientData, stream, result) {
let [line,len] = stream.read_line_finish_utf8(result);
if (line == null) {
print("Disconnect from client " + clientData.serial);
delete this._clients[clientData.serial];
return;
}
let spcIdx = line.indexOf(' ');
let cmd, rest = "";
if (spcIdx == -1)
cmd = line;
else {
cmd = line.substring(0, spcIdx);
rest = line.substring(spcIdx + 1);
}
print("[client " + clientData.serial + ']' + " Got cmd: " + cmd + " rest: " + rest);
if (cmd == 'status') {
this._writeClient(clientData, this._status);
} else if (cmd == 'treecompose') {
this._triggerTreeCompose();
this._writeClient(clientData, 'Treecompose queued');
} else if (cmd == 'pushtask') {
let nextSpcIdx = rest.indexOf(' ');
let taskName;
let args = {};
if (nextSpcIdx != -1) {
taskName = rest.substring(0, nextSpcIdx);
rest = rest.substring(nextSpcIdx + 1);
} else {
taskName = rest;
rest = null;
}
taskName = taskName.replace(/ /g, '');
let parsedArgs = true;
if (rest != null) {
try {
args = this._parseParameters(rest.split(' '));
} catch (e) {
this._writeClient(clientData, 'Invalid parameters: ' + e);
parsedArgs = false;
}
}
if (parsedArgs) {
try {
let lastBuildPath = this._resultsDir.get_child('tasks/treecompose');
let lastBuildRealPath = GSystem.file_realpath(lastBuildPath);
let taskPath = lastBuildRealPath.get_child(taskName);
// Remove an already extant version of this
GSystem.shutil_rm_rf(taskPath, null);
this._taskmaster.pushTask(lastBuildRealPath, taskName, args, { force: true });
this._updateStatus();
this._writeClient(clientData, this._status);
} catch (e) {
this._writeClient(clientData, 'Caught exception: ' + e);
}
}
} else {
this._writeClient(clientData, 'Unknown command: ' + cmd);
}
print("Done processing cmd: " + cmd);
clientData.datainstream.read_line_async(GLib.PRIORITY_DEFAULT, null,
this._onClientLineReady.bind(this, clientData));
},
_onTaskExecuting: function(taskmaster, task) {
print("Task " + task.name + " executing on " + task.buildName);
this._updateStatus();
},
_onTaskCompleted: function(taskmaster, task, success, error) {
let cancellable = null;
if (task.name == 'resolve') {
if (!task.changed) {
print("Resolve is unchanged");
this._buildsDir.deleteCurrentVersion(null);
}
this._runResolve();
}
let resultsPath;
if (success) {
print("Task " + task.name + " complete: " + task.buildName);
resultsPath = this._resultsDir.get_child('successful');
} else {
print("Task " + task.name + " failed: " + task.buildName);
resultsPath = this._resultsDir.get_child('failed');
}
BuildUtil.atomicSymlinkSwap(resultsPath, task.buildPath, null);
this._updateStatus();
},
_updateStatus: function() {
let newStatus = "";
let taskstateList = this._taskmaster.getTaskState();
let runningTasks = [];
let runningTaskNames = [];
let queuedTaskNames = [];
for (let i = 0; i < taskstateList.length; i++) {
let taskstate = taskstateList[i];
if (taskstate.running) {
runningTasks.push(taskstate);
runningTaskNames.push(taskstate.task.name);
} else {
queuedTaskNames.push(taskstate.task.name);
}
}
if (runningTasks.length == 0 && queuedTaskNames.length == 0) {
newStatus = "[idle]";
} else {
newStatus = "running:";
for (let i = 0; i < runningTasks.length; i++) {
newStatus += ' ' + runningTasks[i].task.name;
let params = runningTasks[i].task.taskData.parameters;
for (let n in params) {
newStatus += ' ' + n + '=' + params[n];
}
}
if (queuedTaskNames.length > 0)
newStatus += " queued:";
for (let i = 0; i < queuedTaskNames.length; i++) {
newStatus += " " + queuedTaskNames[i];
}
}
if (newStatus != this._status) {
this._status = newStatus;
print(this._status);
let [success,loadAvg,etag] = Gio.File.new_for_path('/proc/loadavg').load_contents(null);
loadAvg = loadAvg.toString().replace(/\n$/, '').split(' ');
let statusPath = Gio.File.new_for_path('autobuilder-status.json');
JsonUtil.writeJsonFileAtomic(statusPath, {'running': runningTaskNames,
'queued': queuedTaskNames,
'systemLoad': loadAvg}, null);
}
},
_triggerTreeCompose: function() {
let cancellable = null;
if (this._taskmaster.isTaskQueued('treecompose'))
return true;
this._currentBuildPath = this._buildsDir.allocateNewVersion(cancellable);
this._taskmaster.pushTask(this._currentBuildPath, 'treecompose', { });
this._updateStatus();
return true;
}
});

View File

@ -1,78 +0,0 @@
// Copyright (C) 2011,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.
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Builtin = imports.builtin;
const Snapshot = imports.snapshot;
const Vcs = imports.vcs;
const JsonUtil = imports.jsonutil;
const GitMirror = new Lang.Class({
Name: 'GitMirror',
Extends: Builtin.Builtin,
DESCRIPTION: "Update internal git mirror for one or more components",
_init: function() {
this.parent();
this.parser.addArgument('--workdir');
this.parser.addArgument('--manifest');
this.parser.addArgument('--snapshot');
this.parser.addArgument('--timeout-sec', { help: "Cache fetch results for provided number of seconds" });
this.parser.addArgument('--fetch', {action:'storeTrue',
help:"Also do a git fetch for components"});
this.parser.addArgument(['-k', '--keep-going'], {action:'storeTrue',
help: "Don't exit on fetch failures"});
this.parser.addArgument('components', {nargs:'*'});
},
execute: function(args, loop, cancellable) {
this._initWorkdir(args.workdir, cancellable);
if (!args.timeout_sec)
args.timeout_sec = 0;
if (args.manifest != null) {
let manifestPath = Gio.File.new_for_path(args.manifest)
this._snapshot = Snapshot.fromFile(manifestPath, cancellable, { prepareResolve: true });
} else {
this._initSnapshot(null, args.snapshot, cancellable);
}
let componentNames;
if (args.components.length == 0) {
componentNames = this._snapshot.getAllComponentNames();
} else {
componentNames = args.components;
}
componentNames.forEach(Lang.bind(this, function (name) {
let component = this._snapshot.getComponent(name);
if (!args.fetch) {
Vcs.ensureVcsMirror(this.mirrordir, component, cancellable);
} else {
print("Running git fetch for " + name);
Vcs.fetch(this.mirrordir, component, cancellable,
{ keepGoing:args.keep_going,
timeoutSec: args.timeout_sec });
}
}));
}
});

View File

@ -1,103 +0,0 @@
// Copyright (C) 2012,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 GSystem = imports.gi.GSystem;
const Builtin = imports.builtin;
const Task = imports.task;
const Make = new Lang.Class({
Name: 'Make',
Extends: Builtin.Builtin,
DESCRIPTION: "Execute tasks",
_init: function() {
this.parent();
this.parser.addArgument(['-n', '--only'], { action: 'storeTrue',
help: "Don't process tasks after this" });
this.parser.addArgument(['-x', '--skip'], { action: 'append',
help: "Don't process tasks after this" });
this.parser.addArgument('taskname');
this.parser.addArgument('parameters', { nargs: '*' });
},
execute: function(args, loop, cancellable) {
this._initWorkdir(null, cancellable);
this._loop = loop;
this._failed = false;
this._cancellable = cancellable;
this._oneOnly = args.only;
let taskmaster = new Task.TaskMaster(this.workdir,
{ onEmpty: Lang.bind(this, this._onTasksComplete),
processAfter: !args.only,
skip: args.skip });
this._taskmaster = taskmaster;
taskmaster.connect('task-executing', Lang.bind(this, this._onTaskExecuting));
taskmaster.connect('task-complete', Lang.bind(this, this._onTaskCompleted));
let params = this._parseParameters(args.parameters);
let buildPath = Gio.File.new_for_path('local');
GSystem.file_ensure_directory(buildPath, false, cancellable);
taskmaster.pushTask(buildPath, args.taskname, params);
loop.run();
if (!this._failed)
print("Success!")
},
_parseParameters: function(paramStrings) {
let params = {};
for (let i = 0; i < paramStrings.length; i++) {
let param = paramStrings[i];
let idx = param.indexOf('=');
if (idx == -1)
throw new Error("Invalid key=value syntax");
let k = param.substr(0, idx);
let v = JSON.parse(param.substr(idx+1));
params[k] = v;
}
return params;
},
_onTaskExecuting: function(taskmaster, task) {
print("Task " + task.name + " executing on " + task.buildName);
let output = task.taskCwd.get_child('output.txt');
if (this._oneOnly) {
let context = new GSystem.SubprocessContext({ argv: ['tail', '-f', output.get_path() ] });
this._tail = new GSystem.Subprocess({ context: context });
this._tail.init(null);
}
},
_onTaskCompleted: function(taskmaster, task, success, error) {
if (this._oneOnly)
this._tail.request_exit();
if (success) {
print("Task " + task.name + " complete: " + task.buildName);
} else {
this._failed = true;
print("Task " + task.name + " failed: " + task.buildName);
}
},
_onTasksComplete: function() {
this._loop.quit();
}
});

View File

@ -1,54 +0,0 @@
// -*- 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 Builtin = imports.builtin;
const ArgParse = imports.argparse;
const ProcUtil = imports.procutil;
const LibQA = imports.libqa;
const GuestFish = imports.guestfish;
const QaMakeDisk = new Lang.Class({
Name: 'QaMakeDisk',
Extends: Builtin.Builtin,
DESCRIPTION: "Generate a disk image",
_init: function() {
this.parent();
this.parser.addArgument('diskpath');
},
execute: function(args, loop, cancellable) {
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);
LibQA.createDisk(tmppath, cancellable);
GSystem.file_rename(tmppath, path, cancellable);
print("Created: " + path.get_path());
}
});

View File

@ -1,42 +0,0 @@
// Copyright (C) 2012,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 Lang = imports.lang;
const Builtin = imports.builtin;
const Task = imports.task;
const RunTask = new Lang.Class({
Name: 'RunTask',
Extends: Builtin.Builtin,
DESCRIPTION: "Internal helper to execute a task",
_init: function() {
this.parent();
this.parser.addArgument('taskName');
this.parser.addArgument('parameters');
},
execute: function(args, loop, cancellable) {
let taskset = Task.TaskSet.prototype.getInstance();
let taskDef = taskset.getTask(args.taskName);
let params = JSON.parse(args.parameters);
let instance = new taskDef(params);
instance.execute(cancellable);
}
});

View File

@ -1,31 +0,0 @@
// Copyright (C) 2012,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 Lang = imports.lang;
const Builtin = imports.builtin;
const Shell = new Lang.Class({
Name: 'Shell',
Extends: Builtin.Builtin,
DESCRIPTION: "Interactive shell",
execute: function(args, loop, cancellable) {
imports.console.interact();
}
});

View File

@ -1,98 +0,0 @@
// Copyright (C) 2012,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 GSystem = imports.gi.GSystem;
const Params = imports.params;
function walkDirInternal(dir, matchParams, callback, cancellable, queryStr, depth, sortByName) {
let denum = dir.enumerate_children(queryStr, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
cancellable);
let info;
let subdirs = [];
if (depth > 0) {
depth -= 1;
}
let sortedFiles = [];
while ((info = denum.next_file(cancellable)) != null) {
let name = info.get_name();
let child = dir.get_child(name);
let ftype = info.get_file_type();
if (depth != 0) {
if (ftype == Gio.FileType.DIRECTORY) {
subdirs.push(child);
continue;
}
}
if (matchParams.nameRegex && matchParams.nameRegex.exec(name) === null)
continue;
if (matchParams.fileType !== null && matchParams.fileType != info.get_file_type())
continue;
if (matchParams.contentType != null && matchParams.contentType != info.get_content_type())
continue;
if (!sortByName)
callback(child, cancellable);
else
sortedFiles.push(child);
}
if (sortByName) {
sortedFiles.sort(function (a, b) {
return a.get_basename().localeCompare(b.get_basename());
});
for (let i = 0; i < sortedFiles.length; i++) {
callback(sortedFiles[i], cancellable);
}
}
denum.close(cancellable);
if (sortByName) {
subdirs.sort(function (a, b) {
return a.get_basename().localeCompare(b.get_basename());
});
}
for (let i = 0; i < subdirs.length; i++) {
walkDirInternal(subdirs[i], matchParams, callback, cancellable, queryStr, depth);
}
}
function walkDir(dir, matchParams, callback, cancellable) {
matchParams = Params.parse(matchParams, { nameRegex: null,
fileType: null,
contentType: null,
depth: -1,
sortByName: false });
let queryStr = 'standard::name,standard::type,unix::mode';
if (matchParams.contentType)
queryStr += ',standard::fast-content-type';
let depth = matchParams.depth;
walkDirInternal(dir, matchParams, callback, cancellable, queryStr, depth, matchParams.sortByName);
}
function openReplace(path, cancellable) {
GSystem.shutil_rm_rf(path, cancellable);
return path.replace(null, false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
cancellable);
}

View File

@ -1,180 +0,0 @@
// Copyright (C) 2012,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 GSystem = imports.gi.GSystem;
const Params = imports.params;
const ProcUtil = imports.procutil;
const LibGuestfs = new Lang.Class({
Name: 'LibGuestfs',
_init: function(diskpath, params) {
this._params = Params.parse(params, {useLockFile: true,
partitionOpts: ['-i'],
readWrite: false});
this._diskpath = diskpath;
this._readWrite = params.readWrite
this._partitionOpts = params.partitionOpts;
if (params.useLockFile) {
let lockfilePath = diskpath.get_parent().get_child(diskpath.get_basename() + '.guestfish-lock');
this._lockfilePath = lockfilePath;
} else {
this._lockfilePath = null;
}
},
_lock: function() {
if (this._lockfilePath) {
let stream = this._lockfilePath.create(Gio.FileCreateFlags.NONE, cancellable);
stream.close(cancellable);
}
},
_unlock: function() {
if (this._lockfilePath != null) {
GSystem.file_unlink(this._lockfilePath, cancellable);
}
},
_appendOpts: function(argv) {
argv.push.apply(argv, ['-a', this._diskpath.get_path()]);
if (this._readWrite)
argv.push('--rw');
else
argv.push('--ro');
argv.push.apply(argv, this._partitionOpts);
}
});
const GuestFish = new Lang.Class({
Name: 'GuestFish',
Extends: LibGuestfs,
run: function(input, cancellable) {
this._lock();
try {
let guestfishArgv = ['guestfish'];
this._appendOpts(guestfishArgv);
return ProcUtil.runProcWithInputSyncGetLines(guestfishArgv, cancellable, input);
} finally {
this._unlock();
}
}
});
const GuestMount = new Lang.Class({
Name: 'GuestMount',
Extends: LibGuestfs,
mount: function(mntdir, cancellable) {
this._lock();
try {
this._mntdir = mntdir;
this._mountPidFile = mntdir.get_parent().get_child(mntdir.get_basename() + '.guestmount-pid');
if (this._mountPidFile.query_exists(null))
throw new Error("guestfish pid file exists: " + this._mountPidFile.get_path());
let guestmountArgv = ['guestmount', '-o', 'allow_root',
'--pid-file', this._mountPidFile.get_path()];
this._appendOpts(guestmountArgv);
guestmountArgv.push(mntdir.get_path());
print('Mounting ' + mntdir.get_path() + ' : ' + guestmountArgv.join(' '));
let context = new GSystem.SubprocessContext({ argv: guestmountArgv });
let proc = new GSystem.Subprocess({ context: context });
proc.init(cancellable);
// guestfish daemonizes, so this process will exit, and
// when it has, we know the mount is ready. If there was
// a way to get notified instead of this indirect fashion,
// we'd do that.
proc.wait_sync_check(cancellable);
this._mounted = true;
} catch (e) {
this._unlock();
}
},
umount: function(cancellable) {
if (!this._mounted)
return;
let pidStr = GSystem.file_load_contents_utf8(this._mountPidFile, cancellable);
if (pidStr.length == 0) {
this._mounted = false;
return;
}
for (let i = 0; i < 30; i++) {
// See "man guestmount" for why retry loops here might be needed if this
// script is running on a client machine with programs that watch for new mounts
try {
ProcUtil.runSync(['fusermount', '-u', this._mntdir.get_path()], cancellable,
{logInitiation: true});
break;
} catch (e) {
if (!(e.origError && e.origError.domain == GLib.spawn_exit_error_quark()))
throw e;
else {
let proc = GSystem.Subprocess.new_simple_argv(['fuser', '-m', this._mntdir.get_path()],
GSystem.SubprocessStreamDisposition.INHERIT,
GSystem.SubprocessStreamDisposition.INHERIT,
cancellable);
proc.init(cancellable);
proc.wait_sync(cancellable);
let creds = new Gio.Credentials();
proc = GSystem.Subprocess.new_simple_argv(['ls', '-al', '/proc/' + creds.get_unix_pid() + '/fd'],
GSystem.SubprocessStreamDisposition.INHERIT,
GSystem.SubprocessStreamDisposition.INHERIT,
cancellable);
proc.init(cancellable);
proc.wait_sync(cancellable);
GLib.usleep(GLib.USEC_PER_SEC);
}
}
}
let pid = parseInt(pidStr);
let guestfishExited = false;
let guestfishTimeoutSecs = 5 * 60;
for (let i = 0; i < guestfishTimeoutSecs; 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=" + (guestfishTimeoutSecs - i) + "s");
GLib.usleep(GLib.USEC_PER_SEC);
} else {
guestfishExited = true;
break;
}
}
if (!guestfishExited)
throw new Error("guestfish failed to exit");
this._mounted = false;
this._unlock();
},
});

View File

@ -1,82 +0,0 @@
// Copyright (C) 2012,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;
/* jsonutil.js:
* Read/write JSON to/from GFile paths, very inefficiently.
*/
function serializeJson(data) {
return JSON.stringify(data, null, " ");
}
function writeJsonToStream(stream, data, cancellable) {
let buf = serializeJson(data);
stream.write_bytes(new GLib.Bytes(buf), cancellable);
}
function writeJsonToStreamAsync(stream, data, cancellable, onComplete) {
let buf = serializeJson(data);
stream.write_bytes_async(new GLib.Bytes(buf), GLib.PRIORITY_DEFAULT,
cancellable, function(stream, result) {
let err = null;
try {
stream.write_bytes_finish(result);
} catch (e) {
err = e;
}
onComplete(err != null, err);
});
}
function loadJsonFromStream(stream, cancellable) {
let membuf = Gio.MemoryOutputStream.new_resizable();
membuf.splice(stream, Gio.OutputStreamSpliceFlags.CLOSE_TARGET, cancellable);
let bytes = membuf.steal_as_bytes();
return JSON.parse(bytes.toArray().toString());
}
function loadJsonFromStreamAsync(stream, cancellable, onComplete) {
let membuf = Gio.MemoryOutputStream.new_resizable();
membuf.splice_async(stream, Gio.OutputStreamSpliceFlags.CLOSE_TARGET, GLib.PRIORITY_DEFAULT,
cancellable, function(stream, result) {
let err = null;
let res = null;
try {
stream.splice_finish(result);
let bytes = membuf.steal_as_bytes();
res = JSON.parse(bytes.toArray().toString());
} catch (e) {
err = e;
}
onComplete(res, err);
});
}
function writeJsonFileAtomic(path, data, cancellable) {
let s = path.replace(null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
writeJsonToStream(s, data, cancellable);
s.close(cancellable);
}
function loadJson(path, cancellable) {
let [success,contents,etag] = path.load_contents(cancellable);
return JSON.parse(contents);
}

View File

@ -1,23 +0,0 @@
// 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.
function stringEndswith(s, suffix) {
let i = s.lastIndexOf(suffix);
if (i == -1)
return false;
return i == s.length - suffix.length;
}

View File

@ -1,479 +0,0 @@
// Copyright (C) 2012,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 OSTree = imports.gi.OSTree;
const Guestfs = imports.gi.Guestfs;
const GSystem = imports.gi.GSystem;
const Params = imports.params;
const ProcUtil = imports.procutil;
const GuestFish = imports.guestfish;
const JSUtil = imports.jsutil;
const BOOT_UUID = "fdcaea3b-2775-45ef-b441-b46a4a18e8c4";
const ROOT_UUID = "d230f7f0-99d3-4244-8bd9-665428054831";
const SWAP_UUID = "61f066e3-ac18-464e-bcc7-e7c3a623cec1";
const DEFAULT_GF_PARTITION_OPTS = ['-m', '/dev/sda3', '-m', '/dev/sda1:/boot'];
function linuxGetMemTotalMb() {
let [success,contents] = GLib.file_get_contents('/proc/meminfo');
let contentLines = contents.toString().split(/\n/);
for (let i = 0; contentLines.length; i++) {
let line = contentLines[i];
if (line.indexOf('MemTotal:') == 0) {
return parseInt(/([0-9]+) kB/.exec(line)[1]) / 1024;
}
}
throw new Error("Couldn't determine total memory from /proc/meminfo");
}
function getQemuPath() {
let fallbackPaths = ['/usr/libexec/qemu-kvm']
let qemuPathString;
qemuPathString = GLib.find_program_in_path('qemu-system-x86_64');
if (!qemuPathString)
qemuPathString = GLib.find_program_in_path('kvm');
if (qemuPathString == null) {
for (let i = 0; i < fallbackPaths.length; i++) {
let path = Gio.File.new_for_path(fallbackPaths[i]);
if (!path.query_exists(null))
continue;
qemuPathString = path.get_path();
}
}
if (qemuPathString == null) {
throw new Error("Unable to find qemu-kvm or qemu-system-x86_64");
}
return qemuPathString;
}
function getDefaultQemuOptions(params) {
params = Params.parse(params, { parallel: false });
let args = [getQemuPath(), '-vga', 'std', '-usb', '-usbdevice', 'tablet', '-net', 'none'];
let systemMemoryMb = linuxGetMemTotalMb();
let minimumGuestMemoryMb = 768;
let maximumGuestMemoryMb = 4 * 1024;
// As a guess, use 1/4 of host memory, rounded up to the nearest
// multiple of 128M; subject to above constraints as a lame
// default...we need global coordination here.
let guestMemoryGuessMb = Math.floor(systemMemoryMb / 4 / 128) * 128;
let guestMemory = Math.floor(Math.max(minimumGuestMemoryMb,
Math.min(maximumGuestMemoryMb,
guestMemoryGuessMb)));
args.push.apply(args, ['-m', ''+guestMemory]);
if (params.parallel) {
let nCores = Math.min(16, GLib.get_num_processors());
args.push.apply(args, ['-smp', ''+nCores]);
}
return args;
}
function newReadWriteMount(diskpath, cancellable) {
let mntdir = Gio.File.new_for_path('mnt');
GSystem.file_ensure_directory(mntdir, true, cancellable);
let gfmnt = new GuestFish.GuestMount(diskpath, {partitionOpts: DEFAULT_GF_PARTITION_OPTS,
readWrite: true});
gfmnt.mount(mntdir, cancellable);
return [gfmnt, mntdir];
}
function _installSyslinux(gfHandle, cancellable) {
let syslinuxPaths = ['/usr/share/syslinux/mbr.bin', '/usr/lib/syslinux/mbr.bin'].map(function (a) { return Gio.File.new_for_path(a); });
let syslinuxPath = null;
for (let i = 0; i < syslinuxPaths.length; i++) {
let path = syslinuxPaths[i];
if (path.query_exists(null)) {
syslinuxPath = path;
break;
}
}
if (syslinuxPath == null)
throw new Error("Couldn't find syslinux mbr.bin in any of " + JSON.stringify(syslinuxPaths));
let [,syslinuxData,] = syslinuxPath.load_contents(cancellable);
gfHandle.pwrite_device("/dev/sda", syslinuxData, 0);
}
function createDisk(diskpath, cancellable, params) {
params = Params.parse(params, { sizeMb: 8 * 1024,
bootsizeMb: 200,
swapsizeMb: 64 });
let guestfishProcess;
ProcUtil.runSync(['qemu-img', 'create', '-o', 'compat=0.10', '-f', 'qcow2', diskpath.get_path(), '' + params.sizeMb + 'M'], cancellable);
let gfHandle = Guestfs.Session.new();
gfHandle.add_drive(diskpath.get_path(), null);
gfHandle.launch();
gfHandle.part_init("/dev/sda", "mbr");
let diskBytesize = gfHandle.blockdev_getsize64("/dev/sda");
let diskSectorsize = gfHandle.blockdev_getss("/dev/sda");
print(Format.vprintf("bytesize: %s sectorsize: %s", [diskBytesize, diskSectorsize]));
let bootsizeSectors = 0;
if (params.bootsizeMb)
bootsizeSectors = params.bootsizeMb * 1024 / diskSectorsize * 1024;
let swapsizeSectors = 0;
if (params.swapsizeMb)
swapsizeSectors = params.swapsizeMb * 1024 / diskSectorsize * 1024;
let rootsizeSectors = diskBytesize / diskSectorsize - bootsizeSectors - swapsizeSectors - 64;
print(Format.vprintf("boot: %s swap: %s root: %s", [bootsizeSectors, swapsizeSectors, rootsizeSectors]));
let bootOffset = 64;
let swapOffset = bootOffset + bootsizeSectors;
let rootOffset = swapOffset + swapsizeSectors;
let endOffset = rootOffset + rootsizeSectors;
let bootPartitionOffset = -1
let swapPartitionOffset = -1;
let rootPartitionOffset = 1;
if (bootsizeSectors > 0) {
gfHandle.part_add("/dev/sda", "p", bootOffset, swapOffset - 1);
bootPartitionOffset = 1;
rootPartitionOffset++;
}
if (swapsizeSectors > 0) {
gfHandle.part_add("/dev/sda", "p", swapOffset, rootOffset - 1);
swapPartitionOffset = 2;
rootPartitionOffset++;
}
gfHandle.part_add("/dev/sda", "p", rootOffset, endOffset - 1);
if (bootsizeSectors > 0) {
gfHandle.mkfs("ext4", "/dev/sda" + bootPartitionOffset, null);
gfHandle.set_e2uuid("/dev/sda1", BOOT_UUID);
}
if (swapsizeSectors > 0) {
gfHandle.mkswap_U(SWAP_UUID, "/dev/sda" + swapPartitionOffset);
}
let rootPartition = "/dev/sda" + rootPartitionOffset;
gfHandle.mkfs("xfs", rootPartition, null);
gfHandle.xfs_admin(rootPartition, new Guestfs.XfsAdmin({ "uuid": ROOT_UUID}));
gfHandle.mount(rootPartition, "/");
gfHandle.mkdir_mode("/boot", 493);
if (bootPartitionOffset > 0)
gfHandle.mount("/dev/sda" + bootPartitionOffset, "/boot");
gfHandle.extlinux("/boot");
// It's understandable that extlinux wants the loader to be
// immutable...except that later breaks our ability to set SELinux
// security contexts on it.
gfHandle.set_e2attrs("/boot/ldlinux.sys", "i", new Guestfs.SetE2attrs({ clear: Guestfs.Tristate.TRUE }));
gfHandle.umount_all();
_installSyslinux(gfHandle, cancellable);
gfHandle.part_set_bootable("/dev/sda", 1, true);
gfHandle.shutdown();
}
function createDiskSnapshot(diskpath, newdiskpath, cancellable) {
ProcUtil.runSync(['qemu-img', 'create', '-f', 'qcow2', '-o', 'backing_file=' + diskpath.get_path(),
newdiskpath.get_path()], cancellable);
}
function copyDisk(srcpath, destpath, cancellable) {
ProcUtil.runSync(['qemu-img', 'convert', '-O', 'qcow2', srcpath.get_path(),
destpath.get_path()], cancellable);
}
function getSysrootAndCurrentDeployment(mntdir, osname) {
let sysroot = OSTree.Sysroot.new(mntdir);
sysroot.load(null);
let deployments = sysroot.get_deployments().filter(function (deployment) {
return deployment.get_osname() == osname;
});
if (deployments.length == 0)
throw new Error("No deployments for " + osname + " in " + mntdir.get_path());
let current = deployments[0];
return [sysroot, current];
}
function getDeployDirs(mntdir, osname) {
let [sysroot, current] = getSysrootAndCurrentDeployment(mntdir, osname);
let deployDir = sysroot.get_deployment_directory(current);
return [deployDir, deployDir.get_child('etc')];
}
function modifyBootloaderAppendKernelArgs(mntdir, kernelArgs, cancellable) {
let confPath = mntdir.resolve_relative_path('boot/syslinux/syslinux.cfg');
let conf = GSystem.file_load_contents_utf8(confPath, cancellable);
let lines = conf.split('\n');
let modifiedLines = [];
let didModify = false;
let kernelArg = kernelArgs.join(' ');
let kernelLineRe = /\tAPPEND /;
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
let match = kernelLineRe.exec(line);
if (!match) {
modifiedLines.push(line);
} else {
modifiedLines.push(line + ' ' + kernelArg);
didModify = true;
}
}
if (!didModify)
throw new Error("Failed to find APPEND option in syslinux.cfg");
let modifiedConf = modifiedLines.join('\n');
confPath.replace_contents(modifiedConf, null, false,
Gio.FileCreateFlags.NONE,
cancellable);
}
function getMultiuserWantsDir(currentEtcDir) {
return currentEtcDir.resolve_relative_path('systemd/system/multi-user.target.wants');
}
function getDatadir() {
return Gio.File.new_for_path(GLib.getenv('OSTBUILD_DATADIR'));
}
function injectExportJournal(currentDir, currentEtcDir, cancellable) {
let binDir = currentDir.resolve_relative_path('usr/bin');
let multiuserWantsDir = getMultiuserWantsDir(currentEtcDir);
let datadir = getDatadir();
let exportScript = datadir.resolve_relative_path('rpm-ostree-export-journal-to-serialdev');
let exportScriptService = datadir.resolve_relative_path('rpm-ostree-export-journal-to-serialdev.service');
let exportBin = binDir.get_child(exportScript.get_basename());
exportScript.copy(exportBin, Gio.FileCopyFlags.OVERWRITE, cancellable, null, null);
GSystem.file_chmod(exportBin, 493, cancellable);
exportScriptService.copy(multiuserWantsDir.get_child(exportScriptService.get_basename()), Gio.FileCopyFlags.OVERWRITE, cancellable, null, null);
let journalConfPath = currentEtcDir.resolve_relative_path('systemd/journald.conf');
journalConfPath.replace_contents('[Journal]\n\
RateLimitInterval=0\n', null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
}
function injectTestUserCreation(currentDir, currentEtcDir, username, params, cancellable) {
params = Params.parse(params, { password: null });
let execLine;
if (params.password === null) {
execLine = Format.vprintf('/bin/sh -c "/usr/sbin/useradd %s; passwd -d %s"',
[username, username]);
} else {
execLine = Format.vprintf('/bin/sh -c "/usr/sbin/useradd %s; echo %s | passwd --stdin %s',
[username, params.password, username]);
}
let addUserService = '[Unit]\n\
Description=Add user %s\n\
Before=gdm.service\n\
[Service]\n\
ExecStart=%s\n\
Type=oneshot\n';
addUserService = Format.vprintf(addUserService, [username, execLine]);
let addUserServicePath = getMultiuserWantsDir(currentEtcDir).get_child('gnome-ostree-add-user-' + username + '.service');
addUserServicePath.replace_contents(addUserService, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
}
function enableAutologin(currentDir, currentEtcDir, username, cancellable) {
let gdmCustomPath = currentEtcDir.resolve_relative_path('gdm/custom.conf');
let keyfile = new GLib.KeyFile();
keyfile.load_from_file(gdmCustomPath.get_path(), GLib.KeyFileFlags.NONE);
keyfile.set_string('daemon', 'AutomaticLoginEnable', 'true');
keyfile.set_string('daemon', 'AutomaticLogin', username);
gdmCustomPath.replace_contents(keyfile.to_data()[0], null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
}
function _findFirstFileMatching(dir, prefix, cancellable) {
let d = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
let finfo;
try {
while ((finfo = d.next_file(cancellable)) != null) {
let name = finfo.get_name();
if (name.indexOf(prefix) == 0) {
return dir.get_child(name);
}
}
throw new Error("Couldn't find " + prefix + " in " + dir.get_path());
} finally {
d.close(null);
}
return null;
}
function _findCurrentKernel(mntdir, osname, cancellable) {
let [sysroot, current] = getSysrootAndCurrentDeployment(mntdir, osname);
let deployBootdir = sysroot.get_deployment_directory(current).resolve_relative_path('boot');
return [_findFirstFileMatching(deployBootdir, 'vmlinuz-', cancellable),
_findFirstFileMatching(deployBootdir, 'initramfs-', cancellable)];
};
function _findCurrentOstreeBootArg(mntdir, cancellable) {
let bootLoaderEntriesDir = mntdir.resolve_relative_path('boot/loader/entries');
let conf = _findFirstFileMatching(bootLoaderEntriesDir, 'ostree-', cancellable);
let contents = GSystem.file_load_contents_utf8(conf, cancellable);
let lines = contents.split('\n');
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (line.indexOf('options ') != 0)
continue;
let options = line.substr(8).split(' ');
for (let j = 0; j < options.length; j++) {
let opt = options[j];
if (opt.indexOf('ostree=') != 0)
continue;
return opt;
}
}
throw new Error("Failed to find ostree= kernel argument");
}
function pullDeploy(mntdir, srcrepo, osname, target, revision, originRepoUrl, cancellable,
params) {
params = Params.parse(params, { addKernelArgs: ['quiet'] });
let ostreedir = mntdir.get_child('ostree');
let ostreeOsdir = ostreedir.resolve_relative_path('deploy/' + osname);
let adminCmd = ['ostree', 'admin', '--sysroot=' + mntdir.get_path()];
let adminEnv = GLib.get_environ();
adminEnv.push('LIBGSYSTEM_ENABLE_GUESTFS_FUSE_WORKAROUND=1');
let procdir = mntdir.get_child('proc');
if (!procdir.query_exists(cancellable)) {
ProcUtil.runSync(adminCmd.concat(['init-fs', mntdir.get_path()]), cancellable,
{logInitiation: true, env: adminEnv});
}
let revOrTarget;
if (revision)
revOrTarget = revision;
else
revOrTarget = target;
// Remove any existing bootloader configuration, and stub out an
// empty syslinux configuration that we can use to bootstrap.
let bootLoaderLink = mntdir.resolve_relative_path('boot/loader');
GSystem.shutil_rm_rf(bootLoaderLink, cancellable);
let bootLoaderDir0 = mntdir.resolve_relative_path('boot/loader.0');
GSystem.shutil_rm_rf(bootLoaderDir0, cancellable);
bootLoaderLink.make_symbolic_link('loader.0', cancellable);
GSystem.file_ensure_directory(bootLoaderDir0, true, cancellable);
let syslinuxPath = mntdir.resolve_relative_path('boot/loader/syslinux.cfg');
syslinuxPath.replace_contents('TIMEOUT 20\nPROMPT 1\n', null, false, Gio.FileCreateFlags.NONE, cancellable);
// A compatibility symlink for syslinux
let syslinuxDir = mntdir.resolve_relative_path('boot/syslinux');
GSystem.shutil_rm_rf(syslinuxDir, cancellable);
GSystem.file_ensure_directory(syslinuxDir, true, cancellable);
let syslinuxLink = mntdir.resolve_relative_path('boot/syslinux/syslinux.cfg');
syslinuxLink.make_symbolic_link('../loader/syslinux.cfg', cancellable);
// Also blow alway all existing deployments here for the OS; this
// will clean up disks that were using the old ostree model.
GSystem.shutil_rm_rf(ostreeOsdir, cancellable);
let repoPath = ostreedir.get_child('repo');
let repoArg = '--repo=' + repoPath.get_path();
ProcUtil.runSync(adminCmd.concat(['os-init', osname]), cancellable,
{logInitiation: true, env: adminEnv});
if (originRepoUrl)
ProcUtil.runSync(['ostree', repoArg,
'remote', 'add', osname, originRepoUrl, target],
cancellable, { logInitiation: true });
ProcUtil.runSync(['ostree', repoArg,
'pull-local', '--disable-fsync', '--remote=' + osname, srcrepo.get_path(), revOrTarget], cancellable,
{logInitiation: true, env: adminEnv});
let origin = GLib.KeyFile.new();
origin.set_string('origin', 'refspec', osname + ':' + target);
let [originData, len] = origin.to_data();
let tmpOrigin = Gio.File.new_for_path('origin.tmp');
tmpOrigin.replace_contents(originData, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
let deployCmd = adminCmd.concat([]);
deployCmd.push('deploy');
let rootArg = 'root=UUID=' + ROOT_UUID;
deployCmd.push('--karg=' + rootArg);
for (let i = 0; i < params.addKernelArgs.length; i++)
deployCmd.push('--karg=' + params.addKernelArgs[i]);
deployCmd.push.apply(deployCmd, ['--os=' + osname, '--origin-file=' + tmpOrigin.get_path(), revOrTarget]);
ProcUtil.runSync(deployCmd, cancellable,
{logInitiation: true, env: adminEnv});
let sysroot = OSTree.Sysroot.new(mntdir);
sysroot.load(null);
let deployments = sysroot.get_deployments();
let newDeployment = deployments[0];
let newDeploymentDirectory = sysroot.get_deployment_directory(newDeployment);
let defaultFstab = 'UUID=' + ROOT_UUID + ' / xfs defaults 1 1\n\
UUID=' + BOOT_UUID + ' /boot ext4 defaults 1 2\n\
UUID=' + SWAP_UUID + ' swap swap defaults 0 0\n';
let fstabPath = newDeploymentDirectory.resolve_relative_path('etc/fstab');
fstabPath.replace_contents(defaultFstab, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
print("Labeling deployment root");
let relabelCmd = adminCmd.concat(['instutil', 'selinux-ensure-labeled', newDeploymentDirectory.get_path(), ""]);
ProcUtil.runSync(relabelCmd,
cancellable,
{ logInitiation: true });
};
function requestSnapshotForTree(task, resultdir, refname, revision) {
let imageCacheDir = task.cachedir.get_child('images');
let osname = task._products['osname'];
let originRepoUrl = this._products['repo'];
let refUnix = ref.replace(/\//g, '-');
let diskDir = this._imageCacheDir.get_child(refUnix);
GSystem.file_ensure_directory(diskDir, true, cancellable);
let diskName = revision + '.qcow2';
let diskPath = diskDir.get_child(diskName);
let diskPathTmp = diskDir.get_child(diskName);
if (!diskPath.query_exists(null))
LibQA.createDisk(diskPath, cancellable);
let mntdir = Gio.File.new_for_path('mnt');
GSystem.file_ensure_directory(mntdir, true, cancellable);
let gfmnt = new GuestFish.GuestMount(diskPath, { partitionOpts: LibQA.DEFAULT_GF_PARTITION_OPTS,
readWrite: true });
gfmnt.mount(mntdir, cancellable);
try {
LibQA.pullDeploy(mntdir, this.repo, osname, ref, revision, originRepoUrl,
cancellable);
} finally {
gfmnt.umount(cancellable);
}
print("Successfully updated " + diskDir.get_path() + " to " + revision);
}
function getACachedDisk(diskDir, cancellable) {
let cachedDisk = null;
let e = null;
try {
e = diskDir.enumerate_children('standard::name', 0, cancellable);
let info;
while ((info = e.next_file(cancellable)) != null) {
let name = info.get_name();
if (!JSUtil.stringEndswith(name, '.qcow2'))
continue;
cachedDisk = e.get_child(info);
break;
}
} finally {
if (e) e.close(null);
}
return cachedDisk;
}

View File

@ -1,96 +0,0 @@
// Copyright (C) 2011 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 Format = imports.format;
const BUILTINS = ['autobuilder',
'git-mirror',
'make',
'qa-make-disk',
'run-task',
'shell'];
function getModule(unixName) {
return imports.builtins[unixName.replace(/-/g, '_')];
}
function getClass(unixName) {
let module = getModule(unixName);
let camelParts = unixName.split(/-/);
let camel = camelParts.map(function (part) {
return part[0].toLocaleUpperCase() + part.substr(1);
}).join('');
return module[camel];
}
function usage(ecode) {
print("Builtins:");
for (let i = 0; i < BUILTINS.length; i++) {
let unixName = BUILTINS[i];
let description = getClass(unixName).prototype.DESCRIPTION;
print(Format.vprintf(" %s - %s", [unixName, description]));
}
return ecode;
}
let ecode;
if (ARGV.length > 0 && (ARGV[0] == '-h' || ARGV[0] == '--help')) {
ecode = usage(0);
} else {
let name;
if (ARGV.length < 1) {
name = "autobuilder";
} else {
name = ARGV[0];
}
let found = false;
for (let i = 0; i < BUILTINS.length; i++) {
if (BUILTINS[i] == name) {
found = true;
break;
}
}
if (!found) {
usage(1);
} else {
let argv = ARGV.concat();
argv.shift();
let loop = GLib.MainLoop.new(null, true);
let cls = getClass(name);
let instance = new cls;
let cancellable = null;
GLib.idle_add(GLib.PRIORITY_DEFAULT,
function() {
ecode = 1;
try {
instance.main(argv, loop, cancellable);
ecode = 0;
} finally {
loop.quit();
}
return false;
});
loop.run();
}
}
ecode;

View File

@ -1,34 +0,0 @@
// Taken from gnome-shell/js/misc/params.js under the GNU General Public License
// parse:
// @params: caller-provided parameter object, or %null
// @defaults: function-provided defaults object
// @allowExtras: whether or not to allow properties not in @default
//
// Examines @params and fills in default values from @defaults for
// any properties in @defaults that don't appear in @params. If
// @allowExtras is not %true, it will throw an error if @params
// contains any properties that aren't in @defaults.
//
// If @params is %null, this returns the values from @defaults.
//
// Return value: a new object, containing the merged parameters from
// @params and @defaults
function parse(params, defaults, allowExtras) {
let ret = {}, prop;
if (!params)
params = {};
for (prop in params) {
if (!(prop in defaults) && !allowExtras)
throw new Error('Unrecognized parameter "' + prop + '"');
ret[prop] = params[prop];
}
for (prop in defaults) {
if (!(prop in params))
ret[prop] = defaults[prop];
}
return ret;
}

View File

@ -1,240 +0,0 @@
// Copyright (C) 2012,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 Format = imports.format;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const GSystem = imports.gi.GSystem;
const Params = imports.params;
const StreamUtil = imports.streamutil;
function objectToEnvironment(o) {
let r = [];
for (let k in o)
r.push(k + "=" + o[k]);
return r;
}
function _newContext(argv, params) {
let context = new GSystem.SubprocessContext({argv: argv});
params = Params.parse(params, {cwd: null,
env: null,
stderr: null,
logInitiation: false });
if (typeof(params.cwd) == 'string')
context.set_cwd(params.cwd);
else if (params.cwd)
context.set_cwd(params.cwd.get_path());
if (params.env)
context.set_environment(params.env);
if (params.stderr != null)
context.set_stderr_disposition(params.stderr);
return [context, params];
}
function _wait_sync_check_internal(proc, cancellable) {
try {
proc.wait_sync_check(cancellable);
} catch (e) {
if (e.domain == GLib.spawn_exit_error_quark() ||
e.matches(GLib.SpawnError, GLib.SpawnError.FAILED)) {
let err = new Error(Format.vprintf("Child process %s: %s", [JSON.stringify(proc.context.argv), e.message]));
err.origError = e;
throw err;
} else {
throw e;
}
}
}
function runSync(argv, cancellable, params) {
let [context, pparams] = _newContext(argv, params);
let proc = new GSystem.Subprocess({context: context});
proc.init(cancellable);
if (pparams.logInitiation)
print(Format.vprintf("Started child process %s: pid=%s", [proc.context.argv.map(GLib.shell_quote).join(' '), proc.get_pid()]));
_wait_sync_check_internal(proc, cancellable);
}
function _runSyncGetOutputInternal(argv, cancellable, params, subParams) {
subParams = Params.parse(subParams, { splitLines: false,
grep: null });
let [context, pparams] = _newContext(argv, params);
context.set_stdout_disposition(GSystem.SubprocessStreamDisposition.PIPE);
context.set_stderr_disposition(GSystem.SubprocessStreamDisposition.INHERIT);
let proc = new GSystem.Subprocess({context: context});
proc.init(cancellable);
if (pparams.logInitiation)
print(Format.vprintf("Started child process %s: pid=%s", [JSON.stringify(proc.context.argv), proc.get_pid()]));
let input = proc.get_stdout_pipe();
let dataIn = Gio.DataInputStream.new(input);
let result = null;
if (subParams.grep) {
let grep = subParams.grep;
while (true) {
let [line, len] = dataIn.read_line_utf8(cancellable);
if (line == null)
break;
result = grep.exec(line);
if (result != null) {
break;
}
}
} else if (subParams.splitLines) {
result = StreamUtil.dataInputStreamReadLines(dataIn, cancellable);
} else {
result = '';
while (true) {
let [line, len] = dataIn.read_line_utf8(cancellable);
if (line == null)
break;
result += (line + '\n');
}
}
dataIn.close(cancellable);
_wait_sync_check_internal(proc, cancellable);
return result;
}
function runSyncGetOutputLines(args, cancellable, params) {
return _runSyncGetOutputInternal(args, cancellable, params, { splitLines: true });
}
function runSyncGetOutputUTF8(args, cancellable, params) {
return _runSyncGetOutputInternal(args, cancellable, params);
}
function runSyncGetOutputUTF8Stripped(args, cancellable, params) {
return _runSyncGetOutputInternal(args, cancellable, params).replace(/[ \n]+$/, '');
}
function runSyncGetOutputUTF8StrippedOrNull(args, cancellable, params) {
if (!params)
params = {};
try {
params.stderr = GSystem.SubprocessStreamDisposition.NULL;
return runSyncGetOutputUTF8Stripped(args, cancellable, params);
} catch (e) {
if (e.origError && e.origError.domain == GLib.spawn_exit_error_quark())
return null;
throw e;
}
}
function runSyncGetOutputGrep(args, pattern, cancellable, params) {
return _runSyncGetOutputInternal(args, cancellable, params, { grep: pattern });
}
function getExitStatusAndString(ecode) {
try {
GLib.spawn_check_exit_status(ecode);
return [true, null];
} catch (e) {
if (e.domain == GLib.spawn_exit_error_quark() ||
e.matches(GLib.SpawnError, GLib.SpawnError.FAILED))
return [false, e.message];
else
throw e;
}
}
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);
});
}

View File

@ -1,199 +0,0 @@
//
// 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.
const Lang = imports.lang;
const JsonUtil = imports.jsonutil;
const Params = imports.params;
function _componentDict(snapshot) {
let r = {};
let components = snapshot['components'];
for (let i = 0; i < components.length; i++) {
let component = components[i];
let name = component['name'];
if (r[name])
throw new Error("Duplicate component name " + name);
r[name] = component;
}
let patches = snapshot['patches'];
if (patches['name'])
r[patches['name']] = patches;
let base = snapshot['base'];
r[base['name']] = base;
return r;
}
function snapshotDiff(a, b) {
let a_components = _componentDict(a);
let b_components = _componentDict(b);
let added = [];
let modified = [];
let removed = [];
for (let name in a_components) {
let c_a = a_components[name];
let c_b = b_components[name];
if (c_b == undefined) {
removed.push(name);
} else if (c_a['revision'] != c_b['revision']) {
modified.push(name);
}
}
for (let name in b_components) {
if (a_components[name] == undefined) {
added.push(name);
}
}
return [added, modified, removed];
}
function fromFile(path, cancellable, args) {
let data = JsonUtil.loadJson(path, cancellable);
return new Snapshot(data, path, args);
}
const Snapshot = new Lang.Class({
Name: 'Snapshot',
_init: function(data, path, params) {
params = Params.parse(params, { prepareResolve: false });
this.data = data;
this.path = path;
if (params.prepareResolve) {
data['patches'] = this._resolveComponent(data, data['patches']);
data['base'] = this._resolveComponent(data, data['base']);
for (let i = 0; i < data['components'].length; i++) {
let component = this._resolveComponent(data, data['components'][i]);
data['components'][i] = component;
}
}
this._componentDict = _componentDict(data);
this._componentNames = [];
for (let k in this._componentDict)
this._componentNames.push(k);
},
_resolveComponent: function(manifest, componentMeta) {
if (!componentMeta)
return {};
let result = {};
Lang.copyProperties(componentMeta, result);
let origSrc = componentMeta['src'];
let name = componentMeta['name'];
if (origSrc.indexOf('tarball:') == 0) {
if (!name)
throw new Error("Component src " + origSrc + " has no name attribute");
if (!componentMeta['checksum'])
throw new Error("Component src " + origSrc + " has no checksum attribute");
return result;
}
let didExpand = false;
let vcsConfig = manifest['vcsconfig'];
for (let vcsprefix in vcsConfig) {
let expansion = vcsConfig[vcsprefix];
let prefix = vcsprefix + ':';
if (origSrc.indexOf(prefix) == 0) {
result['src'] = expansion + origSrc.substr(prefix.length);
didExpand = true;
break;
}
}
let src, idx;
if (name == undefined) {
if (didExpand) {
src = origSrc;
idx = src.lastIndexOf(':');
name = src.substr(idx+1);
} else {
src = result['src'];
idx = src.lastIndexOf('/');
name = src.substr(idx+1);
}
let i = name.lastIndexOf('.git');
if (i != -1 && i == name.length - 4) {
name = name.substr(0, name.length - 4);
}
name = name.replace(/\//g, '-');
result['name'] = name;
}
let branchOrTag = result['branch'] || result['tag'];
if (!branchOrTag) {
result['branch'] = 'master';
}
return result;
},
_expandComponent: function(component) {
let r = {};
Lang.copyProperties(component, r);
let patchMeta = this.data['patches'];
if (patchMeta) {
let componentPatchFiles = component['patches'] || [];
if (componentPatchFiles.length > 0) {
let patches = {};
Lang.copyProperties(patchMeta, patches);
patches['files'] = componentPatchFiles;
r['patches'] = patches;
}
}
let configOpts = (this.data['config-opts'] || []).concat();
configOpts.push.apply(configOpts, component['config-opts'] || []);
r['config-opts'] = configOpts;
return r;
},
getAllComponentNames: function() {
return this._componentNames;
},
getComponentMap: function() {
return this._componentDict;
},
getComponent: function(name, allowNone) {
let r = this._componentDict[name] || null;
if (!r && !allowNone)
throw new Error("No component " + name + " in snapshot");
return r;
},
getMatchingSrc: function(src, allowNone) {
let result = [];
for (let i = 0; i < this._componentNames.length; i++) {
let name = this._componentNames[i];
let component = this.getComponent(name, false);
if (component['src'] == src)
result.push(component);
}
return result;
},
getExpanded: function(name) {
return this._expandComponent(this.getComponent(name));
}
});

View File

@ -1,27 +0,0 @@
// Copyright (C) 2012,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.
function dataInputStreamReadLines(dataIn, cancellable) {
let result = [];
while (true) {
let [line, len] = dataIn.read_line_utf8(cancellable);
if (line == null)
break;
result.push(line);
}
return result;
}

View File

@ -1,471 +0,0 @@
// Copyright (C) 2012,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 Format = imports.format;
const Lang = imports.lang;
const Signals = imports.signals;
const GSystem = imports.gi.GSystem;
const OSTree = imports.gi.OSTree;
const Params = imports.params;
const VersionedDir = imports.versioneddir;
const JsonUtil = imports.jsonutil;
const ProcUtil = imports.procutil;
const BuildUtil = imports.buildutil;
const DefaultTaskDef = {
TaskName: '',
TaskAfter: [],
TaskScheduleMinSecs: 0,
PreserveStdout: true,
RetainFailed: 5,
RetainSuccess: 5,
};
var _tasksetInstance = null;
const TaskSet = new Lang.Class({
Name: 'TaskSet',
_init: function() {
this._tasks = [];
let taskdir = Gio.File.new_for_path(GLib.getenv('OSTBUILD_DATADIR')).resolve_relative_path('js/tasks');
let denum = taskdir.enumerate_children('standard::*', 0, null);
let finfo;
for (let taskmodname in imports.tasks) {
let taskMod = imports.tasks[taskmodname];
for (let defname in taskMod) {
if (defname.indexOf('Task') !== 0
|| defname == 'Task')
continue;
let cls = taskMod[defname];
this.register(cls);
}
}
denum.close(null);
},
register: function(task) {
this._tasks.push(task);
},
getAllTasks: function() {
return this._tasks;
},
getTask: function(taskName) {
for (let i = 0; i < this._tasks.length; i++) {
let taskConstructor = this._tasks[i];
let taskDef = taskConstructor.prototype.TaskDef;
let curName = taskDef.TaskName;
if (curName == taskName)
return taskConstructor;
}
throw new Error("No task definition matches " + taskName);
},
getTaskDef: function(taskName) {
let taskDef = this.getTask(taskName).prototype.TaskDef;
return Params.parse(taskDef, DefaultTaskDef);
},
getInstance: function() {
if (!_tasksetInstance)
_tasksetInstance = new TaskSet();
return _tasksetInstance;
},
getTasksAfter: function(taskName) {
let ret = [];
for (let i = 0; i < this._tasks.length; i++) {
let taskConstructor = this._tasks[i];
let taskDef = Params.parse(taskConstructor.prototype.TaskDef, DefaultTaskDef);
let after = taskDef.TaskAfter;
for (let j = 0; j < after.length; j++) {
let a = after[j];
if (a == taskName) {
ret.push(taskDef.TaskName);
break;
}
}
}
return ret;
}
});
const TaskData = new Lang.Class({
Name: 'TaskData',
_init: function(taskDef, parameters) {
this.name = taskDef.TaskName;
this.taskDef = taskDef;
this.parameters = parameters;
},
});
const TaskMaster = new Lang.Class({
Name: 'TaskMaster',
_init: function(workdir, params) {
params = Params.parse(params, { onEmpty: null,
processAfter: true,
skip: [] });
this.workdir = workdir;
this.tasksPath = workdir.get_child('tasks');
GSystem.file_ensure_directory(this.tasksPath, true, null);
this.completedTasksPath = workdir.get_child('results/tasks');
GSystem.file_ensure_directory(this.completedTasksPath, true, null);
this._processAfter = params.processAfter;
this._skipTasks = {};
for (let i = 0; i < params.skip.length; i++)
this._skipTasks[params.skip[i]] = true;
this.maxConcurrent = GLib.get_num_processors();
this._onEmpty = params.onEmpty;
this.cancellable = null;
this._idleRecalculateId = 0;
this._executing = [];
this._pendingTasksList = [];
this._seenTasks = {};
this._taskErrors = {};
this._caughtError = false;
this._taskset = TaskSet.prototype.getInstance();
// string -> [ lastExecutedSecs, taskData ]
this._scheduledTaskTimeouts = {};
},
_setTaskBuildPath: function(taskName, buildPath) {
let taskLink = this.tasksPath.get_child(taskName);
BuildUtil.atomicSymlinkSwap(taskLink, buildPath, this.cancellable);
},
_pushTaskDataImmediate: function(taskData) {
this._pendingTasksList.push(taskData);
this._queueRecalculate();
},
pushTask: function(buildPath, taskName, parameters, pushParams) {
this._setTaskBuildPath(taskName, buildPath);
this._pushTask(taskName, parameters, pushParams);
},
_pushTask: function(name, parameters, pushParams) {
let taskDef = this._taskset.getTaskDef(name);
let taskData = new TaskData(taskDef, parameters);
pushParams = Params.parse(pushParams, { force: false });
if (!this._isTaskPending(name)) {
let scheduleMinSecs = taskDef.TaskScheduleMinSecs;
if (!pushParams.force && scheduleMinSecs > 0) {
let info = this._scheduledTaskTimeouts[name];
if (!info) {
info = [ 0, null ];
this._scheduledTaskTimeouts[name] = info;
}
let lastExecutedSecs = info[0];
let pendingExecData = info[1];
let currentTime = GLib.get_monotonic_time() / GLib.USEC_PER_SEC;
if (pendingExecData != null) {
// Nothing, already scheduled
let delta = (lastExecutedSecs + scheduleMinSecs) - currentTime;
print("Already scheduled task " + name + " remaining=" + delta);
} else if (lastExecutedSecs == 0) {
print("Scheduled task " + name + " executing immediately");
this._pushTaskDataImmediate(taskData);
info[0] = currentTime;
} else {
let delta = (lastExecutedSecs + scheduleMinSecs) - currentTime;
print("Scheduled task " + name + " delta=" + delta);
let timeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
Math.max(delta, 0),
Lang.bind(this, this._executeScheduledTask, name));
info[0] = currentTime;
info[1] = taskData;
}
} else {
this._pushTaskDataImmediate(taskData);
}
}
},
_executeScheduledTask: function(name) {
print("Executing scheduled task " + name);
let currentTime = GLib.get_monotonic_time() / GLib.USEC_PER_SEC;
let info = this._scheduledTaskTimeouts[name];
let taskData = info[1];
info[0] = currentTime;
info[1] = null;
this._pushTaskDataImmediate(taskData);
},
_isTaskPending: function(taskName) {
for (let i = 0; i < this._pendingTasksList.length; i++) {
let pending = this._pendingTasksList[i];
if (pending.name == taskName)
return true;
}
return false;
},
isTaskQueued: function(taskName) {
return this._isTaskPending(taskName) || this.isTaskExecuting(taskName);
},
isTaskExecuting: function(taskName) {
for (let i = 0; i < this._executing.length; i++) {
let executingRunner = this._executing[i];
if (executingRunner.taskData.name == taskName)
return true;
}
return false;
},
getTaskState: function() {
let r = [];
for (let i = 0; i < this._pendingTasksList.length; i++) {
r.push({running: false, task: this._pendingTasksList[i] });
}
for (let i = 0; i < this._executing.length; i++) {
r.push({running: true, task: this._executing[i] });
}
return r;
},
_queueRecalculate: function() {
if (this._idleRecalculateId > 0)
return;
this._idleRecalculateId = GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, this._recalculate));
},
_recalculate: function() {
this._idleRecalculateId = 0;
if (this._executing.length == 0 &&
this._pendingTasksList.length == 0) {
if (this._onEmpty)
this._onEmpty();
return;
} else if (this._pendingTasksList.length == 0) {
return;
}
let notExecuting = [];
let executing = [];
for (let i = 0; i < this._pendingTasksList.length; i++) {
let pending = this._pendingTasksList[i];
if (this.isTaskExecuting(pending.name))
executing.push(pending);
else
notExecuting.push(pending);
}
this._pendingTasksList = notExecuting.concat(executing);
this._reschedule();
},
_onComplete: function(success, error, runner) {
let taskName = runner.taskData.name;
let idx = -1;
for (let i = 0; i < this._executing.length; i++) {
let executingRunner = this._executing[i];
if (executingRunner !== runner)
continue;
idx = i;
break;
}
if (idx == -1)
throw new Error("TaskMaster: Internal error - Failed to find completed task:" + taskName);
this._executing.splice(idx, 1);
let link = this.completedTasksPath.get_child(taskName);
BuildUtil.atomicSymlinkSwap(link, runner.buildPath, this.cancellable);
if (success && runner.changed) {
let taskDef = runner.taskData.taskDef;
let after = this._taskset.getTasksAfter(taskName);
for (let i = 0; i < after.length; i++) {
let afterTaskName = after[i];
this._setTaskBuildPath(afterTaskName, runner.buildPath);
if (!this._skipTasks[afterTaskName] && this._processAfter)
this._pushTask(afterTaskName, {});
}
}
this.emit('task-complete', runner, success, error);
this._queueRecalculate();
},
_reschedule: function() {
while (this._executing.length < this.maxConcurrent &&
this._pendingTasksList.length > 0 &&
!this.isTaskExecuting(this._pendingTasksList[0].name)) {
let task = this._pendingTasksList.shift();
let runner = new TaskRunner(this, task, Lang.bind(this, function(success, error) {
this._onComplete(success, error, runner);
}));
runner.executeInSubprocess(this.cancellable);
this._executing.push(runner);
this.emit('task-executing', runner);
}
}
});
Signals.addSignalMethods(TaskMaster.prototype);
const Task = new Lang.Class({
Name: 'Task',
DefaultParameters: {},
_init: function(parameters) {
this.name = this.TaskName;
this.parameters = Params.parse(parameters, this.DefaultParameters);
this.workdir = Gio.File.new_for_path(GLib.getenv('_OSTBUILD_WORKDIR'));
BuildUtil.checkIsWorkDirectory(this.workdir);
this.builddir = Gio.File.new_for_path(GLib.getenv('_OSTBUILD_BUILDDIR'));
let relpath = this.workdir.get_relative_path(this.builddir);
if (relpath == 'local') {
this._buildName = 'local';
} else if (relpath.indexOf('builds/') == 0) {
relpath = relpath.substring(7);
this._buildName = VersionedDir.relpathToVersion(relpath);
} else {
throw new Error();
}
this.mirrordir = this.workdir.get_child('src');
GSystem.file_ensure_directory(this.mirrordir, true, null);
this.cachedir = this.workdir.resolve_relative_path('cache/raw');
GSystem.file_ensure_directory(this.cachedir, true, null);
this.libdir = Gio.File.new_for_path(GLib.getenv('OSTBUILD_LIBDIR'));
this.repo = this.workdir.get_child('repo');
this.ostreeRepo = new OSTree.Repo({ path: this.repo });
if (!this.ostreeRepo.get_path().query_exists(null))
this.ostreeRepo.create(OSTree.RepoMode.ARCHIVE_Z2, null);
this.ostreeRepo.open(null);
},
execute: function(cancellable) {
throw new Error("Not implemented");
},
});
const TaskRunner = new Lang.Class({
Name: 'TaskRunner',
_init: function(taskmaster, taskData, onComplete) {
this.taskmaster = taskmaster;
this.taskData = taskData;
this.onComplete = onComplete;
this.name = taskData.name;
this.workdir = taskmaster.workdir;
BuildUtil.checkIsWorkDirectory(this.workdir);
},
executeInSubprocess: function(cancellable) {
this._cancellable = cancellable;
this._startTimeMillis = GLib.get_monotonic_time() / 1000;
// To prevent tasks from stomping on each other's toes, we put the task
// cwd in its own task dir. If a task has any results it wants to pass
// on between builds, it needs to write to _OSTBUILD_BUILDDIR.
let buildPath = this.taskmaster.tasksPath.resolve_relative_path(this.name);
this.buildPath = GSystem.file_realpath(buildPath);
this.buildName = this.workdir.get_relative_path(this.buildPath);
this.taskCwd = this.buildPath.get_child(this.name);
GSystem.shutil_rm_rf(this.taskCwd, cancellable);
GSystem.file_ensure_directory(this.taskCwd, false, cancellable);
let baseArgv = ['rpm-ostree-autobuilder', 'run-task', this.name, JSON.stringify(this.taskData.parameters)];
let context = new GSystem.SubprocessContext({ argv: baseArgv });
context.set_cwd(this.taskCwd.get_path());
let childEnv = GLib.get_environ();
childEnv.push('_OSTBUILD_BUILDDIR=' + this.buildPath.get_path());
childEnv.push('_OSTBUILD_WORKDIR=' + this.workdir.get_path());
context.set_environment(childEnv);
if (this.taskData.taskDef.PreserveStdout) {
let outPath = this.taskCwd.get_child('output.txt');
GSystem.shutil_rm_rf(outPath, cancellable);
context.set_stdout_file_path(outPath.get_path());
context.set_stderr_disposition(GSystem.SubprocessStreamDisposition.STDERR_MERGE);
} else {
context.set_stdout_disposition(GSystem.SubprocessStreamDisposition.NULL);
let errPath = this.taskCwd.get_child('errors.txt');
context.set_stderr_file_path(errPath.get_path());
}
this._proc = new GSystem.Subprocess({ context: context });
this._proc.init(cancellable);
let targetPath = this.workdir.get_relative_path(this.taskCwd);
let meta = { taskMetaVersion: 0,
buildPath: this.buildName,
complete: false,
path: targetPath };
JsonUtil.writeJsonFileAtomic(this.taskCwd.get_child('meta.json'), meta, cancellable);
this._proc.wait(cancellable, Lang.bind(this, this._onChildExited));
},
_onChildExited: function(proc, result) {
let cancellable = this._cancellable;
let [success, errmsg] = ProcUtil.asyncWaitCheckFinish(proc, result);
let target;
this.changed = true;
let modifiedPath = this.taskCwd.get_child('modified.json');
if (modifiedPath.query_exists(null)) {
let data = JsonUtil.loadJson(modifiedPath, null);
this.changed = data['modified'];
}
this.onComplete(success, errmsg);
if (!this.changed)
return;
let elapsedMillis = GLib.get_monotonic_time() / 1000 - this._startTimeMillis;
let targetPath = this.workdir.get_relative_path(this.taskCwd);
let meta = { taskMetaVersion: 0,
buildPath: this.buildName,
complete: true,
success: success,
errmsg: errmsg,
elapsedMillis: elapsedMillis,
path: targetPath };
let statusTxtPath = this.taskCwd.get_child('status.txt');
if (statusTxtPath.query_exists(null)) {
let contents = GSystem.file_load_contents_utf8(statusTxtPath, cancellable);
meta['status'] = contents.replace(/[ \n]+$/, '');
}
JsonUtil.writeJsonFileAtomic(this.taskCwd.get_child('meta.json'), meta, cancellable);
}
});

View File

@ -1,123 +0,0 @@
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
// Copyright (C) 2013,2014 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 OSTree = imports.gi.OSTree;
const Format = imports.format;
const GSystem = imports.gi.GSystem;
const Builtin = imports.builtin;
const ArgParse = imports.argparse;
const Task = imports.task;
const ProcUtil = imports.procutil;
const BuildUtil = imports.buildutil;
const LibQA = imports.libqa;
const JsonUtil = imports.jsonutil;
const JSUtil = imports.jsutil;
const GuestFish = imports.guestfish;
const TaskEnsureDiskCaches = new Lang.Class({
Name: 'TaskEnsureDiskCaches',
Extends: Task.Task,
TaskDef: {
TaskName: "ensure-disk-caches",
TaskAfter: ['treecompose'],
},
DefaultParameters: { regenerate: null },
_ensureDiskForProduct: function(ref, revision, cancellable) {
let refUnix = ref.replace(/\//g, '-');
let diskDir = this._imageCacheDir.get_child(refUnix);
GSystem.file_ensure_directory(diskDir, true, cancellable);
let cachedDisk = LibQA.getACachedDisk(diskDir, cancellable);
if (cachedDisk) {
let name = cachedDisk.get_basename();
if (name.indexOf(revision) == 0) {
print("Cached disk is up to date");
return;
}
if (!this.parameters.regenerate) {
print("Found cached disk " + cachedDisk.get_path() + " for " + ref);
return;
} else {
print("Regenerating...");
}
}
let diskPath = diskDir.get_child(revision + '.qcow2');
let diskPathTmp = diskDir.get_child(revision + '.qcow2.tmp');
LibQA.createDisk(diskPathTmp, cancellable);
let mntdir = Gio.File.new_for_path('mnt');
GSystem.file_ensure_directory(mntdir, true, cancellable);
let gfmnt = new GuestFish.GuestMount(diskPathTmp, { partitionOpts: LibQA.DEFAULT_GF_PARTITION_OPTS,
readWrite: true });
gfmnt.mount(mntdir, cancellable);
try {
let osname = this._products['osname'];
let originRepoUrl = this._products['repo'];
let addKernelArgs = this._products['kargs'];
if (!addKernelArgs) addKernelArgs = [];
LibQA.pullDeploy(mntdir, this.repo, osname, ref, revision, originRepoUrl,
cancellable, { addKernelArgs: addKernelArgs });
print("Doing initial labeling");
ProcUtil.runSync(['ostree', 'admin', '--sysroot=' + mntdir.get_path(),
'instutil', 'selinux-ensure-labeled',
mntdir.get_path(),
""],
cancellable,
{ logInitiation: true });
} finally {
gfmnt.umount(cancellable);
}
GSystem.file_rename(diskPathTmp, diskPath, cancellable);
let newDiskName = diskPath.get_basename();
print("Successfully created disk cache " + diskPath.get_path());
let e = null;
try {
e = diskDir.enumerate_children('standard::name', 0, cancellable);
let info;
while ((info = e.next_file(cancellable)) != null) {
let name = info.get_name();
if (!JSUtil.stringEndswith(name, '.qcow2'))
continue;
if (name != newDiskName) {
let child = e.get_child(info);
print("Removing old " + child.get_path());
GSystem.file_unlink(child, cancellable);
}
}
} finally {
if (e) e.close(null);
}
},
execute: function(cancellable) {
this._imageCacheDir = this.cachedir.get_child('images');
this._products = JsonUtil.loadJson(this.workdir.get_child('products.json'), cancellable);
this._productsBuilt = JsonUtil.loadJson(this.builddir.get_child('products-built.json'), cancellable);
let productTrees = this._productsBuilt['trees'];
for (let ref in productTrees) {
this._ensureDiskForProduct(ref, productTrees[ref]['rev'], cancellable);
}
},
});

View File

@ -1,156 +0,0 @@
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
// Copyright (C) 2013,2014 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 OSTree = imports.gi.OSTree;
const Format = imports.format;
const GSystem = imports.gi.GSystem;
const Builtin = imports.builtin;
const ArgParse = imports.argparse;
const Task = imports.task;
const ProcUtil = imports.procutil;
const BuildUtil = imports.buildutil;
const LibQA = imports.libqa;
const JsonUtil = imports.jsonutil;
const JSUtil = imports.jsutil;
const GuestFish = imports.guestfish;
const TaskRepoweb = new Lang.Class({
Name: 'TaskRepoweb',
Extends: Task.Task,
TaskDef: {
TaskName: "repoweb",
TaskAfter: ['build']
},
MAXDEPTH: 100,
DefaultParameters: { },
_readFileLinesToObject: function(path, cancellable) {
let stream = path.read(cancellable);
let datain = Gio.DataInputStream.new(stream);
let result = {};
while (true) {
let [line, len] = dataIn.read_line_utf8(cancellable);
if (line == null)
break;
result[line] = 1;
}
datain.close(null);
return result;
},
_statsForDiff: function(diffTxt) {
let nAdded = 0;
let nRemoved = 0;
let nModified = 0;
let i = 0;
while (true) {
let next = diffTxt.indexOf('\n', i);
if (diffTxt[i] == 'A')
nAdded++;
else if (diffTxt[i] == 'D')
nRemoved++;
else if (diffTxt[i] == 'M')
nModified++;
if (next < 0)
break;
else
i = next+1;
}
return [nAdded, nRemoved, nModified];
},
_generateHistoryFor: function(refname, revision, curDepth, cancellable) {
let commitDataOut = this._repoWebPath.get_child('commit-' + revision + '.json');
if (curDepth > this.MAXDEPTH)
return;
if (commitDataOut.query_exists(null))
return;
let [,commitObject] = this._repo.load_variant(OSTree.ObjectType.COMMIT,
revision);
let ts = OSTree.commit_get_timestamp(commitObject);
let parent = OSTree.commit_get_parent(commitObject);
if (parent) {
let [,parentCommit] = this._repo.load_variant_if_exists(OSTree.ObjectType.COMMIT,
parent);
if (!parentCommit) {
print("For ref " + refname + ": couldn't find parent " + parent);
parent = null;
}
}
let commitData = { 't': ts,
'parent': parent };
if (parent) {
let argv = ['ostree', '--repo='+this._repo.get_path().get_path(), 'diff', parent, revision];
let procctx = new GSystem.SubprocessContext({ argv: argv });
let diffTxtPath = Gio.File.new_for_path('diff.txt');
procctx.set_stdout_file_path(diffTxtPath.get_path());
let proc = GSystem.Subprocess.new(procctx, cancellable);
proc.init(null);
proc.wait_sync_check(cancellable);
let diffTxt = GSystem.file_load_contents_utf8(diffTxtPath, cancellable);
let [nAdded, nRemoved, nModified] = this._statsForDiff(diffTxt);
GSystem.file_unlink(diffTxtPath, cancellable);
commitData['diffstats'] = { 'added': nAdded,
'removed': nRemoved,
'modified': nModified
};
let diffData = {'difftxt': diffTxt};
JsonUtil.writeJsonFileAtomic(this._repoWebPath.get_child('diff-' + revision + '.json'), diffData, cancellable);
}
print("Generated data for " + revision);
JsonUtil.writeJsonFileAtomic(commitDataOut, commitData, cancellable);
if (parent)
this._generateHistoryFor(refname, parent, curDepth+1, cancellable);
},
execute: function(cancellable) {
this._repoPath = this.workdir.get_child('repo');
this._repoWebPath = this.workdir.get_child('repoweb-data');
GSystem.file_ensure_directory(this._repoWebPath, true, cancellable);
this._repo = new OSTree.Repo({ path: this._repoPath });
this._repo.open(cancellable);
let [,allRefs] = this._repo.list_refs(null, cancellable);
let allRefsCopy = {};
for (let refName in allRefs) {
let revision = allRefs[refName];
allRefsCopy[refName] = revision;
print("Generating history for " + refName);
this._generateHistoryFor(refName, revision, 0, cancellable);
}
JsonUtil.writeJsonFileAtomic(this._repoWebPath.get_child('refs.json'), { 'refs': allRefsCopy }, cancellable);
},
});

View File

@ -1,99 +0,0 @@
// Copyright (C) 2011 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 Gio = imports.gi.Gio;
const Lang = imports.lang;
const GSystem = imports.gi.GSystem;
const Task = imports.task;
const ProcUtil = imports.procutil;
const JsonUtil = imports.jsonutil;
const Snapshot = imports.snapshot;
const Vcs = imports.vcs;
const TaskResolve = new Lang.Class({
Name: "TaskResolve",
Extends: Task.Task,
TaskDef: {
TaskName: "resolve",
},
DefaultParameters: {fetchAll: false,
fetchSrcUrls: [],
fetchComponents: [],
timeoutSec: 10},
_writeSnapshotToBuild: function(cancellable) {
let data = this._snapshot.data;
let buf = JsonUtil.serializeJson(data);
let oldSnapshot = this.builddir.get_child('last-build/snapshot.json');
if (oldSnapshot.query_exists(cancellable)) {
let oldBytes = GSystem.file_map_readonly(oldSnapshot, cancellable);
let oldCsum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256, oldBytes);
let newCsum = GLib.compute_checksum_for_string(GLib.ChecksumType.SHA256, buf, -1);
if (oldCsum == newCsum)
return false;
}
let snapshot = this.builddir.get_child('snapshot.json');
JsonUtil.writeJsonFileAtomic(snapshot, data, cancellable);
return true;
},
execute: function(cancellable) {
let manifestPath = this.workdir.get_child('manifest.json');
this._snapshot = Snapshot.fromFile(manifestPath, cancellable, { prepareResolve: true });
let componentsToFetch = this.parameters.fetchComponents.slice();
let srcUrls = this.parameters.fetchSrcUrls;
for (let i = 0; i < srcUrls; i++) {
let matches = snapshot.getMatchingSrc(srcUrls[i]);
componentsToFetch.push.apply(matches);
}
let gitMirrorArgs = ['rpm-ostree-autobuilder', 'git-mirror', '--timeout-sec=' + this.parameters.timeoutSec,
'--workdir=' + this.workdir.get_path(),
'--manifest=' + manifestPath.get_path()];
if (this.parameters.fetchAll || componentsToFetch.length > 0) {
gitMirrorArgs.push('--fetch');
gitMirrorArgs.push('-k');
gitMirrorArgs.push.apply(gitMirrorArgs, componentsToFetch);
}
ProcUtil.runSync(gitMirrorArgs, cancellable, { logInitiation: true });
let componentNames = this._snapshot.getAllComponentNames();
for (let i = 0; i < componentNames.length; i++) {
let component = this._snapshot.getComponent(componentNames[i]);
let tagOrBranch = component['tag'] || component['branch'];
let mirrordir = Vcs.ensureVcsMirror(this.mirrordir, component, cancellable);
let revision = Vcs.describeVersion(mirrordir, tagOrBranch);
component['revision'] = revision;
}
let modified = this._writeSnapshotToBuild(cancellable);
if (modified) {
print("New source snapshot");
} else {
print("Source snapshot unchanged");
}
let modifiedPath = Gio.File.new_for_path('modified.json');
JsonUtil.writeJsonFileAtomic(modifiedPath, { modified: modified }, cancellable);
}
});

View File

@ -1,51 +0,0 @@
// -*- 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 Builtin = imports.builtin;
const ArgParse = imports.argparse;
const ProcUtil = imports.procutil;
const Task = imports.task;
const TestBase = imports.tasks.testbase;
const LibQA = imports.libqa;
const JSUtil = imports.jsutil;
const JSONUtil = imports.jsonutil;
const TaskSmoketest = new Lang.Class({
Name: 'TaskSmoketest',
Extends: TestBase.TestBase,
TaskDef: {
TaskName: "smoketest",
TaskAfter: ['ensure-disk-caches'],
},
RequiredMessageIDs: ["39f53479d3a045ac8e11786248231fbf" // multi-user.target
],
FailedMessageIDs: ["fc2e22bc6ee647b6b90729ab34a250b1", // coredump
],
CompletedTag: 'smoketested'
});

View File

@ -1,204 +0,0 @@
// Copyright (C) 2011,2013,2014 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 OSTree = imports.gi.OSTree;
const Task = imports.task;
const Params = imports.params;
const FileUtil = imports.fileutil;
const ProcUtil = imports.procutil;
const JSUtil = imports.jsutil;
const StreamUtil = imports.streamutil;
const JsonUtil = imports.jsonutil;
const Snapshot = imports.snapshot;
const BuildUtil = imports.buildutil;
const Vcs = imports.vcs;
const TaskTreeCompose = new Lang.Class({
Name: "TaskTreeCompose",
Extends: Task.Task,
TaskDef: {
TaskName: "treecompose",
},
DefaultParameters: {onlyTreesMatching: null},
BuildState: { 'failed': 'failed',
'successful': 'successful',
'unchanged': 'unchanged' },
_composeProduct: function(ref, treefileData, cancellable) {
let [,origRevision] = this.ostreeRepo.resolve_rev(ref, true);
if (origRevision == null)
print("Starting new build of " + ref);
else
print("Starting build of " + ref + " previous: " + origRevision);
let argv = ['rpm-ostree'];
let treefilePath = Gio.File.new_for_path('treefile.json');
JsonUtil.writeJsonFileAtomic(treefilePath, treefileData, cancellable);
argv.push.apply(argv, ['treecompose', treefilePath.get_path()]);
let productNameUnix = ref.replace(/\//g, '_');
let buildOutputPath = Gio.File.new_for_path('log-' + productNameUnix + '.txt');
print("Running: " + argv.map(GLib.shell_quote).join(' '));
let procContext = new GSystem.SubprocessContext({ argv: argv });
GSystem.shutil_rm_rf(buildOutputPath, cancellable);
procContext.set_stdout_file_path(buildOutputPath.get_path());
procContext.set_stderr_disposition(GSystem.SubprocessStreamDisposition.STDERR_MERGE);
let proc = new GSystem.Subprocess({ context: procContext });
proc.init(cancellable);
try {
proc.wait_sync_check(cancellable);
} catch (e) {
print("Build of " + ref + " failed");
return this.BuildState.failed;
}
let [,newRevision] = this.ostreeRepo.resolve_rev(ref, true);
treefileData.rev = newRevision;
if (origRevision == newRevision)
return this.BuildState.unchanged;
return this.BuildState.successful;
},
_inheritAttribute: function(obj, source, attrName, defaultValue) {
let value = source[attrName];
if (!value)
value = this._productData[attrName];
if (!value)
value = defaultValue;
obj[attrName] = value;
},
_inheritExtendAttributeList: function(obj, source, attrName) {
let result = [];
let value = this._productData[attrName];
if (value)
result.push.apply(result, value);
let subValue = source[attrName];
if (subValue)
result.push.apply(result, subValue);
obj[attrName] = result;
},
_generateTreefile: function(ref, release, architecture,
subproductData) {
let treefile = JSON.parse(JSON.stringify(this._productData));
delete treefile.products;
treefile.ref = ref;
this._inheritExtendAttributeList(treefile, subproductData, 'packages');
this._inheritExtendAttributeList(treefile, subproductData, 'postprocess');
this._inheritExtendAttributeList(treefile, subproductData, 'units');
treefile.release = release;
delete treefile.releases;
treefile.architecture = architecture;
delete treefile.architectures;
this._inheritAttribute(treefile, subproductData, "comment", "");
this._inheritExtendAttributeList(treefile, subproductData, 'repos');
this._inheritExtendAttributeList(treefile, subproductData, 'repos_data');
let overrideRepo = this.workdir.get_child('overrides');
if (overrideRepo.query_exists(null)) {
print("Using override repo: " + overrideRepo.get_path());
treefile.repos_data.push('[rpm-ostree-internal-overrides]\n' +
'name=Internal rpm-ostee overrides\n' +
'baseurl=file://' + overrideRepo.get_path() + '\n' +
'metadata_expire=1m\n' +
'enabled=1\ngpgcheck=0\n');
}
return treefile;
},
execute: function(cancellable) {
let productPath = this.workdir.get_child('products.json');
let productData = JsonUtil.loadJson(productPath, cancellable);
this._productData = productData;
let releases = productData['releases'];
let architectures = productData['architectures'];
let products = productData['products'];
let successful = [];
let failed = [];
let unchanged = [];
let productTrees = {};
let buildmasterName = 'buildmaster';
for (let i = 0; i < releases.length; i++) {
for (let j = 0; j < architectures.length; j++) {
for (let productName in products) {
for (let treeName in products[productName]) {
let release = releases[i];
let architecture = architectures[j];
let ref = [this._productData['osname'], release, architecture, buildmasterName, productName, treeName].join('/');
if (this.parameters.onlyTreesMatching &&
ref.indexOf(this.parameters.onlyTreesMatching) == -1) {
log("Skipping " + ref + " which does not match " + this.parameters.onlyTreesMatching);
continue;
}
let subproductData = products[productName][treeName];
let treefileData = this._generateTreefile(ref, release, architecture, subproductData);
let result = this._composeProduct(ref, treefileData, cancellable);
switch (result) {
case this.BuildState.successful: {
productTrees[ref] = treefileData;
successful.push(ref);
}
break;
case this.BuildState.failed: {
failed.push(ref);
}
break;
case this.BuildState.unchanged: {
productTrees[ref] = treefileData;
unchanged.push(ref);
}
break;
default:
throw new Error("Invalid result from composeProduct: " + result);
}
}
}
}
}
let productsBuilt = { successful: successful,
failed: failed,
unchanged: unchanged,
trees: productTrees };
let productsBuiltPath = this.builddir.get_child('products-built.json');
JsonUtil.writeJsonFileAtomic(productsBuiltPath, productsBuilt, cancellable);
print("Successful: " + successful.join(' '));
print("Failed: " + failed.join(' '));
print("Unchanged: " + unchanged.join(' '));
let modifiedPath = Gio.File.new_for_path('modified.json');
JsonUtil.writeJsonFileAtomic(modifiedPath, { modified: successful.length > 0 }, cancellable);
}
});

View File

@ -1,145 +0,0 @@
// -*- indent-tabs-mode: nil; tab-width: 2; -*-
// Copyright (C) 2013,2014 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 GSystem = imports.gi.GSystem;
const Builtin = imports.builtin;
const ArgParse = imports.argparse;
const Task = imports.task;
const ProcUtil = imports.procutil;
const BuildUtil = imports.buildutil;
const LibQA = imports.libqa;
const JsonUtil = imports.jsonutil;
const JSUtil = imports.jsutil;
const GuestFish = imports.guestfish;
const TaskZDisks = new Lang.Class({
Name: 'TaskZDisks',
Extends: Task.Task,
TaskDef: {
TaskName: "zdisks",
TaskAfter: ['ensure-disk-caches']
},
_exportDiskForProduct: function(ref, revision, cancellable) {
let refUnix = ref.replace(/\//g, '-');
let diskDir = this._imageExportDir.get_child(refUnix);
GSystem.file_ensure_directory(diskDir, true, cancellable);
let latestDisk = LibQA.getACachedDisk(this._imageCacheDir.get_child(refUnix), cancellable);
if (!latestDisk) {
throw new Error("No cached disk found for " + ref);
}
let formats = this._products['image_formats'];
if (!formats) formats = [];
let newDiskName = null;
let newVdiName = null;
if (formats.indexOf('qcow2') >= 0) {
let newDiskPath = diskDir.get_child(revision + '.qcow2.xz');
newDiskName = newDiskPath.get_basename();
if (!newDiskPath.query_exists(null)) {
let newDiskPathTmp = Gio.File.new_for_path(revision + '.qcow2.xz.tmp');
print("Creating " + newDiskPath.get_path());
let xzCtx = new GSystem.SubprocessContext({ argv: [ 'xz' ] })
xzCtx.set_stdin_file_path(latestDisk.get_path());
xzCtx.set_stdout_file_path(newDiskPathTmp.get_path());
let xz = new GSystem.Subprocess({ context: xzCtx });
xz.init(cancellable);
xz.wait_sync_check(cancellable);
print("Completed compression, renaming to " + newDiskPath.get_path());
GSystem.file_rename(newDiskPathTmp, newDiskPath, cancellable);
} else {
print("Already have " + newDiskPath.get_path());
}
BuildUtil.atomicSymlinkSwap(newDiskPath.get_parent().get_child('latest-qcow2.xz'),
newDiskPath, cancellable);
}
if (formats.indexOf('vdi') >= 0) {
let vdiTmpPath = Gio.File.new_for_path(revision + '.vdi.tmp');
let newVdiPath = diskDir.get_child(revision + '.vdi.bz2');
newVdiName = newVdiPath.get_basename();
if (!newVdiPath.query_exists(null)) {
let newVdiPathTmp = Gio.File.new_for_path(revision + '.vdi.bz2.tmp');
print("Creating " + vdiTmpPath.get_path());
ProcUtil.runSync(['qemu-img', 'convert', '-O', 'vdi',
latestDisk.get_path(), vdiTmpPath.get_path()],
cancellable,
{ logInitiation: true });
print("Creating " + newVdiPathTmp.get_path());
let xzCtx = new GSystem.SubprocessContext({ argv: [ 'bzip2' ] })
xzCtx.set_stdin_file_path(vdiTmpPath.get_path());
xzCtx.set_stdout_file_path(newVdiPathTmp.get_path());
let xz = new GSystem.Subprocess({ context: xzCtx });
xz.init(cancellable);
xz.wait_sync_check(cancellable);
print("Completed VDI compression, renaming to " + newVdiPathTmp.get_path());
GSystem.file_rename(newVdiPathTmp, newVdiPath, cancellable);
GSystem.file_unlink(vdiTmpPath, cancellable);
} else {
print("Already have " + newVdiPath.get_path());
}
BuildUtil.atomicSymlinkSwap(newVdiPath.get_parent().get_child('latest-vdi.bz2'),
newVdiPath, cancellable);
}
let e = null;
try {
e = diskDir.enumerate_children('standard::name', 0, cancellable);
let info;
while ((info = e.next_file(cancellable)) != null) {
let name = info.get_name();
if (name == newDiskName || name == newVdiName)
continue;
let child = e.get_child(info);
if (JSUtil.stringEndswith(name, '.qcow2.xz') ||
JSUtil.stringEndswith(name, '.vdi.bz2')) {
print("Removing old " + child.get_path());
GSystem.file_unlink(child, cancellable);
}
}
} finally {
if (e) e.close(null);
}
},
execute: function(cancellable) {
this._imageCacheDir = this.cachedir.get_child('images');
this._imageExportDir = this.workdir.get_child('images/auto');
this._products = JsonUtil.loadJson(this.workdir.get_child('products.json'), cancellable);
this._productsBuilt = JsonUtil.loadJson(this.builddir.get_child('products-built.json'), cancellable);
let productTrees = this._productsBuilt['trees'];
for (let ref in productTrees) {
this._exportDiskForProduct(ref, productTrees[ref]['rev'], cancellable);
}
}
});

View File

@ -1,666 +0,0 @@
// -*- 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 Builtin = imports.builtin;
const ArgParse = imports.argparse;
const ProcUtil = imports.procutil;
const Params = imports.params;
const Task = imports.task;
const LibQA = imports.libqa;
const JSUtil = imports.jsutil;
const JsonUtil = imports.jsonutil;
const GuestFish = imports.guestfish;
const TIMEOUT_SECONDS = 10 * 60;
const CommandSocketIface = '<node> \
<interface name="org.gnome.Continuous.Command"> \
<method name="AsyncMessage"> \
<arg name="msgId" direction="in" type="s"/> \
<arg name="value" direction="in" type="v"/> \
</method> \
<method name="Screenshot"> \
<arg name="name" direction="in" type="s"/> \
</method> \
<signal name="ScreenshotComplete"> \
<arg name="name" direction="out" type="s"/> \
</signal> \
</interface> \
</node>';
const CommandSocketProxy = new Lang.Class({
Name: 'CommandSocketProxy',
_init: function(connection,
asyncMessageHandler,
screenshotHandler) {
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(CommandSocketIface, this);
this._dbusImpl.export(connection, '/org/gnome/Continuous/Command');
this._asyncMessageHandler = asyncMessageHandler;
this._screenshotHandler = screenshotHandler;
},
Screenshot: function(name) {
this._screenshotHandler(name);
},
AsyncMessage: function(msgId, value) {
this._asyncMessageHandler(msgId, value);
}
});
const TestOneDisk = new Lang.Class({
Name: 'TestOneDisk',
_init: function(parentTask, testRequiredMessageIds, testFailedMessageIds, testStatusMessageId) {
this._parentTask = parentTask;
this._testRequiredMessageIds = testRequiredMessageIds;
this._testFailedMessageIds = testFailedMessageIds;
this._statusMessageId = testStatusMessageId;
},
_fail: function(message) {
if (this._failed)
return;
this._failed = true;
this._failedMessage = message;
this._screenshot({ isFinal: true });
},
_onQemuExited: function(proc, result) {
let [success, status] = ProcUtil.asyncWaitCheckFinish(proc, result);
this._qemu = null;
this._loop.quit();
if (!success) {
this._fail("Qemu exited with status " + status);
}
},
_onTimeout: function() {
print("Timeout reached");
for (let msgid in this._pendingRequiredMessageIds) {
print("Did not see MESSAGE_ID=" + msgid);
}
this._fail("Timed out");
this._loop.quit();
},
_onJournalOpen: function(file, result) {
try {
this._journalStream = file.read_finish(result);
this._journalDataStream = Gio.DataInputStream.new(this._journalStream);
this._openedJournal = true;
this._readingJournal = true;
this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
Lang.bind(this, this._onJournalReadLine));
} catch (e) {
this._fail("Journal open failed: " + e);
this._loop.quit();
}
},
_onJournalReadLine: function(stream, result) {
this._readingJournal = false;
let line, len;
try {
[line, len] = stream.read_line_finish_utf8(result);
} catch (e) {
this._fail(e.toString());
this._loop.quit();
throw e;
}
if (this._foundAllMessageIds || this._failed)
return;
if (!line)
return;
let data = JSON.parse(line);
let message = data['MESSAGE'];
let messageId = data['MESSAGE_ID'];
let identifier = data['SYSLOG_IDENTIFIER'] || data['_EXE'];
if (message)
this._journalTextStream.write_all(identifier + ': ' + message + "\n", null);
if (line) {
if (messageId) {
if (this._pendingRequiredMessageIds[messageId]) {
print("Found required message ID " + messageId);
print(message);
delete this._pendingRequiredMessageIds[messageId];
this._countPendingRequiredMessageIds--;
} else if (this._failMessageIds[messageId]) {
this._fail("Found failure message ID " + messageId);
print(message);
this._loop.quit();
}
if (messageId === this._statusMessageId) {
print(message);
let statusTxtPath = Gio.File.new_for_path('status.txt');
statusTxtPath.replace_contents(message + '\n', null, false,
Gio.FileCreateFlags.REPLACE_DESTINATION, this._cancellable);
}
this._parentTask._handleMessage(data, this._cancellable);
}
if (this._countPendingRequiredMessageIds == 0 && !this._foundAllMessageIds) {
print("Found all required message IDs, waiting for " + this._parentTask.CompleteIdleWaitSeconds);
this._foundAllMessageIds = true;
this._parentTask._onSuccess();
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, this._parentTask.CompleteIdleWaitSeconds,
Lang.bind(this, this._onFinalWait));
} else {
this._readingJournal = true;
this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
Lang.bind(this, this._onJournalReadLine));
}
}
},
_onJournalChanged: function(monitor, file, otherFile, eventType) {
if (this._foundAllMessageIds || this._failed)
return;
if (!this._openedJournal) {
this._openedJournal = true;
file.read_async(GLib.PRIORITY_DEFAULT,
this._cancellable,
Lang.bind(this, this._onJournalOpen));
} else if (!this._readingJournal) {
this._readingJournal = true;
this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
Lang.bind(this, this._onJournalReadLine));
}
},
_onQemuCapabiltiesRead: function(datain, result) {
print("QMP server greeting received");
let [response, len] = datain.read_line_finish_utf8(result);
this._qmpGreetingReceived = true;
this._qmpCommand({ "execute": "qmp_capabilities" },
Lang.bind(this, function() {
print("qmp_capabilities complete");
this._qmpCapabilitiesReceived = true;
}));
},
_ensureQemuConnection: function() {
if (this._qmpSocketConn)
return false;
let monitorPath = this._subworkdir.get_child("qemu.monitor");
if (!monitorPath.query_exists (null)) {
this._qmpConnectionAttempts++;
if (this._qmpConnectionAttempts > 10)
throw new Error("Failed to connect to qemu monitor after " + this._qmpConnectionAttempts + " attempts");
return true;
}
let path = Gio.File.new_for_path('.').get_relative_path(monitorPath);
let address = Gio.UnixSocketAddress.new_with_type(path, Gio.UnixSocketAddressType.PATH);
let socketClient = new Gio.SocketClient();
this._qmpSocketConn = socketClient.connect(address, this._cancellable);
this._qmpOut = Gio.DataOutputStream.new(this._qmpSocketConn.get_output_stream());
this._qmpIn = Gio.DataInputStream.new(this._qmpSocketConn.get_input_stream());
this._qmpIn.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
Lang.bind(this, this._onQemuCapabiltiesRead));
return false;
},
_onQemuCommandComplete: function(datain, result) {
let [response, len] = datain.read_line_finish_utf8(result);
let responseData = null;
try {
responseData = JSON.parse(response);
} catch (e) {}
if (responseData && responseData.error)
print("command response error=" + JSON.stringify(responseData.error));
let onComplete = this._qmpCommandOutstanding.shift();
if (this._qmpCommandOutstanding.length == 1)
this._qmpIn.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
Lang.bind(this, this._onQemuCommandComplete));
onComplete();
},
_qmpCommand: function(cmd, onComplete) {
this._ensureQemuConnection();
let cmdStr = JSON.stringify(cmd);
if (!this._qmpGreetingReceived)
throw new Error("Attempting QMP command without having received greeting");
this._qmpOut.put_string(cmdStr, this._cancellable);
this._qmpCommandOutstanding.push(onComplete);
if (this._qmpCommandOutstanding.length == 1)
this._qmpIn.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
Lang.bind(this, this._onQemuCommandComplete));
},
_onScreenshotComplete: function(filename, isFinal, name) {
print("screenshot complete for " + filename);
let filePath = this._subworkdir.get_child(filename);
let modified = true;
if (!isFinal && name == null) {
let contentsBytes = GSystem.file_map_readonly(filePath, this._cancellable);
let csum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256,
contentsBytes);
modified = this._lastScreenshotChecksum != csum;
if (!modified) {
GSystem.file_unlink(filePath, this._cancellable);
} else {
this._lastScreenshotChecksum = csum;
}
this._screenshotSerial++;
}
// Convert to PNG if possible
let screenshotPath = filePath;
if (modified && imports.gi.GdkPixbuf) {
let GdkPixbuf = imports.gi.GdkPixbuf;
let pixbuf = null;
try {
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filePath.get_path());
} catch (e) {
if (e.domain != GdkPixbuf.PixbufError)
throw e;
print(e);
}
if (pixbuf != null) {
let outFilename = this._subworkdir.get_child(filename.replace(/ppm$/, 'png'));
screenshotPath = outFilename;
pixbuf.savev(outFilename.get_path(), "png", [], []);
}
GSystem.file_unlink(filePath, this._cancellable);
}
if (name == null) {
this._requestingScreenshot = false;
} else {
this._parentTask._screenshotTaken(screenshotPath);
this._commandProxy._dbusImpl.emit_signal('ScreenshotComplete',
GLib.Variant.new('(s)', [name]));
}
if (isFinal) {
print("Final screenshot complete");
this._qmpCommand({"execute": "system_powerdown"},
Lang.bind(this, this._onFinalPoweroff));
}
},
_onFinalPoweroff: function() {
print("Poweroff request sent");
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 5,
Lang.bind(this, function() {
print("Poweroff timeout ");
this._loop.quit();
}));
},
_screenshot: function(params) {
params = Params.parse(params, { isFinal: false,
name: null });
if (params.name == null) {
if (this._requestingScreenshot)
return;
this._requestingScreenshot = true;
}
let filename;
if (params.name == null) {
if (params.isFinal)
filename = "screenshot-final.ppm";
else
filename = "screenshot-" + this._screenshotSerial + ".ppm";
} else {
filename = "screenshot-" + params.name + ".ppm";
}
this._qmpCommand({"execute": "screendump", "arguments": { "filename": filename }},
Lang.bind(this, this._onScreenshotComplete, filename, params.isFinal, params.name));
},
_idleScreenshot: function() {
if (this._foundAllMessageIds)
return false;
if (this._qmpCapabilitiesReceived)
this._screenshot(false);
return true;
},
_onFinalWait: function() {
print("Final wait complete at " + GLib.DateTime.new_now_local().format('%c'));
this._screenshot({ isFinal: true });
},
_onCommandChannelScreenshot: function(name) {
this._screenshot({ name: name });
},
_onCommandSocketDBusReady: function(iostream, result) {
this._commandSocketDBus = Gio.DBusConnection.new_finish(result);
this._commandProxy = new CommandSocketProxy(this._commandSocketDBus,
Lang.bind(this._parentTask, this._parentTask._onCommandChannelAsyncMessage),
Lang.bind(this, this._onCommandChannelScreenshot));
print("Command DBus connection open");
},
_onCommandSocketConnected: function(client, result) {
this._commandSocketConn = client.connect_finish(result);
print("Connected to command socket");
Gio.DBusConnection.new(this._commandSocketConn, null, 0,
null, this._cancellable,
Lang.bind(this, this._onCommandSocketDBusReady));
},
_tryCommandConnection: function() {
if (this._commandSocketDBus || this._complete)
return false;
printerr("DEBUG: Querying command socket");
if (!this._commandSocketPath.query_exists(null)) {
print("commandSocketPath " + this._commandSocketPath.get_path() + " does not exist yet");
this._commandConnectionAttempts++;
if (this._commandConnectionAttempts > 5) {
this._fail("Command connection didn't appear at " + this._commandSocketPath.get_path());
this._loop.quit();
return false;
}
return true;
}
printerr("Connecting to command socket...");
let path = Gio.File.new_for_path('.').get_relative_path(this._commandSocketPath);
let address = Gio.UnixSocketAddress.new_with_type(path, Gio.UnixSocketAddressType.PATH);
let socketClient = new Gio.SocketClient();
socketClient.connect_async(address, this._cancellable,
Lang.bind(this, this._onCommandSocketConnected));
return false;
},
execute: function(subworkdir, osname, repo, diskPath, cancellable) {
print("Testing disk " + diskPath.get_path());
this._repo = repo;
this._subworkdir = subworkdir;
this._loop = GLib.MainLoop.new(null, true);
this._foundAllMessageIds = false;
this._complete = false;
this._failed = false;
this._journalStream = null;
this._journalDataStream = null;
this._commandConnectionAttempts = 0;
this._commandSocketDBus = null;
this._openedJournal = false;
this._readingJournal = false;
this._pendingRequiredMessageIds = {};
this._requestingScreenshot = false;
this._failMessageIds = {};
this._countPendingRequiredMessageIds = 0;
this._screenshotSerial = 0;
this._lastScreenshotChecksum = null;
this._qmpGreetingReceived = false;
this._qmpSocket = null;
this._qmpCommandOutstanding = [];
this._qmpConnectionAttempts = 0;
this._qmpCapabilitiesReceived = false;
print("Will wait for message IDs: " + JSON.stringify(this._testRequiredMessageIds));
for (let i = 0; i < this._testRequiredMessageIds.length; i++) {
this._pendingRequiredMessageIds[this._testRequiredMessageIds[i]] = true;
this._countPendingRequiredMessageIds += 1;
}
for (let i = 0; i < this._testFailedMessageIds.length; i++) {
this._failMessageIds[this._testFailedMessageIds[i]] = true;
}
this._cancellable = cancellable;
// HACK
if (diskPath.get_basename().indexOf('x86_64') >= 0)
this._diskArch = 'x86_64';
else
this._diskArch = 'i686';
let qemuArgs = LibQA.getDefaultQemuOptions({ parallel: true });
let [gfmnt, mntdir] = LibQA.newReadWriteMount(diskPath, cancellable);
try {
LibQA.modifyBootloaderAppendKernelArgs(mntdir, ["console=ttyS0"], cancellable);
let [currentDir, currentEtcDir] = LibQA.getDeployDirs(mntdir, osname);
LibQA.injectExportJournal(currentDir, currentEtcDir, cancellable);
this._parentTask._prepareDisk(mntdir, this._diskArch, cancellable);
} finally {
gfmnt.umount(cancellable);
}
let consoleOutput = subworkdir.get_child('console.out');
let journalJson = subworkdir.get_child('journal.json');
let journalText = subworkdir.get_child('journal.txt');
GSystem.shutil_rm_rf(journalText, cancellable);
this._journalTextStream = journalText.replace(null, false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
cancellable);
this._commandSocketPath = subworkdir.get_child('command.sock');
let commandSocketRelpath = subworkdir.get_relative_path(this._commandSocketPath);
qemuArgs.push.apply(qemuArgs, ['-drive', 'file=' + diskPath.get_path() + ',if=virtio',
'-vnc', 'none',
'-serial', 'file:' + consoleOutput.get_path(),
'-chardev', 'socket,id=charmonitor,path=qemu.monitor,server,nowait',
'-mon', 'chardev=charmonitor,id=monitor,mode=control',
'-device', 'virtio-serial',
'-chardev', 'file,id=journaljson,path=' + journalJson.get_path(),
'-device', 'virtserialport,chardev=journaljson,name=org.gnome.journaljson',
'-chardev', 'socket,id=commandchan,server,path=' + commandSocketRelpath,
'-device', 'virtserialport,chardev=commandchan,name=org.gnome.commandchan']);
let qemuContext = new GSystem.SubprocessContext({ argv: qemuArgs });
qemuContext.set_cwd(subworkdir.get_path());
let qemu = new GSystem.Subprocess({context: qemuContext});
this._qemu = qemu;
print("starting qemu : " + qemuArgs.join(' '));
qemu.init(cancellable);
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, Lang.bind(this, this._ensureQemuConnection));
qemu.wait(cancellable, Lang.bind(this, this._onQemuExited));
let journalMonitor = journalJson.monitor_file(0, cancellable);
journalMonitor.connect('changed', Lang.bind(this, this._onJournalChanged));
let commandConnectAttemptTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1,
Lang.bind(this, this._tryCommandConnection));
let timeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, TIMEOUT_SECONDS,
Lang.bind(this, this._onTimeout));
// Let's only do a screenshot every 3 seconds, I think it's slowing things down...
let screenshotTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3,
Lang.bind(this, this._idleScreenshot));
this._loop.run();
this._complete = true;
if (this._qemu) {
this._qemu.force_exit();
}
if (this._journalTextStream)
this._journalTextStream.close(null);
GLib.source_remove(timeoutId);
let [gfmnt, mntdir] = LibQA.newReadWriteMount(diskPath, cancellable);
try {
this._parentTask._postQemu(mntdir, cancellable);
} finally {
gfmnt.umount(cancellable);
}
if (this._failed) {
return this._failedMessage;
}
print("Completed testing of " + diskPath.get_basename());
return null;
}
});
const TestBase = new Lang.Class({
Name: 'TestBase',
Extends: Task.Task,
TaskDef: {
TaskName: "testbase",
TaskAfter: ['builddisks'],
},
TestTrees: ['-runtime'],
CompleteIdleWaitSeconds: 10,
BaseRequiredMessageIDs: ["f77379a8490b408bbe5f6940505a777b", // systemd-journald
],
BaseFailedMessageIDs: [],
RequiredMessageIDs: [],
FailedMessageIDs: [],
StatusMessageID: [],
CompletedTag: null,
_prepareDisk: function(mntdir, cancellable) {
// Nothing, intended for subclasses
},
// For subclasses
_onCommandChannelAsyncMessage: function(msgId, value) {
print("Received command async message " + msgId);
},
_handleMessage: function(message, cancellable) {
return false;
},
_onSuccess: function() {
},
_postQemu: function(mntdir, cancellable) {
},
_screenshotTaken: function(path) {
},
_fileLinkHere: function(diskPath, cancellable) {
let dest = Gio.File.new_for_path(diskPath.get_basename());
try {
GSystem.file_linkcopy(diskPath, dest, 0, cancellable);
return dest;
} catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
throw e;
}
return null;
},
getDiskSnapshotForRevision: function(ref, revision, cancellable) {
let osname = this._products['osname'];
let originRepoUrl = this._products['repo'];
let snapshotPath = Gio.File.new_for_path('overlay-' + revision + '.qcow2');
let refUnix = ref.replace(/\//g, '-');
let diskDir = this._imageCacheDir.get_child(refUnix);
GSystem.file_ensure_directory(diskDir, true, cancellable);
let exactDiskName = revision + '.qcow2';
let exactDiskPath = diskDir.get_child(exactDiskName);
let cwdDiskLink = this._fileLinkHere(exactDiskPath, cancellable);
if (cwdDiskLink) {
print("Acquired link to exact disk " + cwdDiskLink.get_path());
LibQA.createDiskSnapshot(cwdDiskLink, snapshotPath, cancellable);
return snapshotPath;
}
let e = null;
try {
e = diskDir.enumerate_children('standard::name', 0, cancellable);
let info;
while ((info = e.next_file(cancellable)) != null) {
let name = info.get_name();
if (!JSUtil.stringEndswith(name, '.qcow2'))
continue;
cwdDiskLink = this._fileLinkHere(e.get_child(info), cancellable);
if (cwdDiskLink)
break;
}
} finally {
if (e) e.close(null);
}
if (!cwdDiskLink) {
throw new Error("Unable to find cached disk in " + diskDir.get_path());
}
print("Acquired link to disk " + cwdDiskLink.get_path());
print("Creating snapshot " + snapshotPath.get_path() + " for update");
LibQA.createDiskSnapshot(cwdDiskLink, snapshotPath, cancellable);
let mntdir = Gio.File.new_for_path('mnt');
GSystem.file_ensure_directory(mntdir, true, cancellable);
let gfmnt = new GuestFish.GuestMount(snapshotPath, { partitionOpts: LibQA.DEFAULT_GF_PARTITION_OPTS,
readWrite: true });
gfmnt.mount(mntdir, cancellable);
try {
LibQA.pullDeploy(mntdir, this.repo, osname, ref, revision, originRepoUrl,
cancellable);
} finally {
gfmnt.umount(cancellable);
}
print("Successfully updated " + snapshotPath.get_path() + " to " + revision);
return snapshotPath;
},
execute: function(cancellable) {
this._imageCacheDir = this.cachedir.get_child('images');
GSystem.file_ensure_directory(this._imageCacheDir, true, cancellable);
this._products = JsonUtil.loadJson(this.workdir.get_child('products.json'), cancellable);
this._productsBuilt = JsonUtil.loadJson(this.builddir.get_child('products-built.json'), cancellable);
let productTrees = this._productsBuilt['trees'];
for (let ref in productTrees) {
let revision = productTrees[ref]['rev'];
let snapshotDisk = this.getDiskSnapshotForRevision(ref, revision, cancellable);
try {
let refUnix = ref.replace(/\//g, '-');
let refWorkdir = Gio.File.new_for_path('work-' + refUnix);
GSystem.file_ensure_directory(refWorkdir, true, cancellable);
let test = new TestOneDisk(this,
this.BaseRequiredMessageIDs.concat(this.RequiredMessageIDs),
this.BaseFailedMessageIDs.concat(this.FailedMessageIDs),
this.StatusMessageID);
let failedMsg = test.execute(refWorkdir, this._products['osname'], this.repo,
snapshotDisk, cancellable);
if (failedMsg) {
print("Testing of " + ref + " at " + revision + " failed");
}
} finally {
// These are too big to stick around
GSystem.shutil_rm_rf(snapshotDisk, cancellable);
}
}
}
});

View File

@ -1,431 +0,0 @@
// Copyright (C) 2011,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.
const Params = imports.params;
const Format = imports.format;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GSystem = imports.gi.GSystem;
const ProcUtil = imports.procutil;
const BuildUtil = imports.buildutil;
const JSUtil = imports.jsutil;
function getMirrordir(mirrordir, keytype, uri, params) {
params = Params.parse(params, {prefix: ''});
let colon = uri.indexOf('://');
let scheme, rest;
if (colon >= 0) {
scheme = uri.substr(0, colon);
rest = uri.substr(colon+3);
} else {
scheme = 'file';
if (GLib.path_is_absolute(uri))
rest = uri.substr(1);
else
rest = uri;
}
let prefix = params.prefix ? params.prefix + '/' : '';
let relpath = prefix + keytype + '/' + scheme + '/' + rest;
return mirrordir.resolve_relative_path(relpath);
}
function _processCheckoutSubmodules(mirrordir, parentUri, cwd, cancellable) {
let lines = ProcUtil.runSyncGetOutputLines(['git', 'submodule', 'status'],
cancellable, {cwd: cwd});
let haveSubmodules = false;
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (line == '') continue;
haveSubmodules = true;
line = line.substr(1);
let [subChecksum, subName, rest] = line.split(' ');
let configKey = Format.vprintf('submodule.%s.url', [subName]);
let subUrl = ProcUtil.runSyncGetOutputUTF8Stripped(['git', 'config', '-f', '.gitmodules', configKey],
cancellable, {cwd: cwd});
print("processing submodule " + subUrl);
if (subUrl.indexOf('../') == 0) {
subUrl = _makeAbsoluteUrl(parentUri, subUrl);
}
let localMirror = getMirrordir(mirrordir, 'git', subUrl);
ProcUtil.runSync(['git', 'config', configKey, 'file://' + localMirror.get_path()],
cancellable, {cwd:cwd});
ProcUtil.runSync(['git', 'submodule', 'update', '--init', subName], cancellable, {cwd: cwd});
_processCheckoutSubmodules(mirrordir, subUrl, cwd.get_child(subName), cancellable);
}
}
function getVcsCheckout(mirrordir, component, dest, cancellable, params) {
params = Params.parse(params, {overwrite: true,
quiet: false});
let [keytype, uri] = parseSrcKey(component['src']);
let revision;
let moduleMirror;
let addUpstream;
if (keytype == 'git' || keytype == 'local') {
revision = component['revision'];
moduleMirror = getMirrordir(mirrordir, keytype, uri);
addUpstream = true;
} else if (keytype == 'tarball') {
revision = 'tarball-import-' + component['checksum'];
moduleMirror = getMirrordir(mirrordir, 'tarball', component['name']);
addUpstream = false;
} else {
throw new Error("Unsupported src uri");
}
let checkoutdirParent = dest.get_parent();
GSystem.file_ensure_directory(checkoutdirParent, true, cancellable);
let tmpDest = checkoutdirParent.get_child(dest.get_basename() + '.tmp');
GSystem.shutil_rm_rf(tmpDest, cancellable);
let ftype = dest.query_file_type(Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
if (ftype == Gio.FileType.SYMBOLIC_LINK) {
GSystem.file_unlink(dest, cancellable);
} else if (ftype == Gio.FileType.DIRECTORY) {
if (params.overwrite) {
GSystem.shutil_rm_rf(dest, cancellable);
} else {
tmpDest = dest;
}
}
ftype = tmpDest.query_file_type(Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
if (ftype != Gio.FileType.DIRECTORY) {
ProcUtil.runSync(['git', 'clone', '-q', '--origin', 'localmirror',
'--no-checkout', moduleMirror.get_path(), tmpDest.get_path()], cancellable);
if (addUpstream)
ProcUtil.runSync(['git', 'remote', 'add', 'upstream', uri], cancellable, {cwd: tmpDest});
} else {
ProcUtil.runSync(['git', 'fetch', 'localmirror'], cancellable, {cwd: tmpDest});
}
ProcUtil.runSync(['git', 'checkout', '-q', revision], cancellable, {cwd: tmpDest});
_processCheckoutSubmodules(mirrordir, uri, tmpDest, cancellable);
if (!tmpDest.equal(dest)) {
GSystem.file_rename(tmpDest, dest, cancellable);
}
return dest;
}
function clean(keytype, checkoutdir, cancellable) {
ProcUtil.runSync(['git', 'clean', '-d', '-f', '-x'], cancellable,
{cwd: checkoutdir});
}
function parseSrcKey(srckey) {
let idx = srckey.indexOf(':');
if (idx < 0) {
throw new Error("Invalid SRC uri=" + srckey);
}
let keytype = srckey.substr(0, idx);
if (!(keytype == 'git' || keytype == 'local' || keytype == 'tarball')) {
throw new Error("Unsupported SRC uri=" + srckey);
}
let uri = srckey.substr(idx+1);
return [keytype, uri];
}
function checkoutPatches(mirrordir, patchdir, component, cancellable) {
let patches = component['patches'];
let [patchesKeytype, patchesUri] = parseSrcKey(patches['src']);
if (patchesKeytype == 'local')
return Gio.File.new_for_path(patchesUri);
else if (patchesKeytype != 'git')
throw new Error("Unhandled keytype " + patchesKeytype);
let patchesMirror = getMirrordir(mirrordir, patchesKeytype, patchesUri);
getVcsCheckout(mirrordir, patches, patchdir, cancellable,
{overwrite: true,
quiet: true});
return patchdir;
}
function getLastfetchPath(mirrordir, keytype, uri, branch) {
let mirror = getMirrordir(mirrordir, keytype, uri);
let branchSafename = branch.replace(/[\/.]/g, '_');
return mirror.get_parent().get_child(mirror.get_basename() + '.lastfetch-' + branchSafename);
}
function _listSubmodules(mirrordir, mirror, keytype, uri, branch, cancellable) {
let currentVcsVersion = ProcUtil.runSyncGetOutputUTF8(['git', 'rev-parse', branch], cancellable,
{cwd: mirror}).replace(/[ \n]/g, '');
let tmpCheckout = getMirrordir(mirrordir, keytype, uri, {prefix:'_tmp-checkouts'});
GSystem.shutil_rm_rf(tmpCheckout, cancellable);
GSystem.file_ensure_directory(tmpCheckout.get_parent(), true, cancellable);
ProcUtil.runSync(['git', 'clone', '-q', '--no-checkout', mirror.get_path(), tmpCheckout.get_path()], cancellable);
ProcUtil.runSync(['git', 'checkout', '-q', '-f', currentVcsVersion], cancellable,
{cwd: tmpCheckout});
let submodules = []
let lines = ProcUtil.runSyncGetOutputLines(['git', 'submodule', 'status'],
cancellable, {cwd: tmpCheckout});
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (line == '') continue;
line = line.substr(1);
let [subChecksum, subName, rest] = line.split(' ');
let subUrl = ProcUtil.runSyncGetOutputUTF8Stripped(['git', 'config', '-f', '.gitmodules',
Format.vprintf('submodule.%s.url', [subName])], cancellable,
{cwd: tmpCheckout});
submodules.push([subChecksum, subName, subUrl]);
}
GSystem.shutil_rm_rf(tmpCheckout, cancellable);
return submodules;
}
function _makeAbsoluteUrl(parent, relpath)
{
let origParent = parent;
let origRelpath = relpath;
if (JSUtil.stringEndswith(parent, '/'))
parent = parent.substr(0, parent.length - 1);
let methodIndex = parent.indexOf("://");
if (methodIndex == -1)
throw new Error("Invalid method");
let firstSlash = parent.indexOf('/', methodIndex + 3);
if (firstSlash == -1)
throw new Error("Invalid url");
let parentPath = parent.substr(firstSlash);
while (relpath.indexOf('../') == 0) {
let i = parentPath.lastIndexOf('/');
if (i < 0)
throw new Error("Relative path " + origRelpath + " is too long for parent " + origParent);
relpath = relpath.substr(3);
parentPath = parentPath.substr(0, i);
}
parent = parent.substr(0, firstSlash) + parentPath;
if (relpath.length == 0)
return parent;
return parent + '/' + relpath;
}
function ensureVcsMirror(mirrordir, component, cancellable,
params) {
params = Params.parse(params, { fetch: false,
fetchKeepGoing: false,
timeoutSec: 0 });
let [keytype, uri] = parseSrcKey(component['src']);
if (keytype == 'git' || keytype == 'local') {
let branch = component['branch'] || component['tag'];
return _ensureVcsMirrorGit(mirrordir, uri, branch, cancellable, params);
} else if (keytype == 'tarball') {
let name = component['name'];
let checksum = component['checksum'];
if (!checksum) {
throw new Error("Component " + name + " missing checksum attribute");
}
return _ensureVcsMirrorTarball(mirrordir, name, uri, checksum, cancellable, params);
} else {
throw new Error("Unhandled keytype=" + keytype);
}
}
function _ensureVcsMirrorGit(mirrordir, uri, branch, cancellable, params) {
let keytype = 'git';
let fetch = params.fetch;
let mirror = getMirrordir(mirrordir, keytype, uri);
let tmpMirror = mirror.get_parent().get_child(mirror.get_basename() + '.tmp');
let didUpdate = false;
let lastFetchPath = getLastfetchPath(mirrordir, keytype, uri, branch);
let lastFetchContents = null;
let currentTime = GLib.DateTime.new_now_utc();
let lastFetchContents = null;
let lastFetchInfo = null;
try {
lastFetchInfo = lastFetchPath.query_info('time::modified', Gio.FileQueryInfoFlags.NONE, cancellable);
} catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
throw e;
}
if (lastFetchInfo != null) {
lastFetchContents = GSystem.file_load_contents_utf8(lastFetchPath, cancellable).replace(/[ \n]/g, '');
if (params.timeoutSec > 0) {
let lastFetchTime = GLib.DateTime.new_from_unix_local(lastFetchInfo.get_attribute_uint64('time::modified'));
let diff = currentTime.difference(lastFetchTime) / 1000 / 1000;
if (diff < params.timeoutSec) {
fetch = false;
}
}
}
GSystem.shutil_rm_rf(tmpMirror, cancellable);
if (!mirror.query_exists(cancellable)) {
ProcUtil.runSync(['git', 'clone', '--mirror', uri, tmpMirror.get_path()], cancellable,
{ logInitiation: true });
ProcUtil.runSync(['git', 'config', 'gc.auto', '0'], cancellable,
{ cwd: tmpMirror,
logInitiation: true });
GSystem.file_rename(tmpMirror, mirror, cancellable);
} else if (fetch) {
try {
ProcUtil.runSync(['git', 'fetch'], cancellable, { cwd: mirror,
logInitiation: true });
} catch (e) {
if (!params.fetchKeepGoing)
throw e;
}
}
let currentVcsVersion = ProcUtil.runSyncGetOutputUTF8(['git', 'rev-parse', branch], cancellable,
{cwd: mirror}).replace(/[ \n]/g, '');
let changed = currentVcsVersion != lastFetchContents;
if (changed) {
print(Format.vprintf("last fetch %s differs from branch %s", [lastFetchContents, currentVcsVersion]));
_listSubmodules(mirrordir, mirror, keytype, uri, branch, cancellable).forEach(function (elt) {
let [subChecksum, subName, subUrl] = elt;
print("Processing submodule " + subName + " at " + subChecksum + " from " + subUrl);
if (subUrl.indexOf('../') == 0) {
subUrl = _makeAbsoluteUrl(uri, subUrl);
print("Absolute URL: " + subUrl);
}
_ensureVcsMirrorGit(mirrordir, subUrl, subChecksum, cancellable, params);
});
}
if (changed || (fetch && params.timeoutSec > 0)) {
lastFetchPath.replace_contents(currentVcsVersion, null, false, 0, cancellable);
}
return mirror;
}
function _ensureVcsMirrorTarball(mirrordir, name, uri, checksum, cancellable, params) {
let fetch = params.fetch;
let mirror = getMirrordir(mirrordir, 'tarball', name);
let tmpMirror = mirror.get_parent().get_child(mirror.get_basename() + '.tmp');
if (!mirror.query_exists(cancellable)) {
GSystem.shutil_rm_rf(tmpMirror, cancellable);
GSystem.file_ensure_directory(tmpMirror, true, cancellable);
ProcUtil.runSync(['git', 'init', '--bare'], cancellable,
{ cwd: tmpMirror, logInitiation: true });
ProcUtil.runSync(['git', 'config', 'gc.auto', '0'], cancellable,
{ cwd: tmpMirror, logInitiation: true });
GSystem.file_rename(tmpMirror, mirror, cancellable);
}
let importTag = 'tarball-import-' + checksum;
let gitRevision = ProcUtil.runSyncGetOutputUTF8StrippedOrNull(['git', 'rev-parse', importTag],
cancellable, { cwd: mirror });
if (gitRevision != null) {
return mirror;
}
// First, we get a clone of the tarball git repo
let tmpCheckoutPath = mirrordir.get_child('tarball-cwd-' + name);
GSystem.shutil_rm_rf(tmpCheckoutPath, cancellable);
ProcUtil.runSync(['git', 'clone', mirror.get_path(), tmpCheckoutPath.get_path()], cancellable,
{ logInitiation: true });
// Now, clean the contents out
ProcUtil.runSync(['git', 'rm', '-r', '--ignore-unmatch', '.'], cancellable,
{ cwd: tmpCheckoutPath,
logInitiation: true });
// Download the tarball
let tmpPath = mirrordir.get_child('tarball-' + name);
GSystem.shutil_rm_rf(tmpPath, cancellable);
GSystem.file_ensure_directory(tmpPath.get_parent(), true, cancellable);
ProcUtil.runSync(['curl', '-L', '-v', '-o', tmpPath.get_path(), uri], cancellable,
{ logInitiation: true });
// And verify the checksum
let tarballData = GSystem.file_map_readonly(tmpPath, cancellable);
let actualChecksum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256, tarballData);
if (actualChecksum != checksum) {
throw new Error("Downloaded " + uri + " expected checksum=" + checksum + " actual=" + actualChecksum);
}
let decompOpt = null;
if (JSUtil.stringEndswith(uri, '.xz'))
decompOpt = '--xz';
else if (JSUtil.stringEndswith(uri, '.bz2'))
decompOpt = '--bzip2';
else if (JSUtil.stringEndswith(uri, '.gz'))
decompOpt = '--gzip';
// Extract the tarball to our checkout
let args = ['tar', '-C', tmpCheckoutPath.get_path(), '-x'];
if (decompOpt !== null)
args.push(decompOpt);
args.push('-f');
args.push(tmpPath.get_path());
ProcUtil.runSync(args, cancellable, { logInitiation: true });
tarballData = null; // Clear this out in the hope the GC eliminates it
GSystem.file_unlink(tmpPath, cancellable);
// Automatically strip the first element if there's exactly one directory
let e = tmpCheckoutPath.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
cancellable);
let info;
let nFiles = 0;
let lastFileType = null;
let lastFile = null;
let lastInfo = null;
while ((info = e.next_file(cancellable)) != null) {
if (info.get_name() == '.git')
continue;
nFiles++;
lastFile = e.get_child(info);
lastInfo = info;
}
e.close(cancellable);
if (nFiles == 1 && lastInfo.get_file_type() == Gio.FileType.DIRECTORY) {
e = lastFile.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
cancellable);
while ((info = e.next_file(cancellable)) != null) {
let child = e.get_child(info);
if (!child.equal(lastFile))
GSystem.file_rename(child, tmpCheckoutPath.get_child(info.get_name()), cancellable);
}
lastFile.delete(cancellable);
e.close(cancellable);
}
let msg = 'Automatic import of ' + uri;
ProcUtil.runSync(['git', 'add', '.'],
cancellable, { cwd: tmpCheckoutPath });
ProcUtil.runSync(['git', 'commit', '-a', '--author=Automatic Tarball Importer <ostree-list@gnome.org>', '-m', msg ],
cancellable, { cwd: tmpCheckoutPath });
ProcUtil.runSync(['git', 'tag', '-m', msg, '-a', importTag ],
cancellable, { cwd: tmpCheckoutPath });
ProcUtil.runSync(['git', 'push', '--tags', 'origin', "master:master" ],
cancellable, { cwd: tmpCheckoutPath });
GSystem.shutil_rm_rf(tmpCheckoutPath, cancellable);
return mirror;
}
function uncacheRepository(mirrordir, keytype, uri, branch, cancellable) {
let lastFetchPath = getLastfetchPath(mirrordir, keytype, uri, branch);
GSystem.shutil_rm_rf(lastFetchPath, cancellable);
}
function fetch(mirrordir, component, cancellable, params) {
params = Params.parse(params, {keepGoing: false, timeoutSec: 0});
ensureVcsMirror(mirrordir, component, cancellable,
{ fetch:true,
fetchKeepGoing: params.keepGoing,
timeoutSec: params.timeoutSec });
}
function describeVersion(dirpath, branch) {
let args = ['git', 'describe', '--long', '--abbrev=42', '--always'];
if (branch) {
args.push(branch);
}
return ProcUtil.runSyncGetOutputUTF8(args, null, {cwd:dirpath}).replace(/[ \n]/g, '');
}

View File

@ -1,194 +0,0 @@
// Copyright (C) 2012,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 BuildUtil = imports.buildutil;
const FileUtil = imports.fileutil;
const JsonUtil = imports.jsonutil;
function relpathToVersion(relpath) {
let parts = relpath.split('/');
return parts[0] + parts[1] + parts[2] + '.' + parts[3];
}
const VersionedDir = new Lang.Class({
Name: 'VersionedDir',
_YMD_SERIAL_VERSION_RE: /^(\d+)(\d\d)(\d\d)\.(\d+)$/,
_YEAR_OR_SERIAL_VERSION_RE: /^(\d+)$/,
_MONTH_OR_DAY_VERSION_RE: /^\d\d$/,
_init: function(path) {
this.path = path;
this._cachedResults = null;
GSystem.file_ensure_directory(this.path, true, null);
},
_createPathForParsedVersion: function(year, month, day, serial) {
return this.path.resolve_relative_path(Format.vprintf('%s/%s/%s/%s',
[year, month, day, serial]));
},
createPathForVersion: function(ymdSerial) {
let match = this._YMD_SERIAL_VERSION_RE.exec(ymdSerial);
if (!match) throw new Error();
return this.path.resolve_relative_path(match[1] + '/' + match[2] + '/' +
match[3] + '/' + match[4]);
},
_iterateChildrenMatching: function(dir, pattern, callback, cancellable) {
let e = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
let info;
while ((info = e.next_file(cancellable)) != null) {
let srcpath = e.get_child(info);
let name = info.get_name();
let match = pattern.exec(name);
if (!match)
continue;
callback(srcpath, match, cancellable);
}
e.close(null);
},
_loadYear: function(yeardir, results, cancellable) {
this._iterateChildrenMatching(yeardir, this._MONTH_OR_DAY_VERSION_RE,
Lang.bind(this, function(srcpath, match, cancellable) {
this._loadMonth(srcpath, results, cancellable);
}), cancellable);
},
_loadMonth: function(monthdir, results, cancellable) {
this._iterateChildrenMatching(monthdir, this._MONTH_OR_DAY_VERSION_RE,
Lang.bind(this, function(srcpath, match, cancellable) {
this._loadDay(srcpath, results, cancellable);
}), cancellable);
},
_loadDay: function(daydir, results, cancellable) {
this._iterateChildrenMatching(daydir, this._YEAR_OR_SERIAL_VERSION_RE,
Lang.bind(this, function(srcpath, match, cancellable) {
results.push(this.pathToVersion(srcpath));
}), cancellable);
},
_convertLegacyLayout: function(cancellable) {
this._iterateChildrenMatching(this.path, this._YMD_SERIAL_VERSION_RE,
Lang.bind(this, function(srcpath, match, cancellable) {
let path = this._createPathForParsedVersion(match[1], match[2], match[3], match[4]);
print("convert " + srcpath.get_path() + " -> " + path.get_path());
GSystem.file_ensure_directory(path.get_parent(), true, cancellable);
GSystem.file_rename(srcpath, path, cancellable);
}), cancellable);
},
pathToVersion: function(path) {
return relpathToVersion(this.path.get_relative_path(path));
},
loadVersions: function(cancellable) {
if (this._cachedResults !== null)
return this._cachedResults;
let results = [];
this._convertLegacyLayout(cancellable);
this._iterateChildrenMatching(this.path, this._YEAR_OR_SERIAL_VERSION_RE,
Lang.bind(this, function(srcpath, match, cancellable) {
this._loadYear(srcpath, results, cancellable);
}), cancellable);
results.sort(BuildUtil.compareVersions);
this._cachedResults = results;
return results;
},
currentVersion: function(cancellable) {
let versions = this.loadVersions(cancellable);
if (versions.length > 0)
return versions[versions.length - 1];
return null;
},
_makeDirUpdateIndex: function(path, cancellable) {
let relpath = this.path.get_relative_path(path);
if (relpath == null) {
GSystem.file_ensure_directory(path, true, cancellable);
return;
}
let parent = path.get_parent();
this._makeDirUpdateIndex(parent, cancellable);
let created = false;
try {
path.make_directory(cancellable);
created = true;
} catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS))
throw e;
}
let indexJsonPath = parent.get_child('index.json');
if (!created)
created = !indexJsonPath.query_exists(null);
if (created) {
let childNames = [];
FileUtil.walkDir(parent, { depth: 1,
fileType: Gio.FileType.DIRECTORY },
Lang.bind(this, function(filePath, cancellable) {
childNames.push(filePath.get_basename());
}), cancellable);
JsonUtil.writeJsonFileAtomic(indexJsonPath,
{ 'subdirs': childNames }, cancellable);
}
},
allocateNewVersion: function(cancellable) {
let currentTime = GLib.DateTime.new_now_utc();
let currentYmd = Format.vprintf('%d%02d%02d', [currentTime.get_year(),
currentTime.get_month(),
currentTime.get_day_of_month()]);
let versions = this.loadVersions(cancellable);
let newVersion = null;
if (versions.length > 0) {
let last = versions[versions.length-1];
let match = this._YMD_SERIAL_VERSION_RE.exec(last);
if (!match) throw new Error();
let lastYmd = match[1] + match[2] + match[3];
let lastSerial = match[4];
if (lastYmd == currentYmd) {
newVersion = currentYmd + '.' + (parseInt(lastSerial) + 1);
}
}
if (newVersion === null)
newVersion = currentYmd + '.0';
let path = this.createPathForVersion(newVersion);
this._makeDirUpdateIndex(path, cancellable);
this._cachedResults.push(newVersion);
return path;
},
deleteCurrentVersion: function(cancellable) {
let versions = this.loadVersions(cancellable);
if (versions.length == 0)
throw new Error();
let last = versions.pop();
let path = this.createPathForVersion(last);
GSystem.shutil_rm_rf(path, cancellable);
}
});

View File

@ -1,113 +0,0 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2014 Colin Walters <walters@verbum.org>
*
* This program 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 licence 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.
*/
#include "config.h"
#include <string.h>
#include <glib-unix.h>
#include <gio/gunixsocketaddress.h>
#include <stdio.h>
#include <readline/readline.h>
#include "libgsystem.h"
static GOptionEntry option_entries[] = {
{ NULL }
};
int
main (int argc, char **argv)
{
GError *local_error = NULL;
GError **error = &local_error;
GCancellable *cancellable = NULL;
GOptionContext *context = g_option_context_new ("- netcat console");
gs_unref_object GSocketAddress *address = NULL;
gs_unref_object GSocketClient *socketclient = NULL;
gs_unref_object GSocketConnection *socketconn = NULL;
GOutputStream *out;
GInputStream *in;
gs_unref_object GDataInputStream *datain = NULL;
const char *address_arg;
g_option_context_add_main_entries (context, option_entries, NULL);
if (argc > 1)
address_arg = argv[1];
else
address_arg = "cmd.socket";
address = g_unix_socket_address_new (address_arg);
socketclient = g_socket_client_new ();
socketconn = g_socket_client_connect (socketclient,
(GSocketConnectable*)address,
cancellable, error);
if (!socketconn)
goto out;
out = g_io_stream_get_output_stream ((GIOStream*)socketconn);
in = g_io_stream_get_input_stream ((GIOStream*)socketconn);
datain = g_data_input_stream_new (in);
while (TRUE)
{
char *line = readline ("> ");
gsize bytes_written;
const guint8 nl = '\n';
if (!line)
break;
if (!g_output_stream_write_all (out, line, strlen (line),
&bytes_written,
cancellable, error))
goto out;
free (line);
if (!g_output_stream_write_all (out, &nl, 1,
&bytes_written,
cancellable, error))
goto out;
{
gsize bytes_read;
gs_free char *resline =
g_data_input_stream_read_line_utf8 (datain,
&bytes_read,
cancellable, error);
if (!resline)
goto out;
g_print ("%s\n", resline);
}
}
out:
if (local_error)
{
g_printerr ("%s\n", local_error->message);
g_error_free (local_error);
return 1;
}
return 0;
}

View File

@ -1,35 +0,0 @@
#!/bin/sh
# Copyright (C) 2012,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.
jsdir=@pkgdatadir@-autobuilder/js
export GI_TYPELIB_PATH="@pkglibdir@/girepository-1.0${GI_TYPELIB_PATH:+:$GI_TYPELIB_PATH}"
export LD_LIBRARY_PATH="@pkglibdir@/${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
# Don't auto-spawn a session bus
export GIO_USE_VFS=local
export OSTBUILD_DATADIR=@pkgdatadir@-autobuilder
export OSTBUILD_LIBDIR=@pkglibdir@-autobuilder
argv1="$1"
externalpath="$OSTBUILD_LIBDIR/rpm-ostree-autobuilder-builtin-$1"
if test -x "$externalpath"; then
shift
exec "$externalpath" "$@"
fi
exec $OSTBUILD_GDB gjs -I "${jsdir}" "${jsdir}/main.js" "$@"

View File

@ -1,2 +0,0 @@
#!/bin/bash
exec journalctl -o json -b -f --no-tail > /dev/virtio-ports/org.gnome.journaljson

View File

@ -1,5 +0,0 @@
[Unit]
Description=QA: Copy bootup journal log to /dev/ttyS1
[Service]
ExecStart=/usr/bin/rpm-ostree-export-journal-to-serialdev

View File

@ -1,3 +0,0 @@
#!/bin/sh
set -e
rpm -qa --dbpath=$(pwd) | sort