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-rpm-ostree.am
include Makefile-man.am include Makefile-man.am
include Makefile-autobuilder.am

View File

@ -1,6 +1,6 @@
Summary: Commit RPMs to an OSTree repository Summary: Commit RPMs to an OSTree repository
Name: rpm-ostree Name: rpm-ostree
Version: 2015.1 Version: 2014.1
Release: 1%{?dist} Release: 1%{?dist}
#VCS: https://github.com/cgwalters/rpm-ostree #VCS: https://github.com/cgwalters/rpm-ostree
# This tarball is generated via "make -f Makefile.dist-packaging dist-snapshot" # 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 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. 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 %prep
%setup -q -n %{name}-%{version} %setup -q -n %{name}-%{version}
@ -53,12 +40,6 @@ make install DESTDIR=$RPM_BUILD_ROOT INSTALL="install -p -c"
%{_bindir}/rpm-ostree %{_bindir}/rpm-ostree
%{_libdir}/%{name}/ %{_libdir}/%{name}/
%files autobuilder
%{_bindir}/rpm-ostree-autobuilder
%{_libdir}/%{name}-autobuilder/
%{_datadir}/%{name}-autobuilder/
%{_mandir}/man1/*.gz
%changelog %changelog
* Fri Mar 07 2014 Colin Walters <walters@verbum.org> - 2014.5-1 * Fri Mar 07 2014 Colin Walters <walters@verbum.org> - 2014.5-1
- Initial package - 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